Учебное руководство для обхода ANTLR ASTs в C#?

Я предполагаю, что вы знаете, что C ++ допускает перегрузку операторов. В общем случае вы перегружаете операторы, только если семантика полностью переносима (например, перегрузка сложения для векторного класса для сложения двух векторов вместе). Я думаю, что ваш вопрос относится к тому, почему можно использовать операторы bithift, перегружать их для iostream и придавать им совершенно иной смысл, чем их первоначальное назначение. Причина, по которой это может быть сделано, заключается в том, что операции сдвига битов настолько далеки от того, что делают iostreams, и никто не может быть сбит с толку, думая, что < < или >> выполняет сдвиг на Iostream. И причина, по которой их удобно использовать, заключается также в том, что их порядок состоит в том, чтобы сначала оценить операнд слева, а затем справа и выполнить операцию. Это соответствует тому, что вы хотели бы, когда вы используете операторы для добавления или извлечения чего-либо из iostream.

Но, к первоначальному вопросу, почему? Я действительно не знаю, мне просто кажется, что < < и >> довольно легко понять как получение информации от одного объекта и размещение ее в другом. Почему причина должна быть более сложной, чем эта? Представляется целесообразным использовать их, потому что их значение очевидно .. что лучше вы можете спросить у оператора?

21
задан kpozin 20 May 2009 в 11:31
поделиться

4 ответа

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

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

  1. Создайте экземпляр ANTLRStringStream или подкласс с вашими входными данными (это может быть файл или строка).
  2. Создайте экземпляр вашего сгенерированного лексера, передав этот строковый поток.
  3. поток токенов с лексером.
  4. Создайте экземпляр вашего синтаксического анализатора с этим потоком токенов.
  5. Получите значение верхнего уровня от вашего анализатора и превратите его в CommonTree .
  6. Пройдите по дереву :

Чтобы получить буквальный текст узла, используйте node.Text . Чтобы получить имя токена узла, используйте 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;

      ....
    }
}
19
ответ дан 29 November 2019 в 21:32
поделиться

Вам следует подумать о написании TreeParser; он может значительно упростить работу по интерпретации дерева.

Для ANTLR 2.x см. http://www.antlr2.org/doc/sor.html Для ANTLR 3.x см. http://www.antlr.org/wiki/display/ANTLR3/Tree+construction (пример синтаксического анализатора на основе java и дерева)

1
ответ дан 29 November 2019 в 21:32
поделиться

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

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

0
ответ дан 29 November 2019 в 21:32
поделиться

Обычно вы проходите AST с рекурсией и выполняете различные действия в зависимости от типа узла. Если вы используете узлы полиморфного дерева (т. Е. Разные подклассы для разных узлов в дереве), тогда может быть уместна двойная отправка в шаблоне «Посетитель»; однако это обычно не очень удобно с 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 []) при генерации кода для стековой машины, такой как 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)

Обычно все выполняется рекурсивно в зависимости от типа текущего узла и т. Д.

8
ответ дан 29 November 2019 в 21:32
поделиться