我正在尝试创建自己的ANTLR4语法,以便我可以解析一些表达式,例如:
STARTS_WITH(this.sequence,“ startSeq”)
ENDS_WITH(“ sequenceToTest”,“ endSeq”)
(我混合使用常量和变量作为函数参数)。
已经创建了Lexer和Parser规则,但是当我尝试显示函数 ToStringTree 时,似乎树被错误地解析了。
我正在将Antlr 4与ASP.Net Core 2.1结合使用。
语法文件:
grammar expression;
expression : bool_function (OPERATOR bool_function)* ;
bool_function : (FUNCTION_NAME PAR_OPEN parameter (COMMA parameter)* PAR_CLOSE) ;
parameter : constant | current_object_field ;
constant : DOUBLE_QUOTE ANY_BUT_DOUBLE_QUOTE DOUBLE_QUOTE ;
current_object_field : THIS ALPHANUM ;
WHITESPACE : (' ' | '\t' | '\n' |'\r' )+ -> skip ;
COMPARATOR : ('==' | '<=' | '>=' | '<>') ;
OPERATOR : ('&&' | '||') ;
PAR_OPEN : '(' ;
PAR_CLOSE : ')' ;
COMMA : ',' ;
DOLLAR : '$' ;
DOUBLE_QUOTE : '"' ;
THIS : 'this.' ;
NUMBER : [0-9]+ ;
ALPHANUM : [a-zA-Z_0-9]+ ;
ANY_BUT_DOUBLE_QUOTE : ~('"')+ ;
FUNCTION_NAME : ('STARTS_WITH' | 'ENDS_WITH' | 'CONTAINS' | 'EQUALS') ;
单元测试,试图解析一个基本表达式:
try
{
expressionParser expressionParser = Setup("STARTS_WITH(this.sequence, \"startSeq\")");
expressionParser.ExpressionContext expressionContext = expressionParser.expression();
var a = expressionContext.ToStringTree(expressionParser);
}
catch (Exception e)
{
var a = e.Message;
throw;
}
通过 ToStringTree 解析表达式时收到的输出如下:
(expression (bool_function STARTS_WITH(this.sequence, " startSeq " )))
但是我希望结果会更深入,例如:
(expression (bool_function STARTS_WITH((parameter(current_objet_field this.sequence)),(parameter(constant "startSeq")))))
我定义Lexer / Parser的方式是否存在明显的错误?
答案 0 :(得分:1)
您没有得到想要的树,因为您的输入根本无法正确解析。解析失败,并出现以下语法错误:
第1:0行的输入'STARTS_WITH(this.sequence,'期待FUNCTION_NAME
因此,您应该检查的第一件事就是为什么没有看到错误消息。默认错误侦听器会将语法错误输出到stderr。如果您在看不见stderr的环境中运行,则应安装自己的错误侦听器,该侦听器将以明显的方式通知用户输入中的语法错误。
现在为什么不解析输入?嗯,该错误消息似乎令人怀疑,因为它一开始就抱怨缺少FUNCTION_NAME
,而实际上正是STARTS_WITH
的含义(或至少应该如此)。似乎也将STARTS_WITH(this.sequence,
视为单个令牌,这显然不是我们想要的。因此,您的词法分析器规则似乎有些不对。
当您认为词法分析器可能生成错误的标记时,您应该做的第一件事是实际打印出词法分析器生成的标记。您可以通过将grun
与-tokens
选项一起使用来实现此目的(这要求您使用Java,但这并不是什么大问题,因为您的语法不包含任何操作),也可以通过遍历您的令牌流C#代码(请注意,您必须在对其进行迭代之后重置流,否则解析器将仅看到空流)。
这样做,我们将看到词法分析器生成了以下标记:
[@0,0:26='STARTS_WITH(this.sequence, ',<ANY_BUT_DOUBLE_QUOTE>,1:0]
[@1,27:27='"',<'"'>,1:27]
[@2,28:35='startSeq',<ALPHANUM>,1:28]
[@3,36:36='"',<'"'>,1:36]
[@4,37:37=')',<')'>,1:37]
[@5,38:37='<EOF>',<EOF>,1:38]
现在我们可以在这里看到的第一个问题是开头的ANY_BUT_DOUBLE_QUOTE
令牌。显然,我们希望它是多个令牌,但都不是ANY_BUT_DOBULE_QUOTE
。发生这种情况是因为ANY_BUT_DOUBLE_QUOTE
可以匹配整个字符串STARTS_WITH(this.sequence,
,而FUNCTION_NAME
仅可以匹配STARTS_WITH
。 ANTLR生成的词法分析器遵循最大的munch规则,该规则规定始终使用产生最长匹配项的规则(如果使用平局,则使用语法中最先出现的规则)。
另一个问题是startSeq
是ALPHANUM
,因为语法允许两个双引号之间的唯一内容是ANY_BUT_DOUBLE_QUOTE
。在这里,词法分析器生成的是ALPHANUM
而不是ANY_BUT_DOUBLE_QUOTE
,因为两个规则都将生成相同长度的匹配项,但是ALPHANUM
在语法中排在第一位。请注意,如果仅切换ALPHANUM
和ANY_BUT_DOUBLE_QUOTE
的顺序,则词法分析器将永远不会产生ALPHANUM
令牌,这也不是您想要的。
这两个问题均源于ANY_BUT_DOUBLE_QUOTE
基本上可以匹配任何内容,因此与大多数其他规则重叠的事实。这是一件坏事。
您应该做的是为字符串文字使用单个lexer规则(因此将constant
变成lexer规则,然后将DOUBLE_QUOTE
和ANY_BUT_DOUBLE_QUOTE
变成片段或直接内联它们放入CONSTANT
中)。这样,就不再存在与所有内容冲突的ANY_BUT_DOUBLE_QUOTE
规则,并且CONSTANT
不会与任何内容冲突,因为它是唯一以双引号开头的规则。这样还可以防止在双引号内丢弃空格。
完成此操作后,您会收到关于STARTS_WITH
是ALPHANUM
而不是FUNCTION_NAME
的错误,但是可以通过移动FUNCTION_NAME
来解决此问题。在语法中ALPHANUM
之前。请注意,这意味着您的函数名称永远不能用作成员名称。如果您不希望这样做,则不应该使函数名称成为关键字(即,您应该只允许使用任意标识符作为函数名称,然后在以后检查您是否知道具有该名称的函数)或使它们成为上下文关键字(具有解析器规则,可以匹配ALPHANUM
或任何函数名称。