2017-10-09 9 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

내가 내가 ParseTree를 만드는 방법과 내가 이후에 위의 XML로 변환 다음 문자열

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

를 생성 toStringTree 방법을 사용하여 사용 (I 작동 간단한 일반적인 코드를 사용 어떤 문법든지를 위해). 나는이 접근법이 거짓임을 안다. toStringTree없이 해결할 수 있습니까? 방문자의 각 문법 규칙을 무시할 필요가 없습니다. (나는 그들의 수백이있다).

편집

나는 기본적으로 XML 형식으로 일반 파스 트리 직렬화의 일종이 필요합니다. 주요 목표는 각 규칙에 대해 Java로 특수 직렬화 메소드를 작성할 필요가 없다는 것입니다.

+0

"printXml (ParseTree tree)"'정적 메서드를 사용하여 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 
; 

당신의 방문자는 다음과 같이 수 :

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); 
:

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

실행, 다음 코드를이 방문객을 테스트하려면

인쇄 할 내용 :

<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 있습니다.자바

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입니다.