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

# #go #parsing #antlr #antlr4

Вопрос:

Я слежу https://blog.gopheracademy.com/advent-2017/parsing-with-antlr4-and-go/, и
я смотрю на https://github.com/bramp/goadvent-antlr/blob/master/example2.go, у которого нет выхода. Сейчас,

Я хочу использовать EnterEveryRule для вывода каждого встречающегося символа с их символическими именами. Как я могу это сделать?

Т. е., как каким-то образом получить доступ к symbolicNames срезу на любом конкретном этапе, чтобы знать, с каким символическим именем я имею дело.

Комментарии:

1. CalcParser является подклассом из синтаксического анализатора, который является подклассом из распознавателя , содержащего GetSymbolicNames() . Вызов p.GetSymbolicNames() , где p-структура калькулятора.

2. Спасибо @kaby76. Я считаю , что мне все еще нужно использовать antlr.ParseTreeWalkerDefault.Walk , но изнутри , предположим, я каким-то образом использовал GetSymbolicNames() срез ..."OPP", "CLP", "MUL", "DIV", "ADD", "SUB", "NUMBER", "WHITESPACE" , как бы я узнал на определенном этапе, с каким символическим именем я имею дело? Вот вопрос, который я хочу решить. Тх!

3. ParserRuleContext дерева синтаксического анализа может быть либо внутренними узлами, либо конечными узлами. Узел должен быть приведен к конечному узлу (возможно, что-то вроде t, ok := ctx.(TerminalNode) ). С помощью указателя на TerminalNode вы можете получить тип токена ( t.GetSymbol().GetTokenType() ), который представляет собой целое число, используемое в массиве, возвращаемом GetSymbolicNames().

4. Может быть, что-то вроде этого: func (l *calcListener) EnterEveryRule(ctx antlr.ParserRuleContext) { t, ok := ctx.(antlr.TerminalNode); if ok { x := t.GetSymbol().GetTokenType(); y := l.parser.GetSymbolicNames(); fmt.Println(y[x]); } } за вычетом правильного форматирования Go, удаления точек с запятой и т. Д. И добавления parser antlr.Parser в структуру calcListener.

5. Вау, я бы никогда не смог придумать такое решение сам. Не могли бы вы ответить на вопрос вместо @kaby76, пожалуйста? Я все приготовил для тебя в play.golang.org/p/dXFgk4FPGfQ . До сих пор кажется , что для самого t, ok := , ok всегда будет ложным.

Ответ №1:

Я, конечно, не эксперт в Go, но этот пример содержит следующие строки:

     // Finally parse the expression (by walking the tree)
    antlr.ParseTreeWalkerDefault.Walk(amp;calcListener{}, p.Start())
 

Это немного вводит в заблуждение. p.Start() Часть этого оператора фактически выполняет синтаксический анализ (вызов p.Start() должен возвращать дерево синтаксического анализа ANTLR. На этом этапе вы выполнили синтаксический анализ.

antlr.ParseTreeWalkerDefault.Walk() это то, как вы проходите по дереву синтаксического анализа, вызывая методы listener , которые передаются вместе с деревом синтаксического анализа, полученным в результате p.start() вызова. Возможно, вы захотите сохранить его во временной переменной, прежде чем передавать ее ходоку (возможно, проверьте его в отладчике, чтобы понять это, или в нем должно быть что-то вроде toStringTree(...) функции, которая даст вам строковое представление дерева синтаксического анализа).

похоже, что calcListener просто есть ссылка на а parser.BaseCalcListener . Сгенерированные Base*Listener ANTLR s являются слушателями «ничего не делать», которые просто предоставляют пустые реализации всех методов, которые должен был реализовать слушатель.

Вам нужно будет что-то сделать с calcListener функцией, чтобы «переопределить» EnterEveryRule функцию, и что-то сделать с *Context переданным ей объектом.

Комментарии:

1. Спасибо! Что касается последнего шага, «сделайте что-нибудь с объектом контекста*» , Вот где я застрял и не знаю, как перейти от *Context объекта antlr.ParserRuleContext к токенам ( NUMBER , ADD и т. Д.), С которыми я Имею дело в этом конкретном дереве синтаксического анализа.

2. Я не программист Go, поэтому я не знаком с целью Go ANTLR. Судя по комментариям, похоже, что @kaby76 затронул эти детали.

3. Спасибо! Начинайте голосовать!

Ответ №2:

На самом деле я отвечаю за @kaby76, который привел меня к https://play.golang.org/p/R8Sik7sdZaz, так что я могу перейти к https://play.golang.org/p/uZqfUhHE0mt

Вот полный код:

 package main

import (
    "fmt"

    "github.com/antlr/antlr4/runtime/Go/antlr"

    "github.com/bramp/goadvent-antlr/parser"
)

type calcListener struct {
    *parser.BaseCalcListener
    parser *parser.CalcParser
}

// ExitEveryRule is called when any rule is exited.
func (l *calcListener) ExitEveryRule(ctx antlr.ParserRuleContext) {
    count := ctx.GetChildCount()
    // count == 1, NUMBER
    ch := ctx.GetChild(0)
    if count == 3 {
        // operation
        ch = ctx.GetChild(1)
    }
    q := ch.(antlr.Tree)
    t, ok := q.(antlr.TerminalNode)
    if ok {
        s := t.GetSymbol()
        x := s.GetTokenType()
        y := l.parser.GetSymbolicNames()
        fmt.Println(y[x], ctx.GetText())
    }
}

func main() {
    // Setup the input
    is := antlr.NewInputStream("1   2 * 3")

    // Create the Lexer
    lexer := parser.NewCalcLexer(is)
    stream := antlr.NewCommonTokenStream(lexer, antlr.TokenDefaultChannel)

    // Create the Parser
    p := parser.NewCalcParser(stream)

    // Finally parse the expression (by walking the tree)
    listener := calcListener{}
    listener.parser = p
    antlr.ParseTreeWalkerDefault.Walk(amp;listener, p.Start())
}
 

Результат выполнения:

 NUMBER 1
NUMBER 2
NUMBER 3
MUL 2*3
ADD 1 2*3