ANLTR не обходит все дерево, когда появляется определенный синтаксис - PullRequest
0 голосов
/ 22 февраля 2020

Я работаю над проектом, который читает исходный код на разных языках. Сам проект написан на scala, но то, что я делаю, должно быть знакомо, если вы знаете antlr. Я использовал грамматику scala.g4 на github для генерации парсера, Lexer et c для antlr4. Я написал подкласс ScalaBaseListener, который просто печатает при переопределении методов Enter

например

override def enterClassDef(ctx: ScalaParser.ClassDefContext): Unit = {
        println(ctx.getText)
    }

В основном приложении я пытаюсь пройтись по всему дереву из источника файла, например так:

import ScalaLexer._
import org.antlr.v4.runtime._
import org.antlr.v4.runtime.tree._
import scala.io.Source

object Main extends App {
  val fileContents = Source.fromFile(args(0)).getLines.mkString
  val charStream = new ANTLRInputStream(fileContents)
  val lexer = new ScalaLexer(charStream)
  val tokens = new CommonTokenStream(lexer)
  val parser = new ScalaParser(tokens)
  val tree = parser.compilationUnit
  ParseTreeWalker.DEFAULT
    .walk(new ScalaMySubclassListener(), tree)
}

Я обнаружил, что если исходный файл скажет, просто пара классов:

class Foo {
    def bar = {
        1
    }
    def baz = 1
}

class Foo1 {
    def bar = {
        1
    }
    def baz = 1
}

Я могу видеть из вывода моей программы, что каждый лист в дереве

Однако, если бы я добавил оператор импорта в начало файла (как это часто будет в scala исходном файле)

import Thing._

class Foo {
    def bar = {
        1
    }
    def baz = 1
}

class Foo1 {
    def bar = {
        1
    }
    def baz = 1
}

только листья в операторе import ходят. Остальная часть файла игнорируется.

Когда я анализирую исходный файл с помощью antlr4 GUI, все дерево видно.

1 Ответ

1 голос
/ 22 февраля 2020

Первое, что нужно сделать, когда дерево разбора кажется обрезанным, - это проверить, есть ли какие-либо синтаксические ошибки, так как это будет наиболее распространенной причиной. Поскольку вы вообще не связывались с обработкой ошибок в своем коде, это означает, что любые синтаксические ошибки должны быть напечатаны в stderr. Так как их нет, по-видимому, не было никаких синтаксических ошибок.

Но давайте пока не отказываться от идеи о том, что синтаксическая ошибка только что возникла. Одна распространенная ошибка, когда дело доходит до синтаксических ошибок в ANTLR, это если ваше правило запуска не заканчивается на EOF. Если это так, ANTLR просто попытается найти префикс ввода, который синтаксически допустим, и проигнорирует все остальное. То есть он остановится на первой синтаксической ошибке без фактического создания сообщения об ошибке (при условии, что существует допустимая программа, приводящая к этой ошибке - поскольку многие грамматики принимают пустые программы, что очень часто имеет место). И конечно же: если мы посмотрим на Scala.g4, то в грамматике нигде нет EOF (во всяком случае, на момент написания этой статьи). Итак, давайте добавим EOF в конце правила compilationUnit. Теперь, если мы все перекомпилируем и снова запустим ваш код, мы, наконец, получим синтаксическую ошибку:

line 1:20 mismatched input 'Foo' expecting {<EOF>, '.', ',', 'implicit', 'lazy', 'case', '@', 'override', 'abstract', 'final', 'sealed', 'private', 'protected', 'import', 'class', 'object', 'trait', 'package'}

Теперь есть две вещи, которые могут показаться вам любопытными:

  1. Почему ANTLR обнаруживать синтаксическую ошибку при запуске из вашего кода, но не из TestRig GUI (даже после добавления EOF, GUI все равно покажет правильное дерево).
  2. Почему появляется сообщение об ошибке утверждаете, что Foo появляется в столбце 20 строки 1, когда он фактически находится в строке 3?

Ответ на оба эти вопроса один и тот же: вводимые вами ANTLR данные не соответствуют в вашем тестовом файле. Чтобы убедиться в этом, попробуйте напечатать fileContents после того, как прочитаете его. Вы увидите, что весь ввод находится в одной строке, начиная с import Thing._class Foo, что явно не соответствует синтаксису.

Причина этого в том, что getLines дает вам список строк без окончаний строк, а mkString объединяет их без разделителя. Быстрое решение состоит в том, чтобы просто передать "\n" в качестве разделителя в mkString, но лучшее решение - вообще не читать файл.

Вместо этого вы можете заставить ANTLR делать это, создавая свой ввод поток с использованием CharStreams.fromFileName. Это также избавит от предупреждения об устаревании ANTLRInputStream.

...