Почему многострочные комментарии в flex/bison так уклончивы?

Я пытаюсь проанализировать многострочные комментарии в стиле C в моем файле flex (.l):

%s ML_COMMENT
%%

...

<INITIAL>"/*"                   BEGIN(ML_COMMENT);
<ML_COMMENT>"*/"                BEGIN(INITIAL);  
<ML_COMMENT>[.\n]+              { }

Я не возвращаю никаких токенов, и моя грамматика (.y) никоим образом не касается комментариев.

Когда я запускаю свой исполняемый файл, я получаю ошибку синтаксического анализа:

$ ./a.out
/*
abc 
def
Parse error: parse error
$ echo "/* foo */" | ./a.out
Parse error: parse error

(Моя функция yyerror выполняет printf("Ошибка синтаксического анализа: %s\n"), откуда появляется первая половина избыточного сообщения об ошибке).

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

Также сбивает с толку:

$ ./a.out
/* foo */
a = b;
Parse error: parse error

В этом случае комментарий закрывается до фактического действительного ввода (который без комментария анализируется просто отлично). На самом деле сбой происходит после синтаксического анализа "a", а не после попытки синтаксического анализа присваивания "a = b;". Если я ввожу «а» в отдельной строке, все равно выдает ошибку.

Учитывая, что сообщение об ошибке является ошибкой синтаксического анализатора, а не ошибкой сканера, есть ли что-то важное, что мне не хватает в моем файле .y? Или я делаю что-то не так в правилах сканера, которые распространяются на сторону парсера?

EDIT: По предложению @Rudi я включил отладку и обнаружил:

$ ./a.out
Starting parse
Entering state 0
Reading a token: /*
foo
Next token is 44 (IDENTIFER)
Shifting token 44 (IDENTIFER), Entering state 4
Reducing via rule 5 (line 130), IDENTIFER  -> identifier
state stack now 0
Entering state 5

Я отключил отладку и обнаружил, что /* foo */ = bar; действительно анализирует то же самое, что и foo = bar;. Я использую Flex 2.5.4; он не дает мне никаких предупреждений о правилах с отслеживанием состояния, которые я пытаюсь использовать.


person adelarge    schedule 10.11.2010    source источник
comment
Я изменил тег flex на gnu-flex. Ваши правила сканера выглядят нормально. Ошибка синтаксического анализа указывает на неверный ввод токена в синтаксический анализатор. Возможно, вы захотите опубликовать некоторые соответствующие правила Bison. Кроме того, может быть хорошей идеей поместить операторы printf() в ваши правила bison, чтобы вы могли видеть, какие правила пробует синтаксический анализатор во время сканирования токена.   -  person Kizaru    schedule 10.11.2010
comment
Также неплохо было бы создать для сканера отдельную тестовую обвязку. Таким образом, вы можете изолировать дефекты сканера от дефектов парсера. Любая система сканера-парсера достаточно сложна, поэтому вам не нужно вводить дополнительную сложность, выполняя интеграционное тестирование, когда вы действительно хотите выполнять модульное тестирование...   -  person bstpierre    schedule 10.11.2010
comment
Когда вы добавляете флаг --debug к своему вызову bison и устанавливаете yydebug=1 перед вызовом yyparse(), синтаксический анализатор выдает отладочную информацию для каждого токена, который он видит из лексера.   -  person Rudi    schedule 10.11.2010
comment
Я бы предложил - в ожидании причин не делать этого - просто написать препроцессор Perl, чтобы жевать комментарии.   -  person Paul Nathan    schedule 10.11.2010


Ответы (4)


Я думаю, вам нужно объявить начальное условие ML_COMMENT исключительным начальным условием, чтобы активными были только правила ML_COMMENT. %x ML_COMMENT вместо %s ML_COMMENT

В противном случае также активны правила без начальных условий.

person Craig    schedule 10.11.2010
comment
Ах! Это, кажется, сделало свое дело. Мой единственный вопрос сейчас: почему содержимое моих многострочных комментариев возвращается обратно? Когда я набираю /* foo bar */ в STDIN, я получаю foo bar в STDOUT. - person adelarge; 10.11.2010
comment
[.\n] не делает того, что вы думаете. замените его на 2 правила, одно для . и один для \n. Flex по умолчанию отображает ввод, который не соответствует ни одному правилу. Вот почему многие наборы правил lex заканчиваются на . поэтому каждый ввод соответствует чему-то. - person Craig; 10.11.2010

Разбор комментариев таким образом может привести к ошибкам, потому что:

  • вам нужно добавить условия ко всем вашим правилам lex
  • становится еще сложнее, если вы также хотите обрабатывать // комментарии
  • у вас все еще есть риск, что yacc/bison объединит два комментария, включая все, что между ними

В моем парсере я обрабатываю такие комментарии. Сначала определите правила lex для начала комментария, например:

\/\*     {
         if (!SkipComment())
            return(-1);
         }

\/\/     {
         if (!SkipLine())
            return(-1);
         }

затем напишите функции SkipComment и SkipLine. Им нужно потреблять весь ввод, пока не будет найден конец комментария (это довольно старый код, так что простите меня за несколько архаичные конструкции):

bool SkipComment (void)
{
int Key;

Key=!EOF;
while (true)
   {
   if (Key==EOF)
      {
      /* yyerror("Unexpected EOF within comment."); */
      break;
      }
   switch ((char)Key)
      {
      case '*' :
         Key=input();
         if (char)Key=='/') return true;
         else               continue;
         break;
      case '\n' :
         ++LineNr;
         break;
      }
   Key=input();
   }

return false;
}

bool SkipLine (void)
{
int Key;

Key=!EOF;
while (true)
   {
   if (Key==EOF)
      return true;
   switch ((char)Key)
      {
      case '\n' :
         unput('\n');
         return true;
         break;
      }
   Key=input();
   }

return false;
}
person Patrick    schedule 10.11.2010
comment
Обрабатывает ли это последовательность символов начала/конца комментария, если она встречается в цитируемом тексте? (например, foo = "this doesn't contain a /* comment */") - person Dan Moulding; 10.11.2010
comment
Я не упомянул об этом явно, но вы должны анализировать строки точно так же. Вам особенно нужно сделать это, если вы хотите поддерживать экранирование обратной косой черты, как в C/C++. - person Patrick; 11.11.2010
comment
Это более сложно, более подвержено ошибкам, более многословно и труднее, чем просто правильно использовать гибкие начальные состояния. По сути, это просто рукописная часть вашего лексера - если вам не нравится flex, почему бы просто не написать все вручную? - person Chris Dodd; 18.03.2016

Помимо проблемы с %x против %s, у вас также есть проблема, заключающаяся в том, что . в [.\n] соответствует (только) буквальному ., а не «любому символу, кроме новой строки», как это делает голый .. Вы хотите правило вроде

<ML_COMMENT>.|"\n"     { /* do nothing */ }

вместо

person Chris Dodd    schedule 10.11.2010

Я нашел это описание грамматики языка C (на самом деле только лексера) очень полезным. Я думаю, что это в основном то же самое, что и ответ Патрика, но немного другое.

http://www.lysator.liu.se/c/ANSI-C-grammar-l.html

person Mark Lakata    schedule 11.02.2013