Я программировал много лет, но одна задача, которая все еще занимает у меня слишком много времени, - это задать грамматику для синтаксического анализатора, и даже после этого чрезмерного усилия, я никогда не уверен, что грамматика, которую я придумал, хороша ( любой разумной мерой "хорошо").
Я не ожидаю, что существует алгоритм для автоматизации процесса задания грамматики, но я надеюсь, что есть способы структурировать проблему, которые устраняют большую часть догадок и проб и ошибок моего нынешнего подхода.
Моей первой мыслью было прочитать о синтаксических анализаторах, и я сделал кое-что из этого, но все, что я прочитал на эту тему, принимает грамматику как данность (или достаточно тривиальную, чтобы ее можно было определить путем проверки), и фокусируется на проблема перевода этой грамматики в парсер. Меня интересует проблема непосредственно перед тем, как задать грамматику в первую очередь.
Меня в первую очередь интересует проблема определения грамматики, которая формально представляет собой набор конкретных примеров (положительных и отрицательных). Это отличается от проблемы разработки нового синтаксиса . Спасибо Макнейлу за то, что он указал на это различие.
Я никогда по-настоящему не ценил различие между грамматикой и синтаксисом, но теперь, когда я начинаю это видеть, я мог бы уточнить свое первое разъяснение, сказав, что меня в первую очередь интересует проблема определения грамматики, которая будет приводить в исполнение предопределенный синтаксис: просто так получается, что в моем случае основой этого синтаксиса обычно является набор положительных и отрицательных примеров.
Как грамматика указана для парсера? Существует ли книга или справочник, являющийся стандартом де-факто для описания передовых методов, методологий проектирования и другой полезной информации о задании грамматики для анализатора? На каких пунктах, читая о грамматике парсера, я должен сосредоточиться?
Ответы:
Из примеров файлов вам нужно будет принять решение на основе того, сколько вы хотите обобщить из этих примеров. Предположим, у вас были следующие три примера: (каждый отдельный файл)
Вы можете тривиально указать две грамматики, которые будут принимать эти образцы:
Тривиальная грамматика первая:
Тривиальная грамматика два:
Первый тривиален, потому что он принимает только три образца. Второй тривиален, потому что он принимает все, что может использовать эти типы токенов. [В этом обсуждении я собираюсь предположить, что вас не очень беспокоит дизайн токенизатора: просто принять идентификаторы, числа и знаки препинания в качестве токенов, и вы можете позаимствовать любой набор токенов из любого языка сценариев, который захотите. как бы то ни было.]
Итак, процедура, которой вы должны следовать, - это начать с высокого уровня и решить, «сколько из каждого экземпляра я хочу разрешить?» Если синтаксическая конструкция может иметь смысл повторять любое количество раз, например
method
s в классе, вам понадобится правило с такой формой:Который лучше сформулировать в EBNF как:
Это, вероятно, будет очевидно, когда вам нужно только один или несколько экземпляров (это означает, что конструкция является необязательной, как в случае с
extends
предложением для класса Java), или когда вы хотите разрешить один или несколько экземпляров (как с инициализатором переменной в объявлении). ). Вам нужно помнить о проблемах, таких как требование разделителя между элементами (как,
в списке аргументов), требование разделителя после каждого элемента (как в;
операторах для разделения) или отсутствие разделителя или терминатора (в зависимости от ситуации). с методами в классе).Если ваш язык использует арифметические выражения, вам будет легко скопировать из существующих правил приоритета существующего языка. Лучше придерживаться чего-то общеизвестного, например, правил выражений C, чем стремиться к чему-то экзотическому, но только при условии, что все остальное равно.
В дополнение к проблемам предшествования (что анализируется друг с другом) и проблемам повторения (сколько каждого элемента должно происходить, как они разделены?), Вам также нужно подумать о порядке: должно ли что-то всегда появляться перед чем-то другим? Если одна вещь включена, должна ли быть исключена другая?
В этот момент у вас может возникнуть соблазн грамматически применять некоторые правила, такие как, например, если
Person
указан возраст, вы не хотите, чтобы их дату рождения также указывали. Несмотря на то, что вы можете создать свою грамматику для этого, вам может оказаться проще применить это с помощью прохода «семантической проверки» после того, как все проанализировано. Это упрощает грамматику и, на мой взгляд, улучшает сообщения об ошибках, когда правило нарушается.источник
Для большинства генераторов анализаторов, обычно это некоторый вариант Бэкуса-Наура «s
<nonterminal> ::= expression
формат. Я собираюсь исходить из предположения, что вы используете что-то подобное и не пытаетесь создавать свои парсеры вручную. Если вы можете создать синтаксический анализатор для формата, в котором вам задан синтаксис (ниже я привел пример проблемы), указание грамматики не является вашей проблемой.Я думаю, вы противостоите синтаксису из набора сэмплов, который на самом деле больше распознает паттерны, чем анализирует. Если вам приходится прибегать к этому, это означает, что тот, кто предоставляет ваши данные, не может дать вам его синтаксис, потому что у него нет хорошего управления его форматом. Если у вас есть возможность отодвинуться и сказать им, чтобы дать вам формальное определение, сделайте это. Для них было бы несправедливо давать вам неопределенную проблему, если бы вы могли нести ответственность за последствия синтаксического анализатора, основанного на выведенном синтаксисе, принимающем неправильный ввод или отклоняющем хороший ввод.
«Хорошо» в вашей ситуации должно означать «анализирует положительные стороны и отклоняет отрицательные». Без какой-либо другой формальной спецификации синтаксиса вашего входного файла примеры являются вашими единственными тестовыми примерами, и вы не можете добиться большего успеха, чем это. Вы можете опустить ногу и сказать, что только положительные примеры хороши и отвергнуть что-либо еще, но это, вероятно, не в духе того, чего вы пытаетесь достичь.
При более разумных обстоятельствах тестирование грамматики похоже на тестирование чего-либо еще: вам нужно придумать достаточно тестовых случаев, чтобы использовать все варианты нетерминалов (и терминалов, если они генерируются лексером).
Пример проблемы
Напишите грамматику, которая будет анализировать текстовые файлы, содержащие список, как определено правилами ниже:
Пример ввода (все допустимо):
источник
Ответы Macneil и Blrfl великолепны. Я просто хочу добавить несколько комментариев о начале процесса.
Синтаксис просто способ представления программы . Таким образом, синтаксис вашего языка должен определяться вашим ответом на этот вопрос: что такое программа?
Вы можете сказать, что программа представляет собой набор классов. Хорошо, это дает нам
в качестве отправной точки. Или вам, возможно, придется написать это
Теперь, что такое класс? У него есть имя; необязательная спецификация суперкласса; и куча объявлений конструктора, метода и поля. Кроме того, вам нужен какой-то способ группировки класса в один (однозначный) блок, и это должно включать некоторые уступки юзабилити (например, пометить его зарезервированным словом
class
). Ладно:Это одна нотация («конкретный синтаксис»), которую вы можете выбрать. Или вы могли бы так же легко решить это:
или
Вы, вероятно, уже приняли это решение неявно, особенно если у вас есть примеры, но я просто хочу подчеркнуть это: структура синтаксиса определяется структурой программ, которые он представляет. Это то, что помогает вам пройти «тривиальные грамматики» из ответа Макнейла. Тем не менее, примеры программ по-прежнему очень важны. Они служат двум целям. Во-первых, они помогают вам понять, на абстрактном уровне, что такое программа. Во-вторых, они помогают вам решить, какой конкретный синтаксис вы должны использовать для представления структуры вашего языка.
Разобравшись со структурой, вы должны вернуться и заняться такими проблемами, как разрешение пробелов и комментариев, исправление неоднозначностей и т. Д. Это важно, но они вторичны по отношению к общему дизайну и сильно зависят от Используемая вами технология парсинга.
Наконец, не пытайтесь представить все о своем языке в грамматике. Например, вы можете запретить определенные виды недоступного кода (например, оператор после a
return
, как в Java). Вы, вероятно, не должны пытаться втиснуть это в грамматику, потому что вы либо пропустите что-то (к сожалению, что, еслиreturn
в скобках, или что, если обе ветвиif
оператора вернутся?), Или вы сделаете свою грамматику слишком сложной справляться. Это контекстно-зависимое ограничение; напишите это как отдельный проход. Другим очень распространенным примером контекстно-зависимого ограничения является система типов. Вы можете отказаться от выражений, как1 + "a"
в грамматике, если вы работали достаточно усердно, но не могли отказаться1 + x
(гдеx
есть строка типа). Такизбегайте полуготовых ограничений в грамматике и делайте их правильно как отдельный проход.источник