Transpiling/code 生成 - 变量声明问题
Transpiling/code generation - declaration of variables issue
我最近一直在研究 ANTLR 和 Java,我构建了一个简单的语法来解析这段代码并生成一个 AST。我还编写了一个内置解释器来执行这段代码,它似乎运行良好:
关于我的玩具语言的一些笔记:
- 我的语言只有一种变体"double"
- 所有变量都在赋值时隐式声明。
- 所有变量都具有全局作用域。 IE。即使在分配变量的块之外,我也可以在分配变量后使用它。
/* A sample program */
BEGIN
j := 1;
WHILE j <= 5 DO
PRINT "ITERATION NO: "; PRINTLN j;
sumA1 := 0;
WHILE 1 = 1 DO
PRINT "Enter a number, 0 to quit: ";
i := INPUT;
IF i = 0 THEN
BREAK;
ENDIF
sumA1 := ADD sumA1, i;
ENDWHILE
j := ADD j, 1;
PRINT "The sum is: "; PRINTLN sumA1;
ENDWHILE
j := MINUS j;
PRINTLN j;
END
然后我将代码生成函数写入 AST 以将其从我的 AST 输出到 C class 我得到了这个结果(美化):
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char * argv[]) {
double j;
j = 1.00000;
while (j <= 5.0) {
printf("ITERATION NO: ");
printf("%g\n", j);
double sumA1;
sumA1 = 0.00000;
while (1.0 == 1.0) {
printf("Enter a number, 0 to quit: ");
double i;
scanf("%lf", & i);
if (i == 0.0) {
break;
}
sumA1 = sumA1 + i;
}
j = j + 1.00000;
printf("The sum is: ");
printf("%g\n", sumA1);
}
j = -j;
printf("%g\n", j);
}
在代码生成过程中,我首先检查变量名在HashMap 中是否可用。对于赋值 statements/input 语句,我在赋值之前添加了变量声明,如您所见。对于赋值以外的变量的使用,我在使用前抛出一个未初始化变量的异常。
一切都很好。上面的代码适用于这个例子,因为在我的源程序中我没有使用声明范围之外的任何变量。
但是有一个问题。因为我在块内初始化某些变量(比如 while
它们不能在范围外使用),我需要一种方法来收集我的源程序中使用的所有变量作为 C 中的全局变量(或至少在main() 函数)。如果在块外的程序中使用变量,则在 C 中使用之前声明变量将导致源语言中的有效程序无法在 C 中编译。
我想我可以通过首先解析所有变量并在C程序开始时声明它们然后生成代码来解决它。
但是如果我在生成代码之前更新符号 table (HashMap),我将无法知道变量在使用前是否实际赋值。
重新设计它以确保:
的最佳方法是什么?
- 代码生成器应在使用前检查赋值。 IE。如果它在赋值前发现了一个用法,它应该抛出一个 exception/compilation 错误。
- 同时,我的代码中的所有变量都应该在 C 生成的源代码中作为全局变量可用。因此,如果之前在内部块中分配变量,则即使在块外使用变量也是可能的,因为在我的源语言中它是 acceptable。
这是我第一次尝试这样的事情。请为我提供任何可能的解决方案。
在一般情况下,在分配之前检测使用是不可能的。考虑以下(不是很好)C 代码:
int sum; /* uninitialised */
for (i = 0; i < n; ++i) {
if (check(i)) sum = 0;
sum += val[i]; /* Is sum initialised here? */
process(sum);
}
如果check(i)
是i % 10 == 0
,那么sum
肯定会被初始化。但如果它是 i % 10 == 1
,则 sum
在第一次迭代中未初始化使用。一般来说,sum
是否未初始化使用取决于check(0)
的值。但可能没有办法知道那是什么。 check()
可能是一个外部函数。或者它的 return 值可能取决于输入。或者它可能基于困难的计算。
这并不意味着您不应该尝试检测问题。例如,您可以使用 symbolic execution 来尝试计算未定义使用的保守估计。如果你能证明未定义的使用,你可以抛出一个异常,如果你不能证明所有的使用都被定义,你可以发出警告。 (许多编译器使用这种技术的变体。)这可能是控制流分析中的一个有趣练习。
但对于现实世界的解决方案,鉴于所有变量都是数字,我建议将所有变量自动初始化为 0,作为语言语义的一部分。
我通过从各个赋值节点中删除变量声明并仅将赋值节点中使用的变量添加到全局哈希图中来解决它,然后在 运行ning 之后通过树生成声明.
它的工作原理是这样的:
- 走 AST。如果我遇到变量的用法(除了在 assignment/input 语句中,生成一个 exception/error.
- 如果我遇到 assignment/input 语句,请将变量添加到全局哈希图中。但是不要在特定节点的代码生成中声明它。
- 生成完整代码后 - 运行 通过全局哈希映射并生成声明
- 通过连接声明语句和生成的代码将主程序放在一起。
但我意识到,这可能会导致潜在的问题,即变量可能在 IF 块内初始化并在外部使用。如果程序已经执行了 IF 块,那么没问题,但是如果跳过 IF 块,那么我的解释器就会出现异常,但是 C 中的代码生成仍然可以正常工作。但是,如果不执行 IF 块,则 C 程序中的输出是未初始化的变量。
以(在我的代码中)为例
BEGIN
i := INPUT;
IF i < 10 THEN
j := MUL i, 10;
ENDIF
PRINT j;
END
吐出这段C代码(美化)
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
double i;
double j;
scanf("%lf", &i);
if (i < 10.0)
{
j = i *10.0000;
}
printf("%g\n", j);
}
在这种情况下,如果未到达和编译 IF
块,即当 i >= 10
时(因为 j
将保持未初始化状态,我的内置解释器将抛出异常).然而,等效的 C 代码已生成并正确编译,但 j
将是一个未初始化的变量,导致 运行time 行为。
但就目前而言,我认为我可以接受这种行为,因为无论如何,使用可能未初始化的变量是程序本身设计的问题。
我想另一种选择是使用隐式 NULL(或 NaN)值初始化变量以表示未初始化并检查它。
我最近一直在研究 ANTLR 和 Java,我构建了一个简单的语法来解析这段代码并生成一个 AST。我还编写了一个内置解释器来执行这段代码,它似乎运行良好:
关于我的玩具语言的一些笔记:
- 我的语言只有一种变体"double"
- 所有变量都在赋值时隐式声明。
- 所有变量都具有全局作用域。 IE。即使在分配变量的块之外,我也可以在分配变量后使用它。
/* A sample program */
BEGIN
j := 1;
WHILE j <= 5 DO
PRINT "ITERATION NO: "; PRINTLN j;
sumA1 := 0;
WHILE 1 = 1 DO
PRINT "Enter a number, 0 to quit: ";
i := INPUT;
IF i = 0 THEN
BREAK;
ENDIF
sumA1 := ADD sumA1, i;
ENDWHILE
j := ADD j, 1;
PRINT "The sum is: "; PRINTLN sumA1;
ENDWHILE
j := MINUS j;
PRINTLN j;
END
然后我将代码生成函数写入 AST 以将其从我的 AST 输出到 C class 我得到了这个结果(美化):
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char * argv[]) {
double j;
j = 1.00000;
while (j <= 5.0) {
printf("ITERATION NO: ");
printf("%g\n", j);
double sumA1;
sumA1 = 0.00000;
while (1.0 == 1.0) {
printf("Enter a number, 0 to quit: ");
double i;
scanf("%lf", & i);
if (i == 0.0) {
break;
}
sumA1 = sumA1 + i;
}
j = j + 1.00000;
printf("The sum is: ");
printf("%g\n", sumA1);
}
j = -j;
printf("%g\n", j);
}
在代码生成过程中,我首先检查变量名在HashMap 中是否可用。对于赋值 statements/input 语句,我在赋值之前添加了变量声明,如您所见。对于赋值以外的变量的使用,我在使用前抛出一个未初始化变量的异常。
一切都很好。上面的代码适用于这个例子,因为在我的源程序中我没有使用声明范围之外的任何变量。
但是有一个问题。因为我在块内初始化某些变量(比如 while
它们不能在范围外使用),我需要一种方法来收集我的源程序中使用的所有变量作为 C 中的全局变量(或至少在main() 函数)。如果在块外的程序中使用变量,则在 C 中使用之前声明变量将导致源语言中的有效程序无法在 C 中编译。
我想我可以通过首先解析所有变量并在C程序开始时声明它们然后生成代码来解决它。
但是如果我在生成代码之前更新符号 table (HashMap),我将无法知道变量在使用前是否实际赋值。
重新设计它以确保:
的最佳方法是什么?- 代码生成器应在使用前检查赋值。 IE。如果它在赋值前发现了一个用法,它应该抛出一个 exception/compilation 错误。
- 同时,我的代码中的所有变量都应该在 C 生成的源代码中作为全局变量可用。因此,如果之前在内部块中分配变量,则即使在块外使用变量也是可能的,因为在我的源语言中它是 acceptable。
这是我第一次尝试这样的事情。请为我提供任何可能的解决方案。
在一般情况下,在分配之前检测使用是不可能的。考虑以下(不是很好)C 代码:
int sum; /* uninitialised */
for (i = 0; i < n; ++i) {
if (check(i)) sum = 0;
sum += val[i]; /* Is sum initialised here? */
process(sum);
}
如果check(i)
是i % 10 == 0
,那么sum
肯定会被初始化。但如果它是 i % 10 == 1
,则 sum
在第一次迭代中未初始化使用。一般来说,sum
是否未初始化使用取决于check(0)
的值。但可能没有办法知道那是什么。 check()
可能是一个外部函数。或者它的 return 值可能取决于输入。或者它可能基于困难的计算。
这并不意味着您不应该尝试检测问题。例如,您可以使用 symbolic execution 来尝试计算未定义使用的保守估计。如果你能证明未定义的使用,你可以抛出一个异常,如果你不能证明所有的使用都被定义,你可以发出警告。 (许多编译器使用这种技术的变体。)这可能是控制流分析中的一个有趣练习。
但对于现实世界的解决方案,鉴于所有变量都是数字,我建议将所有变量自动初始化为 0,作为语言语义的一部分。
我通过从各个赋值节点中删除变量声明并仅将赋值节点中使用的变量添加到全局哈希图中来解决它,然后在 运行ning 之后通过树生成声明.
它的工作原理是这样的:
- 走 AST。如果我遇到变量的用法(除了在 assignment/input 语句中,生成一个 exception/error.
- 如果我遇到 assignment/input 语句,请将变量添加到全局哈希图中。但是不要在特定节点的代码生成中声明它。
- 生成完整代码后 - 运行 通过全局哈希映射并生成声明
- 通过连接声明语句和生成的代码将主程序放在一起。
但我意识到,这可能会导致潜在的问题,即变量可能在 IF 块内初始化并在外部使用。如果程序已经执行了 IF 块,那么没问题,但是如果跳过 IF 块,那么我的解释器就会出现异常,但是 C 中的代码生成仍然可以正常工作。但是,如果不执行 IF 块,则 C 程序中的输出是未初始化的变量。
以(在我的代码中)为例
BEGIN
i := INPUT;
IF i < 10 THEN
j := MUL i, 10;
ENDIF
PRINT j;
END
吐出这段C代码(美化)
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[])
{
double i;
double j;
scanf("%lf", &i);
if (i < 10.0)
{
j = i *10.0000;
}
printf("%g\n", j);
}
在这种情况下,如果未到达和编译 IF
块,即当 i >= 10
时(因为 j
将保持未初始化状态,我的内置解释器将抛出异常).然而,等效的 C 代码已生成并正确编译,但 j
将是一个未初始化的变量,导致 运行time 行为。
但就目前而言,我认为我可以接受这种行为,因为无论如何,使用可能未初始化的变量是程序本身设计的问题。
我想另一种选择是使用隐式 NULL(或 NaN)值初始化变量以表示未初始化并检查它。