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



我正在使用ANTLR(带有访问者的javascript目标(编写transpiler(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

如何最好地消除歧义并生成不同的代码?我立刻想到使用规则名称(IDSTRLIT(作为不同用途的承载
但我找不到这些在RuleContext API中公开的内容。我看了java的,假设在JS运行时也是如此。

getText()给出值('hello there'varGreeting(,没有我可以利用的元/属性信息。

我深入研究了tree/ctx对象,并没有以容易消费的方式找到它们。

问题:如何最好地做到这一点,而不构建丑陋的黑客?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)来访问print语句的参数。这将为您提供一个包含IDSTRLIT令牌的TerminalNode。您可以使用getSymbol()方法访问令牌,然后使用.type属性访问令牌的类型。类型将是一个可以与MyLanguageParser.IDMyLanaguageParser.STRLIT等常量进行比较的数字。

不过,使用getChild并不一定是访问节点子节点的最佳方式。每个上下文类的每个子类都有特定的访问器。

具体地,PrintContext对象将具有方法ID()STRLIT()。其中一个返回null,另一个返回包含给定令牌的TerminalNode对象。因此,您可以通过查看哪一个不是null来知道它是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返回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();
}
}

附言:请注意,单引号在JavaScript中工作得很好,所以您实际上不需要去掉单引号并用双引号代替它们。在这两种情况下,您都可以只使用.getText()而不进行任何后处理,这仍然是有效的JavaScript。

最新更新