Compiler-compiler
От: konsoletyper Россия https://github.com/konsoletyper
Дата: 31.03.07 11:14
Оценка: 98 (5)
Зацените, пожалуйста, вот эту утилитку. В архиве лежат исходники и бинарники. Чтобы бинарники нормально запускались, нужно положить рядом с ними Nemerle.dll, желательно как можно более свежий.

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

Документации пока нет. Так что разбираться придётся по следующим источникам:

1. *.bnf — файлы в папке src/Generator. Правда, эти файлы содержат не всё, что необходимо, кроме того, они несколько устарели. Единственное рабочее описание грамматики — test2.bnf.
2. проект TestParser. Собственно, парсер в нём сгенерирован утилитой по описанию из test2.bnf.

Постараюсь некоторые вещи прокомментировать. Вот кусок test2.bnf с пояснениями:

// Здесь размещается список функций, конструирующих AST. Эти функции должны реально
// присутствовать в некотором модуле (NodeBuilder). Параметры этих
// функций должны совпадать с параметрами, описанными здесь. См. src/TestParser/NodeBuilder.n.
// Так же возможно описывать типы параметров после двоеточия (не тип .NET). На данный момент 
// параметры могут иметь типы node и bool, причём по умолчанию параметрам присваивается
// тип node. Соответсвенно, параметры типа node должны быть описаны как Node
// в NodeBuilder, а параметры типа bool должны быть описаны как bool в NodeBuilder.
ast
{
  fun Binary(first, op, second);
  fun Prefix(op, expr);
  fun Function(id, args);
  fun List(head, tail);
}

// Правила для лексического анализатора.
lexer (generate = false)
{
  // Подстановочные правила. Не будет создано типов лексем с именами таких правил.
  // Кроме того, эти правила видны только внутри текущего блока. Подстановочные
  // правила не могут быть рекурсивными.
  subst IdStart ::= "a".."z" | "A".."Z";
  subst Digit ::= "0".."9";
  subst IdPart ::= IdStart | Digit;
  subst Digits ::= Digit+;
  
  // А это обычные правила. По ним будут создаваться лексемы. Кроме того, на них
  // можно ссылаться из раздела parser.
  Identifier ::= IdStart IdPart*;
  Number ::= Digits ("." Digits)?;
  Space ::= " "+;
  
  // К таким правилам в разделе parser можно ссылаться не только по иднтификатору,
  // но и по строке.
  Comma ::= ",";
  Minus ::= "-";
  Plus ::= "+";
  Star ::= "*";
  Slash ::= "/";
  LeftParen ::= "(";
  RightParen ::= ")";
  
  // Так же определены операции:
  //   ~x - дополнение x 
  //   x & y - пересечение x и y
  //   x \ y - разность x и y (эквивалентно x & ~y)
  // Правда, эти операции работают только для односимвольных множеств
  // Существуют так же специальные предопределённые правила для символов Юникода и для некоторых других вещей.
  // Вот наиболее полезные: eof, any, empty.
}

// Правила для синтаксического анализатора. Синтаксис эквивалентен предыдущему разделу.
// Правда, для некоторых операций (+, *, ~, &, \) генератор напишет, что они не поддерживаются (пока).
// Нельзя использовать произвольные строки, как в lexer. Любая строка здесь рассматривается как 
// ссылка на правило из раздела lexer.
parser (type = LALR, root = Negative)
{
  // Эквивалентно Minus[op] Additive[expr] | Additive;
  Negative[Prefix] ::= "-"[op] Additive[expr] | Additive;

  // Казалось бы, можно сократить это и написать как-то так:
  //   (Additive[first] ("+" | "-")[op])? Multiplicative
  // но этот вариант неправильный, т.к. если бы свёртка произошла по правилу Additive -> Multiplicative, 
  // то была бы всё равно вызвана функция Binary с первыми двумя параметрами равными Node.Null. А
  // так ничего не вызвается, просто копируется вершина стека. Хотя, если залезть в NodeBuilder.Binary и
  // немного поменять его логику, то приемлем и сокращённый вариант.
  Additive[Binary] ::= Additive[first] ("+" | "-")[op] Multiplicative[second] | Multiplicative;
  
  Multiplicative[Binary] ::= Multiplicative[first] ("*" | "/")[op] Primitive[second] | Primitive;
  Primitive ::= Parenthesized | FunctionCall | Identifier | Number;
  Parenthesized ::= "(" Negative ")";
  FunctionCall[Function] ::= Identifier[id] "(" Args[args] ")";
  
  // А вот здесь ? допустим, т.к. для последнего элемента списка второй параметр должен быть равен null.
  Args[List] ::= Arg[head] ("," Args[tail])?;
  Arg ::= Negative;
}


Теперь по поводу самого парсера. Для нормальной работы в том же пространстве имён должны присуствовать два модуля — NodeBuilder и TokenManager. Вот как выглядит TokenManager для сгенерированного парсера:

  module TokenManager
  {
    public Filter(token : Token) : bool
    {
      match (token.TokenType)
      {
        | TokenType.Space 
        | TokenType.Error => false
        | _ => true
      }
    }
    
    public Eval(token : Token) : Node
    {
      match (token.TokenType)
      {
        | TokenType.Identifier => Node.Id(token.Locator, token.Text)
        // Нужно иметь ввиду небольшое ограничение (может быть, будет устранено в дальнейшем). А именно:
        // для лексем нельзя возвращать Node.Null
        | _ => Node.Token(token.Locator, token.TokenType)
      }
    }
  }


А вот NodeBuilder, в котором определены функции, описанные в разделе ast:

namespace Rsdn.ParserTools.Test
{
  module NodeBuilder
  {
    // Первые два параметра - loc и log, а остальные соответствуют описанию в разделе ast.
    // (правда, в дальнейшем нужно не передавать log каждый раз, а переделать NodeBuilder из
    // модуля в класс, в конструкторе которого при необходимости присутствует log)
    public Binary(loc : TextLocator, _ : IErrorLog, first : Node, op : Node, second : Node) : Node
    {
      match (op)
      {
        | Node.Token(TokenType.Plus) => Node.Function(loc, "+", [first, second])
        | Node.Token(TokenType.Minus) => Node.Function(loc, "-", [first, second])
        | Node.Token(TokenType.Star) => Node.Function(loc, "*", [first, second])
        | Node.Token(TokenType.Slash) => Node.Function(loc, "/", [first, second])
        | _ => throw Exception();
      }
    }
    
    public Prefix(loc : TextLocator, _ : IErrorLog, op : Node, expr : Node) : Node
    {
      match (op)
      {
        | Node.Token(TokenType.Minus) => Node.Function(loc, "-", [expr])
        | _ => throw Exception();
      }
    }
    
    public Function(loc : TextLocator, _ : IErrorLog, id : Node, args : Node) : Node
    {
      match ((id, args))
      {
        | (Node.Id(id), Node.List(args)) => Node.Function(loc, id, args)
        | _ => throw Exception();
      }
    }
    
    public List(loc : TextLocator, _ : IErrorLog, head : Node, tail : Node) : Node
    {
      match (tail)
      {
        | Node.List(l) => Node.List(loc, head :: l);
        | Node.Null() => Node.List(loc, [head]);
        | _ => throw Exception();
      }
    }
  }
}


Вы сами можете оценить сырость проекта. Но уже сделано немало. Надеюсь за 3-4 недели разобраться с генератором (сюда так же входит генерация парсера для самого генератора) и приступить к макросу, который то же самое может делать, не отрываясь от компилятора. Правда, пока я смутно представляю, как такое можно сделать.

Собственно, вопрос к народу: кто-нибудь хочет мне помочь? Если некогда писать код, так хотя бы поделитесь идеями и соображениями по поводу того, что можно такого прикрутить к генератору, что изменить, а что выкинуть. Да, кстати, нужно ещё подобрать хорошее название проекту (а то не называть же exe-шник именем generator.exe).

PS: Пример из TestParser продифференцирует введённое выражение (рядом с exe-шником должен лежать файл formula.txt) по x. Это я так, повеселился чуть-чуть
... << RSDN@Home 1.2.0 alpha rev. 672>>
Re: Compiler-compiler
От: VladD2 Российская Империя www.nemerle.org
Дата: 31.03.07 17:02
Оценка:
Здравствуйте, konsoletyper, Вы писали:

K>Чтобы бинарники нормально запускались, нужно положить рядом с ними Nemerle.dll, желательно как можно более свежий.


Ты бы лучше сам это сделал. Она не такая большая чтобы ее не смогли скачать. А вот людям мучиться.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re: Compiler-compiler
От: VladD2 Российская Империя www.nemerle.org
Дата: 31.03.07 17:02
Оценка:
Здравствуйте, konsoletyper, Вы писали:

K>// Здесь размещается список функций, конструирующих AST. Эти функции должны реально
K>// присутствовать в некотором модуле (NodeBuilder).

То есть используется паттерн Сторитель? Что же, разумно. В традиционных ООЯ — это, правда, приводит к тому, что делево получается излишне обобщенное, но паттерн-матчинг должен устранить этот недостаток.

K>// Параметры этих
K>// функций должны совпадать с параметрами, описанными здесь. См. src/TestParser/NodeBuilder.n.


Хм. А зачем дублировать описания? Зачем эта предекларация? Мемболее без типов.

K>// Так же возможно описывать типы параметров после двоеточия (не тип .NET).

Хм, а что значит "не тип .NET"?

K>// На данный момент 
K>// параметры могут иметь типы node и bool, причём по умолчанию параметрам присваивается
K>// тип node. Соответсвенно, параметры типа node должны быть описаны как Node
K>// в NodeBuilder, а параметры типа bool должны быть описаны как bool в NodeBuilder.
K>ast
K>{
K>  fun Binary(first, op, second);
K>  fun Prefix(op, expr);
K>  fun Function(id, args);
K>  fun List(head, tail);
K>}

Все это выглядить весма туманно и сумбурно.

K>// Правила для синтаксического анализатора. Синтаксис эквивалентен предыдущему разделу.
K>// Правда, для некоторых операций (+, *, ~, &, \) генератор напишет, что они не поддерживаются (пока).
K>// Нельзя использовать произвольные строки, как в lexer. Любая строка здесь рассматривается как 
K>// ссылка на правило из раздела lexer.

Вот это "Нельзя использовать произвольные строки" очень полхо. Как раз очень удобно определять промтые строки по месту. В Коке я это исползовал с большим удовольствием. Все же куда лучше читается "if" нежели какой-то идетнификатор.

K>parser (type = LALR, root = Negative)
K>{
K>  // Эквивалентно Minus[op] Additive[expr] | Additive;
K>  Negative[Prefix] ::= "-"[op] Additive[expr] | Additive;

K>  // Казалось бы, можно сократить это и написать как-то так:
K>  //   (Additive[first] ("+" | "-")[op])? Multiplicative
K>  // но этот вариант неправильный, т.к. если бы свёртка произошла по правилу Additive -> Multiplicative, 
K>  // то была бы всё равно вызвана функция Binary с первыми двумя параметрами равными Node.Null. А
K>  // так ничего не вызвается, просто копируется вершина стека. Хотя, если залезть в NodeBuilder.Binary и
K>  // немного поменять его логику, то приемлем и сокращённый вариант.
K>  Additive[Binary] ::= Additive[first] ("+" | "-")[op] Multiplicative[second] | Multiplicative;
  
K>  Multiplicative[Binary] ::= Multiplicative[first] ("*" | "/")[op] Primitive[second] | Primitive;
K>  Primitive ::= Parenthesized | FunctionCall | Identifier | Number;
K>  Parenthesized ::= "(" Negative ")";
K>  FunctionCall[Function] ::= Identifier[id] "(" Args[args] ")";
  
K>  // А вот здесь ? допустим, т.к. для последнего элемента списка второй параметр должен быть равен null.
K>  Args[List] ::= Arg[head] ("," Args[tail])?;
K>  Arg ::= Negative;
K>}
K>

[/c#]

Парсер выглидт очень хоршо. Информация о параметрах не перегружает грамматику и в то же время парсер может делать что-то полезное.

Только возникает один вопрос. А получится ли в таком раскладе обходиться без АСТ? Ведь для некоторых целей было бы удобно избегать его. Ну, например, если мы делаем интерпретатор простого ЯП. Или если мы реализуем некий механизм генерации документации.

Потом не ясно что такое Node? Где он описан? Кем? Можно ли его настраивать по себя?

K>Собственно, вопрос к народу: кто-нибудь хочет мне помочь? Если некогда писать код, так хотя бы поделитесь идеями и соображениями по поводу того, что можно такого прикрутить к генератору, что изменить, а что выкинуть. Да, кстати, нужно ещё подобрать хорошее название проекту (а то не называть же exe-шник именем generator.exe).


Помечь могу, к сожалению, только советами. Но у меня есть встречное предложение. Есть реальная (боевая) задача. На РСДН дико тормозит форматер. Тормозит он из-за того, что использует регексы производства MS, а они написаны из рук вон плохо (исползуют НКА).
Вот если бы твой лексер приспособить для использования в форматере, то пользователем твоего лексера могли бы стать сразу по 15 тысяч в день .

K>PS: Пример из TestParser продифференцирует введённое выражение (рядом с exe-шником должен лежать файл formula.txt) по x. Это я так, повеселился чуть-чуть
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[2]: Compiler-compiler
От: konsoletyper Россия https://github.com/konsoletyper
Дата: 31.03.07 19:10
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>
K>>// Параметры этих
K>>// функций должны совпадать с параметрами, описанными здесь. См. src/TestParser/NodeBuilder.n.
VD>


VD>Хм. А зачем дублировать описания? Зачем эта предекларация? Мемболее без типов.


Ну, не парсить же мне файл NodeBuilder.n. Вот в макросе эта проблема, думаю, будет решена.

VD>
K>>// Так же возможно описывать типы параметров после двоеточия (не тип .NET). 
VD>

VD>Хм, а что значит "не тип .NET"?

Это тип данных генератора. Никакого отношения к типам данных из .NET Framework не имеет. Опять же, будет пофиксено в макросе.

VD>
K>>// На данный момент 
K>>// параметры могут иметь типы node и bool, причём по умолчанию параметрам присваивается
K>>// тип node. Соответсвенно, параметры типа node должны быть описаны как Node
K>>// в NodeBuilder, а параметры типа bool должны быть описаны как bool в NodeBuilder.
K>>ast
K>>{
K>>  fun Binary(first, op, second);
K>>  fun Prefix(op, expr);
K>>  fun Function(id, args);
K>>  fun List(head, tail);
K>>}
VD>

VD>Все это выглядить весма туманно и сумбурно.

Да, я так и не нашёл подходящих слов, чтобы описать это. Думаю, достаточно одним глазом взглянуть на NodeBuilder и станет понятно, что я имел в виду.

VD>
K>>// Правила для синтаксического анализатора. Синтаксис эквивалентен предыдущему разделу.
K>>// Правда, для некоторых операций (+, *, ~, &, \) генератор напишет, что они не поддерживаются (пока).
K>>// Нельзя использовать произвольные строки, как в lexer. Любая строка здесь рассматривается как 
K>>// ссылка на правило из раздела lexer.
VD>

VD>Вот это "Нельзя использовать произвольные строки" очень полхо. Как раз очень удобно определять промтые строки по месту. В Коке я это исползовал с большим удовольствием. Все же куда лучше читается "if" нежели какой-то идетнификатор.

Ну так и тут можно. Просто тот же "if" долен быть определён в секции lexer. Так нужно, потому что большинство языков проектируются из тех соображений, что их будут парсить сначала лексическим анализатором, а потом синтаксическим. Если же смешивать сразу два анализа в одном, то кроме снижения быстродействия мы получим несколько неожиданный результат.

VD>
K>>parser (type = LALR, root = Negative)
K>>{
K>>  // Эквивалентно Minus[op] Additive[expr] | Additive;
K>>  Negative[Prefix] ::= "-"[op] Additive[expr] | Additive;

K>>  // Казалось бы, можно сократить это и написать как-то так:
K>>  //   (Additive[first] ("+" | "-")[op])? Multiplicative
K>>  // но этот вариант неправильный, т.к. если бы свёртка произошла по правилу Additive -> Multiplicative, 
K>>  // то была бы всё равно вызвана функция Binary с первыми двумя параметрами равными Node.Null. А
K>>  // так ничего не вызвается, просто копируется вершина стека. Хотя, если залезть в NodeBuilder.Binary и
K>>  // немного поменять его логику, то приемлем и сокращённый вариант.
K>>  Additive[Binary] ::= Additive[first] ("+" | "-")[op] Multiplicative[second] | Multiplicative;
  
K>>  Multiplicative[Binary] ::= Multiplicative[first] ("*" | "/")[op] Primitive[second] | Primitive;
K>>  Primitive ::= Parenthesized | FunctionCall | Identifier | Number;
K>>  Parenthesized ::= "(" Negative ")";
K>>  FunctionCall[Function] ::= Identifier[id] "(" Args[args] ")";
  
K>>  // А вот здесь ? допустим, т.к. для последнего элемента списка второй параметр должен быть равен null.
K>>  Args[List] ::= Arg[head] ("," Args[tail])?;
K>>  Arg ::= Negative;
K>>}
K>>


VD>Парсер выглидт очень хоршо. Информация о параметрах не перегружает грамматику и в то же время парсер может делать что-то полезное.


VD>Только возникает один вопрос. А получится ли в таком раскладе обходиться без АСТ? Ведь для некоторых целей было бы удобно избегать его. Ну, например, если мы делаем интерпретатор простого ЯП. Или если мы реализуем некий механизм генерации документации.


VD>Потом не ясно что такое Node? Где он описан? Кем? Можно ли его настраивать по себя?


Node — это вариант, который нужно описать самостоятельно. Требование к нему — общее для всех опций поле loc типа TextLocator, конструктор, принимающий первым параметром loc, метод и наличие опции под названием Null без параметров. Причём от последнего требования можно отказаться, используя повсюду option[Node], но это менее удобно.

Что касается AST. Node (в общем случае, вообще что угодно, зависит от настроек) не обязан быть элементом AST. Node — это то, что возвращают функции builder'а и принимают в качестве параметра. При этом функции вовсе не обязаны конструировать дерево.

K>>Собственно, вопрос к народу: кто-нибудь хочет мне помочь? Если некогда писать код, так хотя бы поделитесь идеями и соображениями по поводу того, что можно такого прикрутить к генератору, что изменить, а что выкинуть. Да, кстати, нужно ещё подобрать хорошее название проекту (а то не называть же exe-шник именем generator.exe).


VD>Помечь могу, к сожалению, только советами. Но у меня есть встречное предложение. Есть реальная (боевая) задача. На РСДН дико тормозит форматер. Тормозит он из-за того, что использует регексы производства MS, а они написаны из рук вон плохо (исползуют НКА).

VD>Вот если бы твой лексер приспособить для использования в форматере, то пользователем твоего лексера могли бы стать сразу по 15 тысяч в день .

Это можно. На данный момент всё достаточно сильно захардкожено. Но как только я это исправлю, можно будет скармливать bnf-файл без раздела parser, тогда будет генериться один только лексер. Так же я думаю, не стоит делать в TokenizerHelper поддержку ITokenSource, а вместо этого генерить (опционально) класс Tokenizer, который оборачивает TokenizerHelper для работы с парсером. Если же работа с парсером не критична, но нужны все лексемы, в т.ч. пробелы и комментарии, то можно будет не генерить Tokenizer, а непосредственно использовать TokenizerHelper.

Кстати, примемлема ли статическая компиляция в данном случае? А то возможно, придётся написать дополнительный класс (врочем, использующую ту же базу), который генерил бы DFA в рантайме во временную сборку. И ещё насчёт лексера. Всё, что относится к преобразованию Regexps -> NFA -> DFA писал mаrvin, за что ему большое спасибо. В принципе, я могу заниматься поддержкой его кода при необходимости. Но не знаю, как он сам к этому отнесётся.
... << RSDN@Home 1.2.0 alpha rev. 672>>
Re[2]: Compiler-compiler
От: konsoletyper Россия https://github.com/konsoletyper
Дата: 31.03.07 19:10
Оценка:
Здравствуйте, VladD2, Вы писали:

K>>Чтобы бинарники нормально запускались, нужно положить рядом с ними Nemerle.dll, желательно как можно более свежий.


VD>Ты бы лучше сам это сделал. Она не такая большая чтобы ее не смогли скачать. А вот людям мучиться.


Ага, а места на личные файлы мне кто даст? А то это уже третья ревизия которую я сюда высылаю.
... << RSDN@Home 1.2.0 alpha rev. 672>>
Re[3]: Compiler-compiler
От: VladD2 Российская Империя www.nemerle.org
Дата: 31.03.07 20:11
Оценка:
Здравствуйте, konsoletyper, Вы писали:

K>Ага, а места на личные файлы мне кто даст? А то это уже третья ревизия которую я сюда высылаю.


А что сайт тебе не дает?
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: Compiler-compiler
От: VladD2 Российская Империя www.nemerle.org
Дата: 31.03.07 20:11
Оценка:
Здравствуйте, konsoletyper, Вы писали:

K>Ну, не парсить же мне файл NodeBuilder.n. Вот в макросе эта проблема, думаю, будет решена.


А почему бы и нет? Компилятор это позволяет сделать.

K>Это тип данных генератора. Никакого отношения к типам данных из .NET Framework не имеет. Опять же, будет пофиксено в макросе.


Ясно.

K>Ну так и тут можно. Просто тот же "if" долен быть определён в секции lexer.


Тупое дублирование? Нехорошо...

K> Так нужно, потому что большинство языков проектируются из тех соображений, что их будут парсить сначала лексическим анализатором, а потом синтаксическим. Если же смешивать сразу два анализа в одном, то кроме снижения быстродействия мы получим несколько неожиданный результат.


У Коки это проблем не вызвает.

K>Node — это вариант, который нужно описать самостоятельно. Требование к нему — общее для всех опций поле loc типа TextLocator,


Почему не просто Location из компилятора?

K>Что касается AST. Node (в общем случае, вообще что угодно, зависит от настроек) не обязан быть элементом AST. Node — это то, что возвращают функции builder'а и принимают в качестве параметра. При этом функции вовсе не обязаны конструировать дерево.


Ясно. А как в него помещается информацию о распасенных данных? Ну, там значения идентификторов, литералов...

K>Это можно. На данный момент всё достаточно сильно захардкожено. Но как только я это исправлю, можно будет скармливать bnf-файл без раздела parser, тогда будет генериться один только лексер. Так же я думаю, не стоит делать в TokenizerHelper поддержку ITokenSource, а вместо этого генерить (опционально) класс Tokenizer, который оборачивает TokenizerHelper для работы с парсером. Если же работа с парсером не критична, но нужны все лексемы, в т.ч. пробелы и комментарии, то можно будет не генерить Tokenizer, а непосредственно использовать TokenizerHelper.


Ну, вот как раз на реальной задаче и обкатаешь все.

Кстати, у тебя лексер ДКА стрит? В смысмле, он будет максимально шустрый? Потому как у нас это основное требование окромя декларативности.

K>Кстати, примемлема ли статическая компиляция в данном случае?


Более чем. Языки добавляются раз в год по обещанию. Важно только чтобы этот процесс был прост. А перекомпиляцию мы переживем.

K> А то возможно, придётся написать дополнительный класс (врочем, использующую ту же базу), который генерил бы DFA в рантайме во временную сборку.


Ага, ну, на вопрос с ДКА ответ уже есть . Рантайма не надо. Надо на стадии компиляции все разруливать. Макрос был бы вообще идеальным решением.

K> И ещё насчёт лексера. Всё, что относится к преобразованию Regexps -> NFA -> DFA писал mаrvin, за что ему большое спасибо. В принципе, я могу заниматься поддержкой его кода при необходимости. Но не знаю, как он сам к этому отнесётся.


Ну, а в чем проблема у него самого и спросить? Можно его сюда позвать?
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: Compiler-compiler
От: konsoletyper Россия https://github.com/konsoletyper
Дата: 01.04.07 04:23
Оценка:
Здравствуйте, VladD2, Вы писали:

K>>Ну так и тут можно. Просто тот же "if" долен быть определён в секции lexer.


VD>Тупое дублирование? Нехорошо...


Это не тупое дублирование. Просто в секции lexer мы этому "if" присвоим имя, которое будет добавленов TokenType. Это полезно, если мы захотим использовать лексер независимо. Ну а дублирование и так может получиться из-за особенностей грамматики. Например, те же круглые скобки во многих языках встречаются во многих местах.

K>>Node — это вариант, который нужно описать самостоятельно. Требование к нему — общее для всех опций поле loc типа TextLocator,


VD>Почему не просто Location из компилятора?


Потому что со временем я буду сближать проект с ncc. А пока лень разбираться с кодом компилятора из-за того, что нет интеграции и, соответственно, навигации по коду.

K>>Что касается AST. Node (в общем случае, вообще что угодно, зависит от настроек) не обязан быть элементом AST. Node — это то, что возвращают функции builder'а и принимают в качестве параметра. При этом функции вовсе не обязаны конструировать дерево.


VD>Ясно. А как в него помещается информацию о распасенных данных? Ну, там значения идентификторов, литералов...


Значения идентификаторов и литералов вычисляются методом ITokenSource.Eval : Token -> Node. Стандартная реализация данного интерфейса перенаправляет вызов к TokenManager.Eval.

VD>Кстати, у тебя лексер ДКА стрит? В смысмле, он будет максимально шустрый? Потому как у нас это основное требование окромя декларативности.


Да, лекер максимально шустрый. Можно ещё на досуге поковыряться с битовыжиманием, чтобы получилось ещё быстрее. В макросе вообще можно перейти от лексера-интерпретатора к скомпилированному лексеру. Например, бинарный поиск по таблице можно будет заменить кодом, что даст некоторый прирост. Вот только интересно, насколько быстро выполняется инструкция IL switch? Не быстрее ли выйдет "бинарный поиск" на if-ах?

В общем, если возникла необходимость, я могу немного притормозить работу с парсером и сделать специальный макрос для лексичесекого анализа.
... << RSDN@Home 1.2.0 alpha rev. 672>>
Re[5]: Compiler-compiler
От: VladD2 Российская Империя www.nemerle.org
Дата: 01.04.07 15:07
Оценка:
Здравствуйте, konsoletyper, Вы писали:

K>Это не тупое дублирование. Просто в секции lexer мы этому "if" присвоим имя, которое будет добавленов TokenType.


Это детали реализации. Всегда надо думать о пользователе, а не о своих проблемах реализации. Точнее так, желательно выбирать более удобные варианты.

K> Это полезно, если мы захотим использовать лексер независимо.


Для этого их достаточно разрести в разные классы. В общем, я бы все же предоставил возможность использования строковых литералов по месту (без объявления в лексере).

K>Потому что со временем я буду сближать проект с ncc.


Ясно.

K>А пока лень разбираться с кодом компилятора из-за того, что нет интеграции и, соответственно, навигации по коду.


Ну, это в общем-то есть.

K>Да, лекер максимально шустрый.


Это то что нам нужно. А то вот буквально недавно была авральная проблема когда кто-то (если не ошибаюсь) Ява-скрипт большой запихнул. Сервер встал раком. Хотя казалось бы что за проблема подсветить скрипт?

K> Можно ещё на досуге поковыряться с битовыжиманием, чтобы получилось ещё быстрее.


Ну, это, я думаю, не обязательно. Главное, чтобы алгоритмически все было ОК.

K> В макросе вообще можно перейти от лексера-интерпретатора к скомпилированному лексеру.


Да, а вот это полезно. Скомпилированный код обычно в 10 раз шустрее интерпретируемого. В прочем, если есть алгоритмические проблемы как MS regexp, то компиляция будет как мервтому припарка.

K> Например, бинарный поиск по таблице можно будет заменить кодом, что даст некоторый прирост.


А зачем бинарный поиск использовать? Лучше хэш-таблицу использовать. У нее, на больших объемах, производительность существнно лучше.

K> Вот только интересно, насколько быстро выполняется инструкция IL switch? Не быстрее ли выйдет "бинарный поиск" на if-ах?


Она хитро переписывается в зависимости от типа данных. Для строк применяется хэш-табица (причем не самого лучшего исполнения). Для целых довольно хитрая логика. В худшем случае будет как раз бинарный поиск. Так что об этом думать смысла нет. switch в любом случае будет не худшим выходом.

Но для строк проще Dictionary[] использовать.

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


Ну, у нас сейчас подготовительная фаза. Ругаемся по поводу выбора инструментов и подходоы . Так что время еще есть. Но затягивать конечно не стоит.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[6]: Compiler-compiler
От: konsoletyper Россия https://github.com/konsoletyper
Дата: 01.04.07 18:05
Оценка:
Здравствуйте, VladD2, Вы писали:

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


K>>Это не тупое дублирование. Просто в секции lexer мы этому "if" присвоим имя, которое будет добавленов TokenType.


VD>Это детали реализации. Всегда надо думать о пользователе, а не о своих проблемах реализации. Точнее так, желательно выбирать более удобные варианты.


OK. Тогда, думаю, стоит сделать опцию (включённую по-умолчанию). Если эта опция включена, то для всех строк внутри секции parser генерится по лексеме, при этом делается попытка сгенерить наиболее удобночитаемое название (для switch — "SwitchKeyword", для "*=" — "StarEq" и т.д.). Если же пользователь хочет сам задать имена для таких лексем и заодно получить compile-time контроль (чтобы гарантировать, что он не наберёт по ошибке в двух разных местах "switch" и "swicht"), то он выключает опцию (думаю, это будет особенно полезно для больших парсеров).

Кроме того, в ближайших планах сделать для лексера автоопределение ключевых слов. При этом будет предварительно строиться ДКА для всех правил, исключая правила вида

Идентификатор ::= "строка";


Затем каждое такое правило прогоняется на ДКА. Те правила, которые принимаются автоматом, заносятся в список ключевых слов и в окончательный ДКА не заносятся (будут определяться по-другому). Благодаря этому полученный автомат будет гораздо меньше (иногда в сотни раз) и, как не странно, генериться гораздо быстрее.

Так вот, а что если подобную технику применить в парсере? Если в разделе parser где-то встречается строка, то она прогоняется на ДКА. При этом, если стока принимается, то правило грамматики немного модифицируется — вместо строки подставляется терминал, который генерит ДКА по этой строке, причём делается специальная пометка, что нужно дополнительно проверить строку на равенство. Фактически, так можно облегчить парсинг псевдо-ключевых слов (вроде get, set, yield в C#).

K>> Это полезно, если мы захотим использовать лексер независимо.


VD>Для этого их достаточно разрести в разные классы. В общем, я бы все же предоставил возможность использования строковых литералов по месту (без объявления в лексере).


Это кого нужно разносить в разные классы?

K>> Например, бинарный поиск по таблице можно будет заменить кодом, что даст некоторый прирост.


VD>А зачем бинарный поиск использовать? Лучше хэш-таблицу использовать. У нее, на больших объемах, производительность существнно лучше.


Хэш-таблицу не всегда возможно использовать. Если лексика простая и каждый переход между состояниями включает не более ста символов, то можно обойтись и хэш-таблицей. А вот если активно используется юникод (кстати, для этого определены правила вида U_Код, где код — двухбуквенное обозначение класса юникода), то придётся иметь дело уже с тысячами символов. Зато там будут очень длинные непрерывне интервалы. Вот такие вещи удобнее всего отслеживать при помощи бинарного поиска. Причём бинарный поиск можно сделать в compile-time, т.е. будет генериться что-то вроде:

if (x < 50)
    if (x < 25)
        if (x < 12)
            0
        else
            1
    else 2
else -1


Соотвественно, здесь захардкожен посик по "таблице" из 50 элементов, где первые 12 элеметов — нули, следующие 12 — единицы, и остальные 25 — двойки. Нетрудно представить, какой выигрыш это даст, если "таблица" состоит из 5000 тыс. элементов, при этом поделена на 5 — 6 непрерывных интервалов.

K>> Вот только интересно, насколько быстро выполняется инструкция IL switch? Не быстрее ли выйдет "бинарный поиск" на if-ах?


VD>Она хитро переписывается в зависимости от типа данных. Для строк применяется хэш-табица (причем не самого лучшего исполнения). Для целых довольно хитрая логика. В худшем случае будет как раз бинарный поиск. Так что об этом думать смысла нет. switch в любом случае будет не худшим выходом.


VD>Но для строк проще Dictionary[] использовать.


Какие строки? ДКА работает целиком на символах. Словарь планируется использовать для ключевых слов.

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


VD>Ну, у нас сейчас подготовительная фаза. Ругаемся по поводу выбора инструментов и подходоы . Так что время еще есть. Но затягивать конечно не стоит.


На самом деле, макрос — это программа-максимум. Если не успею (мало ли что случится), то можно вполне обойтись генератором.
... << RSDN@Home 1.2.0 alpha rev. 672>>
Re[7]: Compiler-compiler
От: VladD2 Российская Империя www.nemerle.org
Дата: 02.04.07 15:28
Оценка:
Здравствуйте, konsoletyper, Вы писали:

K>OK. Тогда, думаю, стоит сделать опцию (включённую по-умолчанию). Если эта опция включена, то для всех строк внутри секции parser генерится по лексеме, при этом делается попытка сгенерить наиболее удобночитаемое название (для switch — "SwitchKeyword", для "*=" — "StarEq" и т.д.).


+1

K>Так вот, а что если подобную технику применить в парсере? Если в разделе parser где-то встречается строка, то она прогоняется на ДКА. При этом, если стока принимается, то правило грамматики немного модифицируется — вместо строки подставляется терминал, который генерит ДКА по этой строке, причём делается специальная пометка, что нужно дополнительно проверить строку на равенство. Фактически, так можно облегчить парсинг псевдо-ключевых слов (вроде get, set, yield в C#).


Такие "ключевые" слова я просто добавлял в правило Identifier (которое было определно не в лексере, а как правило грамматики). Примерно так:
Identifier ::= identifier | "get" | "set" | "yield";

Тогда нет никаких проблем использовать их в правилах других элементов как ключевые слова.

K>Это кого нужно разносить в разные классы?


Код лексера и парсера. Тогда кому нужно просто подключит класс лексера и будет использовать только его.

K>Хэш-таблицу не всегда возможно использовать. Если лексика простая и каждый переход между состояниями включает не более ста символов, то можно обойтись и хэш-таблицей. А вот если активно используется юникод (кстати, для этого определены правила вида U_Код, где код — двухбуквенное обозначение класса юникода), то придётся иметь дело уже с тысячами символов. Зато там будут очень длинные непрерывне интервалы. Вот такие вещи удобнее всего отслеживать при помощи бинарного поиска.


Я в модифицированной Коке делал так.
В коке список символов опеделелялся битовым массивом (изначально 256 элементов). При генерации кода распознования производился проход по этим битовым массивам и для них генерировался код вроде того что ты привел (см. ниже). Я добавил в этом список еще один бит означающий, что это юникодный симовол. Обычно в разборе не важно, что за символ, но важно что это символ. Решение конечно не полное, но эффективное.

K> Причём бинарный поиск можно сделать в compile-time, т.е. будет генериться что-то вроде:


K>
K>if (x < 50)
K>    if (x < 25)
K>        if (x < 12)
K>            0
K>        else
K>            1
K>    else 2
K>else -1
K>


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

K>Соотвественно, здесь захардкожен посик по "таблице" из 50 элементов, где первые 12 элеметов — нули, следующие 12 — единицы, и остальные 25 — двойки. Нетрудно представить, какой выигрыш это даст, если "таблица" состоит из 5000 тыс. элементов, при этом поделена на 5 — 6 непрерывных интервалов.


Таблицы для юникода вряд ли будут эффективны. Ну, да тебе виднее.

K>Какие строки? ДКА работает целиком на символах. Словарь планируется использовать для ключевых слов.


О них и речь. Они, будучи записанными как ДКА, займут у тебя 80% объема кода и будут работать медленее в слествии вылетакния из кэша.

K>На самом деле, макрос — это программа-максимум. Если не успею (мало ли что случится), то можно вполне обойтись генератором.


Как показывает практика, генератор — это очень неудобно. Хорошо бы конечно иметь более встроенное решение.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[6]: Compiler-compiler
От: _pk_sly  
Дата: 14.04.07 10:02
Оценка:
VD>Это то что нам нужно. А то вот буквально недавно была авральная проблема когда кто-то (если не ошибаюсь) Ява-скрипт большой запихнул. Сервер встал раком. Хотя казалось бы что за проблема подсветить скрипт?

На сколько я понимаю, подсветка генерится в момент отображения сообщения?

Может быть, не в кассу.. но напрашивается два решения проблемы:
1. парсить подсветку в момент добавления сообщения. парсить придётся один раз, подсветку можно либо внедрить в само сообщение тэгами либо хранить в каком-то виде рядом.
2. написать подвечивалку на жаваскрипте и перенести эту нагрузку на клиентов. реализовать это можно так, что даже в случае гигабайтных скриптов, клиент не зависнет.
Re[7]: Compiler-compiler
От: VladD2 Российская Империя www.nemerle.org
Дата: 14.04.07 22:04
Оценка: +1
Здравствуйте, _pk_sly, Вы писали:

__>На сколько я понимаю, подсветка генерится в момент отображения сообщения?


__>Может быть, не в кассу.. но напрашивается два решения проблемы:

__>1. парсить подсветку в момент добавления сообщения. парсить придётся один раз, подсветку можно либо внедрить в само сообщение тэгами либо хранить в каком-то виде рядом.

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

__>2. написать подвечивалку на жаваскрипте и перенести эту нагрузку на клиентов. реализовать это можно так, что даже в случае гигабайтных скриптов, клиент не зависнет.


Вариант с ява-скриптом конечно интересен, но боюсь это будет а) не очень универсально, б) тормозить на клиенте.

Все же самое верное решение — это генерация полноценного ДКА и компиляция его хорошим компилятором.

Собсвтенно вот на базе этого
Автор: konsoletyper
Дата: 13.04.07
— это и можно сделать.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[7]: Compiler-compiler
От: ie Россия http://ziez.blogspot.com/
Дата: 15.04.07 06:40
Оценка:
Здравствуйте, _pk_sly, Вы писали:

__>Может быть, не в кассу.. но напрашивается два решения проблемы:

__>1. парсить подсветку в момент добавления сообщения. парсить придётся один раз,

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

__>подсветку можно либо внедрить в само сообщение тэгами


Это плохо, выще написал почему.

__>либо хранить в каком-то виде рядом.


Это уже более удачный вариант, но не факт, что он будет лучше, чем применение ДКА.

__>2. написать подвечивалку на жаваскрипте и перенести эту нагрузку на клиентов. реализовать это можно так, что даже в случае гигабайтных скриптов, клиент не зависнет.


Тормоза на клиенте — это последнее, за счет чего можно оптимизировать сервер.
... << RSDN@Home 1.2.0 alpha rev. 655>>
Превратим окружающую нас среду в воскресенье.
Re: Compiler-compiler
От: konsoletyper Россия https://github.com/konsoletyper
Дата: 30.04.07 19:56
Оценка:
Сделал макрос, создающий по BNF лексер. Архив с исходниками и бинарниками лежит здесь. Кроме макроса в архиве лежит последняя версия генератора. В папке bin лежат бинарники — генератор и макрос. В папке src кроме всего прочего присутствует проект TestMacro, который советую посмотреть в первую очередь.

Макрос пока ещё сыроват. Нельзя задать имена для генерируемых классов. Так же наверняка в макросе есть куча невыявленных глюков.

Я проделал некоторую работу, чтобы уменьшить объём кода, но у меня это не очень-то получилось. Так что придётся бить лексер на методы, а то, боюсь, один большой метод может и непроJITиться.

Кстати, макрос очень даже дружит с Интеграцией.

PS: есть к народу маленькая просьба: не могли бы вы сделать маленький бенчмарк? А то у меня нет некоторых вещей: опыта делать бенчмарки, хороших генераторов лексеров и больших файлов на C#.
... << RSDN@Home 1.2.0 alpha rev. 672>>
Re[2]: Compiler-compiler
От: konsoletyper Россия https://github.com/konsoletyper
Дата: 01.05.07 09:23
Оценка:
Решил сам сделать бенчмарк, чтобы сравнить хотя бы макрос и генератор.

Замерял производительность вот такого кода:

using Nemerle.Utility;
using Nemerle.Imperative;
using System;
using System.IO;
using Rsdn.ParserTools;


namespace Rsdn.ParserTools.Benchmark
{
  [assembly:Bnf(
    bnf
    {
      lexer
      {
        Comment ::= SingleLineComment | DelimitedComment;
        subst SingleLineComment ::= "//" CommentInputChar* (NewLineChar | "\u000D" "\u000A");
        subst CommentInputChar ::= ~NewLineChar;
        subst NewLineChar ::= "\u000D" | "\u000A" | "\u0085" | "\u2028" | "\u2029" | eof;
        subst DelimitedComment ::= "/*" (~"*" | "*"+ ~"/")* "*"+ "/" | eof;
        Whitespace ::= (PureWhitespace | NewLineChar | eof)+;
        subst PureWhitespace ::= U_Zs | "\u0009" | "\u000B" | "\u000C";
        subst UnicodeEscapeSequence ::=
          "\\u" HexDigit HexDigit HexDigit HexDigit |
          "\\U" HexDigit HexDigit HexDigit HexDigit HexDigit HexDigit HexDigit HexDigit;
        subst HexDigit ::= "0".."9" | "A".."F" | "a".."f";
        Identifier ::= "@"? IdentifierStartCharacter IdentifierPartCharacter*;
        subst IdentifierStartCharacter ::= U_Ll | U_Lu | U_Lt | U_Lm | U_Lo | 
            UnicodeEscapeSequence | "_";
        subst IdentifierPartCharacter ::=
            U_Nl | U_Ll | U_Lu | U_Lt | U_Lm | U_Lo | U_Nd |
            UnicodeEscapeSequence | U_Mc | U_Mn | "_";
          
        Keyword ::=
            "abstract" | "as" | "base" | "bool" | "break" | "byte" | "case" | "catch" |
            "char" | "checked" | "class" | "const" | "continue" | "decimal" | "default" |
            "delegate" | "do" | "double" | "else" | "enum" | "event" | "explicit" |
            "extern" | "false" | "finally" | "fixed" | "float" | "for" | "foreach" |
            "goto" | "if" | "implicit" | "in" | "int" | "interface" | "internal" | "is" |
            "lock" | "long" | "namespace" | "new" | "null" | "object" | "operator" |
            "out" | "override" | "params" | "private" | "protected" | "public" | "readonly" |
            "ref" | "return" | "sbyte" | "sealed" | "short" | "sizeof" | "stackalloc" |
            "static" | "string" | "struct" | "switch" | "this" | "throw" | "true" |
            "try" | "typeof" | "uint" | "ulong" | "unchecked" | "unsafe" | "ushort" |
            "using" | "virtual" | "void" | "volatile" | "while";
        
        RealLiteral ::=
            DecimalDigit+ "." DecimalDigit+ ExponentPart? RealTypeSuffix? |
            "." DecimalDigit+ ExponentPart? RealTypeSuffix? |
            DecimalDigit+ ExponentPart? RealTypeSuffix? |
            DecimalDigit+ RealTypeSuffix?;
        IntegerLiteral ::= DecIntLiteral | HexIntLiteral;
        subst DecIntLiteral ::= DecimalDigit+ IntegerTypeSuffix?;
        subst DecimalDigit ::= "0".."9";
        subst LSuff ::= "L" | "l";
        subst USuff ::= "U" | "u";
        subst IntegerTypeSuffix ::= USuff | LSuff | USuff LSuff | LSuff USuff;
        subst HexIntLiteral ::= ("0x" | "0X") HexDigit+ IntegerTypeSuffix?;
        subst ExponentPart ::= ("e" | "E") Sign? DecimalDigit+;
        subst Sign ::= "+" | "-";
        subst RealTypeSuffix ::= "F" | "f" | "D" | "d" | "M" | "m";
        CharLiteral ::= "'" Character "'";
        subst Character ::=
          SingleCharacter | SimpleEscapeSequence |
          HexEscapeSequence | UnicodeEscapeSequence;
        subst SingleCharacter ::= ~("\u0027" | "\u005C" | NewLineChar);
        subst SimpleEscapeSequence ::=
          "\\'" | "\\\"" | "\\\\" | "\\0" | "\\a" | "\\b" |
          "\\f" | "\\n" | "\\r" | "\\t" | "\\v";
        subst HexEscapeSequence ::= "\\x" HexDigit HexDigit? HexDigit? HexDigit?;
        StringLiteral ::= RegularStringLiteral | VerbatimStringLiteral;
        subst RegularStringLiteral ::= "\"" RegularStringChar* "\"";
        subst RegularStringChar ::= ~("\u0022" | "\u005C" | NewLineChar) |
          SimpleEscapeSequence | HexEscapeSequence | UnicodeEscapeSequence;
        subst VerbatimStringLiteral ::= "@\"" VerbatimStringChar* "\"";
        subst VerbatimStringChar ::= ~"\"" | "\"\"";
        
        Brace ::= "{" | "}";
        Paren ::= "[" | "]" | "(" | ")";
        Dot ::= ".";
        Comma ::= ",";
        Semicolon ::= ";";
        
        Symbol ::=
            ":" | "::" | "+" | "-" | "*" | "/" | "%" | "&" | "|" | "^" | "!" |
            "~" | "=" | "<" | ">" | "?" | "??" | "++" | "--" | "&&" | "||" |
            "<<" | ">>" | "==" | "!=" | "<=" | ">=" | "+=" | "-=" | "*=" |
            "/=" | "%=" | "&=" | "|=" | "^=" | "<<=" | ">>=" | "->";
        Preprocessor ::= "#" (any \ NewLineChar)*;

        /*
         *  Hacks
         */
        IntegerLiteral_Dot ::= DecIntLiteral ".";
      }
    }
  )]

  module Starter
  {
    Main(args : array[string]) : void
    {
      def url = args[0];
      def reader = StreamReader(url);
      def lexer = LexerHelper(reader, url);
      
      def start = DateTime.Now.Ticks;
      mutable count = 0;
      while (true)
      {
        def token = lexer.GetToken();
        when (token.TokenType == TokenType.Eof)
          break;
        count++;
      }
      def end = DateTime.Now.Ticks;
      def elapsed = (end - start) / 10000;
      reader.Close();
      
      Console.WriteLine($"elapsed: $elapsed, tokens: $count");
    }
  }
}


Для генератора код такой же, только содержимое макроса перенесено в отдельный bnf-файл.

Сразу хочу сказать, что я не записал результатов первоначального бенчмарка. А результаты меня поразили: оказалось, что лексер, полученный при помощи npg (далее, сгенерированный лексер) работает в 4 раза медленнее, чем лексер, полученный из макроса (далее, макролексер). Так что я принялся за оптимизацию.

Первое, что я сделал — разбил тело метода GetNextState, вынеся особо объёмные состояния в отдельные методы. Это неожиданно привело к резкому уменьшению размера сборки и времени компиляции. Затем убрал "оптимизацию", при которой некоторые переходные символы записывались в словарь — это только тормозило ДКА. Наконец, рефлектор показал, что ncc никак не оптимизирует match'и вида:

match (n)
{
    0 => ...
    1 => ...
    2 => ...
    ...
}


Так что метод TransformToken (отвечающий за ключевые слова) я сильно ускорил за счёт статического словаря, а метод GetNextState — за счёт статического массива array[int -> int]. Странно это как-то — по идее ncc обязан оптитмизировать такие случаи и генерить по ним switch вместо последовательности if'ов, либо же в Nemerle нужен switch/case (лично я за первый подход).

Так вот, в результата моих мучений я пришёл к следующим результатам. Тестирование производилось на машине с процессором Athlon XP 2100, анализировался файл размером 16Mб с 648840 строками и 3713015 лексемами (включая пробелы и комментарии).


Как видите, макролексер работает несколько медленнее, что странно, т.к. сгенерированный лексер испольует таблицу, а в макролексере часть ДКА компилируется в код. Надо бы с этим разобраться.

PS: улучшенную версию BnfMacro я вышлю несколько позже, как только добавлю несколько маленьких фич.
... << RSDN@Home 1.2.0 alpha rev. 672>>
Re[3]: Compiler-compiler
От: VladD2 Российская Империя www.nemerle.org
Дата: 01.05.07 12:23
Оценка:
Здравствуйте, konsoletyper, Вы писали:

K>

Из сказанного не ясно что же такое "макролексер" и чем от отличается от ]сгенерированнного.

К тому же 6 секунд как-то много. Я конечно понимаю 16 Мб, но мне кажется и на 16 Мб все должно работать шустрее. Отсюда ворос — а что за код был в этом файле? Правильно ли я понимаю, что производился только лексический разбор C#-подобного языка?
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re: Compiler-compiler
От: konsoletyper Россия https://github.com/konsoletyper
Дата: 01.05.07 12:23
Оценка:
Поторопился я с макросом... Вот отсюда можно взять версию с исправленными глюками, а главное, с ускоренным результирующим лексером. Правда, здесь я не выложил в бинарниках npg — всё равно с прошлого раза в нём ничего не изменилось. Добавил поддержку параметров lexerClass, tokenClass, tokenTypeClass (см. TestMacro), так что теперь можно настраивать имена классов для соответствующих сущностей.

В принципе на основе этой версии макроса можно делать раскраску кода для новой версии RSDN. Надеюсь, 2.6МБ/сек — вполне приемлемая скорость для данной задачи.
... << RSDN@Home 1.2.0 alpha rev. 672>>
Re[4]: Compiler-compiler
От: konsoletyper Россия https://github.com/konsoletyper
Дата: 01.05.07 12:49
Оценка:
Здравствуйте, VladD2, Вы писали:

K>>

VD>Из сказанного не ясно что же такое "макролексер" и чем от отличается от ]сгенерированнного.


См. выше по тексту прошлого поста. "Макролексер" — лексер, полученный при помощи макроса, "сгенерированный лексер" — лексер, полученный с помощью внешней утилиты.

VD>К тому же 6 секунд как-то много. Я конечно понимаю 16 Мб, но мне кажется и на 16 Мб все должно работать шустрее. Отсюда ворос — а что за код был в этом файле? Правильно ли я понимаю, что производился только лексический разбор C#-подобного языка?


Да, именно. Причём это "подобие" заключается в том, что не совсем правильно поддерживается препроцессор. В остальном вся лексика в точности соотвествует 4-ой редакции (июнь 2006) Ecma-334. Производится только лексический анализ, т.к. макроса, порождающего парсер, я пока не сделал.

Вообще, я сам удивлён, что даже после оптимизаций макроса я не смог сделать макролексер быстрее, чем сгенерированный. То же метод GetNextState в сгенерированным лексере осуществляет бинарный поиск в таблице, при этом в макроверсии тот же поиск захардкожен на if'ах, что, как я ожидал, должно быть гораздо быстрее.

А вообще, с какой скоростью работают лексеры, полученные с помощью хороших генераторов:

PS: Чтобы не было вопросов по поводу того, как тестировал, я в предыдущем посте выложил полный код теста для макролексера. Он вполне самодостаточен: для компиляции нужно сделать референсы на BnfMacro.dll и Common.dll.

PPS: А если в некоторых правилах заменить идентификаторы вида U_Xx на соответствующие "наивные" интервалы, то время работы макролексера уменьшается до 3,9 сек, когда как сгенерированному лексеру такая "оптимизация" не помогает.
... << RSDN@Home 1.2.0 alpha rev. 672>>
Re[5]: Compiler-compiler
От: VladD2 Российская Империя www.nemerle.org
Дата: 01.05.07 15:25
Оценка:
Здравствуйте, konsoletyper, Вы писали:


K>PS: Чтобы не было вопросов по поводу того, как тестировал, я в предыдущем посте выложил полный код теста для макролексера. Он вполне самодостаточен: для компиляции нужно сделать референсы на BnfMacro.dll и Common.dll.


K>PPS: А если в некоторых правилах заменить идентификаторы вида U_Xx на соответствующие "наивные" интервалы, то время работы макролексера уменьшается до 3,9 сек, когда как сгенерированному лексеру такая "оптимизация" не помогает.


К сожалению, на все нужно время. Так что вряд ли кто-то тут возьмется за данные бенчмарки.
... << RSDN@Home 1.2.0 alpha rev. 637>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.