代码生成的变化,使用 ANTLR4 解析树访问者

variation in code generation, using ANTLR4 parse tree visitors

我正在使用 ANTLR(javascript 访问者目标)编写转译器 (myLang -> JS)。
重点是目标代码生成部分,来自解析树。
如,如何处理语言源代码的变化。

为了使问题更清楚,请考虑以下两种变体 -

来源#1:
PRINT 'hello there'

来源#2:

varGreeting = 'hey!'

PRINT varGreeting

案例1,我处理的是字符串。而在案例 2 中,它是一个变量。 然后 JS 目标代码需要不同(如下)。案例 1 有引号,案例 2 没有。

目标#1(JS):

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

目标#2(JS):

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

我怎样才能最好地消除歧义并生成不同的代码? 一下子想到了用规则名(ID,STRLIT)作为不同用法的承载
但是我找不到这些在 RuleContext API 中公开的内容。我查看了 java ones,假设在 JS 运行时相同。

getText() 给出了价值 ('hello there', varGreeting),没有 meta/attribute 我可以利用的信息。

我深入研究了 tree/ctx 对象,但没有以易于使用的方式找到它们。

问题:如何在不构建丑陋的 hack 的情况下最好地解决这个问题? 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);
};

使用 ANTLR 4.8

您正在使用 getChild(1) 访问打印语句的参数。这将为您提供一个包含 IDSTRLIT 标记的 TerminalNode。您可以使用 getSymbol() 方法访问令牌,然后可以使用 .type 属性 访问令牌的类型。该类型将是一个数字,您可以将其与 MyLanguageParser.IDMyLanaguageParser.STRLIT.

等常量进行比较

虽然使用 getChild 不一定是访问节点子节点的最佳方式。每个上下文 class 将为其每个子项提供特定的访问器。

具体来说,PrintContext 对象将具有方法 ID()STRLIT()。其中一个将 return null,另一个将 return 包含给定标记的 TerminalNode 对象。因此,您可以通过查看哪个不为空来知道它是 ID 还是字符串文字。

也就是说,更常见的解决方案是在 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 方法来处理这两种情况,方法是查看哪些 getter return 为 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() 而无需任何 post 处理,并且仍然有效 JavaScript.