3

Java のような言語から複数の言語への翻訳プログラムを作成しようとしています。

現在、私は2つの問題に直面しています:

まず、複雑な表現を一連の基本的な操作に分解し、目的の言語に翻訳します。

たとえば、私の開始言語は次のようになります。

var a = (ln(b) + avg(c))*2

私はそれを次のように翻訳したいと思います:

var x1 = log_N(b);
var x2 = average(c);
var x3 = sum(x1, x2);
var a = multiply(x3, 2);

Treeパーサーを使用する必要があると思いますが、StringTemplateと統合する方法がわかりません。さらに、x1、x2、x3 などの変数を追加していますが、この状況を処理する方法がわかりません。

2 番目の問題は、目的の言語の 1 つが plsql のような言語であることです。この場合、すべての出力変数がカーソルになり、それらを関数に渡すようにする必要があります。

たとえば、式:

var a = (ln(b) + avg(c))*2

このように翻訳する必要があります:

log_N(x1, b);
average(x2, c);
sum(x3, x1, x2);
multiply(a, x3, 2);

ここで、x1、x2、x3、および a が出力カーソルになります。

誰かが正しい解決策を見つけるのを手伝ってくれますか?

ありがとう

4

1 に答える 1

4

Tree パーサーを使用する必要があると思いますが、StringTemplate と統合する方法がわかりません。

StringTemplate をツリー パーサーに統合することは、基本的にそれをトークン パーサーに統合することと同じです。outputオプションを として定義しtemplate、それに応じてルール生成を記述します。

以下は、出力にテンプレートを使用する小さなツリー文法です。この文法と以前の回答で説明した文法との唯一の意味のある違いは、これがトークンではなくツリー ノードで動作することです。テンプレートは同じように機能します。

tree grammar AstToTemplateParser;

options { 
    output = template;
    tokenVocab = JavaLikeToAst;
    ASTLabelType = CommonTree;
}


program
    : ^(PROGRAM decls+=decl+) -> write(text={$decls})
    ;

decl
    : ^(DECL ID ^(op args+=arg*)) -> assign(name={$ID.text}, op={$op.st}, args={$args})
    | ^(DECL ID ^(CALL method args+=arg*)) -> assign(name={$ID.text}, op={$method.st}, args={$args})
    ;

arg 
    : ID -> write(text={$ID.text})
    | INT -> write(text={$INT.text})
    ;

method
    : ID -> op(name={$ID.text})
    ;

op  : STAR  -> op(name={$STAR.text})
    | DIV   -> op(name={$DIV.text})
    | PLUS  -> op(name={$PLUS.text})
    | MINUS -> op(name={$MINUS.text})
    ;

さらに、x1、x2、x3 などの変数を追加していますが、この状況を処理する方法がわかりません。

それがキッカーです。私が知っている最も簡単な方法 (これはそれほど簡単ではありません) は、最初にトークン パーサーを使用してベースライン AST を生成し、次にツリー パーサーを使用して AST を宣言のリストにフラット化し、それぞれが元の入力からの式を表すことです -- x1x2、およびx3あなたの質問に。

中間段階は次のようになります。

元の入力

var a = (ln(b) + avg(c))*2

ベースラインAST

ベースライン AST

平坦化された AST

平坦化されたAST

標準テンプレート適用

 var var0 = log_N(b);
 var var1 = average(c); 
 var var2 = add(var0, var1);
 var a = multiply(var2, 2);

PLSQL テンプレートの適用

  log_N(var0, b);
  average(var1, c);
  add(var2, var0, var1);
  multiply(a, var2, 2);

これは、質問にあるものに似たベースライン AST を単純に生成するトークン パーサーの文法です。ここでは特に注目すべき点はなく、典型的な AST を生成するだけです。

grammar JavaLikeToAst;

options { 
    output = AST;
}

tokens { 
    PROGRAM; DECL; CALL; 
}

compilationUnit : statement* EOF -> ^(PROGRAM statement*);
statement       : decl;
decl            : VAR ID EQ expr -> ^(DECL ID expr);
expr            : add_expr;
add_expr        : mul_expr ((PLUS|MINUS)^ mul_expr)*;
mul_expr        : call_expr ((STAR|DIV)^ call_expr)*;
call_expr       : ID LPAR arglist? RPAR -> ^(CALL ID arglist?)
                | primary_expr;
arglist         : expr (COMMA! expr)*;
primary_expr    : ID | INT | LPAR! expr RPAR!; 

VAR     : 'var';
ID      : ('a'..'z'|'A'..'Z')('a'..'z'|'A'..'Z'|'_'|'0'..'9')*;
INT     : ('0'..'9')+;
COMMA   : ',';
SEMI    : ';';
LCUR    : '{';
RCUR    : '}';
LPAR    : '(';
RPAR    : ')';
EQ      : '=';
PLUS    : '+';
MINUS   : '-';
STAR    : '*';
DIV     : '/';
WS      : (' '|'\t'|'\f'|'\r'|'\n'){skip();};

ここが醜いところです(少なくとも私が選んだ方法では;))。以下のツリー文法は、上から生成されたベースライン AST を、AST の式 (フラット化された AST) に対応する宣言のリストに変換します。この下で、文法の楽しい部分について説明します。

tree grammar AstToFlatAstParser;

options { 
    output = AST;
    tokenVocab = JavaLikeToAst;
    ASTLabelType = CommonTree;
    filter = true;
}

@header { 
    import java.util.HashMap;
}

@members {
    private int currentId = 0;
    private HashMap<Integer, Object> exprs = new HashMap<Integer, Object>();
    private boolean newDecls = false;

    private int nextId() { 
        return currentId++;
    }

    private Object generateId(int id) { 
        return adaptor.create(ID, "var" + id);
    }  

    private void saveExpr(int id, Object expr){
        newDecls = true;
        exprs.put(id, expr);
    }

    private Object buildNewDecls() {
        Object newDecls = adaptor.nil();

        for (int i = 0; i < currentId; ++i){
            if (!exprs.containsKey(i)){
                continue; //This id was generated but not used.
            }

            Object expr = exprs.get(i);
            Object decl = adaptor.create(DECL, tokenNames[DECL]);
            adaptor.addChild(decl, adaptor.create(ID, "var" + i));
            adaptor.addChild(decl, expr);
            adaptor.addChild(newDecls, decl);
        }

        exprs.clear();

        return newDecls;
    }
}

bottomup
    : exit_program
    | exit_op
    ;

exit_op
    @init {
        int myId = nextId();
    }
    : ^(binary_op reduced reduced)
        {$start.parent != null && $start.parent.getType() != DECL}? 
        {saveExpr(myId, $start);} 
        -> {generateId(myId)}
    | ^(CALL ID .*) 
        {$start.parent != null && $start.parent.getType() != DECL}? 
        {saveExpr(myId, $start);} 
        -> {generateId(myId)}
    ;   

binary_op       : STAR | DIV | PLUS | MINUS;

reduced         : ID | INT; 

exit_program
    //Only rebuild PROGRAM if a new declaration is going to be built, that is, when "newDecls" is true.
    //Otherwise PROGRAM is considered changed when it isn't and the processing never ends.
    : {newDecls}? ^(PROGRAM old+=.*) {newDecls = false;} 
        -> ^(PROGRAM {buildNewDecls()} $old*)
    ;

まず、文法はほとんどが Java コードであることに注意してください。パーサー規則は 5 つしかなく、そのほとんどは単純です。これはfilterツリー文法なので、規則bottomuptopdownはエントリ ポイントです。この場合のみbottomupが必要なので、topdown指定されていません。出力ツリーが変更されなくなるまでルールbottomupが繰り返されます。これは、生成する宣言がなくなり、ツリーが完全に平坦化されることを意味します。

exit_program次に、新しい宣言が AST に書き出される場所がルールであることに注意してください。セマンティック述語 ( {newDecls}?) を使用してPROGRAM、新しい宣言が追加されたときにのみ変更されるようにしています。bottomup変更が行われなくなるまで呼び出されると言ったことを覚えていますか? このセマンティック述語がなければ、exit_program常に変更PROGRAMされ、ツリーの解析は決して処理を停止しませんbottomup。これは、この特殊なケースの大雑把な回避策ですが、機能します。PROGRAM新しい宣言は、参照される前に確実に表示されるように、の先頭に挿入されます。x110行も予定通りに定義してはダメです。

3 番目に、ルールが式 ( など) を宣言 ( など) にexit_op置き換えることに注意してください。次のいずれかに該当する場合、式は置き換えられます。ln(b)var0

  • 式は、オペランドが両方とも「縮小」されている (つまり、両方とも整数または変数 ID である) 二項演算であり、DECLノードの子ではありません。は宣言の子であるvar a = 1 + 2ため変更されません。は、2 つの「縮小された」オペランドがあり、ノードの子ではない ( in の子である)ため、変更されます。1 + 2var b = a + (2 + c)(2 + c)DECL+a + ...

  • 式は、ノードCALLの子ではないです。は変更されていませんが、 の子であるため変更されています。DECLvar a = ln(b)var a = ln(b) + 3ln(b)+

exprs式はID に置き換えられる前に格納されます。exit_programルールが を呼び出すと、ルールで再構成されますbuildNewDeclsbuildNewDeclsパーサーの組み込みTreeAdaptorメンバー (という名前adaptor) を使用DECLして、フラット化された AST に表示されるノードを生成するだけです。アダプター メソッドの Javadoc は、呼び出しが何をするかを説明する適切な仕事をするので、詳細には触れません。

警告:上記の文法によって生成されたパーサーは、提示された非常に限られたケースでうまく機能します。より広範なシナリオに適用した場合にどのようなエラーが発生するかはわかりません。

2 番目の問題は、目的の言語の 1 つが plsql のような言語であることです。この場合、すべての出力変数がカーソルになり、それらを関数に渡すようにする必要があります...

上記のように、AST が単なる宣言のフラット リストである場合、テンプレートが管理できるものになります。

フラット化された AST をテンプレート ベースのツリー パーサー (上の図のようなパーサー) に渡して、リストしたようなさまざまなテキスト バージョンを生成します。この場合、テンプレートは宣言のすべての部分 (変数名、操作/メソッド名、およびオペランド/引数) を受け入れ、使用するテンプレートに応じてvariable = method(arg0, arg1)またはのようなテキストを生成します。method(variable, arg0, arg1)重要なのは、入力がフラットであることと、テンプレートが宣言に関連するすべてを受け取ることです。


以下は、すべてを結び付けるテスト アプリケーションです。

JavaLikeToAstTest.java

public class JavaLikeToAstTest {

    public static void main(String[] args) throws Exception {

        final String code = "var a = (ln(b) + avg(c))*2";

        CharStream input = new ANTLRStringStream(code);
        JavaLikeToAstLexer lexer = new JavaLikeToAstLexer(input);
        CommonTokenStream tokens = new CommonTokenStream(lexer);

        JavaLikeToAstParser parser = new JavaLikeToAstParser(tokens);

        JavaLikeToAstParser.compilationUnit_return result = parser
                .compilationUnit();

        if (lexer.getNumberOfSyntaxErrors() > 0 || parser.getNumberOfSyntaxErrors() > 0) {
            throw new Exception("Syntax Errors encountered!");
        }

        CommonTree tree = (CommonTree) result.tree;

        System.out.printf("Baseline AST: %s%n%n", tree.toStringTree());

        tree = flatten(tree);

        System.out.printf("Flattened AST: %s%n%n", tree.toStringTree());

        translate(tree, "AstToPlsql.stg");
        translate(tree, "AstToGlobal.stg");
    }

    private static CommonTree flatten(CommonTree tree) {
        AstToFlatAstParser parser = new AstToFlatAstParser(
                new CommonTreeNodeStream(tree));
        return (CommonTree) parser.downup(tree, true);
    }

    private static void translate(CommonTree tree, String templateResourceName)
            throws Exception {
        AstToTemplateParser parser = new AstToTemplateParser(
                new CommonTreeNodeStream(tree));
        InputStream stream = JavaLikeToTemplateTest.class
                .getResourceAsStream(templateResourceName);
        Reader reader = new InputStreamReader(stream);
        parser.setTemplateLib(new StringTemplateGroup(reader));
        reader.close();
        stream.close();

        System.out.printf("Result for %s%n%n%s%n%n", templateResourceName,
                parser.program().st.toString());

    }

翻訳プロセスを処理する 2 つの単純な StringTemplate グループ ファイルを次に示します。

AstToGlobal.stg

group AstToGlobal;

methods ::= ["*":"multiply", "/":"divide", "+":"add", "-":"subtract", "avg":"average", "ln":"log_N", default:key]

assign(name, op, args) ::= <<var <name> = <op>(<args;separator=", ">) >>

op(name) ::= "<methods.(name)>"

write(text) ::= << <text;separator="\n"> >>

AsToPlsql.stg

group AstToPlsql;

methods ::= ["*":"multiply", "/":"divide", "+":"add", "-":"subtract", "avg":"average", "ln":"log_N", default:key]

assign(name, op, args) ::=<< <op>(<name>, <args;separator=", ">) >>

op(name) ::= "<methods.(name)>"

write(text) ::= << <text;separator="\n"> >>

アプリケーションは次の出力を生成します。

Baseline AST: (PROGRAM (DECL a (* (+ (CALL ln b) (CALL avg c)) 2)))

(CALL ln b) -> var0
(CALL avg c) -> var1
(+ var0 var1) -> var2
(PROGRAM (DECL a (* var2 2))) -> (PROGRAM (DECL var0 (CALL ln b)) (DECL var1 (CALL avg c)) (DECL var2 (+ var0 var1)) (DECL a (* var2 2)))
Flattened AST: (PROGRAM (DECL var0 (CALL ln b)) (DECL var1 (CALL avg c)) (DECL var2 (+ var0 var1)) (DECL a (* var2 2)))

Result for AstToPlsql.stg

  log_N(var0, b ) 
  average(var1, c ) 
  add(var2, var0 , var1 ) 
  multiply(a, var2 , 2 )  

Result for AstToGlobal.stg

 var var0 = log_N(b ) 
 var var1 = average(c ) 
 var var2 = add(var0 , var1 ) 
 var a = multiply(var2 , 2 )  

AstToTemplate.gまたはテンプレートには、 のような単純な割り当てを処理するコードはありませんがvar a = 3、op/メソッドの割り当てをガイドとして使用して、それを処理するコードを追加するのは簡単だと思います。

于 2012-12-12T01:03:22.760 に答える