2009-05-20 6 views
21

Czy ktoś jest świadomy samouczków do chodzenia generowanych przez ANTLR AST w C#? Najbliższe, co udało mi się znaleźć, to this, ale nie jest to zbyt pomocne.Samouczek na spacery ANTLR AST w C#?

Moim celem jest przejście przez drzewa, które generuję w oparciu o język specyficzny dla domeny, nad którym pracuję, oraz użycie drzew do wygenerowania wygenerowanego kodu C#.

Pomocny może być także samouczek oparty na języku Java - wszystko, co daje jasne przykłady tego, jak przemierzyć ANTLR AST.

Odpowiedz

19

Udało mi się to rozwiązać, dostosowując przykład pod koniec Manuel Abadia's article.

Oto moja wersja, której używam do konwersji parsowanego kodu na C#. Są to kroki:

  1. instancję ANTLRStringStream lub podklasy z wejścia (może to być plik lub ciąg znaków).
  2. Wywołaj wygenerowany lexer, przekazując ten strumień.
  3. Wywołaj strumień tokenów z lexerem.
  4. Wywołaj swój analizator składni tym strumieniem tokenów.
  5. Uzyskaj najwyższy poziom wartości z analizatora składni i przekształć go w CommonTree.
  6. Traverse drzewa:

Aby uzyskać dosłownego tekstu węzła, użyj node.Text. Aby uzyskać nazwę tokena węzła, należy użyć node.Token.Text.

Należy pamiętać, że node.Token.Text poda tylko rzeczywistą nazwę tokenu, jeśli jest to wyimaginowany token bez odpowiedniego ciągu. Jeśli jest to prawdziwy token, to node.Token.Text zwróci ciąg znaków.

Na przykład, jeśli miał następujące w gramatyce:

tokens { PROGRAM, FUNCDEC } 

EQUALS : '=='; 
ASSIGN : '='; 

Wtedy dostaniesz "PROGRAM", "FUNCDEC", "==" i "=" z odpowiednich dostępów z node.Token.Text.

Możesz zobaczyć część mojego przykładu poniżej lub możesz przeglądać full version.


public static string Convert(string input) 
{ 
    ANTLRStringStream sStream = new ANTLRStringStream(input); 
    MyGrammarLexer lexer = new MyGrammarLexer(sStream); 

    CommonTokenStream tStream = new CommonTokenStream(lexer); 

    MyGrammarParser parser = new MyGrammarParser (tStream); 
    MyGrammarParser.program_return parserResult = parser.program(); 

    CommonTree ast = (CommonTree)parserResult.Tree; 

    Print(ast); 
    string output = header + body + footer; 

    return output; 
} 

public static void PrintChildren(CT ast) 
{ 
    PrintChildren(ast, " ", true); 
} 

public static void PrintChildren(CT ast, string delim, bool final) 
{ 
    if (ast.Children == null) 
    { 
     return; 
    } 

    int num = ast.Children.Count; 

    for (int i = 0; i < num; ++i) 
    { 
     CT d = (CT)(ast.Children[i]); 
     Print(d); 
     if (final || i < num - 1) 
     { 
      body += delim; 
     } 
    } 
} 

public static void Print(CommonTree ast) 
{ 
    switch (ast.Token.Text) 
    { 
     case "PROGRAM": 
      //body += header; 
      PrintChildren(ast); 
      //body += footer; 
      break; 
     case "GLOBALS": 
      body += "\r\n\r\n// GLOBALS\r\n"; 
      PrintChildren(ast); 
      break; 
     case "GLOBAL": 
      body += "public static "; 
      PrintChildren(ast); 
      body += ";\r\n"; 
      break; 

     .... 
    } 
} 
8

Zazwyczaj chodzisz po AST z rekursją i wykonujesz różne akcje w zależności od rodzaju węzła. Jeśli używasz polimorficznych węzłów drzewa (tj. Różnych podklas dla różnych węzłów w drzewie), może być właściwe podwójne wysyłanie we wzorcu Visitor; jednak zazwyczaj nie jest to bardzo wygodne z Antlr.

W Pseudokod, idzie zwykle wygląda nieco jak poniżej:

func processTree(t) 
    case t.Type of 
     FOO: processFoo t 
     BAR: processBar t 
    end 

// a post-order process 
func processFoo(foo) 
    // visit children 
    for (i = 0; i < foo.ChildCount; ++i) 
     processTree(foo.GetChild(i)) 
    // visit node 
    do_stuff(foo.getText()) 

// a pre-order process 
func processBoo(bar) 
    // visit node 
    do_stuff(bar.getText()) 
    // visit children 
    for (i = 0; i < foo.ChildCount; ++i) 
     processTree(foo.GetChild(i)) 

Rodzaje obróbki są bardzo zależne od semantyki języka. Na przykład, przenoszenia oświadczenie IF, ze strukturą (IF <predicate> <if-true> [<if-false>]), podczas generowania kodu na maszynie stosu jak JVM lub CLR, może wyglądać mniej więcej tak:

func processIf(n) 
    predicate = n.GetChild(0) 
    processExpr(predicate) // get predicate value on stack 
    falseLabel = createLabel() 
    genCode(JUMP_IF_FALSE, falseLabel) // JUMP_IF_FALSE is called brfalse in CLR, 
             // ifeq in JVM 
    if_true = n.GetChild(1) 
    processStmt(if_true) 
    if_false = n.ChildCount > 2 ? n.GetChild(2) : null 
    if (if_false != null) 
     doneLabel = createLabel() 
     genCode(JUMP, doneLabel) 
    markLabel(falseLabel) 
    if (if_false != null) 
     processStmt(if_false) // if-false branch 
     markLabel(doneLabel) 

Generalnie wszystko odbywa się rekurencyjnie w zależności od rodzaju prądu węzeł itp.

+0

dowolny kod przykładowy drzewa gramatyki dla C#? – Kiquenet

0

Zrobiłem coś podobnego (ale nie bardzo) i skończyło się z TreeParser.

Proponuję również zakup książki ANTLR. Uważam, że jest bardziej wartościowy niż jakikolwiek inny zasób sieciowy. Może nie mieć wszystkich odpowiedzi, ale na pewno pomaga w podstawach.