Jay*_*ske 10 c lex bison read-eval-print-loop flex-lexer
我为类似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);
}
}
Run Code Online (Sandbox Code Playgroud)
除了提示和回显逻辑之外,这种方法很好.如果用户在一行上输入多个语句,则此循环将打印出多余的提示和表达式.如果表达式在多行上继续,则此代码不会打印出连续提示.出现这些问题是因为提示/回显逻辑的粒度是top_level语法中的一个语句,但是行读取逻辑在词法分析器中很深.
重构评估循环以处理REPL提示和回显的最佳方法是什么?那是:
(我宁愿不改变扫描仪语言来传递换行标记,因为这会严重改变语法.修改YY_INPUT和添加一些动作到Bison语法会很好.而且,我使用的是Flex 2.5.35和与Xcode一起发货的Bison 2.3.)
在查看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;
Run Code Online (Sandbox Code Playgroud)
词法分析器输入例程:
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;
}
}
Run Code Online (Sandbox Code Playgroud)
顶级解释器循环变为:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
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; }
Run Code Online (Sandbox Code Playgroud)
该YY_USER_ACTION处理在语言语法令牌和输入线之间的棘手相互作用.我的语言就像C和ML,因为需要一个特殊字符(';')来结束语句.在输入流中,该字符后面可以跟一个换行符来表示行尾,或者后面可以跟着属于新语句的字符.如果自上一个语句结束后扫描的唯一字符是换行符或其他空格,则输入例程需要显示主提示符; 否则它应该显示继续提示.