使用Flex / Bison进行口译的REPL

时间:2011-07-09 18:56:21

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

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

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

  1. 显示提示
  2. 接受行上的一个或多个陈述
  3. 如果表达不完整
    1. 显示继续提示
    2. 允许用户继续输入行
  4. 当该行以完整表达式结束时
    1. 回显评估最后一个表达式的结果
    2. 显示主提示
  5. 我的语法以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库存。)

2 个答案:

答案 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则是对该列表尾部的评估。