Гибкое сканирование новой строки для зубров - PullRequest
1 голос
/ 11 марта 2012

Я хотел бы использовать один и тот же flex / bison сканер / парсер для интерпретатора и для загрузки файла для интерпретации. Я не могу заставить синтаксический анализ новой строки работать правильно в обоих случаях.

  1. Интерпретатор: есть приглашение, и я могу вводить команды, прерванные нажатием клавиши ENTER.
  2. Файл: Вот пример входного файла:

----- порез ---------

* * 1010

---- покрой -------

Итак, в первой строке и после '(' есть новая строка, которую следует съесть.

В моем сканере. У меня есть

%%
[ \t]                       {   errorLineCol += strlen(yytext); }

\n                          {   errorLineNumber++;
                                errorLineCol = 0; }

("-"?[0-9])[0-9]*           {   errorLineCol += strlen(yytext);
                                yylval = stringToInteger(yytext);
                                return TINTEGER; }

.....

Это тогда работает для сценария файла, но не для интерпретатора. Я должен нажать и дополнительные Ctrl + D после ENTER. Если я изменю на

\n                          {   errorLineNumber++;
                                errorLineCol = 0;
                                return 0; }

Тогда работает интерпретатор, но не чтение файла; который затем останавливается после первой новой строки, с которой встречается. Как можно решить эту проблему?

Edit:

Вот верхний уровень парсера:

input: uexpr                        {   parseValue = $1; }
    | /* empty */                   {   parseValue = myNull; }
    | error                         {   parseValue = myNull; }
    ;

uexpr: list                          
    | atom                         
    ;

Возможное решение: , похоже, использует

\n                          {   errorLineNumber++;
                                errorLineCol = 0;
                                if (yyin == stdin) return 0; }

Ответы [ 2 ]

2 голосов
/ 12 марта 2012

Основная проблема в том, что ваша функция синтаксического анализатора ypparse не возвращается, пока не сведет весь язык к начальному символу.

Если верхний уровень вашей грамматики что-то вроде:

language : commands ;

commands : command commands | /* empty */ ;

Конечно, машина будет ожидать полный сценарий (завершенный нажатием Ctrl-D).Если ваш интерпретатор придерживается этой логики:

loop:
  print("prompt>")
  yyparse()
  if (empty statement)
    break

, он не будет работать, поскольку yyparse потребляет весь сценарий перед возвратом.

return 0; решает проблему для этого интерактивного режимапоскольку значение токена 0 указывает парсеру EOF, что заставляет его думать, что сценарий завершился.

Я не согласен с решением сделать токен \n.Это только усложнит грамматику (до сих пор незначительный кусок пробела теперь имеет большое значение) и в конечном итоге не сработает, потому что функция yyparse все равно захочет обработать всю грамматику.То есть, если у вас есть символ новой строки в качестве токена, но начальный символ грамматики представляет весь скрипт, yyparse все равно не вернется в ваш цикл интерактивной подсказки.

Быстрый и грязный хак позволяетлексер знает, действует ли интерактивный режим.Тогда он может обусловить return 0; для каждого экземпляра новой строки, если он находится в интерактивном режиме.Если ввод не полный оператор, будет синтаксическая ошибка, так как сценарий в целом заканчивается на новой строке.В обычном режиме чтения файлов ваш лексер может съесть все пустое пространство, не возвращаясь, как раньше, позволяя обрабатывать весь файл одним yyparse.

Если вы хотите интерактивный ввод и чтение файла без реализации двух режимов:Поведение в лексере, что вы можете сделать, это изменить грамматику так, чтобы она анализировала только одну инструкцию языка: функция yyparse возвращает значение для каждой инструкции высшего уровня вашего языка.(И лексер ест новые строки, как и раньше, не возвращая 0).Т.е. начальный символ грамматики - это всего лишь одно утверждение (возможно, пустое).Тогда ваш анализатор файлов должен быть реализован как цикл (написанный вами), который вызывает yyparse для получения всех операторов из файла, пока yyparse не встретит пустой ввод.Недостатком этого подхода является то, что если пользователь вводит неполный синтаксис (например, висящие открытые скобки), синтаксический анализатор будет продолжать сканировать ввод, пока он не будет удовлетворен.Это недружелюбно, как программы, которые используют scanf для интерактивного ввода данных пользователем (это та же проблема: scanf - это синтаксический анализатор, который не возвращает до тех пор, пока он не будет удовлетворен).

Другая возможность заключается винтерактивный режим, который выполняет собственный пользовательский ввод вместо вызова yyparse, чтобы получить входные данные и для его анализа.В этом режиме вы читаете ввод пользователя в строковый буфер.Тогда у вас есть парсер, обрабатывающий строковый буфер.Обрабатывать строковый буфер вместо потока FILE * вполне возможно.Вам просто нужно написать собственную обработку ввода (ваше собственное определение макроса YY_INPUT).Это подход, который вам в конечном итоге понадобится, если вы реализуете приличный интерактивный режим с редактированием строк и вызовом истории, например, с помощью libedit или GNU readline.

2 голосов
/ 11 марта 2012

Если нажатие клавиши ENTER завершает команду, лексер должен вернуть токен для \ n. Возврат 0 говорит парсеру, что источник ввода завершен (конец файла для файла или ^ D для терминала). Добавьте токен конца строки в свою грамматику и попросите лексера вернуть его, когда он увидит \ n.

ETA: Но не забудьте обработать регистр последней строки, не заканчивающейся на ENTER. Пусть ваш лексер вернет маркер конца строки в конце файла, если последний символ не равен \ n.

...