изменение в генерации кода с использованием посетителей дерева разбора ANTLR4 - PullRequest
1 голос
/ 20 марта 2020

Я пишу транспортер ( myLang -> JS), используя ANTLR (javascript цель с посетителем).
Фокус на генерации кода цели часть, от дерево синтаксического анализа.
Как, например, работать с вариациями исходных кодов языка

Чтобы прояснить вопрос, рассмотрим два варианта ниже -

source # 1:
PRINT 'hello there'

source # 2:

varGreeting = 'hey!'

PRINT varGreeting

В случае 1 я имею дело со строкой. Хотя в случае 2 это переменная. JS целевой код должен быть другим (ниже). случай 1 с кавычками, случай 2 без.

target # 1 (JS):

console.log("hello there");   // <-- string

target # 2 (JS):

var varGreeting = "hey!";
console.log(varGreeting);  // <-- var

Как лучше всего устранить неоднозначность и генерировать различные код? Сразу же я подумал об использовании имени правила (ID, STRLIT) в качестве носителя различного использования.
Но я не смог найти их раскрывающимися в RuleContext API. Я посмотрел на java единицы , предполагая то же самое в JS времени выполнения.

getText() дает значение ('hello there', varGreeting), нет метаданных / атрибутов, которые я могу использовать.

Я копался в объекте tree / ctx и не нашел их легко потребляемым способом.

Вопрос: как лучше go об этом, не создавая уродливых хаков? Transpiler, кажется, находится в пределах области применения ANTLR, я что-то упустил?

(соответствующая часть) Грамматика:

print : PRINTKW (ID | STRLIT) NEWLINE;

STRLIT: '\'' .*? '\'' ;
ID    : [a-zA-Z0-9_]+;

Переопределение посетителя :

// sample code for generating code for case 1 (with quotes) 
myVisitor.prototype.visitPrint = function(ctx) {


    const Js = 
    `console.log("${ctx.getChild(1).getText()}");`;

    // ^^ this is the part which needs different treatment for case 1 and 2 

    // write to file
    fs.writeFile(targetFs + fileName + '.js', Js, 'utf8', function (err) {
        if (err) return console.log(err);
        console.log(`done`);
      });

  return this.visitChildren(ctx);
};

с использованием АНТЛР 4.8

1 Ответ

2 голосов
/ 21 марта 2020

Вы используете getChild(1) для доступа к аргументу оператора печати. Это даст вам TerminalNode, содержащий токен ID или STRLIT. Вы можете получить доступ к токену с помощью метода getSymbol(), а затем получить доступ к типу токена с помощью свойства .type. Типом будет число, которое вы можете сравнить с такими константами, как MyLanguageParser.ID или MyLanaguageParser.STRLIT.

Использование getChild не обязательно является лучшим способом доступа к дочерним узлам узла. Каждый контекстный класс будет иметь определенные c средства доступа для каждого из его дочерних элементов.

В частности, у объекта PrintContext будут методы ID() и STRLIT(). Один из них вернет null, другой вернет TerminalNode объект, содержащий данный токен. Таким образом, вы знаете, был ли это идентификатор или строковый литерал, увидев, какой из них не равен нулю.

Тем не менее, более распространенным решением будет отсутствие объединения возможных типов аргументов в print правило, но вместо этого разрешите любое выражение в качестве аргумента print. Затем вы можете использовать помеченные альтернативы в своем правиле expression, чтобы получить разные методы посетителя для каждого вида выражения:

print : PRINTKW expression NEWLINE;

expression
    : STRLIT #StringLiteral
    | ID #Variable
    ;

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

myVisitor.prototype.visitPrint = function(ctx) {
    const arg = this.visit(ctx.expression());
    const Js = `console.log(${arg});`;

    // write to file
    fs.writeFile(targetFs + fileName + '.js', Js, 'utf8', function (err) {
        if (err) return console.log(err);
        console.log(`done`);
    });
};

myVisitor.prototype.visitStringLiteral = function(ctx) {
    const text = ctx.getText();
    return `"${text.substring(1, text.length - 1)}"`;
}

myVisitor.prototype.visitVariable = function(ctx) {
    return ctx.getText();
}

В качестве альтернативы Вы могли бы опустить метки и вместо этого определить метод visitExpression, который обрабатывает оба случая, посмотрев, какой метод получения возвращает значение null:

myVisitor.prototype.visitExpression = function(ctx) {
    if (ctx.STRLIT !== null) {
        const text = ctx.getText();
        return `"${text.substring(1, text.length - 1)}"`;
    } else {
        return ctx.getText();
    }
}

PS: обратите внимание, что одинарные кавычки прекрасно работают в JavaScript, поэтому вам на самом деле не нужно разбирать одинарные кавычки и заменять их двойными кавычками. Вы можете просто использовать .getText() без какой-либо последующей обработки в обоих случаях, и это все равно будет иметь значение JavaScript.

...