REPL для переводчика с использованием Flex / Bison - PullRequest
10 голосов
/ 09 июля 2011

Я написал переводчик для C-подобного языка, используя Flex и Bison для сканера / анализатора. Работает нормально при выполнении полных программных файлов.

Сейчас я пытаюсь реализовать REPL в интерпретаторе для интерактивного использования. Я хочу, чтобы он работал как интерпретаторы командной строки в Ruby или ML:

  1. Показать подсказку
  2. Принять одно или несколько утверждений в строке
  3. Если выражение неполное
    1. показать приглашение на продолжение
    2. позволяет пользователю продолжать вводить строки
  4. Когда строка заканчивается полным выражением
    1. выводит результат вычисления последнего выражения
    2. показать главное приглашение

Моя грамматика начинается с производства top_level, которое представляет собой одно утверждение на языке. Лексер настроен для интерактивного режима на стандартный ввод. Я использую один и тот же сканер и грамматику как в режиме полного файла, так и в режиме 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 было бы хорошо. шток Flex 2.5.35 и Bison 2.3, которые поставляются с Xcode.)

Ответы [ 2 ]

7 голосов
/ 14 августа 2011

Посмотрев на то, как языки, такие как 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 голос
/ 11 июля 2011

Я тоже работаю над таким переводчиком, я еще не дошел до того, чтобы сделать REPL, поэтому мое обсуждение может быть несколько расплывчатым.

Допустимо ли, если при заданной последовательности операторов в одной строке печататься только результат последнего выражения? Потому что вы можете пересчитать ваше правило грамматики верхнего уровня следующим образом:

top_level = оператор top_level | заявление;

Выход вашего top_level может быть связанным списком операторов, а интерпретатор.result будет оценкой хвоста этого списка.

...