REPL для интерпретатора с использованием Flex/Bison
Я написал интерпретатор для C-подобного языка, используя Flex и Bison для сканера/парсера. Он отлично работает при выполнении полных программных файлов.
теперь я пытаюсь реализовать REPL в интерпретаторе для интерактивного использования. Я хочу, чтобы он работал как интерпретаторы командной строки в Ruby или ML:
- показывать подсказки
- примите одно или несколько утверждений в строке
- если выражение неполное
- дисплей a продолжение строки
- разрешить пользователю продолжать вводить строки
- когда строка заканчивается полным выражением
- Эхо результат оценки последнего выражения
- показать основные строки
моя грамматика начинается с top_level
production, который представляет собой один оператор на языке. Лексер настроен для интерактивного режима на stdin. Я использую тот же сканер и грамматика в обоих режимах full-file и 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);
}
}
это отлично работает, за исключением логики подсказки и Эха. Если пользователь вводит несколько операторов в строке, этот цикл выводит лишние подсказки и выражения. И если выражение продолжается в нескольких строках, этот код не распечатывает приглашения на продолжение. Эти проблемы возникают потому, что гранулярность логики prompt / echo-это top_level
утверждение в грамматике, но логика чтения строк находится глубоко в лексере.
каков наилучший способ реструктуризации цикла оценки для обработки запроса и Эха REPL? То есть:
- как я могу отобразить одной строки на строку
- как я могу отобразить приглашение продолжения в нужное время
- как я могу сказать, когда полное выражение является последним на линия
(Я бы предпочел не менять язык сканера для передачи токенов новой строки, так как это серьезно изменит грамматику. Изменение YY_INPUT
и добавление нескольких действий в грамматику бизонов было бы хорошо. Кроме того, я использую stock Flex 2.5.35 и Bison 2.3, которые поставляются с Xcode.)
2 ответов
посмотрев, как языки, такие как Python и SML/NJ, обрабатывают свои реплики, я получил хороший, работающий в моем интерпретаторе. Вместо логики prompt/echo в самом внешнем цикле драйвера парсера я поместил ее в самую внутреннюю процедуру ввода лексера. Действия в синтаксическом анализаторе и лексере устанавливают флаги, управляющие запросом с помощью процедуры ввода.
Я использую реентрантный сканер, так что 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
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 в том, что для завершения утверждения требуется специальный символ (';'). Во входном потоке за этим символом может следовать новая строка символ для сигнала конца строки, или за ним могут следовать символы, которые являются частью нового оператора. Процедура ввода должна показывать главное приглашение, если единственными символами, отсканированными с момента последнего конца инструкции, являются новые строки или другие пробелы; в противном случае она должна показывать приглашение продолжения.
Я тоже работаю над таким переводчиком, я еще не дошел до того, чтобы сделать REPL, поэтому моя дискуссия может быть несколько расплывчатой.
допустимо ли, если задана последовательность операторов в одной строке, печатается только результат последнего выражения? Потому что вы можете изменить свое правило грамматики верхнего уровня следующим образом:
top_level = оператор | оператор top_level;
вывод вашего top_level тогда может быть связанным списком операторов, и переводчик.результатом будет оценка конца этого списка.