c - 使用 Flex/Bison 的解释器的 REPL

标签 c bison lex read-eval-print-loop flex-lexer

我为类 C 语言编写了一个解释器,使用 Flex 和 Bison 作为扫描器/解析器。它在执行完整程序文件时工作正常。

现在我正在尝试在解释器中实现一个 REPL 以供交互使用。我希望它像 Ruby 或 ML 中的命令行解释器一样工作:

  1. 显示提示
  2. 在线接受一个或多个声明
  3. 如果表达式不完整
    1. 显示继续提示
    2. 允许用户继续输入行
  4. 当行以一个完整的表达式结束时
    1. 回显最后一个表达式的计算结果
    2. 显示主要提示

我的语法以 top_level 产生式开始,它代表语言中的单个语句。词法分析器在 stdin 上配置为交互模式。我在全文件模式和 REPL 模式下使用相同的扫描器和语法,因为这两个接口(interface)没有语义差异。

我的主要评估循环结构如下。

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);
    }
}            

除了提示和回显逻辑外,这工作正常。如果用户在一行中输入多个语句,这个循环会打印出多余的提示和表达式。如果表达式在多行中继续,则此代码不会打印出继续提示。出现这些问题是因为prompt/echo逻辑的粒度在语法中是top_level语句,而line-reading逻辑在词法分析器中很深。

重构评估循环以处理 REPL 提示和回显的最佳方法是什么?即:

  • 如何每行显示一个提示
  • 如何在正确的时间显示继续提示
  • 我如何判断一个完整的表达式何时是一行中的最后一个

(我宁愿不更改扫描器语言来传递换行符,因为这会严重改变语法。修改 YY_INPUT 并向 Bison 语法添加一些操作就可以了。此外,我使用的是 Xcode 附带的 Flex 2.5.35 和 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;

词法分析器输入例程:

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

关于c - 使用 Flex/Bison 的解释器的 REPL,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6636808/

相关文章:

c++ - 使用 Bison/Antlr/Packrat/Elkhound/编写的 LLVM JIT 解析器

c - 内核输入/输出

mysql - 如何将字符串作为查询参数传递

c - 为什么 yacc 或 bison 将 $1 翻译为 yyvsp[(1) - (1)].s?

c - 解析编译器语法和错误错误恢复

debugging - 那里有任何 flex ("Fast LEXical analyzer") 调试器吗?

c++ - 'YYSTYPE' 没有成员

c++ - 有很多读者时使用 pthread_rwlock 的效率

c++ - 在访问冲突时创建转储文件

bison - `debut' 的错误 $1 没有声明类型 Bison/Yacc