Используете ANTLR 3.3? - PullRequest
       22

Используете ANTLR 3.3?

72 голосов
/ 09 декабря 2010

Я пытаюсь начать работу с ANTLR и C #, но считаю, что это чрезвычайно сложно из-за отсутствия документации / учебных пособий. Я нашел пару нерешительных учебных пособий для старых версий, но, похоже, с тех пор в API произошли серьезные изменения.

Может ли кто-нибудь дать мне простой пример того, как создать грамматику и использовать ее в короткой программе?

Мне наконец-то удалось собрать мой файл грамматики в лексере и парсере, и я могу получить те, которые скомпилированы и запущены в Visual Studio (после перекомпиляции исходного кода ANTLR, потому что двоичные файлы C # тоже устарели ! - не говоря уже о том, что исходный код не компилируется без некоторых исправлений), но я до сих пор не знаю, что делать с моими классами анализатора / лексера. Предположительно, он может произвести AST при некотором входе ... и тогда я смогу сделать что-то необычное с этим.

Ответы [ 4 ]

132 голосов
/ 09 декабря 2010

Допустим, вы хотите разобрать простые выражения, состоящие из следующих токенов:

  • - вычитание (также унарное);
  • + сложение;
  • * умножение;
  • / деление;
  • (...) группирование (под) выражений;
  • целые и десятичные числа.

Грамматика ANTLR может выглядеть следующим образом:

grammar Expression;

options {
  language=CSharp2;
}

parse
  :  exp EOF 
  ;

exp
  :  addExp
  ;

addExp
  :  mulExp (('+' | '-') mulExp)*
  ;

mulExp
  :  unaryExp (('*' | '/') unaryExp)*
  ;

unaryExp
  :  '-' atom 
  |  atom
  ;

atom
  :  Number
  |  '(' exp ')' 
  ;

Number
  :  ('0'..'9')+ ('.' ('0'..'9')+)?
  ;

Теперь, чтобы создать правильный AST, вы добавляете output=AST; в свой раздел options { ... } и смешиваете некоторые "операторы дерева" в своей грамматике.определить, какие токены должны быть корнем дерева.Есть два способа сделать это:

  1. добавить ^ и ! после ваших токенов.^ приводит к тому, что токен становится корнем, а ! исключает токен из ast;
  2. с помощью "правил перезаписи": ... -> ^(Root Child Child ...).

Takeправило foo, например:

foo
  :  TokenA TokenB TokenC TokenD
  ;

и, допустим, вы хотите, чтобы TokenB стал корнем, а TokenA и TokenC стали его дочерними элементами, и вы хотите исключить TokenDс дерева.Вот как это сделать с помощью опции 1:

foo
  :  TokenA TokenB^ TokenC TokenD!
  ;

и вот как это сделать с помощью опции 2:

foo
  :  TokenA TokenB TokenC TokenD -> ^(TokenB TokenA TokenC)
  ;

Итак, вот грамматика с операторами дерева:

grammar Expression;

options {
  language=CSharp2;
  output=AST;
}

tokens {
  ROOT;
  UNARY_MIN;
}

@parser::namespace { Demo.Antlr }
@lexer::namespace { Demo.Antlr }

parse
  :  exp EOF -> ^(ROOT exp)
  ;

exp
  :  addExp
  ;

addExp
  :  mulExp (('+' | '-')^ mulExp)*
  ;

mulExp
  :  unaryExp (('*' | '/')^ unaryExp)*
  ;

unaryExp
  :  '-' atom -> ^(UNARY_MIN atom)
  |  atom
  ;

atom
  :  Number
  |  '(' exp ')' -> exp
  ;

Number
  :  ('0'..'9')+ ('.' ('0'..'9')+)?
  ;

Space 
  :  (' ' | '\t' | '\r' | '\n'){Skip();}
  ;

Я также добавил правило Space, чтобы игнорировать любые пробелы в исходном файле, и добавил некоторые дополнительные токены и пространства имен для лексера и анализатора.Обратите внимание, что порядок важен (сначала options { ... }, затем tokens { ... } и, наконец, объявления @... {} -namespace).

Вот и все.

Теперь сгенерируйте лексер и парсер из вашегофайл грамматики:

java -cp antlr-3.2.jar org.antlr.Tool Expression.g

и поместите файлы .cs в свой проект вместе с DLL-библиотеками времени выполнения *1069*.

. Вы можете проверить это с помощью следующего класса:

using System;
using Antlr.Runtime;
using Antlr.Runtime.Tree;
using Antlr.StringTemplate;

namespace Demo.Antlr
{
  class MainClass
  {
    public static void Preorder(ITree Tree, int Depth) 
    {
      if(Tree == null)
      {
        return;
      }

      for (int i = 0; i < Depth; i++)
      {
        Console.Write("  ");
      }

      Console.WriteLine(Tree);

      Preorder(Tree.GetChild(0), Depth + 1);
      Preorder(Tree.GetChild(1), Depth + 1);
    }

    public static void Main (string[] args)
    {
      ANTLRStringStream Input = new ANTLRStringStream("(12.5 + 56 / -7) * 0.5"); 
      ExpressionLexer Lexer = new ExpressionLexer(Input);
      CommonTokenStream Tokens = new CommonTokenStream(Lexer);
      ExpressionParser Parser = new ExpressionParser(Tokens);
      ExpressionParser.parse_return ParseReturn = Parser.parse();
      CommonTree Tree = (CommonTree)ParseReturn.Tree;
      Preorder(Tree, 0);
    }
  }
}

, который выдает следующий вывод:

ROOT
  *
    +
      12.5
      /
        56
        UNARY_MIN
          7
    0.5

, что соответствует следующему AST:

alt text

(диаграмма, созданная с использованием graph.gafol.net )

Обратите внимание, что ANTLR 3.3 только что выпущен, а цель CSharp находится "в бета-версии".Вот почему я использовал ANTLR 3.2 в моем примере.

В случае довольно простых языков (как мой пример выше), вы также можете оценить результат на лету, не создавая AST.Вы можете сделать это, внедрив простой код C # в ваш файл грамматики и позволив правилам вашего синтаксического анализатора возвращать определенное значение.

Вот пример:

grammar Expression;

options {
  language=CSharp2;
}

@parser::namespace { Demo.Antlr }
@lexer::namespace { Demo.Antlr }

parse returns [double value]
  :  exp EOF {$value = $exp.value;}
  ;

exp returns [double value]
  :  addExp {$value = $addExp.value;}
  ;

addExp returns [double value]
  :  a=mulExp       {$value = $a.value;}
     ( '+' b=mulExp {$value += $b.value;}
     | '-' b=mulExp {$value -= $b.value;}
     )*
  ;

mulExp returns [double value]
  :  a=unaryExp       {$value = $a.value;}
     ( '*' b=unaryExp {$value *= $b.value;}
     | '/' b=unaryExp {$value /= $b.value;}
     )*
  ;

unaryExp returns [double value]
  :  '-' atom {$value = -1.0 * $atom.value;}
  |  atom     {$value = $atom.value;}
  ;

atom returns [double value]
  :  Number      {$value = Double.Parse($Number.Text, CultureInfo.InvariantCulture);}
  |  '(' exp ')' {$value = $exp.value;}
  ;

Number
  :  ('0'..'9')+ ('.' ('0'..'9')+)?
  ;

Space 
  :  (' ' | '\t' | '\r' | '\n'){Skip();}
  ;

, который можно протестировать с помощью класса:

using System;
using Antlr.Runtime;
using Antlr.Runtime.Tree;
using Antlr.StringTemplate;

namespace Demo.Antlr
{
  class MainClass
  {
    public static void Main (string[] args)
    {
      string expression = "(12.5 + 56 / -7) * 0.5";
      ANTLRStringStream Input = new ANTLRStringStream(expression);  
      ExpressionLexer Lexer = new ExpressionLexer(Input);
      CommonTokenStream Tokens = new CommonTokenStream(Lexer);
      ExpressionParser Parser = new ExpressionParser(Tokens);
      Console.WriteLine(expression + " = " + Parser.parse());
    }
  }
}

и производит следующий вывод:

(12.5 + 56 / -7) * 0.5 = 2.25

РЕДАКТИРОВАТЬ

В комментариях Ральф писал:

Совет для тех, кто использует Visual Studio: вы можете поместить что-то вроде java -cp "$(ProjectDir)antlr-3.2.jar" org.antlr.Tool "$(ProjectDir)Expression.g" в события перед сборкой, затем вы можете просто изменить свою грамматику и запустить проект, не беспокоясь о перестройке лексера / парсера.

13 голосов
/ 09 декабря 2010

Вы смотрели на Irony.net ? Он нацелен на .Net и поэтому работает действительно хорошо, имеет надлежащие инструменты, правильные примеры и просто работает. Единственная проблема заключается в том, что он все еще немного «альфа-иш», поэтому документация и версии, похоже, немного меняются, но если вы просто придерживаетесь версии, вы можете делать изящные вещи.

p.s. извините за плохой ответ, когда вы задаете вопрос о X, а кто-то предлагает что-то другое, используя Y; ^)

8 голосов
/ 31 июля 2012

Мой личный опыт показывает, что прежде чем изучать ANTLR на C # / .NET, вы должны уделить достаточно времени изучению ANTLR на Java.Это дает вам знания по всем строительным блокам, и позже вы можете подать заявку на C # / .NET.

Недавно я написал несколько постов в блоге:

Предполагается, что вы знакомы с ANTLRна Java и готов перенести ваш файл грамматики в C # /. NET.

4 голосов
/ 09 декабря 2010

Здесь есть отличная статья о том, как использовать antlr и C # вместе:

http://www.codeproject.com/KB/recipes/sota_expression_evaluator.aspx

это статья "как это было сделано" создателя NCalc, который является оценщиком математических выражений для C # - http://ncalc.codeplex.com

Вы также можете скачать грамматику для NCalc здесь: http://ncalc.codeplex.com/SourceControl/changeset/view/914d819f2865#Grammar%2fNCalc.g

пример работы NCalc:

Expression e = new Expression("Round(Pow(Pi, 2) + Pow([Pi2], 2) + X, 2)"); 

  e.Parameters["Pi2"] = new Expression("Pi * Pi"); 
  e.Parameters["X"] = 10; 

  e.EvaluateParameter += delegate(string name, ParameterArgs args) 
    { 
      if (name == "Pi") 
      args.Result = 3.14; 
    }; 

  Debug.Assert(117.07 == e.Evaluate()); 

надеюсь, что это полезно

...