developer tip

ANTLR 3.3을 사용하십니까?

copycodes 2020. 10. 26. 08:06
반응형

ANTLR 3.3을 사용하십니까?


ANTLR 및 C #을 시작하려고하는데 문서 / 튜토리얼이 부족하여 매우 어렵습니다. 이전 버전에 대한 반감이있는 튜토리얼을 몇 개 찾았지만 그 이후로 API에 몇 가지 주요 변경 사항이있는 것 같습니다.

누구든지 문법을 만들고 짧은 프로그램에서 사용하는 방법에 대한 간단한 예를 줄 수 있습니까?

마침내 문법 파일을 렉서와 파서로 컴파일하고 Visual Studio에서 컴파일하고 실행할 수 있습니다 (C # 바이너리도 오래된 것처럼 보이기 때문에 ANTLR 소스를 다시 컴파일해야합니다!- 소스가 몇 가지 수정없이 컴파일되지 않는다는 것은 말할 것도없고),하지만 여전히 내 파서 / 어휘 분석기 클래스로 무엇을 해야할지 모르겠습니다. 아마도 그것은 약간의 입력이 주어지면 AST를 생성 할 수 있고 ... 그런 다음 나는 그것으로 멋진 것을 할 수 있어야합니다.


다음 토큰으로 구성된 간단한 표현식을 구문 분석하려고한다고 가정 해 보겠습니다.

  • - 빼기 (단항);
  • + 부가;
  • * 곱셈;
  • / 분할;
  • (...) 그룹화 (서브) 표현식;
  • 정수 및 십진수.

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 ...).

foo예를 들어 규칙 을 사용 하십시오 .

foo
  :  TokenA TokenB TokenC TokenD
  ;

및하자 당신이 원하는 말을 TokenB루트가 될 및 TokenATokenC그 자녀가, 당신은 제외 할 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과 함께 파일을 프로젝트에서 C #을 런타임 DLL의 .

다음 클래스를 사용하여 테스트 할 수 있습니다.

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);
    }
  }
}

다음과 같은 출력이 생성됩니다.

뿌리
  *
    +
      12.5
      /
        56
        UNARY_MIN
          7
    0.5

다음 AST에 해당합니다.

대체 텍스트

( 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

편집하다

댓글에서 Ralph는 다음과 같이 썼습니다.

Visual Studio를 사용하는 사람들을위한 팁 : java -cp "$(ProjectDir)antlr-3.2.jar" org.antlr.Tool "$(ProjectDir)Expression.g"빌드 전 이벤트에 같은 것을 넣은 다음 렉서 / 파서를 다시 빌드하는 것에 대해 걱정할 필요없이 문법을 수정하고 프로젝트를 실행할 수 있습니다.


Have you looked at Irony.net? It's aimed at .Net and therefore works really well, has proper tooling, proper examples and just works. The only problem is that it is still a bit 'alpha-ish' so documentation and versions seem to change a bit, but if you just stick with a version, you can do nifty things.

p.s. sorry for the bad answer where you ask a problem about X and someone suggests something different using Y ;^)


My personal experience is that before learning ANTLR on C#/.NET, you should spare enough time to learn ANTLR on Java. That gives you knowledge on all the building blocks and later you can apply on C#/.NET.

I wrote a few blog posts recently,

The assumption is that you are familiar with ANTLR on Java and is ready to migrate your grammar file to C#/.NET.


There is a great article on how to use antlr and C# together here:

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

it's a "how it was done" article by the creator of NCalc which is a mathematical expression evaluator for C# - http://ncalc.codeplex.com

You can also download the grammar for NCalc here: http://ncalc.codeplex.com/SourceControl/changeset/view/914d819f2865#Grammar%2fNCalc.g

example of how NCalc works:

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()); 

hope its helpful

참고 URL : https://stackoverflow.com/questions/4396080/using-antlr-3-3

반응형