ANTLR 获取并拆分词法分析器内容
首先,抱歉我的英语,我还在学习中。
我正在为我的框架编写一个Python模块,用来解析CSS文件。我尝试过正则表达式和ply(Python的词法分析器和解析器),但最后发现自己在用ANTLR。
第一次尝试,我需要从CSS文件中解析注释。以下是我需要解析的CSS字符串:
/*test*/
/*
test1
/*
/*test2/*nested*/comment/*
我知道CSS不允许嵌套注释,但我在我的框架中需要这个功能。我写了一个简单的ANTLR语法:
grammar CSS;
options {
language = Python;
}
styleSheet
: comments EOF ;
comments
: NESTED_ML_COMMENT*
;
NESTED_ML_COMMENT
: '/*'
(options {greedy=false;} : (NESTED_ML_COMMENT | . ) )*
'*/'
;
LINEBREAK
: ('\n\r' | '\n')+{$channel=HIDDEN; };
我得到的结果是:
我期望的结果(画得不错 :D):
请注意,我不想在结果中看到 /* 和 */。
有没有办法在纯ANTLR中做到这一点?我对在ANTLR中使用Python没有问题,但如果有办法不使用Python实现这一点,我将非常感激。
2 个回答
你应该使用 !
和 ^
这两个提示来处理抽象语法树(AST)。如果你想让 /*
在你的 AST 中不出现,就在它后面加上 !
。而如果你想控制哪些元素成为 AST 子树的根节点,就在它后面加上 ^
。这可能看起来像这样:
NESTED_ML_COMMENT
: COMMENT_START!
(options {greedy=false;} : (NESTED_ML_COMMENT^ | . ) )*
COMMENT_END!
;
这里有一个专门关于这些操作符的问题,现在你知道它们的存在了,希望对你有帮助:在 ANTLR 语法中,^ 和 ! 代表什么
不,这没有简单的方法。因为 NESTED_ML_COMMENT
是一种词法分析规则(简单的标记),你不能让解析规则在源代码中创建更多的结构,比如 /*test2/*nested*/comment*/
:词法规则总是保持为“平面”的字符序列。当然,有一些简单的方法可以重写这个字符序列(比如去掉 /*
和 */
),但是要创建父子层级结构就不行了。
为了创建你在第二张图片中展示的层级结构,你需要把你的注释规则“提升”到解析器中(也就是把它变成解析规则)。在这种情况下,你的词法分析器会有 COMMENT_START : '/*';
和 COMMENT_END : '*/';
规则。但这样会引发很多问题:在你的词法分析器中,你还需要考虑所有可能出现在 /*
和 */
之间的字符。
你可以创建另一个解析器来解析(嵌套的)注释,并在你的 CSS
语法中使用它。在你的 CSS
语法中,你可以保持原样,而你的第二个解析器是一个专门处理注释的解析器,它会从注释标记中创建层级结构。
这里有个快速演示。语法:
grammar T;
parse
: comment EOF
;
comment
: COMMENT_START (ANY | comment)* COMMENT_END
;
COMMENT_START : '/*';
COMMENT_END : '*/';
ANY : . ;
会把源代码 /*test2/*nested*/comment*/
解析成以下的解析树:
当然,你可以把 /*
和 */
去掉。
在你的 CSS
语法中,你接着这样做:
comment
: NESTED_ML_COMMENT
{
text = $NESTED_ML_COMMENT.text
# invoke the TParser (my demo grammar) on `text`
}
;
编辑
注意,ANTLRWorks 创建了它自己的内部解析树,你无法访问。如果你不告诉 ANTLR 生成一个合适的抽象语法树(AST),你最终只会得到一个平面的标记列表(尽管 ANTLRWorks 表示这是一种树)。
这里有个之前的问答,解释了如何创建一个合适的 AST: 如何输出使用 ANTLR 构建的 AST?
现在让我们回到我上面提到的“注释”语法。我会把 ANY
规则重命名为 TEXT
。目前,这个规则一次只匹配一个字符。但让它匹配到下一个 /*
或 */
更方便。可以通过在词法分析器类中引入一个简单的 Python 方法来实现这个检查。在 TEXT
规则中,我们会在一个谓词中使用这个方法,这样 *
会在不直接跟 /
的情况下被匹配,而 /
会在不直接跟 *
的情况下被匹配:
grammar Comment;
options {
output=AST;
language=Python;
}
tokens {
COMMENT;
}
@lexer::members {
def not_part_of_comment(self):
current = self.input.LA(1)
next = self.input.LA(2)
if current == ord('*'): return next != ord('/')
if current == ord('/'): return next != ord('*')
return True
}
parse
: comment EOF -> comment
;
comment
: COMMENT_START atom* COMMENT_END -> ^(COMMENT atom*)
;
atom
: TEXT
| comment
;
COMMENT_START : '/*';
COMMENT_END : '*/';
TEXT : ({self.not_part_of_comment()}?=> . )+ ;
想了解更多关于谓词语法的信息,{ boolean_expression }?=>
,可以查看这个问答: 什么是 ANTLR 中的“语义谓词”?
要测试这一切,确保你安装了合适的 Python 运行时库(见 ANTLR Wiki)。并确保使用 ANTLR 版本 3.1.3 和这个运行时。
生成词法分析器和解析器的方法如下:
java -cp antlr-3.1.3.jar org.antlr.Tool Comment.g
并用以下 Python 脚本测试词法分析器和解析器:
#!/usr/bin/env python
import antlr3
from antlr3 import *
from antlr3.tree import *
from CommentLexer import *
from CommentParser import *
# http://www.antlr.org/wiki/display/ANTLR3/Python+runtime
# http://www.antlr.org/download/antlr-3.1.3.jar
def print_level_order(tree, indent):
print '{0}{1}'.format(' '*indent, tree.text)
for child in tree.getChildren():
print_level_order(child, indent+1)
input = '/*aaa1/*bbb/*ccc*/*/aaa2*/'
char_stream = antlr3.ANTLRStringStream(input)
lexer = CommentLexer(char_stream)
tokens = antlr3.CommonTokenStream(lexer)
parser = CommentParser(tokens)
tree = parser.parse().tree
print_level_order(tree, 0)
如你所见,从源代码 "/*aaa1/*bbb/*ccc*/*/aaa2*/"
创建了以下的 AST:
COMMENT
aaa1
COMMENT
bbb
COMMENT
ccc
aaa2
编辑 II
我还可以展示如何从你的 CSS 语法中调用注释解析器。这里有个快速演示:
grammar CSS;
options {
output=AST;
language=Python;
}
tokens {
CSS_FILE;
RULE;
BLOCK;
DECLARATION;
}
@parser::header {
import antlr3
from antlr3 import *
from antlr3.tree import *
from CommentLexer import *
from CommentParser import *
}
@parser::members {
def parse_comment(self, text):
lexer = CommentLexer(antlr3.ANTLRStringStream(text))
parser = CommentParser(antlr3.CommonTokenStream(lexer))
return parser.parse().tree
}
parse
: atom+ EOF -> ^(CSS_FILE atom+)
;
atom
: rule
| Comment -> {self.parse_comment($Comment.text)}
;
rule
: Identifier declarationBlock -> ^(RULE Identifier declarationBlock)
;
declarationBlock
: '{' declaration+ '}' -> ^(BLOCK declaration+)
;
declaration
: a=Identifier ':' b=Identifier ';' -> ^(DECLARATION $a $b)
;
Identifier
: ('a'..'z' | 'A'..'Z') ('a'..'z' | 'A'..'Z' | '0'..'9')*
;
Comment
: '/*' (options {greedy=false;} : Comment | . )* '*/'
;
Space
: (' ' | '\t' | '\r' | '\n') {$channel=HIDDEN;}
;
如果你用 CSSParser 解析源代码:
h1 { a: b; c: d;}
/*aaa1/*bbb/*ccc*/*/aaa2*/
p {x : y;}
你将得到以下树:
CSS_FILE
RULE
h1
BLOCK
DECLARATION
a
b
DECLARATION
c
d
COMMENT
aaa1
COMMENT
bbb
COMMENT
ccc
aaa2
RULE
p
BLOCK
DECLARATION
x
y
你可以通过运行这个测试脚本来查看:
#!/usr/bin/env python
import antlr3
from antlr3 import *
from antlr3.tree import *
from CSSLexer import *
from CSSParser import *
def print_level_order(tree, indent):
print '{0}{1}'.format(' '*indent, tree.text)
for child in tree.getChildren():
print_level_order(child, indent+1)
input = 'h1 { a: b; c: d;}\n\n/*aaa1/*bbb/*ccc*/*/aaa2*/\n\np {x : y;}'
char_stream = antlr3.ANTLRStringStream(input)
lexer = CSSLexer(char_stream)
tokens = antlr3.CommonTokenStream(lexer)
parser = CSSParser(tokens)
tree = parser.parse().tree
print_level_order(tree, 0)