#bison #flex-lexer #yacc #lex
Вопрос:
Я создаю лексер для XML — документа. Вот мой XML-документ (обратите внимание, что фактический XML-документ намного сложнее, это простой XML-документ, показывающий проблему):
lt;?xml version="1.0" encoding="UTF-8"?gt; lt;Document xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="test.xsd" version="1.0"gt; lt;messagegt;Hello, worldlt;/messagegt; lt;/Documentgt;
Я хочу, чтобы лексер произвел это:
DOCUMENT_START_TAG ATTRIBUTE_NAME = version ATTRIBUTE_VALUE = "1.0" MESSAGE_START_TAG ELEMENT_VALUE = Hello, world MESSAGE_END_TAG DOCUMENT_END_TAG
То есть я хочу, чтобы лексер игнорировал первую строку (объявление XML), пробелы между элементами и два объявления пространства имен.
Но вместо этого лексер производит это:
ELEMENT_VALUE = DOCUMENT_START_TAG ATTRIBUTE_NAME = version ATTRIBUTE_VALUE = "1.0" ELEMENT_VALUE = MESSAGE_START_TAG ELEMENT_VALUE = Hello, world MESSAGE_END_TAG ELEMENT_VALUE = DOCUMENT_END_TAG
Правило лексера для пробелов не срабатывает. Вместо этого правило для значения элемента срабатывает. Поэтому я знаю, в чем проблема: регулярное выражение для значения элемента неверно. Но я не знаю, что такое правильное регулярное выражение. Любая помощь, которую вы могли бы оказать, будет очень признательна.
Внизу находится весь мой файл .l. Вот объяснение содержащихся в нем правил:
Первая строка-строка объявления XML-это то, что я хочу, чтобы лексер просто отбросил. Вот правило лексера для этого:
"lt;?"[^?gt;] "?gt;"
XML — декларация начинается с lt;?
и заканчивается ?gt;
, и все, что между ними, — это что угодно, кроме ?
и gt;
Я хочу, чтобы лексер удалял пробелы между элементами XML. Вот правило лексера для пробелов:
[ tn]
That gobbles up spaces, tabs, and newlines.
I want the lexer to ignore the two namespace declarations. Here are the lexer rules for them:
[ tn] xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" [ tn] xsi:noNamespaceSchemaLocation="test.xsd"
Namespace declarations are always preceded by at least one whitespace character.
I want the lexer to return the token DOCUMENT_START_TAG
for the lt;Documentgt;
element. The lt;Documentgt;
element has attributes bundled inside of it, so that requires some special care:
"lt;Document"[^gt;]*"gt;" { yyless(9); return(DOCUMENT_START_TAG); }
The lt;Documentgt;
element starts with lt;Document
and then there is some stuff and then it ends with gt;
. The action puts back everything following lt;Document
and returns the token DOCUMENT_START_TAG
.
I want the lexer to return DOCUMENT_END_TAG
for lt;/Documentgt;
. Here’s the lexer rule:
"lt;/Documentgt;" { return(DOCUMENT_END_TAG); }
Here are the lexer rules for the message start and end tags:
"lt;messagegt;" { return(MESSAGE_START_TAG); } "lt;/messagegt;" { return(MESSAGE_END_TAG); }
An XML attribute has a name, equals sign, and value wrapped in quotes. Here is the lexer rule for the name:
[^ tn=] /=[ tn]*» { return(ATTRIBUTE_NAME); }
The name doesn’t contain space, tab, newline, or equals sign. (Using the lookahead operator) following a name is an equals sign, possibly some whitespace, and a quote.
The attribute value is the stuff within quotes:
"[^"]*" { return(ATTRIBUTE_VALUE); }
Я не хочу, чтобы значение атрибута содержало кавычки — как их удалить?
Я хочу, чтобы лексер возвращал значение элементов (например, Привет, мир). Значение элемента не содержит lt;
или gt;
[^lt;gt;] /lt; { return(ELEMENT_VALUE); }
Я использую lookahead, чтобы указать, что за значением всегда следует lt;
Вот мой полный файл .l:
%{ enum yytokentype { DOCUMENT_START_TAG = 258, DOCUMENT_END_TAG = 259, MESSAGE_START_TAG = 260, MESSAGE_END_TAG = 261, ELEMENT_VALUE = 262, ATTRIBUTE_NAME = 263, ATTRIBUTE_VALUE = 264, JUNK = 265 }; int yyval; %} %% "lt;?"[^?gt;] "?gt;" [ tn] "gt;" "=" [ tn] xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" [ tn] xsi:noNamespaceSchemaLocation="test.xsd" "lt;Document"[^gt;]*"gt;" { yyless(9); return(DOCUMENT_START_TAG); } "lt;/Documentgt;" { return(DOCUMENT_END_TAG); } "lt;messagegt;" { return(MESSAGE_START_TAG); } "lt;/messagegt;" { return(MESSAGE_END_TAG); } [^ tn=] /=[ tn]*" { return(ATTRIBUTE_NAME); } "[^"]*" { return(ATTRIBUTE_VALUE); } [^lt;gt;] /lt; { return(ELEMENT_VALUE); } . { return(JUNK); } %% int yywrap(){ return 1;} int main(int argc, char *argv[]) { yyin = fopen(argv[1], "r"); int tok; while (tok = yylex()) { switch (tok){ case 258: printf("DOCUMENT_START_TAGn"); break; case 259: printf("DOCUMENT_END_TAGn"); break; case 260: printf("MESSAGE_START_TAGn"); break; case 261: printf("MESSAGE_END_TAGn"); break; case 262: printf("ELEMENT_VALUE = %sn", yytext); break; case 263: printf("ATTRIBUTE_NAME = %sn", yytext); break; case 264: printf("ATTRIBUTE_VALUE = %sn", yytext); break; case 265: printf("JUNK = %sn", yytext); break; default: printf(" = invalid token, value = %sn", yytext); } } fclose(yyin); return 0; }
Комментарии:
1. Кроме того, пожалуйста, обратите внимание, что правило lex должно иметь действие . Иногда это вообще не работает, но это неопределенное поведение. Если вы хотите написать правило, которое ничего не делает, используйте
;
или{}
в качестве действия. Никаких дополнительных затрат не требуется. 🙂2. Ах! Я этого не понимал. Спасибо @rici
Ответ №1:
Ваше правило для значения элемента всегда выигрывает над вашим правилом пробелов, потому что оно имеет более длинное соответствие. Это связано с тем, что конечный контекст считается частью совпадения, даже если лексер возвращается в конечный контекст перед запуском действия.
Это задокументировано в руководстве по Flex, но его легко пропустить.
Мне непонятно, почему вы чувствуете необходимость в завершающем контексте. Единственными символами, которые могут следовать [^lt;gt;]
, являются lt;
и gt;
; если вы хотите рассматривать gt;
как ошибку, было бы более разумно отметить ошибку в точке, где gt;
она возникает, чем отмечать ее в начале значения элемента, которое в конечном итоге содержит символ-нарушитель. Но, вероятно, еще более разумно просто спокойно воспринимать gt;
его как обычного персонажа. В любом случае, конечный контекст не требуется, и без этого конечного контекста ваше правило пробелов выиграет, если это применимо.
Но обратите внимание, что если в XML-документе использовались окончания строк CRLF, правило пробелов не поймает их. Я всегда предлагаю использовать [[:space:]]
вместо перечисления пробелы, хотя они совпадают с некоторыми символами, которые могут считаться ошибками.
Аналогично, сканирование тега до закрытия gt;
, а затем возврат к тегу совершенно бессмысленно. Либо тег правильно завершен , и вы в конечном итоге достигнете gt;
, либо вы попадете в конец документа, и в этот момент вы можете выдать ошибку. Однако вам следует перехватывать теги , имена которых начинаются с Document
, например lt;Documentarygt;
(которые будет принимать ваш текущий шаблон). Это наводило бы на мысль о чем-то вроде:
lt;Document { return DOCUMENT_START_TAG; } lt;message { return MESSAGE_START_TAG; } lt;/Document { return DOCUMENT_END_TAG; } lt;/message { return MESSAGE_END_TAG; } lt;/[^[:space:]gt;] { return UNKNOWN_END_TAG; } lt;[^[:space:]gt;] { return UNKNOWN_START_TAG; }