ANTLR获取并拆分词法分析器内容

时间:2011-05-08 19:35:11

标签: python antlr

首先,抱歉我的英语,我还在学习。

我为我的框架编写Python模块,它解析CSS文件。我尝试使用正则表达式,ply(python lexer和parser),但我发现自己在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; };

我得到的结果是:

enter image description here

我的期望(油漆工作:D):

enter image description here

请注意,我希望/ *和* /结果。

在纯ANTLR中有没有办法做到这一点?我在ANTLR中使用python没有问题,但是如果有任何方法没有python我会感激不尽。

2 个答案:

答案 0 :(得分:2)

不,没有简单的方法。由于NESTED_ML_COMMENT是词法分析器规则(“简单”标记),因此您不能让解析器规则在源代码中创建任何更多结构,如/*test2/*nested*/comment*/:词法分析器规则将始终保持“平坦”字符序列。当然,有(简单)方法来重写这个字符序列(即删除/**/),但创建父兄弟层次结构,没有。

为了创建您在2 nd 图像中显示的层次结构,您必须将评论规则“提升”到解析器(因此将其作为解析器规则)。在这种情况下,您的词法分析器会有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*/解析为以下解析树:

enter image description here

您可以重写,以便删除/**/

CSS语法中,您可以:

comment
  :  NESTED_ML_COMMENT 
     {
       text = $NESTED_ML_COMMENT.text
       # invoke the TParser (my demo grammar) on `text`
     }
  ;

修改

请注意,ANTLRWorks会创建您自己的内部分析树,您无权访问该分析树。如果你没有告诉ANTLR生成一个合适的AST,你最终会得到一个平坦的标记列表(即使ANTLRWorks建议它是某种树)。

这是以前的Q& A,解释了如何创建正确的AST:How to output the AST built using ANTLR?

现在让我们回到上面发布的“评论”语法。我将ANY规则重命名为TEXT。目前,此规则一次只匹配一个字符。但是让它一直匹配到下一个/**/会更方便。这可以通过在lexer类中引入一个执行此检查的普通Python方法来完成。在TEXT规则中,我们会在谓词中使用该方法,以便{em} 后直接跟*和{/匹配/ {1}}如果后直接跟*匹配,则会匹配:

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 }?=>的更多信息:What is a 'semantic predicate' in ANTLR?

要测试这一切,请确保安装了正确的Python运行时库(请参阅ANTLR Wiki)。并确保在此运行时使用ANTLR version 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语法中调用Comment解析器。这是一个快速演示:

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;}
  ;

如果您解析来源:

h1 {  a: b;  c: d;}

/*aaa1/*bbb/*ccc*/*/aaa2*/

p {x  :  y;}

使用CSSParser,您将获得以下树:

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)

答案 1 :(得分:0)

您应该使用!^ AST提示。要使/*不出现在您的AST中,请将!放在其后面。要控制哪些元素成为AST子树的根,请附加^。它可能看起来像这样:

NESTED_ML_COMMENT
:   COMMENT_START!
    (options {greedy=false;} : (NESTED_ML_COMMENT^ | . ) )* 
    COMMENT_END!
;

这里有一个关于这些运算符的问题,现在你知道存在了,我希望它会有用: What does ^ and ! stand for in ANTLR grammar