Посмотрев на то, как языки, такие как 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 в том смысле, что для завершения оператора требуется специальный символ (';'). Во входном потоке за этим символом может следовать либо символ новой строки для обозначения конца строки, либо за ним могут следовать символы, являющиеся частью нового оператора. Входная подпрограмма должна показывать главное приглашение, если единственными символами, отсканированными после последнего конца оператора, являются символы новой строки или другого пробела; в противном случае должен отображаться запрос на продолжение.