Как разделить обязанности между анализатором и лексером, чтобы соответствовать блоку текста?

#bison #yacc #lex #flex-lexer

#bison #yacc #lex #flex-lexer

Вопрос:

У меня есть текстовый файл, который я пытаюсь разобрать. Файл выглядит следующим образом:

 A_1: - A_10:
Some text.
----------
Some more text.
__________

B_1: - B_5:
Still more text - it may contain dashes as well.
----------
Even more text. Could be multiple sentences with newlines.
Like this.
__________
  

И так далее.

Я пытаюсь разделить синтаксический анализ / токенизацию между bison и flex . Мне удалось разобрать заголовок ( A_1: - A_10: ), используя следующие регулярные выражения в flex :

 [ t]  ;               // ignore whitespace
[A-Z]_[0-9] (_[0-9] )? { ... return ID; }
  

в сочетании с правилом в моей грамматике для объединения двух идентификаторов:

 header:        ID ':' '-' ID ':'
  

Однако следующий сегмент текста вызывает некоторые проблемы. Я почти уверен, что мне нужно включить начальные условия в лексер (например, игнорировать пробелы только при разборе заголовка). Я попытался определить ТЕКСТ токена и разобрать все это до ---------- единого токена. Все еще не могу понять, разумно ли это делать.

Другая возможность, о которой я могу подумать, — это иметь правило в грамматике, которое объединяло бы текстовый сегмент с использованием таких символов, как WORD, ПРОБЕЛ, ТИРЕ, ПЕРЕВОД строки и любой другой возможный символ. Имеет ли это вообще смысл?

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

Ответ №1:

Это то, для чего были разработаны начальные состояния lex. По сути, вы объявляете начальное состояние для каждого отдельного языка, с которым вам нужно иметь дело (в вашем случае два — заголовки и тела), а затем помечаете правила тегами, основанными на том, к какому состоянию они применяются. Итак, вы хотели бы что-то вроде:

 %s header
%s body
%%
<header>[ tn]                   ; /* ignore */
<header>[_a-zA-Z][_a-zA-Z0-9]*    { ... return ID; }
<header>[-:,.;()]                 { return *yytext; }

<body>^----------$                { yylval.text = GetStoredText(); return SECTION_SPLIT; }
<body>^__________$                { yylval.text = GetStoredText(); return SECTION_END; }
<body>.                           { StoreText(*yytext);
%%
void BeginHeader() { BEGIN header; }
void BeginBody() { BEGIN body; }
  

Где StoreText это функция, которая сохраняет символы в буфер (что-то вроде std::stringstream, если вы используете C ) и GetStoredText возвращает весь текст, сохраненный с момента последнего вызова, и очищает буфер. Тогда ваш код yacc / bison будет выглядеть примерно так:

 input: entry | input entry ;
entry: header body ;
header: ..something to match a header.. { BeginBody(); };
body: sections SECTION_END { BeginHeader(); };
sections: /*empty*/ | sections SECTION_SPLIT ;
  

Конечно, вы также захотите, чтобы код делал все, что вы хотите, с содержимым основных разделов…

Комментарии:

1. Спасибо! Это отлично работает. Единственное исправление, которое я внес, это заменить последний шаблон на этот <body>(?s:.) , чтобы соответствовать новым строкам и всему остальному между разделителями разделов.

Ответ №2:

Я пришел к осознанию того, что обработка такого документа построчно (т. Е. написание собственного синтаксического анализатора, подходящего для задачи) может привести к гораздо более чистому решению.

Другая возможность — разделить весь файл по каждому __________ маркеру. Таким образом, мы получим несколько разделов. Затем разделите каждый раздел на ---------- маркеры. Теперь мы можем извлечь второй фрагмент текста текущего раздела («Еще немного текста». в первом разделе в примере выше). Первый фрагмент текста — это просто однострочный заголовок, за которым следует «Некоторый текст». до конца фрагмента.

Этот алгоритм легко реализуется на языках сценариев, таких как Perl, Python, Ruby и т.д.

Комментарии:

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

2. @acidzombie24 Для своих собственных нужд я всегда использую структурированный формат для хранения данных. Однако на этот раз вопрос касается синтаксического анализа файла в заданном формате, который я не могу изменить самостоятельно. Спасибо за предложение 😉

3. черт возьми, я ненавижу эти ограничения. Хорошо, но я думаю, что ответом Доддса легче управлять, чем писать свой собственный синтаксический анализатор. Вы в конечном итоге написали свой собственный? было ли это сложно и сколько времени это заняло?

4. @acidzombie24 Я написал свой собственный на Python, прежде чем Крис опубликовал свой ответ. Это было довольно быстро и просто, и было очень легко сохранить результат в json / plist. На C я бы потратил еще немного времени на выяснение этого.

5. ах, действительно? мне любопытно, как выглядит код. Тогда это звучит нормально. Я определенно не рекомендовал бы это, если бы это было на c (наконец, C получает regex как часть стандарта)