2017-10-09 118 views
2

我有一个编译语法,我想用它将输入序列转换为XML。请注意,在我的情况下,我有一个非常大的语法和许多规则,我想避免覆盖我的代码中的每个语法规则。将解析树转换为XML

我会用一个例子来避免混淆。让我们有以下语法

grammar expr; 

prog: stat+ ; 

stat: expr NEWLINE 
| ID '=' expr NEWLINE 
| NEWLINE 
; 

expr: expr ('*'|'/') expr 
| INT 
| ID 
| '(' expr ')' 
; 

ID : [a-zA-Z]+ ; // match identifiers 
INT : [0-9]+ ; // match integers 
NEWLINE:'\r'? '\n' ; // return newlines to parser (is end-statement signal) 
WS : [ \t]+ -> skip ; // toss out whitespace 

输入序列

A = 10 
B = A * A 

预计输出

<prog> 
    <stat> 
     A = 
     <expr> 10 
     </expr> 
     \r\n 
    </stat> 
    <stat> 
     B = 
     <expr> 
      <expr>A</expr> 
      * 
      <expr> A</expr> 
     </expr> 
     \r\n 
    </stat> 
</prog> 

相当于解析树

enter image description here

目前我使用其中I创建ParseTree的方法以及使用该toStringTree方法我生成下面的字符串

(prog (stat A = (expr 10) \r\n) (stat B = (expr (expr A) * (expr A)) \r\n)) 

我随后转化为上面所示的XML(I使用简单的通用代码工作对于任何语法)。我发现这种方法是虚拟的。没有toStringTree可以解决吗?我想避免重写我的访客中的每个语法规则。 (我有数百个)。

编辑

基本上,我需要某种通用的分析树序列化的XML格式。主要目标是我不必为每个规则编写Java特殊的序列化方法。

+0

如何使用静态方法''printXml(ParseTree树)''在ParseTree上执行递归下降? –

+0

@StefanHaustein我会检查它,谢谢。我对Antlr没有太多经验。 –

+0

生成XML并不是一个好主意。你将如何操纵它? XSLT在大型树上并不是很出色,并且您会为大小适中的Java程序获得巨大的树。更糟糕的是,XSLT不擅长上下文敏感检查,所有java构造都是上下文敏感的(变量意味着不同的事情取决于它们的声明)。解析后的生活为什么你想要的不仅仅是AST:http://www.semdesigns.com/Products/DMS/LifeAfterParsing.html –

回答

1

也许这种方法可能适合您的需求。为了便于阅读,我用额外的标签t包裹了终端符号,同时也跳过了带有空白区域的终端符号。然而,如果需要的话,调整输出应该不是一个大问题。

final exprLexer lexer = new exprLexer(CharStreams.fromString("A=10\nB = A * A\n")); 
final CommonTokenStream tokens = new CommonTokenStream(lexer); 
final exprParser parser = new exprParser(tokens); 
final ParseTree tree = parser.prog(); 
ParseTreeWalker.DEFAULT.walk(new exprBaseListener() 
{ 
    final String INDENT = " "; 
    int level = 0; 
    @Override 
    public void enterEveryRule(final ParserRuleContext ctx) 
    { 
     System.out.printf("%s<%s>%n", indent(), parser.getRuleNames()[ctx.getRuleIndex()]); 
     ++level; 
     super.enterEveryRule(ctx); 
    } 

    @Override 
    public void exitEveryRule(final ParserRuleContext ctx) 
    { 
     --level; 
     System.out.printf("%s</%s>%n", indent(), parser.getRuleNames()[ctx.getRuleIndex()]); 
     super.exitEveryRule(ctx); 
    } 

    @Override 
    public void visitTerminal(final TerminalNode node) 
    { 
     final String value = node.getText(); 
     if (!value.matches("\\s+")) 
     { 
      System.out.printf("%s<t>%s</t>%n", indent(), node.getText()); 
     } 
     super.visitTerminal(node); 
    } 

    private String indent() 
    { 
     return String.join("", Collections.nCopies(level, INDENT)); 
    } 
}, tree); 
+0

优秀的,我新的,有一个更好的解决方案。非常感谢你。 –

3

你可以利用ANTLR4的访问者功能。根据您使用的工具,您可能需要在生成类时添加-visitor command line parameter

为了这个很好地工作,我加入了一些labels to your parser rules

prog 
: stat+ EOF 
; 

stat 
: expr NEWLINE  #exprStat 
| ID '=' expr NEWLINE #assignStat 
| NEWLINE    #emptyStat 
; 

expr 
: lhs=expr op=('*'|'/') rhs=expr #multExpr 
| INT       #intExpr 
| ID        #idExpr 
| '(' expr ')'     #nestedExpr 
; 

你的访问者可能是这样的:

public class XmlVisitor extends exprBaseVisitor<String> { 

    @Override 
    public String visitProg(exprParser.ProgContext ctx) { 
    StringBuilder builder = new StringBuilder("<prog>"); 
    for (exprParser.StatContext stat : ctx.stat()) { 
     builder.append(super.visit(stat)); 
    } 
    return builder.append("</prog>").toString(); 
    } 

    @Override 
    public String visitExprStat(exprParser.ExprStatContext ctx) { 
    return "expr"; 
    } 

    @Override 
    public String visitAssignStat(exprParser.AssignStatContext ctx) { 
    return "<stat>" + ctx.ID() + " = " + super.visit(ctx.expr()) + "\\r\\n</stat>"; 
    } 

    @Override 
    public String visitEmptyStat(exprParser.EmptyStatContext ctx) { 
    return "\\r\\n"; 
    } 

    @Override 
    public String visitMultExpr(exprParser.MultExprContext ctx) { 
    return "<expr>" + super.visit(ctx.lhs) + ctx.op.getText() + super.visit(ctx.rhs) + "</expr>"; 
    } 

    @Override 
    public String visitIntExpr(exprParser.IntExprContext ctx) { 
    return "<expr>" + ctx.INT().getText() + "</expr>"; 
    } 

    @Override 
    public String visitIdExpr(exprParser.IdExprContext ctx) { 
    return "<expr>" + ctx.ID().getText() + "</expr>"; 
    } 

    @Override 
    public String visitNestedExpr(exprParser.NestedExprContext ctx) { 
    return "<expr>" + super.visit(ctx.expr()) + "</expr>"; 
    } 
} 

为了测试这个客人,运行下面的代码:

String source = "A = 10\nB = A * A\n"; 
exprLexer lexer = new exprLexer(CharStreams.fromString(source)); 
exprParser parser = new exprParser(new CommonTokenStream(lexer)); 
ParseTree tree = parser.prog(); 
String xml = new XmlVisitor().visit(tree); 
System.out.println(xml); 

将打印:

<prog><stat>A = <expr>10</expr>\r\n</stat><stat>B = <expr><expr>A</expr>*<expr>A</expr></expr>\r\n</stat></prog> 
+0

非常感谢您的努力。然而,请注意我的最后一句话“我想避免重写我的访客中的每个语法规则(我有数百个)”。而且,如果语法有所改变,访问者必须适当地改变,这在我目前的解决方案中是不必要的。 –

+0

如果你的一个语法规则发生了变化,你总是需要改变别的东西。无论是在访客中,还是以你写的静态方法。我认为这是解决您的问题的“最好”(最清洁的)方式,这就是我发布它的原因。随意使用或不使用:我相信这会使某人受益。当然,欢迎您! :) –

+0

静态方法,你检查你的树并发出你的节点将(可能)成为一大堆意大利面代码。在访问者中,每个节点都以自己的方法发出,保持它的整洁。单元测试也很容易! –

0

我创建了一个读取由ParseTree.toStringTree方法产生的LISP风格树的ANTLR语法。项目可以访问here。它具有以下部分

语法

grammar str; 

expr: 
STRING expr        # exprString 
| LR_BRACKET expr RR_BRACKET expr   # exprParenthesis 
| LR_STRING_BRACKET expr RR_BRACKET expr # exprRule 
| <EOF>         # exprEnd 
| EOF_MARK         # exprEOF 
|           # exprEpsilon 
; 

EOF_MARK:   '<EOF>' ; 
LR_STRING_BRACKET: '(' ~[()]+; 
LR_BRACKET:   '('; 
RR_BRACKET:   ')'; 
STRING:    ~[()]+; 
SPACE:    [ \t\r\n]+ -> skip; // toss out whitespace 

strXMLVisitor。java的

public class strXMLVisitor extends strBaseVisitor<String> { 

    @Override 
    public String visitExprString(strParser.ExprStringContext ctx) 
    { 
    return ctx.STRING().getText() + super.visit(ctx.expr()); 
    } 

    @Override 
    public String visitExprParenthesis(strParser.ExprParenthesisContext ctx) { 
    return "(" + super.visit(ctx.expr(0)) + ")" + super.visit(ctx.expr(1)); 
    } 

    @Override 
    public String visitExprRule(strParser.ExprRuleContext ctx) { 
    String value = ctx.LR_STRING_BRACKET().getText().substring(1); 
    return "<" + value + ">" + super.visit(ctx.expr(0)) + "</" + value + ">" + super.visit(ctx.expr(1)); 
    } 

    @Override 
    public String visitExprEnd(strParser.ExprEndContext ctx) { 
    return ""; 
    } 

    @Override 
    public String visitExprEOF(strParser.ExprEOFContext ctx) { 
    return ""; 
    } 

    @Override 
    public String visitExprEpsilon(strParser.ExprEpsilonContext ctx) { 
    return ""; 
    } 
} 

main.java

import org.antlr.v4.runtime.*; 
import org.antlr.v4.runtime.tree.*; 

public class main { 
    public static void main(String[] args) throws Exception { 
     // create a CharStream that reads from standard input 
     ANTLRInputStream input = new ANTLRInputStream(System.in); 
     // create a lexer that feeds off of input CharStream 
     strLexer lexer = new strLexer(input); 
     // create a buffer of tokens pulled from the lexer 
     CommonTokenStream tokens = new CommonTokenStream(lexer); 
     // create a parser that feeds off the tokens buffer 
     strParser parser = new strParser(tokens); 
     ParseTree tree = parser.expr(); // begin parsing at init rule 

     String xml = "<?xml version=\"1.0\"?>" + new strXMLVisitor().visit(tree); 
     System.out.println(xml);  
    } 
} 

一旦你有antlr4准备就绪(并在CLASSPATH参考以及)你可以使用下面的命令来运行它:

antlr4 -visitor str.g4 
javac *.java 
java main < file 

文件必须包含LISP-tree格式的输入,并且结果是标准输出中的XML。