使用 Python 目标处理 ANTLR4 语法中的换行
Handling line feed in ANTLR4 grammar with Python target
我正在研究 ANTLR4 语法,用于解析 Python DSL 脚本(基本上是 Python 的一个子集)和 目标设置为Python 3。我在处理换行时遇到困难。
在我的语法中,我使用 lexer::members
和 NEWLINE
基于 Bart Kiers's Python3 grammar for ANTLR4 which are ported to Python so that they can be used with Python 3 runtime for ANTLR instead of Java. My grammar differs from the one provided by Bart (which is almost the same used in the Python 3 spec) 的嵌入式代码,因为在我的 DSL 中我只需要针对 Python 的某些元素。根据对我的语法的广泛测试,我确实认为语法的 Python 部分本身并不是问题的根源,因此我暂时不会在这里完整地 post 它。
语法的输入是一个文件,由 file_input 规则捕获:
file_input: (NEWLINE | statement)* EOF;
该语法在我的 DSL 上表现相当好,并生成正确的 AST。我遇到的唯一问题是我的词法分析器规则 NEWLINE
用 \r\n
节点使 AST 混乱,并且在尝试用我自己的 ExtendedListener
继承自的 ExtendedListener
扩展生成的 MyGrammarListener
时证明很麻烦它。
这是我的 NEWLINE
词法分析器规则:
NEWLINE
: ( {self.at_start_of_input()}? SPACES
| ( '\r'? '\n' | '\r' | '\f' ) SPACES?
)
{
import re
from MyParser import MyParser
new_line = re.sub(r"[^\r\n\f]+", "", self._interp.getText(self._input))
spaces = re.sub(r"[\r\n\f]+", "", self._interp.getText(self._input))
next = self._input.LA(1)
if self.opened > 0 or next == '\r' or next == '\n' or next == '\f' or next == '#':
self.skip()
else:
self.emit_token(self.common_token(self.NEWLINE, new_line))
indent = self.get_indentation_count(spaces)
if len(self.indents) == 0:
previous = 0
else:
previous = self.indents[-1]
if indent == previous:
self.skip()
elif indent > previous:
self.indents.append(indent)
self.emit_token(self.common_token(MyParser.INDENT, spaces))
else:
while len(self.indents) > 0 and self.indents[-1] > indent:
self.emit_token(self.create_dedent())
del self.indents[-1]
};
NEWLINE
使用的 SPACES
词法分析器规则片段在这里:
fragment SPACES
: [ \t]+
;
我觉得我还应该补充一点,SPACES
和 COMMENTS
最终都会被语法跳过,但只有在声明 NEWLINE
词法分析器规则之后,到目前为止据我所知,这应该意味着没有任何不利影响,但我想包括它以防万一。
SKIP_
: ( SPACES | COMMENT ) -> skip
;
当输入文件是 运行 且语句之间没有任何空行时,一切都 运行 是应该的。但是,如果我的文件中有空行(例如在导入语句和变量赋值之间),我会收到以下错误:
line 15:4 extraneous input '\r\n ' expecting {<EOF>, 'from', 'import', NEWLINE, NAME}
line 15:0 extraneous input '\r\n' expecting {<EOF>, 'from', 'import', NEWLINE, NAME}
正如我之前所说,当我的输入文件中省略换行符时,语法和我的 ExtendedListener
会正常执行,所以问题肯定是 \r\n
没有被匹配NEWLINE
词法分析器规则——即使是我得到的错误语句也说它不匹配备选 NEWLINE
.
我的语法生成的 AST 如下所示:
我真的很感激任何帮助,因为我不明白为什么我的 NEWLINE
词法分析器规则无法匹配 \r\n
,我想在我的 DSL 中允许空行。
so the problem is definitely with the \r\n not being matched by the
NEWLINE lexer rule
还有一个解释。 LL(1) 解析器会在第一次不匹配时停止,但 ANTLR4 是一个非常聪明的 LL(*) :它会尝试匹配不匹配之后的输入。
由于我没有您的 statement
规则和您在第 15 行附近的输入,我将使用以下语法演示一个可能的情况:
grammar Question;
/* Extraneous input parsing NL and spaces. */
@lexer::members {
public boolean at_start_of_input() {return true;}; // even if it always returns true, it's not the cause of the problem
}
question
@init {System.out.println("Question last update 2108");}
: ( NEWLINE
| statement
{System.out.println("found <<" + $statement.text + ">>");}
)* EOF
;
statement
: 'line ' NUMBER NEWLINE 'something else' NEWLINE
;
NUMBER : [0-9]+ ;
NEWLINE
: ( {at_start_of_input()}? SPACES
| ( '\r'? '\n' | '\r' | '\f' ) SPACES?
)
;
SKIP_
: SPACES -> skip
;
fragment SPACES
: [ \t]+
;
输入文件t.text:
line 1
something else
执行:
$ export CLASSPATH=".:/usr/local/lib/antlr-4.6-complete.jar"
$ alias
alias a4='java -jar /usr/local/lib/antlr-4.6-complete.jar'
alias grun='java org.antlr.v4.gui.TestRig'
$ hexdump -C t.text
00000000 6c 69 6e 65 20 31 0a 20 20 20 73 6f 6d 65 74 68 |line 1. someth|
00000010 69 6e 67 20 65 6c 73 65 0a |ing else.|
00000019
$ a4 Question.g4
$ javac Q*.java
$ grun Question question -tokens -diagnostics t.text
[@0,0:4='line ',<'line '>,1:0]
[@1,5:5='1',<NUMBER>,1:5]
[@2,6:9='\n ',<NEWLINE>,1:6]
[@3,10:23='something else',<'something else'>,2:3]
[@4,24:24='\n',<NEWLINE>,2:17]
[@5,25:24='<EOF>',<EOF>,3:0]
Question last update 2108
found <<line 1
something else
>>
现在像这样更改 statement
:
statement
// : 'line ' NUMBER NEWLINE 'something else' NEWLINE
: 'line ' NUMBER 'something else' NEWLINE // now NL will be extraneous
;
并再次执行:
$ a4 Question.g4
$ javac Q*.java
$ grun Question question -tokens -diagnostics t.text
[@0,0:4='line ',<'line '>,1:0]
[@1,5:5='1',<NUMBER>,1:5]
[@2,6:9='\n ',<NEWLINE>,1:6]
[@3,10:23='something else',<'something else'>,2:3]
[@4,24:24='\n',<NEWLINE>,2:17]
[@5,25:24='<EOF>',<EOF>,3:0]
Question last update 2114
line 1:6 extraneous input '\n ' expecting 'something else'
found <<line 1
something else
>>
请注意,NEWLINE
词法分析器规则已正确匹配 NL 字符和空格。
您可以在The Definitive ANTLR 4 Reference的第9.1节中找到解释:
$ grun Simple prog ➾ class T ; { int i; } ➾EOF ❮ line 1:8 extraneous
input ';' expecting '{'
A Parade of Errors • 153
The parser reports an error at the ; but gives a slightly more
informative answer because it knows that the next token is what it was
actually looking for. This feature is called single-token deletion
because the parser can simply pretend the extraneous token isn’t there
and keep going.
Similarly, the parser can do single-token insertion when it detects a
missing token.
换句话说,ANTLR4 非常强大,即使有几个标记不匹配,它也可以将输入与语法重新同步。如果你 运行 使用 -gui 选项
$ grun Question question -gui t.text
你可以看到 ANTLR4 已经解析了整个文件,尽管 statement
规则中缺少 NEWLINE
,并且输入与语法不完全匹配。
总结:extraneous input
是开发语法时很常见的错误。它可能来自 解析输入 和 规则期望 之间的不匹配,或者还因为某些输入已被另一个标记解释而不是另一个标记我们相信,这可以通过检查 -tokens
选项生成的令牌列表来检测。
我正在研究 ANTLR4 语法,用于解析 Python DSL 脚本(基本上是 Python 的一个子集)和 目标设置为Python 3。我在处理换行时遇到困难。
在我的语法中,我使用 lexer::members
和 NEWLINE
基于 Bart Kiers's Python3 grammar for ANTLR4 which are ported to Python so that they can be used with Python 3 runtime for ANTLR instead of Java. My grammar differs from the one provided by Bart (which is almost the same used in the Python 3 spec) 的嵌入式代码,因为在我的 DSL 中我只需要针对 Python 的某些元素。根据对我的语法的广泛测试,我确实认为语法的 Python 部分本身并不是问题的根源,因此我暂时不会在这里完整地 post 它。
语法的输入是一个文件,由 file_input 规则捕获:
file_input: (NEWLINE | statement)* EOF;
该语法在我的 DSL 上表现相当好,并生成正确的 AST。我遇到的唯一问题是我的词法分析器规则 NEWLINE
用 \r\n
节点使 AST 混乱,并且在尝试用我自己的 ExtendedListener
继承自的 ExtendedListener
扩展生成的 MyGrammarListener
时证明很麻烦它。
这是我的 NEWLINE
词法分析器规则:
NEWLINE
: ( {self.at_start_of_input()}? SPACES
| ( '\r'? '\n' | '\r' | '\f' ) SPACES?
)
{
import re
from MyParser import MyParser
new_line = re.sub(r"[^\r\n\f]+", "", self._interp.getText(self._input))
spaces = re.sub(r"[\r\n\f]+", "", self._interp.getText(self._input))
next = self._input.LA(1)
if self.opened > 0 or next == '\r' or next == '\n' or next == '\f' or next == '#':
self.skip()
else:
self.emit_token(self.common_token(self.NEWLINE, new_line))
indent = self.get_indentation_count(spaces)
if len(self.indents) == 0:
previous = 0
else:
previous = self.indents[-1]
if indent == previous:
self.skip()
elif indent > previous:
self.indents.append(indent)
self.emit_token(self.common_token(MyParser.INDENT, spaces))
else:
while len(self.indents) > 0 and self.indents[-1] > indent:
self.emit_token(self.create_dedent())
del self.indents[-1]
};
NEWLINE
使用的 SPACES
词法分析器规则片段在这里:
fragment SPACES
: [ \t]+
;
我觉得我还应该补充一点,SPACES
和 COMMENTS
最终都会被语法跳过,但只有在声明 NEWLINE
词法分析器规则之后,到目前为止据我所知,这应该意味着没有任何不利影响,但我想包括它以防万一。
SKIP_
: ( SPACES | COMMENT ) -> skip
;
当输入文件是 运行 且语句之间没有任何空行时,一切都 运行 是应该的。但是,如果我的文件中有空行(例如在导入语句和变量赋值之间),我会收到以下错误:
line 15:4 extraneous input '\r\n ' expecting {<EOF>, 'from', 'import', NEWLINE, NAME}
line 15:0 extraneous input '\r\n' expecting {<EOF>, 'from', 'import', NEWLINE, NAME}
正如我之前所说,当我的输入文件中省略换行符时,语法和我的 ExtendedListener
会正常执行,所以问题肯定是 \r\n
没有被匹配NEWLINE
词法分析器规则——即使是我得到的错误语句也说它不匹配备选 NEWLINE
.
我的语法生成的 AST 如下所示:
我真的很感激任何帮助,因为我不明白为什么我的 NEWLINE
词法分析器规则无法匹配 \r\n
,我想在我的 DSL 中允许空行。
so the problem is definitely with the \r\n not being matched by the NEWLINE lexer rule
还有一个解释。 LL(1) 解析器会在第一次不匹配时停止,但 ANTLR4 是一个非常聪明的 LL(*) :它会尝试匹配不匹配之后的输入。
由于我没有您的 statement
规则和您在第 15 行附近的输入,我将使用以下语法演示一个可能的情况:
grammar Question;
/* Extraneous input parsing NL and spaces. */
@lexer::members {
public boolean at_start_of_input() {return true;}; // even if it always returns true, it's not the cause of the problem
}
question
@init {System.out.println("Question last update 2108");}
: ( NEWLINE
| statement
{System.out.println("found <<" + $statement.text + ">>");}
)* EOF
;
statement
: 'line ' NUMBER NEWLINE 'something else' NEWLINE
;
NUMBER : [0-9]+ ;
NEWLINE
: ( {at_start_of_input()}? SPACES
| ( '\r'? '\n' | '\r' | '\f' ) SPACES?
)
;
SKIP_
: SPACES -> skip
;
fragment SPACES
: [ \t]+
;
输入文件t.text:
line 1
something else
执行:
$ export CLASSPATH=".:/usr/local/lib/antlr-4.6-complete.jar"
$ alias
alias a4='java -jar /usr/local/lib/antlr-4.6-complete.jar'
alias grun='java org.antlr.v4.gui.TestRig'
$ hexdump -C t.text
00000000 6c 69 6e 65 20 31 0a 20 20 20 73 6f 6d 65 74 68 |line 1. someth|
00000010 69 6e 67 20 65 6c 73 65 0a |ing else.|
00000019
$ a4 Question.g4
$ javac Q*.java
$ grun Question question -tokens -diagnostics t.text
[@0,0:4='line ',<'line '>,1:0]
[@1,5:5='1',<NUMBER>,1:5]
[@2,6:9='\n ',<NEWLINE>,1:6]
[@3,10:23='something else',<'something else'>,2:3]
[@4,24:24='\n',<NEWLINE>,2:17]
[@5,25:24='<EOF>',<EOF>,3:0]
Question last update 2108
found <<line 1
something else
>>
现在像这样更改 statement
:
statement
// : 'line ' NUMBER NEWLINE 'something else' NEWLINE
: 'line ' NUMBER 'something else' NEWLINE // now NL will be extraneous
;
并再次执行:
$ a4 Question.g4
$ javac Q*.java
$ grun Question question -tokens -diagnostics t.text
[@0,0:4='line ',<'line '>,1:0]
[@1,5:5='1',<NUMBER>,1:5]
[@2,6:9='\n ',<NEWLINE>,1:6]
[@3,10:23='something else',<'something else'>,2:3]
[@4,24:24='\n',<NEWLINE>,2:17]
[@5,25:24='<EOF>',<EOF>,3:0]
Question last update 2114
line 1:6 extraneous input '\n ' expecting 'something else'
found <<line 1
something else
>>
请注意,NEWLINE
词法分析器规则已正确匹配 NL 字符和空格。
您可以在The Definitive ANTLR 4 Reference的第9.1节中找到解释:
$ grun Simple prog ➾ class T ; { int i; } ➾EOF ❮ line 1:8 extraneous input ';' expecting '{'
A Parade of Errors • 153
The parser reports an error at the ; but gives a slightly more informative answer because it knows that the next token is what it was actually looking for. This feature is called single-token deletion because the parser can simply pretend the extraneous token isn’t there and keep going.
Similarly, the parser can do single-token insertion when it detects a missing token.
换句话说,ANTLR4 非常强大,即使有几个标记不匹配,它也可以将输入与语法重新同步。如果你 运行 使用 -gui 选项
$ grun Question question -gui t.text
你可以看到 ANTLR4 已经解析了整个文件,尽管 statement
规则中缺少 NEWLINE
,并且输入与语法不完全匹配。
总结:extraneous input
是开发语法时很常见的错误。它可能来自 解析输入 和 规则期望 之间的不匹配,或者还因为某些输入已被另一个标记解释而不是另一个标记我们相信,这可以通过检查 -tokens
选项生成的令牌列表来检测。