Tutoriel pour marcher ANTLR ASTs en C#?

Est-ce que quelqu'un est au courant des tutoriels pour les ASTs générés par ANTLR en C#? Le plus proche que j'ai pu trouver est this , mais ce n'est pas terriblement utile.

Mon but est de parcourir les arbres que je génère en fonction d'un langage spécifique au domaine sur lequel je travaille, et d'utiliser les arbres pour générer du code C# généré.

Un tutoriel basé sur Java serait également utile - tout ce qui fournit des exemples clairs de la façon de traverser les ASTs ANTLR.

21
demandé sur kpozin 2009-05-20 14:30:40

4 réponses

J'ai réussi à comprendre cela en adaptant l'exemple à la fin de l'article de Manuel Abadia .

Voici ma version, que j'utilise pour convertir le code analysé en C#. Voici les étapes:

  1. instanciez un ANTLRStringStream ou une sous-classe avec votre entrée (ce peut être un fichier ou une chaîne).
  2. instanciez votre lexer généré, en passant dans ce flux de chaîne.
  3. instancier un flux de jetons avec le lexer.
  4. instanciez votre analyseur avec ce flux de jetons.
  5. récupère la valeur de niveau supérieur de votre analyseur et la transforme en CommonTree.
  6. traverser l'arbre:

Pour obtenir le texte littéral d'un nœud, utilisez node.Text. Pour obtenir le nom de jeton d'un nœud, utilisez node.Token.Text.

Notez que node.Token.Text Ne vous donnera le nom réel de votre jeton que s'il s'agit d'un jeton imaginaire sans chaîne correspondante. Si c'est un vrai jeton, alors node.Token.Text retournera sa chaîne.

Pour exemple, si vous aviez ce qui suit dans votre grammaire:

tokens { PROGRAM, FUNCDEC }

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

Alors vous obtiendrez "PROGRAM", "FUNCDEC", "==", et "=" à partir des accès correspondants de node.Token.Text.

, Vous pouvez voir une partie de mon exemple ci-dessous, ou vous pouvez parcourir les version complète.


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;

      ....
    }
}
19
répondu kpozin 2009-05-29 19:06:09

Normalement, vous marchez ASTs avec récursivité, et effectuez différentes actions en fonction du type de nœud. Si vous utilisez des nœuds d'arbre polymorphes (c'est-à-dire différentes sous-classes pour différents nœuds de l'arbre), une double répartition dans le modèle visiteur peut être appropriée; cependant, ce n'est généralement pas très pratique avec Antlr.

En pseudocode, la marche ressemble généralement à ceci:

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

Les types de traitement dépendent fortement de la sémantique du langage. Pour exemple, la gestion d'une instruction IF, avec la structure (IF <predicate> <if-true> [<if-false>]), lors de la génération de code pour une machine de pile comme la JVM ou CLR, peut ressembler un peu à ceci:

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)

Généralement tout est fait récursivement en fonction du type du nœud courant, etc.

8
répondu Barry Kelly 2009-05-20 13:29:32

Vous devriez envisager d'écrire un TreeParser; cela peut rendre le travail d'interprétation de l'arbre beaucoup plus simple.

Pour ANTLR 2.x voir http://www.antlr2.org/doc/sor.html Pour ANTLR 3.x voir http://www.antlr.org/wiki/display/ANTLR3/Tree+construction (Exemple d'analyseur basé sur java et d'analyseur d'arbre)

1
répondu Scott Stanchfield 2009-05-20 14:17:07

J'ai fait quelque chose de similaire (mais pas vraiment) et je me suis retrouvé avec un TreeParser.

Je suggère également d'acheter le livre ANTLR. Je l'ai trouvé plus précieux que n'importe quelle ressource web. Il peut ne pas avoir toutes les réponses, mais il aide sûr avec les bases.

0
répondu Jasper Floor 2009-05-20 14:24:43