Re[10]: Адаптированный алгоритм Эрли - описание
От: mefrill Россия  
Дата: 22.01.06 17:31
Оценка:
Здравствуйте, AndrewVK, Вы писали:

AVK>Здравствуйте, mefrill, Вы писали:


AVK>Вот мой вариант. На C#, но там все очень просто, думаю разберешься.


Ага, там есть одно неудобство, о котором я пока не хотел говорить. Дело в том, что когда алгоритм Эрли работает, он должен как-то взаимодействовать с обработчиком правил. Это взаимодействие легко реализовать, если использовать перечисления для именования правил. Тогда, при обработке того или иного правила, специальной функции-обработчику будет передаваться это перечисление — индекс правила. и в коде обработчика легко потом можно такую обработку реализовать. Хотя, конечно, можно сделать все как в бизоне, в тексте программы определить непосредственно все процедуры обработки правил, а сам конструктор парсера вставит их в генерируемый текст, но для аллгоритма Эрли такой подход кажется мне не очень хорошим. Хотя, понятно, все это обсуждается и я легко могу ошибаться. В общем, что я имею ввиду: для каждого правила определить перечисление и передавать его как индекс в функцию добавления правила в грамматику. Что-то типа такого.
Re[11]: Адаптированный алгоритм Эрли - описание
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 22.01.06 19:17
Оценка:
Здравствуйте, mefrill, Вы писали:

AVK>>А зачем конструктор без параметров? При его наличии придется делать доступ к полям по записи, а это снижает защиту.


M>Дело в том, что я символы в векторе размещаю, а там конструктор по умолчанию необходим.


Т.е. это технический момент и в версии на C# можно без них?

M>Я считаю, что это самый быстрый способ для хранения. Вообще, лучший способ хранить грамматику — это использовать три вектора: вектор терминалов, вектор нетерминалов и вектор правил, где каждый элемент правила — это просто индекс в первых двух векторах. В алгоритм Эрли это все легко укладывается и работает хорошо. Хотя, все несомненно, обсуждается.


Я думал на эту тему. Но вопрос разнесения терминалов и нетерминалов по разным спискам мне не показался бесспорным.

M>Ну да, в реальности ничего не сравнивается по строкам. Каждому символу грамматики дается индекс в векторах, о которых я говорил выше. И все сравнения идут на индексах. Получается очень быстро и удобно.


Так указатели тоже неплохо сравнивать, притом нет лишней косвенности, а код получается проще.

M>typedef std::vector< std::string > input_t;


Т.е. у тебя на вход подается уже порезанная на лексемы строка?

M> std::string get_next()


Надо бы специальный класс для лексемы сделать.

M>struct state{

M> items_t items_;
M>};

А смысл в этом классе?
... << RSDN@Home 1.2.0 alpha rev. 629 on Windows XP 5.1.2600.131072>>
AVK Blog
Re[11]: Адаптированный алгоритм Эрли - описание
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 22.01.06 19:17
Оценка:
Здравствуйте, mefrill, Вы писали:

M>Ага, там есть одно неудобство, о котором я пока не хотел говорить. Дело в том, что когда алгоритм Эрли работает, он должен как-то взаимодействовать с обработчиком правил.


В прототипе обыкновенные события с экземпляром Rule в парметрах. В реальном парсере автогенеренные свитчи. В качестве промежуточного решения можно использовать класс, размеченный атрибутами с описанием правил.
public delegate void RuleHandler(ParserContext context);

public class ParseHandler
{
    [Rule("S", "S", "+", "n")]
    public void Rule1(ParserContext context)
    {
        ...
    }
    
    [Rule("S", "n")]
    public void Rule2(ParserContext context)
    {
        ...
    }
}

...

Grammar g = new Grammar(typeof (ParseHandler));

Реализация такая: в конструкторе при помощи рефлекшена пробегаем по методам и собираем грамматику (либо всю грамматику храним отдельно, а в атрибуте тело правила не указываем). Имя правила = имени метода. По самому методу конструируется делегат, который запихивается в Dictionary<string, RuleHandler> или Dictionary<Rule, RuleHandler>. Ну а потом просто вызываем делегат. Совсем в релизе можно в хешике вместо делегата хранить целое число, которое потом пользовать в генеренном свитче.

M>Хотя, конечно, можно сделать все как в бизоне, в тексте программы определить непосредственно все процедуры обработки правил, а сам конструктор парсера вставит их в генерируемый текст, но для аллгоритма Эрли такой подход кажется мне не очень хорошим.


Согласен. Особенно весело становится, когда нужно у парсера делать в разных случаях разный выход. Например (реальный случай) для дизайн-тайма и рантайма нужны по разному оптимизированные разновидности AST. Эмбеддед-подход в таком разе несколько неудобен.
... << RSDN@Home 1.2.0 alpha rev. 629 on Windows XP 5.1.2600.131072>>
AVK Blog
Re[12]: Адаптированный алгоритм Эрли - описание
От: mefrill Россия  
Дата: 22.01.06 19:34
Оценка:
Здравствуйте, AndrewVK, Вы писали:

AVK>Т.е. это технический момент и в версии на C# можно без них?


Да, конечно.

M>>Я считаю, что это самый быстрый способ для хранения. Вообще, лучший способ хранить грамматику — это использовать три вектора: вектор терминалов, вектор нетерминалов и вектор правил, где каждый элемент правила — это просто индекс в первых двух векторах. В алгоритм Эрли это все легко укладывается и работает хорошо. Хотя, все несомненно, обсуждается.

AVK>Я думал на эту тему. Но вопрос разнесения терминалов и нетерминалов по разным спискам мне не показался бесспорным.

В бизоне используется один вектор и сначала идут терминалы, а только затем нетерминалы. Таким образом, не надо хранить дополнительный булевский параметр в структуре. Здесь, на самом деле, целое поле для фантазии.

M>>typedef std::vector< std::string > input_t;


AVK>Т.е. у тебя на вход подается уже порезанная на лексемы строка?


Нет, это я просто написал для простоты. На самом деле лексер должен возвращать перечисление входного терминальног осимвола, это наверное, лучший вариант.

M>> std::string get_next()

AVK>Надо бы специальный класс для лексемы сделать.

Да, конечно.

M>>struct state{

M>> items_t items_;
M>>};
AVK>А смысл в этом классе?

Ну это как-бы заготовка пока. Вообще как раз туда разные фигни для оптимизации можно поставить.
Re[13]: Адаптированный алгоритм Эрли - описание
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 22.01.06 19:59
Оценка:
Здравствуйте, mefrill, Вы писали:

M>В бизоне используется один вектор и сначала идут терминалы, а только затем нетерминалы. Таким образом, не надо хранить дополнительный булевский параметр в структуре. Здесь, на самом деле, целое поле для фантазии.


А как ты предлагаешь? Базовый класс Symbol и два наследника — Terminal и NonTerminal? Или сделать их абсолютно одинаковыми и распознавать просто по списку, в котором хранится? Но как тогда, имея экземпляр Rule, определится какой конкретно символ в правой части по некоторой позиции х?
... << RSDN@Home 1.2.0 alpha rev. 629 on Windows XP 5.1.2600.131072>>
AVK Blog
Re[14]: Адаптированный алгоритм Эрли - описание
От: mefrill Россия  
Дата: 22.01.06 20:38
Оценка: +1
Здравствуйте, AndrewVK, Вы писали:

AVK>А как ты предлагаешь? Базовый класс Symbol и два наследника — Terminal и NonTerminal? Или сделать их абсолютно одинаковыми и распознавать просто по списку, в котором хранится? Но как тогда, имея экземпляр Rule, определится какой конкретно символ в правой части по некоторой позиции х?


Да, просто сравнивать позицию. Для идущих первыми терминалов это максимальное число. Если номер символа больше этого числа, то значит это нетерминал, меньше или равно, значит терминал.
Re[15]: Адаптированный алгоритм Эрли - описание
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 22.01.06 20:53
Оценка:
Здравствуйте, mefrill, Вы писали:

AVK>>А как ты предлагаешь? Базовый класс Symbol и два наследника — Terminal и NonTerminal? Или сделать их абсолютно одинаковыми и распознавать просто по списку, в котором хранится? Но как тогда, имея экземпляр Rule, определится какой конкретно символ в правой части по некоторой позиции х?


M>Да, просто сравнивать позицию. Для идущих первыми терминалов это максимальное число.


Погоди, ты только что предложил хранить их в разных списках.

M> Если номер символа больше этого числа, то значит это нетерминал, меньше или равно, значит терминал.


ИМХО выглядит на редкость криво.
... << RSDN@Home 1.2.0 alpha rev. 629 on Windows XP 5.1.2600.131072>>
AVK Blog
Re[3]: Адаптированный алгоритм Эрли - описание
От: VladD2 Российская Империя www.nemerle.org
Дата: 23.01.06 09:50
Оценка:
Здравствуйте, mefrill, Вы писали:

Похоже несоотвествие:

Проходим по списку S0:
в ситуации [S --> * S + n, 0] после точки стоит символ S, он не равен текущему символу n входной строки, поэтому пропускаем эту ситуацию. Следующая ситуация — это [S --> * n, 0]. Вот у нее символ после точки равен n. Поэтому, добавляем эту ситуацию в список S1, сдвинув точку на символ правее, имеем ситуацию [S --> n *, 0] в списке S1.

и:

S1
[S --> * n, 0]
[S --> S * + n, 0]


Где ошибка?
... << RSDN@Home 1.2.0 alpha rev. 631>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: Адаптированный алгоритм Эрли - описание
От: VladD2 Российская Империя www.nemerle.org
Дата: 23.01.06 09:50
Оценка:
Здравствуйте, mefrill, Вы писали:

M>Выводится ли строка n+n в нашей граматике? Да, ведь список S3 содержит ситуацию [S --> S + n *, 0], в котрой точка стоит в конце правой части правила, символ в левой части есть начальный нетерминал грамматики и второй компонент правила есть 0.


А что будет если строка не выводится из грамматики?

M>Следующим шагом может быть попытка написать работающий алгоритм Эрли.


Попытался, на выходных, написать реализацию парсера. Для строчки "n + n" вроде как даже работает. Если подсунуть неверную строку, то начиная с некоторого состояния они идут пустыми. Например, для строки "n + + n" выод получается следующим:
+        [0]    State: "([S : *S + n , 0], [S : *n , 0])"    EarleyParser.State
+        [1]    State: "([S : n *, 0], [S : S *+ n , 0])"    EarleyParser.State
+        [2]    State: "([S : S + *n , 0])"    EarleyParser.State
+        [3]    State: "()"    EarleyParser.State
+        [4]    State: "()"    EarleyParser.State

Не обращай внимания на грязь вроде "EarleyParser.State". Это я скопировал массив состояний из VS. Я создал некоторый вывод отладочной информации способствующий отладке.

Для строки "+ 1 + 2" список состояний следующий:
+        [0]    State: "([S : *S + n , 0], [S : *n , 0])"    EarleyParser.State
+        [1]    State: "()"    EarleyParser.State
+        [2]    State: "()"    EarleyParser.State
+        [3]    State: "()"    EarleyParser.State
+        [4]    State: "()"    EarleyParser.State

Для строки "" следующий:
+        [0]    State: "([S : *S + n , 0], [S : *n , 0])"    EarleyParser.State
+        [1]    State: "([S : n *, 0], [S : S *+ n , 0])"    EarleyParser.State
+        [2]    State: "([S : S + *n , 0])"    EarleyParser.State
+        [3]    State: "([S : S + n *, 0], [S : S *+ n , 0])"    EarleyParser.State
+        [4]    State: "([S : S + *n , 0])"    EarleyParser.State


Для строки "1 + 2 + 5" следующий:
+        [0]    State: "([S : *S + n , 0], [S : *n , 0])"    EarleyParser.State
+        [1]    State: "([S : n *, 0], [S : S *+ n , 0])"    EarleyParser.State
+        [2]    State: "([S : S + *n , 0])"    EarleyParser.State
+        [3]    State: "([S : S + n *, 0], [S : S *+ n , 0])"    EarleyParser.State
+        [4]    State: "([S : S + *n , 0])"    EarleyParser.State
+        [5]    State: "([S : S + n *, 0], [S : S *+ n , 0])"    EarleyParser.State


Явно прослеживается, что после несовпадения строки парсер начинает работать в холостую. Видимо наличие пустых состояний свидетельсвует о том, что строка не совпала с грамматикой. Так ли это? И если так, то можно ли останавливать парсинг отталкиваясь от того, что новое состояние пусто?
... << RSDN@Home 1.2.0 alpha rev. 631>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re: Адаптированный алгоритм Эрли - описание
От: VladD2 Российская Империя www.nemerle.org
Дата: 23.01.06 10:27
Оценка:
Здравствуйте, mefrill, Вы писали:

В общем, реализовал я этот алгоритм на C# 2.0. Исходники доступны здесь: http://rsdn.ru/File/73/EarleyParser.zip

Будем надеяться, что глюков не много, и я правильно понял алгоритм.

В качестве лексера я взял лексер C# 2.0 сгенерированный с помощью генератора парсеров — CocoR.
Грамматика задается классом Grammar. Точнее его методами:
void AddRule(string leftMymbolName, params string[] rightMymbolNames);
void AddTerminal(string name);
void AddTerminal(string name, int id);
void AddTerminals(params string[] names);

Надеясь не нужно пояснять, что они делают?

У этого класса есть метод:
Parser GetParser(RSParser.Engine.Scanner lexer);

создающий и возвращающий парсер. Далее остается только вызвать метод Parser.Parse():
bool Parse(int startNonterminalId);
bool Parse(string startNonterminalName)


Естественно, что пока-что парсер не делает ничего кроме определения соотвествия входной строки грамматике.

Следующими шагами должны быть создание олноценного парсера с построением деревьев вывода, и создание парсера способного съесть рельную грамматику.

Поясню последнее. На сегодня парсер есть грамматику в форме:
Rules
    : Rule
    | Rules Rule
    ;
    
Rule
    : Symbol ":" Symbols
    ;

Symbols
    : Symbol
    | Symbols Symbol
    ;

Причем грамматика должна быть задана в виде вызова методов класса Grammar.

В реальном парсере грамматика должна задаваться в отдельном текстовом файли и должна допускать вложенные подправила вида "A : { A B c } A [ c ];" где "{...}" вложенное рекурсивное подправило говорящее что вложенное правило должно применяться ноль или более раз, а "[...]" говорит, что вложенное подправило должно применяться ноль или один раз. Так же возможно вуести вложенное подправило определяюющее, то правило должно применяться один или более раз. Это резко упрощает написание грамматик.

Вопрос тольк в том как преобразовать такую сложную грамматику к более простой используемой в алгоритме?

Далее можно попробовать скормить парсеру грамматику C# 2.0 созданную мной для CocoR и посмотреть на то с какой скорость работает этот.
... << RSDN@Home 1.2.0 alpha rev. 631>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re: Адаптированный алгоритм Эрли - описание
От: VladD2 Российская Империя www.nemerle.org
Дата: 23.01.06 10:44
Оценка:
Здравствуйте, mefrill, Вы писали:

Для большей понятности алгоритма решил привести код основнх методов:
public bool Parse(int startNonterminalId)
{
    PrepareToParse(startNonterminalId);

    State state = AddState();

    // Получаем индексы правил соотвествующих нетерминалу startNonterminalId.
    // и добавляем их в список "нулевого" состояния.
    AddPredictions(startNonterminalId, state, 0);

    do
    {
        // ! Количество ситуаций в стостянии может измениться в процессе
        // их просмотра . По этому кэшируем их число и делаем перебор простым
        // for-ом.
        int count = state.Count;

        for (int i = 0; i < count; i++)
        {
            Situation sit = state[i];

            if (IsComplete(sit))
                Completer(sit, state);
            else
                Predictor(sit, state, 0);
        }
    }
    while (Scanner(ref state));

    // Если последнее состояние содержит "законченную" ситуацию, т.е.
    // ситукациювида [A --> X1...Xk *], значит строка соответствует 
    // грамматике.
    return GetLastState().Exists(IsComplete);
}


/// <summary>
/// Обрабатывает следующий терминал входной строки добавляя в новое 
/// состояние все правила из предыдущего состояния у которых символ 
/// за точкой совпадает со считанный терминальным символом.
/// </summary>
/// <remarks>
/// Описание mefrill: 
/// Scanner — работает только с терминальными символами. Scanner работает
/// на некотором списке state когда тот уже заполнен плностью, т.е. туда 
/// больше ничего добавить нельзя. Поэтому настает время перейти к 
/// заполнению следующего списка nextState. Т.е. Scanner вызывается для 
/// инициализации каждого списка S1...Sn, а список S0 инициализируется
/// особым образом. Что делает Scanner? Он сравнивает символ Next() во 
/// входной терминальной строке с символом после точки в каждой ситуации
/// из state и, если символ после точки совпадает с символом Next(), то
/// Scanner добавляет эту ситуацию в список nextState, преварительно 
/// передвинув точку в правой части правила ситуации на символ правее.
/// </remarks>
/// <param name="state">Текущее состояние.</param>
/// <returns>Нужно ли продолжать сканирование.</returns>
bool Scanner(ref State state)
{
    Token token = Next(); // Считываем следующий треминал входной строки.

    if (token.kind == Tokens.EOF)
        return false; // Если это конец строки, то прекращаем парсинг.

    State nextState = AddState(); // Создаем следующее состоянеие.

    foreach (Situation sit in state)
        if (DotSymId(sit) == token.kind) 
            // Если символ за точкой совпадает со считанный терминальным символом,
            // добавляем символ в новое состояние продвигая в нем точку вперед.
            nextState.Add(sit.CloneWithMoveDot());

    state = nextState; // Делаем следующее состояние текущем.

    return true;
}


/// <summary>
/// Операция Predictor, примененная к ситуации 
/// [B --> alpha * A beta, j], принадлежащей списку Si, делает следующее:
/// 1. Проходит по правилам грамматики.
/// 2. Для каждого правила вида A --> delta, где символ в левой части 
/// совпадает с символом после точки, добаляет в список Si ситуацию 
/// [A --> * delta, i].
/// </summary>
/// <param name="sit">Обрабатываемая ситуация.</param>
/// <param name="state">Текущее состояние.</param>
/// <param name="stateIndex">Индекс текущего состояния.</param>
void Predictor(Situation sit, State state, int stateIndex)
{
    Debug.Assert(!IsComplete(sit));

    // Получаем символ на идущий после точки.
    int dotSymId = DotSymId(sit);
    // Если символ после точки в ситуации является нетерминальным
    // и он не является самым левым символом правила этой ситуации...
    if (IsNonterminal(dotSymId) && dotSymId != Rule(sit).LeftSymbol)
        // то добавляем правила для нетерминала dotSymId в текущее состояние.
        AddPredictions(dotSymId, state, stateIndex);

}

private void AddPredictions(int nonterminalId, State state, int stateIndex)
{
    Debug.Assert(IsNonterminal(nonterminalId));

    foreach (int ruleId in _grammar.NontermRules[nonterminalId])
        state.Add(new Situation(ruleId, 0, 0));
}


/// <summary>
/// Операция Completer, примененная к ситуации [A --> X1...Xk *, j],
/// принадлежащей списку Si, делает следующее:
/// </summary>
/// <param name="sit">Ситуация [A --> X1...Xk *, j]</param>
/// <param name="state">Si</param>
void Completer(Situation sit, State state)
{
    Debug.Assert(IsComplete(sit));

    int leftSymbol = GetLeftSymbol(sit);

    // 1. Проходит по всем ситуациям списка Sj.
    foreach (Situation sit2 in _states[sit.CreationState])
    {
        // 2. Если в ситуации символ 
        // /* VladD2: видимо речь идет о текущем ситуации из Sj, t.e. sit2 */
        // после точки есть символ A, 
        // то добавляет эту ситуацию в список Si, сдвинув предварительно
        // точку в правой части на символ правее.
        if (leftSymbol == DotSymId(sit2))
            state.Add(sit2.CloneWithMoveDot());
    }
}


Собственно очень приятно, что алгоритм очень простенький. Хотя как бы его простота не вышла боком для производительности.
... << RSDN@Home 1.2.0 alpha rev. 631>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[10]: Адаптированный алгоритм Эрли - описание
От: VladD2 Российская Империя www.nemerle.org
Дата: 23.01.06 10:54
Оценка:
Здравствуйте, AndrewVK, Вы писали:

AVK>А что, на C# никак? Тогда желательно с минимальным использованием STL и без всяких boost и loki.


Бустов там нет. Но С++ для выражения алгоритмво это действительно зрелище не для слабонервных.

В общем, вот здесь:
Re: Адаптированный алгоритм Эрли &mdash; описание
Автор: VladD2
Дата: 23.01.06

Re: Адаптированный алгоритм Эрли &mdash; описание
Автор: VladD2
Дата: 23.01.06

алгоритм на Шарпе и работающий проект.

M>> Влад может тоже самое на си шарп реализовать. Надо сначала реализовать грамматику, так чтобы по символам поиск предиктором делать.


AVK>Достаточно пока реализовать AST грамматики и для тестов заполнять ее программно. А потом уже сделать ее парсер на себе самом.


Так и сделал. Правила описываются примитивным классом Rule. К сожалению, он весь из int-ов, так что для его визуализации во время отладки пришлось похимичить. В общем, в отладочном режиме грамматика и парсер должны быть в единственном экземлпяре, так как ситуации для своей визуализации используют глобальную ссылку на них.

M>>символ грамматики можно закодировать так:


M>>
M>>struct symbol{
M>>   std::string name_;
M>>   bool is_terminal_;

M>>   symbol(){}
M>>   symbol( const std::string& _name, bool _is_terminal )
M>>   :
M>>   name_(_name),
M>>   is_terminal_(_is_terminal)
M>>   {}
M>>};
M>>

AVK>А зачем конструктор без параметров? При его наличии придется делать доступ к полям по записи, а это снижает защиту.

Эта замечательная конструкция в C# заменяется вот такой вот замысловатой строчкой:
string[] _symbols;



AVK>А почему хранятся индексы, а не указатели?


Так проще. Да и на Шарпе так должно быть быстрее. Все же райт-барьер отсекается.
А вообще — это же математики. У них указателей в логике нет.
... << RSDN@Home 1.2.0 alpha rev. 631>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[11]: Адаптированный алгоритм Эрли - описание
От: eao197 Беларусь http://eao197.blogspot.com
Дата: 23.01.06 11:10
Оценка:
Здравствуйте, VladD2, Вы писали:

AVK>>А почему хранятся индексы, а не указатели?


VD>Так проще. Да и на Шарпе так должно быть быстрее. Все же райт-барьер отсекается.

VD>А вообще — это же математики. У них указателей в логике нет.

Так не проще, так безопаснее
Вот из-за выделенной строки:
   unsigned int add_symbol( const std::string& _name, bool _is_terminal, bool _is_start = false )
   {
      unsigned int _cur_index = symbols_.size();
      symbols_.push_back( symbol( _name, _is_terminal ) );

хранение ссылок в виде указателей будет черевато ошибками. Если при очередном добавлении элемента в symbols_ произойдет перераспределение памяти (когда symbols_.size() == symbols.capacity()), то все указатели на элементы в symbols_ окажутся повисшими. Если же ссылки на элементы в symbols_ делаются через индексы, то такой проблемы нет.


SObjectizer: <микро>Агентно-ориентированное программирование на C++.
Re[12]: Адаптированный алгоритм Эрли - описание
От: VladD2 Российская Империя www.nemerle.org
Дата: 23.01.06 11:30
Оценка:
Здравствуйте, eao197, Вы писали:

Эта тема посвящена обсуждению алгоритма Эрли. Просьба, не устраивать из нее дисскуссионный зал, по не связанной с этой метой вопросам.
Весь остальной офтопик будет удаляться без предупреждения.
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[9]: Адаптированный алгоритм Эрли - описание
От: VladD2 Российская Империя www.nemerle.org
Дата: 23.01.06 11:47
Оценка:
Здравствуйте, mefrill, Вы писали:

Откровенно говоря есть две проблемы. 1. Рабираться, и темболее отлаживать С++-ный код очень не просто. 2. Твоя реализация содежит ряд дизайнерских просчетов, что резко усложняет рабту с ний и приводит к тому, что она не может быть использована в реальных проектах.

Так большой ошибкой является наличие глобальных массивов. Это сразу не позволяет использовать две или более копии пасрсера. Вторая ошибка — это внесение в грамматику стартового символа. Это не позволит парсить отдельные правила (например, отпарсить функцию). В третьих, в коде смешаны понятия Правило и понятие Нетерминальный симовл.

Если учесть, что код слишком большой для стольк простой задачи я бы предпочел бы изучать алгоритм на безе шарповского варианат приведенного мной здесь:
Re: Адаптированный алгоритм Эрли &mdash; описание
Автор: VladD2
Дата: 23.01.06
... << RSDN@Home 1.2.0 alpha rev. 631>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[11]: Адаптированный алгоритм Эрли - описание
От: VladD2 Российская Империя www.nemerle.org
Дата: 23.01.06 12:38
Оценка:
Здравствуйте, mefrill, Вы писали:

M>Я считаю, что это самый быстрый способ для хранения. Вообще, лучший способ хранить грамматику — это использовать три вектора: вектор терминалов, вектор нетерминалов и вектор правил, где каждый элемент правила — это просто индекс в первых двух векторах. В алгоритм Эрли это все легко укладывается и работает хорошо. Хотя, все несомненно, обсуждается.


Я долго думал над тем как лучше хранить символы и пришел к мнению, что лучше их хранить в едином массиве со сквозной нумерацией. Это позволяет упростить в некоторых местах алгоритм.

Правила действительно лучше хранить в отдельном массиве. Хоря не факт, что имеет смыслссылаться на правила с помощью индекса. Возможно лучше правда использовать указатели. Для С++ это скорее всего так. А для дотнета с его райт-барьером не факт. В общем, нужно сначала заполучить полноценный парсер, а потом погонять его под профайлером.

M>Ну да, в реальности ничего не сравнивается по строкам. Каждому символу грамматики дается индекс в векторах, о которых я говорил выше. И все сравнения идут на индексах. Получается очень быстро и удобно.


Я бы не стал пока что говорить о производительности алгоритма. Уж больно у него много корявых сторон. Что касется доступа к правилам, то носвенная адерсация в виде индекса правила действительно является излишней.

M>В общем, код я щас написал рабочий и без оптимизации. Даю ниже, мне кажется там несложно. С удовольствием отвечу на все вопросы. И еще, код этот только для иллюстрации работаы алгоритма, так об эффективности и красоте я совсем не думал.


Фиг бы с ней с эффективностью. А вот то что не думал о красоте — это очень плохо. Разбираться в таком коде не просто.

M>Я использовал три класса:


M>класс грамматики, описанный выше.

M>класс лексера, примитивный, в несколько строк, легко понятен.
M>класс алгоритма Эрли, называется earley. Оперирует состояниями Эрли (класс state) и ситуациями Эрли (класс item). Вроде ничего там сложного не должно быть. Ниже включаю код по файлам. Код постарался подробно прокомментировать.

А зачем state было делать отдельным классом? По-моему это просто вектор.
И почему Ситуация названа item? Бессмыслица полная. Я назвал ее Situation.

origin_

Я назвал это поле CreationState. Я правильно понял суть этого поля?
... << RSDN@Home 1.2.0 alpha rev. 631>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[12]: Адаптированный алгоритм Эрли - описание
От: VladD2 Российская Империя www.nemerle.org
Дата: 23.01.06 13:18
Оценка:
Здравствуйте, AndrewVK, Вы писали:

AVK>>>А зачем конструктор без параметров? При его наличии придется делать доступ к полям по записи, а это снижает защиту.


M>>Дело в том, что я символы в векторе размещаю, а там конструктор по умолчанию необходим.


AVK>Т.е. это технический момент и в версии на C# можно без них?


Да.

M>>Я считаю, что это самый быстрый способ для хранения. Вообще, лучший способ хранить грамматику — это использовать три вектора: вектор терминалов, вектор нетерминалов и вектор правил, где каждый элемент правила — это просто индекс в первых двух векторах. В алгоритм Эрли это все легко укладывается и работает хорошо. Хотя, все несомненно, обсуждается.


AVK>Я думал на эту тему. Но вопрос разнесения терминалов и нетерминалов по разным спискам мне не показался бесспорным.


Более того, это усложнит парсер.

M>>Ну да, в реальности ничего не сравнивается по строкам. Каждому символу грамматики дается индекс в векторах, о которых я говорил выше. И все сравнения идут на индексах. Получается очень быстро и удобно.


AVK>Так указатели тоже неплохо сравнивать, притом нет лишней косвенности, а код получается проще.


Я это дело вынес в функцию, так что можно крутить как хочешь. Только нужно сначало запоулчить пример на котором ожно гонять профайлер. А то еще не факт, что устранение косвенности даст выигрышь в дотнете. Ведь вместо этого начнется барьер записи, и что дороже я не знаю.

M>>typedef std::vector< std::string > input_t;


AVK>Т.е. у тебя на вход подается уже порезанная на лексемы строка?


Я использовал готовый лексер. Так что на это можно забить.

M>>struct state{

M>> items_t items_;
M>>};

AVK>А смысл в этом классе?


Вот и я говорю. Я пока что унаследовал Состояние от List<Situation>. Так лучше для отладки. А вообще склоняюсь к тому, что бы сделать состояния просто массивами (Situation[]). Думаю, так будет быстрее всего. В большинстве случаев это позволит устранить контроль выхода за пределы массива, что тоже неплохой бонус.
... << RSDN@Home 1.2.0 alpha rev. 631>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[16]: Адаптированный алгоритм Эрли - описание
От: VladD2 Российская Империя www.nemerle.org
Дата: 23.01.06 13:18
Оценка:
Здравствуйте, AndrewVK, Вы писали:

M>> Если номер символа больше этого числа, то значит это нетерминал, меньше или равно, значит терминал.


AVK>ИМХО выглядит на редкость криво.


На самом деле выглядит это нормально, если писать как следует:
        bool IsNonterminal(int sym)
        {
            return sym >= FirstNonterminal && sym <= LastNonterminal;
        }

        bool IsTerminal(int sym)
        {
            return sym >= FirstTerminal && sym <= LastTerminal;
        }


К тому же мест где терминалы нужно отличать от нетерминалов очень не много. IsNonterminal() используется только один раз, в Predictor(), а IsTerminal() вообще ни разу не используется. Так что можно забить. Зато мест где сквозная нумерация символов помогает очень много.
... << RSDN@Home 1.2.0 alpha rev. 631>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[12]: Адаптированный алгоритм Эрли - описание
От: VladD2 Российская Империя www.nemerle.org
Дата: 23.01.06 14:10
Оценка:
Здравствуйте, AndrewVK, Вы писали:

AVK>В прототипе обыкновенные события с экземпляром Rule в парметрах. В реальном парсере автогенеренные свитчи. В качестве промежуточного решения можно использовать класс, размеченный атрибутами с описанием правил.


Нет особого смысла делать статические обработчики у динамического парсера. Тогда уж нужно продумывать и генерацию неких таблиц ускоряющих парсинг.
... << RSDN@Home 1.2.0 alpha rev. 631>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[13]: Адаптированный алгоритм Эрли - описание
От: AndrewVK Россия http://blogs.rsdn.org/avk
Дата: 23.01.06 14:28
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>Нет особого смысла делать статические обработчики у динамического парсера.


Ты считаешь что разница между прямым вызовом и вызовом через делегат на фоне парсинга будет незаметна?

VD> Тогда уж нужно продумывать и генерацию неких таблиц ускоряющих парсинг.


Придумать бы еще каких.
... << RSDN@Home 1.2.0 alpha rev. 631>>
AVK Blog
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.