在查看了 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 一样,需要一个特殊字符 (';') 来结束语句。在输入流中,该字符后面可以跟一个换行符来表示行尾,也可以后面跟一个新语句的一部分。如果自最后一个语句结束后扫描的唯一字符是换行符或其他空格,则输入例程需要显示主提示;否则它应该显示继续提示。