Как мне указать грамматику для парсера?


12

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

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

Моей первой мыслью было прочитать о синтаксических анализаторах, и я сделал кое-что из этого, но все, что я прочитал на эту тему, принимает грамматику как данность (или достаточно тривиальную, чтобы ее можно было определить путем проверки), и фокусируется на проблема перевода этой грамматики в парсер. Меня интересует проблема непосредственно перед тем, как задать грамматику в первую очередь.

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

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

Как грамматика указана для парсера? Существует ли книга или справочник, являющийся стандартом де-факто для описания передовых методов, методологий проектирования и другой полезной информации о задании грамматики для анализатора? На каких пунктах, читая о грамматике парсера, я должен сосредоточиться?


1
Я немного отредактировал ваш вопрос, чтобы сосредоточиться на вашей актуальной проблеме. Этот сайт - именно то место, где вы можете задать свои вопросы о грамматике и парсерах и получить квалифицированные ответы. Если есть внешние ресурсы, на которые стоит обратить внимание, они естественно появятся в ответах, которые непосредственно помогут вам.
Адам Лир

8
@kjo Это прискорбно. Если все, что вы просите, это список ссылок, вы не используете Stack Exchange в полной мере. Ваша мета-проблема не использует сайт по назначению. Список вопросов почти не рекомендуется использовать в Stack Exchange, потому что они не очень хорошо вписываются в модель вопросов и ответов. Я настоятельно рекомендую вам изменить свое мышление на вопросы, на которые есть ответы, а не предметы, идеи или мнения .
Адам Лир

3
@kjo Это, конечно, вопрос, но не тот вопрос, который нужно задавать в Stack Exchange . SE не здесь, чтобы создавать списки ссылок. Это здесь, чтобы быть ссылкой. Пожалуйста, прочитайте мета-пост, на который я ссылался в своем комментарии, для более подробного объяснения.
Адам Лир

5
@kjo: Пожалуйста, не расстраивайтесь! Изменения Анны сохранили суть и суть вашего вопроса, и она помогла вам, придав вашему вопросу больше той формы, которую мы ожидаем от Programmers.SE. Я не знаю точных ссылок, которые вы ищете, но смог дать ответ. [OTOH, если бы я знал такую ​​ссылку, я, конечно, включил бы ее.] Мы хотим поощрять больше ответов, таких как мой, потому что, в данном конкретном случае, я не верю, что есть ссылка на то, что вы ищете, просто опыт общения с другими.
Макнейл

4
@kjo Я откатился к изменениям Анны и попытался включить конкретный призыв к каноническому справочнику на основе нашего руководства по рекомендациям для книг : в предоставленных ответах есть много полезной информации, и чтобы сделать их недействительными, сделав рамки Вопрос только о том, чтобы найти книгу, был бы пустой тратой. Теперь, если мы все сможем просто остановиться на редактировании, это было бы здорово.

Ответы:


12

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

f() {}
f(a,b) {b+a}
int x = 5;

Вы можете тривиально указать две грамматики, которые будут принимать эти образцы:

Тривиальная грамматика первая:

start ::= f() {} | f(a,b) {b+a} | int x = 5;

Тривиальная грамматика два:

start ::= tokens
tokens ::= token tokens | <empty>
token ::= identifier | literal | { | } | ( | ) | , | + | = | ;

Первый тривиален, потому что он принимает только три образца. Второй тривиален, потому что он принимает все, что может использовать эти типы токенов. [В этом обсуждении я собираюсь предположить, что вас не очень беспокоит дизайн токенизатора: просто принять идентификаторы, числа и знаки препинания в качестве токенов, и вы можете позаимствовать любой набор токенов из любого языка сценариев, который захотите. как бы то ни было.]

Итак, процедура, которой вы должны следовать, - это начать с высокого уровня и решить, «сколько из каждого экземпляра я хочу разрешить?» Если синтаксическая конструкция может иметь смысл повторять любое количество раз, например methods в классе, вам понадобится правило с такой формой:

methods ::= method methods | empty

Который лучше сформулировать в EBNF как:

methods ::= {method}

Это, вероятно, будет очевидно, когда вам нужно только один или несколько экземпляров (это означает, что конструкция является необязательной, как в случае с extendsпредложением для класса Java), или когда вы хотите разрешить один или несколько экземпляров (как с инициализатором переменной в объявлении). ). Вам нужно помнить о проблемах, таких как требование разделителя между элементами (как ,в списке аргументов), требование разделителя после каждого элемента (как в ;операторах для разделения) или отсутствие разделителя или терминатора (в зависимости от ситуации). с методами в классе).

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

В дополнение к проблемам предшествования (что анализируется друг с другом) и проблемам повторения (сколько каждого элемента должно происходить, как они разделены?), Вам также нужно подумать о порядке: должно ли что-то всегда появляться перед чем-то другим? Если одна вещь включена, должна ли быть исключена другая?

В этот момент у вас может возникнуть соблазн грамматически применять некоторые правила, такие как, например, если Personуказан возраст, вы не хотите, чтобы их дату рождения также указывали. Несмотря на то, что вы можете создать свою грамматику для этого, вам может оказаться проще применить это с помощью прохода «семантической проверки» после того, как все проанализировано. Это упрощает грамматику и, на мой взгляд, улучшает сообщения об ошибках, когда правило нарушается.


1
+1 для лучшего сообщения об ошибках. Большинство пользователей вашего языка не будут экспертами, будь то 10 или 10 миллионов. Теория синтаксического анализа слишком долго игнорировала этот аспект.
MSalters

10

Где я могу узнать, как задать грамматику для парсера?

Для большинства генераторов анализаторов, обычно это некоторый вариант Бэкуса-Наура «s <nonterminal> ::= expressionформат. Я собираюсь исходить из предположения, что вы используете что-то подобное и не пытаетесь создавать свои парсеры вручную. Если вы можете создать синтаксический анализатор для формата, в котором вам задан синтаксис (ниже я привел пример проблемы), указание грамматики не является вашей проблемой.

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

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

«Хорошо» в вашей ситуации должно означать «анализирует положительные стороны и отклоняет отрицательные». Без какой-либо другой формальной спецификации синтаксиса вашего входного файла примеры являются вашими единственными тестовыми примерами, и вы не можете добиться большего успеха, чем это. Вы можете опустить ногу и сказать, что только положительные примеры хороши и отвергнуть что-либо еще, но это, вероятно, не в духе того, чего вы пытаетесь достичь.

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


Пример проблемы

Напишите грамматику, которая будет анализировать текстовые файлы, содержащие список, как определено правилами ниже:

  • Список состоит из нуля или более вещей .
  • Вещь состоит из идентификатора , открытой скобки, в списке элементов и закрывающая скобки.
  • _Item_list_ состоит из нуля или более элементов .
  • Элемент constsis из идентификатора , знак равенства, другой идентификатор и точка с запятой.
  • Идентификатор представляет собой последовательность из одного или нескольких из символов AZ, AZ, 0-9 или подчеркивания.
  • Пробелы игнорируются.

Пример ввода (все допустимо):

clank { foo = bar; baz = bear; }
clunk {
    quux =bletch;
    281_apple = OU812;
    He_Eats=Asparagus ; }

2
И обязательно используйте «какой-то вариант Бэкуса-Наура», а не сам BNF. BNF может выражать грамматику, но она делает множество очень общих понятий, таких как списки, гораздо более сложными, чем они должны быть. Существуют различные улучшенные версии, такие как EBNF, которые улучшают эти проблемы.
Мейсон Уилер

7

Ответы Macneil и Blrfl великолепны. Я просто хочу добавить несколько комментариев о начале процесса.

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

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

program ::= class*

в качестве отправной точки. Или вам, возможно, придется написать это

program ::= ε
         |  class program

Теперь, что такое класс? У него есть имя; необязательная спецификация суперкласса; и куча объявлений конструктора, метода и поля. Кроме того, вам нужен какой-то способ группировки класса в один (однозначный) блок, и это должно включать некоторые уступки юзабилити (например, пометить его зарезервированным словом class). Ладно:

class ::= "class" identifier extends-clause? "{" class-member-decl * "}"

Это одна нотация («конкретный синтаксис»), которую вы можете выбрать. Или вы могли бы так же легко решить это:

class ::= "(" "class" identifier extends-clause "(" class-member-decl* ")" ")"

или

class ::= "class" identifier "=" "CLASS" extends-clause? class-member-decl* "END"

Вы, вероятно, уже приняли это решение неявно, особенно если у вас есть примеры, но я просто хочу подчеркнуть это: структура синтаксиса определяется структурой программ, которые он представляет. Это то, что помогает вам пройти «тривиальные грамматики» из ответа Макнейла. Тем не менее, примеры программ по-прежнему очень важны. Они служат двум целям. Во-первых, они помогают вам понять, на абстрактном уровне, что такое программа. Во-вторых, они помогают вам решить, какой конкретный синтаксис вы должны использовать для представления структуры вашего языка.

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

Наконец, не пытайтесь представить все о своем языке в грамматике. Например, вы можете запретить определенные виды недоступного кода (например, оператор после a return, как в Java). Вы, вероятно, не должны пытаться втиснуть это в грамматику, потому что вы либо пропустите что-то (к сожалению, что, если returnв скобках, или что, если обе ветви ifоператора вернутся?), Или вы сделаете свою грамматику слишком сложной справляться. Это контекстно-зависимое ограничение; напишите это как отдельный проход. Другим очень распространенным примером контекстно-зависимого ограничения является система типов. Вы можете отказаться от выражений, как 1 + "a"в грамматике, если вы работали достаточно усердно, но не могли отказаться 1 + x(где xесть строка типа). Такизбегайте полуготовых ограничений в грамматике и делайте их правильно как отдельный проход.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.