parsing - 词法分析器和解析器的 ANTLR 令牌识别错误
问题描述
我正在编写一个 ANTLR Lexer 和 Parser 语法,它将解析与 Java 类非常相似的文本。最终它将解析如下文本:
reference schema:"https://schema.org/";
reference dc:"https://www.dublincore.org/";
type dc:Author {
}
我正在慢慢构建 Lexer 和 Parser。我已经成功地解析了reference
s 但在解析type
.
在添加对 的支持之前,type
我能够在解析器中将字符串文字用于空格、冒号和分号,但在遇到cannot create implicit token for string literal
错误之后。我为每个字符定义了一个词法分析器规则,并用规则替换了所有出现的文字。然而,这打破了 s 的解析reference
。
我已经包含了我的词法分析器和解析器,它们成功地解析reference
了下面的 s(连同一个示例输入和解析的抽象语法树)以及不工作的进化版本。我没有收到任何编译错误,但有很多token recognition error
s(截图如下)。
处理解析的正确方法是什么?
在职的
词法分析器
lexer grammar WorkingLexerGrammar;
WS: ('\t' | '\n' | '\r' )+ -> skip ;
fragment Colon : ':';
fragment SemiColon: ';';
fragment Underscores: '_'+ ;
fragment Digits: [0-9]+ ;
fragment LowercaseLetters: [a-z]+ ;
fragment UppercaseLetters: [A-Z]+ ;
fragment String: '"' .*? '"' ;
fragment Prefix: (Underscores | Digits | LowercaseLetters)+ ;
REFERENCE_KEYWORD: 'reference' ;
TYPE_KEYWORD: 'type' ;
PREFIXED_REFERENCE: ' ' -> pushMode(PrefixedReferenceMode) ;
mode PrefixedReferenceMode;
REFERENCE_PREFIX: Prefix;
REFERENCE_PREFIX_SEPARATOR: ':' -> pushMode(IriMode);
END_IRI: ';' -> popMode;
mode IriMode;
IRI: String -> popMode;
解析器
parser grammar WorkingParserGrammar ;
options { tokenVocab=WorkingLexerGrammar; }
document: reference* EOF ;
prefixedReference: REFERENCE_PREFIX ':' IRI;
reference: REFERENCE_KEYWORD ' ' prefixedReference ';';
输入
reference schema:"https://schema.org/";
reference dc:"https://www.dublincore.org/";
输出
进化(不工作)
词法分析器
lexer grammar NotWorkingLexerGrammar;
WS: ('\t' | '\n' | '\r' )+ -> skip ;
fragment Colon : ':';
fragment SemiColon: ';';
fragment Underscores: '_'+ ;
fragment Digits: [0-9]+ ;
fragment LowercaseLetters: [a-z]+ ;
fragment UppercaseLetters: [A-Z]+ ;
fragment String: '"' .*? '"' ;
fragment Prefix: (Underscores | Digits | LowercaseLetters)+ ;
COLON: Colon;
SEMICOLON: SemiColon;
SPACE: ' ';
REFERENCE_KEYWORD: 'reference' ;
TYPE_KEYWORD: 'type' ;
PREFIXED_REFERENCE: SPACE -> pushMode(PrefixedReferenceMode) ;
mode PrefixedReferenceMode;
REFERENCE_PREFIX: Prefix;
REFERENCE_PREFIX_SEPARATOR: COLON -> pushMode(IriMode);
END_IRI: SEMICOLON -> popMode;
mode IriMode;
IRI: String -> popMode;
PREFIXED_NAME: SPACE -> pushMode(PrefixedNameMode) ;
mode PrefixedNameMode;
NAME_PREFIX: Prefix;
NAME_PREFIX_SEPARATOR: COLON -> pushMode(LocalNameMode);
END_NAME: SEMICOLON -> popMode;
mode LocalNameMode;
LOCAL_NAME: (Underscores | Digits | LowercaseLetters | UppercaseLetters)+ -> popMode;
解析器
parser grammar NotWorkingParserGrammar ;
options { tokenVocab=NotWorkingLexerGrammar; }
document: reference* type* EOF ;
prefixedReference: REFERENCE_PREFIX COLON IRI;
reference: REFERENCE_KEYWORD SPACE prefixedReference SEMICOLON;
prefixedName: NAME_PREFIX SPACE LOCAL_NAME;
type: TYPE_KEYWORD SPACE prefixedName;
输出
在Bart Kiers 的帮助下,我对词法分析器和解析器语法进行了两次更新,并取得了不同程度的成功。
第一次更新
此更改正确解析类型定义,但前提是我删除了词法分析器规则以供参考。我认为这样做的原因是这两个规则是相同的(即PREFIXED_REFERENCE: SPACE -> pushMode(PrefixedReferenceMode) ;
用于引用和PREFIXED_NAME: SPACE -> pushMode(PrefixedNameMode) ;
用于类型)——即它们都在空格上匹配。我的第二次更新试图解决这个问题,但完整的词法分析器和解析器语法如下。
词法分析器
lexer grammar NotWorkingLexerGrammar;
WS: ('\t' | '\n' | '\r' )+ -> skip ;
fragment Underscores: '_'+ ;
fragment Digits: [0-9]+ ;
fragment LowercaseLetters: [a-z]+ ;
fragment UppercaseLetters: [A-Z]+ ;
fragment String: '"' .*? '"' ;
fragment Prefix: (Underscores | Digits | LowercaseLetters)+ ;
fragment COLON: ':';
fragment SEMICOLON: ';';
fragment SPACE: ' ';
fragment REFERENCE_KEYWORD: 'reference' ;
fragment TYPE_KEYWORD: 'type' ;
PREFIXED_REFERENCE: SPACE -> pushMode(PrefixedReferenceMode) ;
mode PrefixedReferenceMode;
REFERENCE_PREFIX: Prefix;
REFERENCE_PREFIX_SEPARATOR: COLON -> pushMode(IriMode);
END_IRI: SEMICOLON -> popMode;
mode IriMode;
IRI: String -> popMode;
PREFIXED_NAME: SPACE -> pushMode(PrefixedNameMode) ;
mode PrefixedNameMode;
NAME_PREFIX: Prefix;
NAME_PREFIX_SEPARATOR: COLON -> pushMode(LocalNameMode);
END_NAME: SEMICOLON -> popMode;
mode LocalNameMode;
LOCAL_NAME: (Underscores | Digits | LowercaseLetters | UppercaseLetters)+ -> popMode;
解析器
parser grammar NotWorkingParserGrammar ;
options { tokenVocab=NotWorkingLexerGrammar; }
document: reference* type* EOF ;
prefixedReference: REFERENCE_PREFIX REFERENCE_PREFIX_SEPARATOR IRI;
reference: REFERENCE_KEYWORD PREFIXED_REFERENCE prefixedReference END_IRI;
prefixedName: NAME_PREFIX NAME_PREFIX_SEPARATOR LOCAL_NAME;
type: TYPE_KEYWORD PREFIXED_NAME prefixedName END_NAME;
第二次更新
为了解决这个问题,我将reference
andtype
关键字移到了相应部分的 Lexer 规则中,但这仅在我删除所有 Lexer 规则以供参考时才解析类型。但是引用被正确解析。
词法分析器
lexer grammar NotWorkingLexerGrammar;
WS: ('\t' | '\n' | '\r' )+ -> skip ;
fragment Underscores: '_'+ ;
fragment Digits: [0-9]+ ;
fragment LowercaseLetters: [a-z]+ ;
fragment UppercaseLetters: [A-Z]+ ;
fragment String: '"' .*? '"' ;
fragment Prefix: (Underscores | Digits | LowercaseLetters)+ ;
fragment COLON: ':';
fragment SEMICOLON: ';';
fragment SPACE: ' ';
fragment REFERENCE_KEYWORD: 'reference' ;
fragment TYPE_KEYWORD: 'type' ;
PREFIXED_REFERENCE: REFERENCE_KEYWORD SPACE -> pushMode(PrefixedReferenceMode) ;
mode PrefixedReferenceMode;
REFERENCE_PREFIX: Prefix;
REFERENCE_PREFIX_SEPARATOR: COLON -> pushMode(IriMode);
END_IRI: SEMICOLON -> popMode;
mode IriMode;
IRI: String -> popMode;
TYPE_DEFINITION: TYPE_KEYWORD SPACE -> pushMode(PrefixedNameMode) ;
mode PrefixedNameMode;
NAME_PREFIX: Prefix;
NAME_PREFIX_SEPARATOR: COLON -> pushMode(LocalNameMode);
END_NAME: SEMICOLON -> popMode;
mode LocalNameMode;
LOCAL_NAME: (Underscores | Digits | LowercaseLetters | UppercaseLetters)+ -> popMode;
解析器
parser grammar NotWorkingParserGrammar ;
options { tokenVocab=NotWorkingLexerGrammar; }
document: reference* type* EOF ;
prefixedReference: REFERENCE_PREFIX REFERENCE_PREFIX_SEPARATOR IRI;
reference: PREFIXED_REFERENCE prefixedReference END_IRI;
prefixedName: NAME_PREFIX NAME_PREFIX_SEPARATOR LOCAL_NAME;
type: TYPE_DEFINITION prefixedName END_NAME;
输出
对于以下输入:
reference schema:"https://schema.org/";
reference dc:"https://www.dublincore.org/";
type dc:Author;
这是输出:
line 4:0 token recognition error at: 't'
line 4:1 token recognition error at: 'y'
line 4:2 token recognition error at: 'p'
line 4:3 token recognition error at: 'e'
line 4:4 token recognition error at: ' '
line 4:5 token recognition error at: 'd'
line 4:6 token recognition error at: 'c'
line 4:7 token recognition error at: ':'
line 4:8 token recognition error at: 'A'
line 4:9 token recognition error at: 'u'
line 4:10 token recognition error at: 't'
line 4:11 token recognition error at: 'h'
line 4:12 token recognition error at: 'o'
line 4:13 token recognition error at: 'r;'
我使用模式的理由是限制规则的范围。这是我控制的一种语言,但不希望大幅改变它。这种语言比我在这里展示的要多得多,我们已经有了一个语法(目前是一个组合语法),但它很脆弱。我尝试进行更改以防止前缀中的大写字符,但允许它们出现在本地名称中,但是这个滚雪球和其他规则开始适用。研究表明模式是处理这种情况的一种方法,但我对 ANTLR 不是很熟悉,所以我可能误解了它。
解决方案
当遇到这样的错误/警告时:
line 4:0 token recognition error at: 't'
line 4:1 token recognition error at: 'y'
line 4:2 token recognition error at: 'p'
line 4:3 token recognition error at: 'e'
...
这意味着词法分析器无法为输入构造标记(type ...
在这种情况下)。在您的情况下,这意味着词法分析器无法以当时所处的模式从输入创建标记。
我尝试进行更改以防止前缀中出现大写字符,但允许它们出现在本地名称中,但是这个滚雪球和其他规则开始适用
有两种方法可以解决此类问题:
- 只需像任何普通标识符(大写或小写)一样解析前缀,解析后,遍历生成的解析树并使用 ANTLR 访问者或侦听器验证前缀标识符是否真的小写(参见:https ://github.com/ antlr/antlr4/blob/master/doc/listeners.md )
- 在您的词法分析器中区分小写和大写标识符,并在您的解析器规则中相应地使用它们,这样的事情可能会起作用:
document
: reference* type* EOF
;
reference
: K_REFERENCE LOWER_ID COL STRING SCOL
;
type
: K_TYPE LOWER_ID COL id OPAR CPAR
;
id
: LOWER_ID
| ID
;
K_REFERENCE : 'reference';
K_TYPE : 'type';
LOWER_ID : [a-z_] [a-z_0-9]*;
ID : [a-zA-Z_] [a-zA-Z_0-9]*;
STRING : '"' ~["]* '"';
SCOL : ';';
COL : ':';
OPAR : '{';
CPAR : '}';
SPACES : [ \t\r\n] -> skip;
模式旨在用于实际上是相互嵌入的 2 种(或更多)语言的输入。例如解析 HTML 文件:有内容(文本)和带有属性的标签。据我所知,你没有使用它,因为它是被使用的,IMO。
推荐阅读
- c# - 将日期时间声明为 UTC?
- excel - 从另一个工作簿的各个工作表中的单元格中获取值
- python - 使用 Pandas 获取重复的大型 Excel 文件行
- ansible - Ansible 有没有办法让全局任务在所有剧本中使用
- ios - Swift - 在 MapKit 上的多个图像之间切换的有效方法
- node.js - 在 Mocha/Chai/Sinon 中存根 DB 连接线
- git - 如何获取所有 git 提交到合并分支的列表?
- javascript - 我该如何使用
- java - java.io.FileNotFoundException 当我试图读入我的 Android Studio 项目中的二进制文件时
- android - Recyclerview 数据未通过协程获取