我是一个写语法的新手,我已经读了大约一半的Antlr4:权威指南,我想我应该对我正在写的语法进行一下修改。我被一些听起来很基本的东西卡住了,但事实证明比我想象的要难。
我试图解析小(android反汇编)类声明。它看起来像这样:
.class public Lcom/packageName/example;
规则是,所有完全限定的类都以L
为前缀,然后包部分用/
分隔,然后类名是;
之前的最后一部分。
所以我有一个一般的WS : [ trn]+ -> skip ;
在我的词法分析器部分,但我正在努力与如何禁用解析fullyQualifiedClass
时。我想禁用它,以便在没有L
的情况下返回第一个classPackageComponent
,但如果忘记了L
,则将显示Error: 'L' expected'
。包名也是一样,包名和它们的/
分隔符之间不能有空格。我知道问题是我的解析器甚至没有看到WS字符,因为Lexer只是把它们扔了出去。我应该如何处理这个问题?渠道是答案吗?我还没有读到这一章,但其他帖子暗示它可能是。
我的错误语法代码如下:
grammar smali;
smaliClass : classDeclaration;
classDeclaration : '.class' accessModifier fullyQualifiedClass;
accessModifier: 'public' | 'private';
fullyQualifiedClass: 'L' ~WS classPackage? className;
classPackage: (classPackageComponent ~WS '/')+;
classPackageComponent: Identifier;
className: Identifier;
Identifier
: Letter (Letter|JavaIDDigit)*
;
fragment
Letter
: 'u0024' |
'u0041'..'u005a' |
'u005f' |
'u0061'..'u007a' |
'u00c0'..'u00d6' |
'u00d8'..'u00f6' |
'u00f8'..'u00ff' |
'u0100'..'u1fff' |
'u3040'..'u318f' |
'u3300'..'u337f' |
'u3400'..'u3d2d' |
'u4e00'..'u9fff' |
'uf900'..'ufaff'
;
fragment
JavaIDDigit
: 'u0030'..'u0039' |
'u0660'..'u0669' |
'u06f0'..'u06f9' |
'u0966'..'u096f' |
'u09e6'..'u09ef' |
'u0a66'..'u0a6f' |
'u0ae6'..'u0aef' |
'u0b66'..'u0b6f' |
'u0be7'..'u0bef' |
'u0c66'..'u0c6f' |
'u0ce6'..'u0cef' |
'u0d66'..'u0d6f' |
'u0e50'..'u0e59' |
'u0ed0'..'u0ed9' |
'u1040'..'u1049'
;
WS : [ trn]+ -> skip ;
首先要处理的几个问题:
- 词法分析器和解析器在很大程度上是分开的:解析器不能修改Lexer的操作解析器应该处理令牌,而不是字符或字符字符串——Lexer无法看到解析器中发生了什么,所以您将失去Lexer进行某些优化的能力
- Lexer不插入空白令牌之间的令牌认识到
词法分析程序
Class: '.class' ;
Semi: ';' ;
Modifier: 'public' | 'private';
Slash: '/' ;
ClassPrefix : 'L' { isPrefix() }? ;
Identifier : Letter (Letter|JavaIDDigit)* ;
WS : [ trn]+ -> skip ;
...
Lexing .class public Lcom/packageName/example;
(如果isPrefix()
总是返回false)将产生令牌流:
Class, Modifier, Identifier, Slash, Identifier, Slash, Identifier, Semi
这就是解析器将看到的内容。因此,解析器规则变成
classDeclaration : Class Modifier fullyQualifiedClass ;
fullyQualifiedClass: ClassPrefix? (Identifier Slash)* Identifier Semi ;
ClassPrefix
的问题是,没有自然的分隔符,您可以使用它分离出来。不过,有很多方法可以绕过这个问题。
也许最直接的方法是让Lexer检查,每次Lexer看到'L'时,它是否在看起来像类名的东西的开头。这就是谓词'{isPrefix()}?是打算做的事。查看这里和这里的谓词实现示例。
另一种方法是从Lexer中完全删除ClassPrefix
规则,并在解析器规则操作中检测前缀,或者更好的是,在随后的解析树遍历中检测前缀:
fullyQualifiedClass: (Identifier Slash)* Identifier Semi ;
Identifier的第一个实例是一个Token,其中包含与该Token匹配的底层文本。可以调用生成的解析器类'YourParser'.FullyQualifiedClassContext.Identifier()
的每个实例,以返回所遇到的标识符令牌的列表,该列表按从左到右的顺序排列。检查最左边的前缀,并相应地处理。