ANTLR:树解析给出意想不到的结果
ANTLR : Tree parsing giving unexpected results
我正在尝试创建自己的 ANTLR4 语法,以便我可以解析一些表达式,例如:
STARTS_WITH(this.sequence, "startSeq")
ENDS_WITH("sequenceToTest", "endSeq")
(我将常量和变量混合作为函数参数)。
已经创建了 Lexer 和 Parser Rules,但是当我尝试显示函数 ToStringTree 时,似乎以错误的方式解析了树。
我正在使用带有 ASP.Net Core 2.1 的 Antlr 4。
语法文件:
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 的方式是否有任何明显的错误?
你没有得到你想要的树,因为你的输入根本没有被正确解析。解析失败,出现以下语法错误:
line 1:0 mismatched input 'STARTS_WITH(this.sequence, ' expecting 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
标记。显然,我们希望这是多个标记,none 其中 ANY_BUT_DOBULE_QUOTE
。发生这种情况是因为 ANY_BUT_DOUBLE_QUOTE
可以匹配整个字符串 STARTS_WITH(this.sequence,
而 FUNCTION_NAME
只能匹配 STARTS_WITH
。 ANTLR-generated 词法分析器遵循最大咀嚼规则,即始终使用产生最长匹配的规则(在并列的情况下使用语法中排在第一位的规则)。
另一个问题是 startSeq
是 ALPHANUM
,因为您的语法在两个双引号之间唯一允许的是 ANY_BUT_DOUBLE_QUOTE
。这里词法分析器产生了 ALPHANUM
而不是 ANY_BUT_DOUBLE_QUOTE
,因为这两个规则都会产生相同长度的匹配,但是 ALPHANUM
在语法中排在第一位。请注意,如果您简单地切换 ALPHANUM
和 ANY_BUT_DOUBLE_QUOTE
的顺序,词法分析器将永远不会生成 ALPHANUM
标记,这也不是您想要的。
这两个问题都源于 ANY_BUT_DOUBLE_QUOTE
基本上可以匹配任何内容,因此与您的大多数其他规则重叠。这是一件坏事。
你应该做的是对字符串文字有一个词法分析器规则(所以把 constant
变成一个词法分析器规则,把 DOUBLE_QUOTE
和 ANY_BUT_DOUBLE_QUOTE
变成片段或内联他们直接进入 CONSTANT
)。这样就不再有 ANY_BUT_DOUBLE_QUOTE
规则与所有内容发生冲突,而 CONSTANT
也不会与任何内容发生冲突,因为它是唯一以双引号开头的规则。这也将防止白色 space 在双引号内被丢弃。
一旦你这样做了,你会得到一个关于 STARTS_WITH
是 ALPHANUM
而不是 FUNCTION_NAME
的错误,但是你可以通过移动 [=11] 来解决这个问题=] 在语法中 ALPHANUM
之前。请注意,这意味着您的函数名称永远不能用作成员的名称。如果您不希望这样,则不应将函数名称作为关键字(即您应该只允许任意标识符作为函数名称,然后稍后检查您是否知道具有该名称的函数)或将它们作为上下文关键字(有一个解析器规则可以匹配 ALPHANUM
或任何函数名称)。
我正在尝试创建自己的 ANTLR4 语法,以便我可以解析一些表达式,例如:
STARTS_WITH(this.sequence, "startSeq")
ENDS_WITH("sequenceToTest", "endSeq")
(我将常量和变量混合作为函数参数)。
已经创建了 Lexer 和 Parser Rules,但是当我尝试显示函数 ToStringTree 时,似乎以错误的方式解析了树。
我正在使用带有 ASP.Net Core 2.1 的 Antlr 4。
语法文件:
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 的方式是否有任何明显的错误?
你没有得到你想要的树,因为你的输入根本没有被正确解析。解析失败,出现以下语法错误:
line 1:0 mismatched input 'STARTS_WITH(this.sequence, ' expecting 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
标记。显然,我们希望这是多个标记,none 其中 ANY_BUT_DOBULE_QUOTE
。发生这种情况是因为 ANY_BUT_DOUBLE_QUOTE
可以匹配整个字符串 STARTS_WITH(this.sequence,
而 FUNCTION_NAME
只能匹配 STARTS_WITH
。 ANTLR-generated 词法分析器遵循最大咀嚼规则,即始终使用产生最长匹配的规则(在并列的情况下使用语法中排在第一位的规则)。
另一个问题是 startSeq
是 ALPHANUM
,因为您的语法在两个双引号之间唯一允许的是 ANY_BUT_DOUBLE_QUOTE
。这里词法分析器产生了 ALPHANUM
而不是 ANY_BUT_DOUBLE_QUOTE
,因为这两个规则都会产生相同长度的匹配,但是 ALPHANUM
在语法中排在第一位。请注意,如果您简单地切换 ALPHANUM
和 ANY_BUT_DOUBLE_QUOTE
的顺序,词法分析器将永远不会生成 ALPHANUM
标记,这也不是您想要的。
这两个问题都源于 ANY_BUT_DOUBLE_QUOTE
基本上可以匹配任何内容,因此与您的大多数其他规则重叠。这是一件坏事。
你应该做的是对字符串文字有一个词法分析器规则(所以把 constant
变成一个词法分析器规则,把 DOUBLE_QUOTE
和 ANY_BUT_DOUBLE_QUOTE
变成片段或内联他们直接进入 CONSTANT
)。这样就不再有 ANY_BUT_DOUBLE_QUOTE
规则与所有内容发生冲突,而 CONSTANT
也不会与任何内容发生冲突,因为它是唯一以双引号开头的规则。这也将防止白色 space 在双引号内被丢弃。
一旦你这样做了,你会得到一个关于 STARTS_WITH
是 ALPHANUM
而不是 FUNCTION_NAME
的错误,但是你可以通过移动 [=11] 来解决这个问题=] 在语法中 ALPHANUM
之前。请注意,这意味着您的函数名称永远不能用作成员的名称。如果您不希望这样,则不应将函数名称作为关键字(即您应该只允许任意标识符作为函数名称,然后稍后检查您是否知道具有该名称的函数)或将它们作为上下文关键字(有一个解析器规则可以匹配 ALPHANUM
或任何函数名称)。