假设您要解析由以下标记组成的简单表达式:
-
减法(也一元);
+
加成;
*
乘法;
/
师;
(...)
分组(子)表达式;
- 整数和十进制数。
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 { ... }
部分,并在语法中混合一些“树运算符”,以定义哪些标记应为树的根。有两种方法可以做到这一点:
- 在令牌后添加
^
和!
。在^
使令牌成为根和!
排除从AST令牌;
- 通过使用“重写规则”:
... -> ^(Root Child Child ...)
。
以规则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
忽略源文件中所有空格的规则,并为lexer和解析器添加了一些额外的标记和名称空间。请注意,顺序很重要(options { ... }
首先tokens { ... }
是@... {}
-namespace声明,然后是-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目标是“处于beta中”。这就是为什么我在示例中使用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"
在预生成事件中添加类似内容,然后可以修改语法并运行项目,而不必担心重建词法分析器/解析器。
parse()
私有的,skip()
不可用,并且C#运行时无法使用它。这应该可以帮助我入门,非常感谢!