我正在使用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
如何最好地消除歧义并生成不同的代码?我立刻想到使用规则名称(ID
,STRLIT
(作为不同用途的承载
但我找不到这些在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语句的参数。这将为您提供一个包含ID
或STRLIT
令牌的TerminalNode
。您可以使用getSymbol()
方法访问令牌,然后使用.type
属性访问令牌的类型。类型将是一个可以与MyLanguageParser.ID
或MyLanaguageParser.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。