Неправильный алгоритм Лекса для операторов предпросмотра - PullRequest
2 голосов
/ 29 января 2012

В «современной реализации компилятора в Java» Эндрю Аппеля он утверждает в упражнении, что:

У Lex есть оператор lookahead /, поэтому регулярное выражение abc / def соответствует abc только тогда, когда следует def (но def не является частью совпадающей строки и будет частью следующего токена (ов)). Ахо и соавт. [1986] описывают, и Лекс [Lesk 1975] использует неправильный алгоритм для реализации прогнозирования (он не работает на (a | ab) / ba с вводом aba, сопоставляя ab там, где он должен совпадать с a). Flex [Paxson 1995] использует улучшенный механизм, который работает правильно для (a | ab) / ba, но не работает (с предупреждающим сообщением на zx * / xy *. Разработайте лучший механизм прогнозирования.

Кто-нибудь знает решение того, что он описывает?

1 Ответ

1 голос
/ 20 марта 2012

«Не работает так, как я думаю» и «неправильно», не всегда одно и то же. С учетом ввода

aba

и шаблон

(ab|a)/ab

имеет определенный смысл для жадного совпадения (ab | a), а затем для отдельного ограничения /ab. Вы думаете, что оно должно работать как это регулярное выражение:

(ab|a)(ab)

с ограничением на то, что деталь, соответствующая (ab), не используется. Это, вероятно, лучше, потому что это снимает некоторые ограничения, но поскольку не было никаких внешних требований к тому, что lex должен был сделать во время написания, вы не можете назвать поведение правильным или неправильным.

У наивного способа есть преимущество, заключающееся в том, что добавление конечного контекста не меняет значение токена, а просто добавляет совершенно отдельное ограничение относительно того, что может последовать за ним. Но это приводит к ограничениям / неожиданностям:

 {IDENT}  /* original code */

 {IDENT}/ab   /* ident, only when followed by ab */

Упс, это не сработает, потому что "ab" поглощено IDENT именно потому, что его значение не было изменено конечным контекстом. Это превращается в ограничение, но, возможно, это ограничение, с которым автор хотел жить в обмен на простоту. (В любом случае, какой смысл использовать его для того, чтобы сделать его более контекстным?)

Как насчет другого пути? Это также может иметь сюрпризы:

 {IDENT}/ab  /* input is bracadabra:123 */

Скажите, что пользователь хочет, чтобы это не совпадало, потому что bracadabra не является идентификатором, за которым следует (или заканчивается) ab. Но {IDENT} / ab будет соответствовать bracad, а затем, оставляя abra:123 на входе.

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

lex теперь стандартизирован спецификацией Single Unix, которая гласит:

г / х
Регулярное выражение r должно совпадать только в том случае, если за ним следует вхождение регулярного выражения x (x является экземпляром конечного контекста, более подробно описанного ниже). Токен, возвращенный в yytext, должен соответствовать только r. Если завершающая часть r соответствует началу x, результат не указан. Выражение r не может содержать дополнительный конечный контекст или оператор '$' (match-end-of-line); x не может включать оператор «^» (совпадение начала строки), ни конечный контекст, ни оператор «$». То есть в регулярном выражении lex допускается только одно вхождение конечного контекста, и оператор «^» можно использовать только в начале такого выражения.

Так что вы можете видеть, что здесь есть место для интерпретации. R и x можно рассматривать как отдельные регулярные выражения, при этом совпадение для r вычисляется обычным образом, как если бы оно было одно, а затем x применяется как специальное ограничение.

Спецификация также обсуждает эту проблему (вам повезло):

Следующие примеры проясняют различия между регулярными выражениями lex и регулярными выражениями, встречающимися в других частях этого тома IEEE Std 1003.1-2001. Для регулярных выражений вида "r / x" строка соответствия r всегда возвращается; путаница может возникнуть, когда начало x соответствует завершающей части r. Например, учитывая регулярное выражение «a * b / cc» и ввод «aaabcc», yytext будет содержать строку «aaab» в этом совпадении. Но с учетом регулярного выражения "x * / xy" и ввода "xxxy" токен xxx, а не xx, возвращается некоторыми реализациями, потому что xxx соответствует "x *".

В правиле "ab * / bc", "b *" в конце r расширяет совпадение r в начало конечного контекста, поэтому результат не указан. Однако, если это правило "ab / bc", оно соответствует тексту "ab", когда за ним следует текст "bc". В этом последнем случае совпадение r не может продолжаться до начала x, поэтому результат указывается. Как вы можете видеть, у этой функции есть некоторые ограничения.

Неуказанное поведение означает, что есть несколько вариантов того, каким должно быть поведение, ни один из которых не является более правильным, чем другие (и не пишите такие шаблоны, если вы хотите, чтобы ваша программа lex была переносимой).«Как видите, в этой функции есть некоторые ограничения».

...