Подтвердить что ты не робот

Учебник для ходьбы ANTLR AST в С#?

Кто-нибудь знает о учебниках по ходу ANTLR-генерируемых АСТ на С#? Самое близкое, что я смог найти, это this, но это не очень полезно.

Моя цель - пройти через деревья, которые я создаю, на основе языка, на котором я работаю, и использовать деревья для вывода сгенерированного кода на С#.

Также полезно будет учебное пособие на основе Java - все, что дает четкие примеры прохождения ANTLR AST.

4b9b3361

Ответ 1

Мне удалось понять это, адаптировав пример в конце статьи Мануэля Абадии.

Здесь моя версия, которую я использую для преобразования обработанного кода в С#. Это следующие шаги:

  • Создайте ANTLRStringStream или подкласс с вашим вводом (это может быть файл или строка).
  • Создайте свой сгенерированный лексер, передавая этот поток строк.
  • Создайте поток токенов с помощью lexer.
  • Создайте свой парсер с этим токеном.
  • Получить значение верхнего уровня из вашего синтаксического анализатора и превратить его в CommonTree.
  • Поверните дерево:

Чтобы получить литеральный текст node, используйте node.Text. Чтобы получить имя маркера node, используйте node.Token.Text.

Обратите внимание, что node.Token.Text даст вам фактическое имя вашего токена, если это мнимый токен без соответствующей строки. Если это реальный токен, то node.Token.Text вернет свою строку.

Например, если в вашей грамматике было следующее:

tokens { PROGRAM, FUNCDEC }

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

Затем вы получите "PROGRAM", "FUNCDEC", "==" и "=" из соответствующих обращений node.Token.Text.

Вы можете увидеть часть моего примера ниже, или вы можете просмотреть полную версию .


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;

      ....
    }
}

Ответ 2

Обычно вы проходите АСТ с рекурсией и выполняете разные действия, основанные на типе node. Если вы используете полиморфные древовидные узлы (т.е. Разные подклассы для разных узлов в дереве), то может потребоваться двойная отправка в шаблоне посетителя; однако, что обычно не очень удобно с Antlr.

В псевдокоде ходьба обычно выглядит примерно так:

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

Виды обработки сильно зависят от семантики языка. Например, обработка инструкции IF со структурой (IF <predicate> <if-true> [<if-false>]) при создании кода для стекового компьютера, такого как JVM или CLR, может выглядеть примерно так:

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)

Как правило, все делается рекурсивно в зависимости от типа текущего node и т.д.

Ответ 4

Я сделал что-то подобное (но не совсем), и я закончил с TreeParser.

Я также предлагаю купить книгу ANTLR. Я нашел его более ценным, чем любой веб-ресурс. У него могут быть не все ответы, но он уверен в том, что помогает с основами.