什么方法可以让我最大限度地报告词法错误?
举个简单的例子,我想为下面的文本写一个语法
(空白处被忽略,字符串常量中不能有\",以简单起见(:
myvariable = 2
myvariable = "hello world"
Group myvariablegroup {
myvariable = 3
anothervariable = 4
}
用lexer捕获错误
如何最大限度地发挥lexer的错误报告潜力?
看完这篇文章:lexer和解析器之间的界限应该在哪里?
我知道lexer应该在语法分析器方面尽可能地匹配,但词汇错误报告策略呢?
捕捉词汇错误的常用策略是什么?
我正在想象一种语法,它会有以下"错误"标记:
GROUP_OPEN: 'Group' WS ID WS '{';
EMPTY_GROUP: 'Group' WS ID WS '{' WS '}';
EQUALS: '=';
STRING_CONSTANT: '"~["]+"';
GROUP_CLOSE: '}';
GROUP_ERROR: 'Group' .; // the . character is an invalid token
// you probably meant '{'
GROUP_ERROR2: .'roup' ; // Did you mean 'group'?
STRING_CONSTANT_ERROR: '"' .+; // Unterminated string constant
ID: [a-z][a-z0-9]+;
WS: [ nrt]* -> skip();
SINGLE_TOKEN_ERRORS: .+?;
您的方法显然存在一些问题:
-
您正在跳过
WS
(这很好(,但您正在其他规则中使用它。但你在lexer,这导致我们… -
lexer正在识别您的组。我认为你不希望它们成为一个单一的象征。您的组属于解析器。
-
您编写的语法将为以
roup
结尾的事物创建特定的令牌类型,因此例如croup
可能永远不会与ID
匹配。这不好。 -
STRING_CONSTANT_ERROR
过于宽泛。它能够全局显示整个输入。请参阅下面的UNTERMINATED_STRING
。 -
我不太确定
SINGLE_TOKEN_ERRORS
会发生什么。。。请参阅下面的备选方案。
现在,这里有一些我使用的错误标记的例子,这对错误报告非常有效:
UNTERMINATED_STRING
: '"' ('\' ["\] | ~["\rn])*
;
UNTERMINATED_COMMENT_INLINE
: '/*' ('*' ~'/' | ~'*')*? EOF -> channel(HIDDEN)
;
// This should be the LAST lexer rule in your grammar
UNKNOWN_CHAR
: .
;
请注意,这些未终止的标记表示单个原子值,它们不跨越逻辑结构。
此外,无论怎样,UNKNOWN_CHAR
都将是一个单独的字符,如果您将其定义为.+?
,它无论如何都将只匹配一个字符,因为它将尝试匹配尽可能少的字符,并且最小值是一个字符
当有东西跟在非贪婪量词后面时,它们是有意义的。例如,在表达式.+? '#'
中,.+?
将被强制消耗字符,直到遇到#
符号。如果.+?
表达式是单独的,则它不必消耗超过一个字符来匹配,因此将等效于.
。
我在lexer(.NETANTLR(中使用以下代码:
partial class MyLexer
{
public override IToken Emit()
{
CommonToken token;
RecognitionException ex;
switch (Type)
{
case UNTERMINATED_STRING:
Type = STRING;
token = (CommonToken)base.Emit();
ex = new UnterminatedTokenException(this, (ICharStream)InputStream, token);
ErrorListenerDispatch.SyntaxError(this, UNTERMINATED_STRING, Line, Column, "Unterminated string: " + GetTokenTextForDisplay(token), ex);
return token;
case UNTERMINATED_COMMENT_INLINE:
Type = COMMENT_INLINE;
token = (CommonToken)base.Emit();
ex = new UnterminatedTokenException(this, (ICharStream)InputStream, token);
ErrorListenerDispatch.SyntaxError(this, UNTERMINATED_COMMENT_INLINE, Line, Column, "Unterminated comment: " + GetTokenTextForDisplay(token), ex);
return token;
default:
return base.Emit();
}
}
// ...
}
请注意,当lexer遇到错误的令牌类型时,它会显式地将其更改为有效的令牌,这样解析器就可以真正理解它
现在,识别坏的结构是解析器的工作。ANTLR足够聪明,可以在尝试用无效输入重新同步自身的同时执行单令牌删除和单令牌插入。这也是我让UNKNOWN_CHAR
滑到解析器的原因,这样它就可以用错误消息丢弃它。
只需接受它生成的错误并对其进行更改,以便向用户呈现更好的内容。
所以,只需将您的组组成一个解析器规则。
一个例子:
考虑以下输入:
Group ,ygroup {
这里,,
显然是一个打字错误(用户按下了,
而不是m
(。
如果您使用UNKNOWN_CHAR: .;
,您将获得以下代币:
GROUP
型Group
UNKNOWN_CHAR
型,
ID
型ygroup
'{ '
型{
解析器将能够计算出需要删除的UNKNOWN_CHAR
令牌,并将正确匹配一个组(定义为GROUP
ID
'{'
…(
ANTLR将在发现意外令牌的点插入所谓的错误节点(在这种情况下,在GROUP
和ID
之间(。然后,为了进行解析,这些节点将被忽略,但您可以使用访问者/侦听器来检索它们以处理它们(例如,您可以使用访客的VisitErrorNode
方法(。