代码生成的变化,使用 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)
访问打印语句的参数。这将为您提供一个包含 ID
或 STRLIT
标记的 TerminalNode
。您可以使用 getSymbol()
方法访问令牌,然后可以使用 .type
属性 访问令牌的类型。该类型将是一个数字,您可以将其与 MyLanguageParser.ID
或 MyLanaguageParser.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.
我正在使用 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)
访问打印语句的参数。这将为您提供一个包含 ID
或 STRLIT
标记的 TerminalNode
。您可以使用 getSymbol()
方法访问令牌,然后可以使用 .type
属性 访问令牌的类型。该类型将是一个数字,您可以将其与 MyLanguageParser.ID
或 MyLanaguageParser.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.