我为类似C语言编写了一个解释器,使用Flex和Bison作为扫描器/解析器。它在执行完整的程序文件时工作正常。
现在我正在尝试在解释器中实现REPL以进行交互式使用。我希望它像Ruby或ML中的命令行解释器一样工作:
我的语法以top_level
制作开头,代表语言中的单个陈述。词法分析器配置为stdin上的交互模式。我在完整文件和REPL模式下使用相同的扫描程序和语法,因为这两个接口没有语义差异。
我的主要评估循环结构如下。
while (!interpreter.done) {
if (interpreter.repl)
printf(prompt);
int status = yyparse(interpreter);
if (status) {
if (interpreter.error)
report_error(interpreter);
}
else {
if (interpreter.repl)
puts(interpreter.result);
}
}
除了提示和回显逻辑之外,这个工作正常。如果用户在一行上输入多个语句,则此循环将打印出多余的提示和表达式。如果表达式在多行上继续,则此代码不会打印出连续提示。出现这些问题的原因是提示/回显逻辑的粒度在语法中是top_level
语句,但是行读取逻辑在词法分析器中很深。
重构评估循环以处理REPL提示和回显的最佳方法是什么?那就是:
(我宁愿不改变扫描仪语言来传递换行标记,因为这会严重改变语法。修改YY_INPUT
并在Bison语法中添加一些操作就没问题。而且,我是使用Xcode附带的Flex 2.5.35和Bison 2.3库存。)
答案 0 :(得分:6)
在查看Python和SML / NJ等语言如何处理他们的REPL之后,我在我的翻译中得到了一个很好的工作。我把它放在最里面的词法分析器输入例程中,而不是在最外面的解析器驱动程序循环中使用提示符/回显逻辑。解析器和词法分析器中的操作设置标志,用于控制输入例程的提示。
我正在使用可重入扫描程序,因此yyextra
包含在解释程序层之间传递的状态。看起来大致如下:
typedef struct Interpreter {
char* ps1; // prompt to start statement
char* ps2; // prompt to continue statement
char* echo; // result of last statement to display
BOOL eof; // set by the EOF action in the parser
char* error; // set by the error action in the parser
BOOL completeLine // managed by yyread
BOOL atStart; // true before scanner sees printable chars on line
// ... and various other fields needed by the interpreter
} Interpreter;
词法分析器输入例程:
size_t yyread(FILE* file, char* buf, size_t max, Interpreter* interpreter)
{
// Interactive input is signaled by yyin==NULL.
if (file == NULL) {
if (interpreter->completeLine) {
if (interpreter->atStart && interpreter->echo != NULL) {
fputs(interpreter->echo, stdout);
fputs("\n", stdout);
free(interpreter->echo);
interpreter->echo = NULL;
}
fputs(interpreter->atStart ? interpreter->ps1 : interpreter->ps2, stdout);
fflush(stdout);
}
char ibuf[max+1]; // fgets needs an extra byte for \0
size_t len = 0;
if (fgets(ibuf, max+1, stdin)) {
len = strlen(ibuf);
memcpy(buf, ibuf, len);
// Show the prompt next time if we've read a full line.
interpreter->completeLine = (ibuf[len-1] == '\n');
}
else if (ferror(stdin)) {
// TODO: propagate error value
}
return len;
}
else { // not interactive
size_t len = fread(buf, 1, max, file);
if (len == 0 && ferror(file)) {
// TODO: propagate error value
}
return len;
}
}
顶级解释器循环变为:
while (!interpreter->eof) {
interpreter->atStart = YES;
int status = yyparse(interpreter);
if (status) {
if (interpreter->error)
report_error(interpreter);
}
else {
exec_statement(interpreter);
if (interactive)
interpreter->echo = result_string(interpreter);
}
}
Flex文件获取以下新定义:
%option extra-type="Interpreter*"
#define YY_INPUT(buf, result, max_size) result = yyread(yyin, buf, max_size, yyextra)
#define YY_USER_ACTION if (!isspace(*yytext)) { yyextra->atStart = NO; }
YY_USER_ACTION
处理语言语法中的令牌与输入行之间棘手的相互作用。我的语言就像C和ML,因为需要一个特殊字符(';')来结束语句。在输入流中,该字符后面可以跟一个换行符来表示行尾,或者后面可以跟着属于新语句的字符。如果自上一个语句结束后扫描的唯一字符是换行符或其他空格,则输入例程需要显示主提示符;否则它应该显示继续提示。
答案 1 :(得分:0)
我也在为这样的翻译工作,我还没有达到制作REPL的程度,所以我的讨论可能有些模糊。
如果在一行上给出一系列语句,只打印最后一个表达式的结果,是否可以接受?因为你可以像这样重新考虑你的顶级语法规则:
top_level = top_level声明|声明;
top_level的输出可以是一个链接的语句列表,而interpreter.result则是对该列表尾部的评估。