Як слід вказати граматику для аналізатора?


12

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

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

Перша моя думка - прочитати про парсери, і я щось з цього зробив, але все, що я прочитав з цього приводу, граматику сприймає як задану (або достатньо тривіальну, щоб можна було вказати її за допомогою перевірки), і фокусується на проблема перекладу цієї граматики в аналізатор. Мене цікавить проблема безпосередньо раніше: як в першу чергу вказати граматику.

Мене насамперед цікавить проблема конкретизації граматики, яка формально являє собою збірку конкретних прикладів (позитивних та негативних). Це відрізняється від проблеми проектування нового синтаксису . Дякую Макнейлу за вказівку на цю відмінність.

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

Як визначається граматика для аналізатора? Чи є книга чи посилання там, що є фактичним стандартом для опису найкращих практик, методологій проектування та іншої корисної інформації щодо визначення граматики для аналізатора? На які моменти, читаючи про граматику парсера, я повинен зосереджуватися?


1
Я трохи відредагував ваше запитання, щоб зосередитись на вашій реальній проблемі. Цей веб-сайт є саме тим місцем, де ви можете задати свої питання щодо граматики та аналізаторів та отримати відповіді експертів. Якщо є зовнішні ресурси, на які варто звернути увагу, вони природним чином з’являться у відповідях, які безпосередньо вам допомагають.
Адам Лір

8
@kjo Це прикро. Якщо ви тільки просите, це список посилань, ви не використовуєте Stack Exchange в повному обсязі. Ваша мета-проблема - це не використання сайту за призначенням. Питання щодо списку майже повсюдно не враховують на Stack Exchange, оскільки вони не дуже добре вписуються в модель запитань. Я настійно рекомендую переключити свій розум на запитання, які мають відповіді, а не предмети, ідеї чи думки .
Адам Лір

3
@kjo Це звичайно питання, але не правильне запитання на Stack Exchange . SE тут не для створення списків посилань. Це тут , щоб бути посилання. Будь ласка, прочитайте мета-пост, до якого я посилався у своєму коментарі, для більш детального пояснення.
Адам Лір

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

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) або коли ви хочете дозволити один або кілька екземплярів (як із ініціалізатором змінної в декларації ). Вам потрібно пам’ятати про такі проблеми, як потрібен роздільник між елементами (як ,у списку аргументів), необхідний термінатор після кожного елемента (як ;для окремих операторів) або не потрібний роздільник або термінатор (як це буває) з методами в класі).

Якщо ваша мова використовує арифметичні вирази, вам буде легко скопіювати з існуючих правил пріоритетності мови. Найкраще дотримуватися чогось відомого, як-от правила виразів С, ніж робити щось екзотичне, але лише за умови, що всі інші рівні.

Окрім питань пріоритетності (що розбирається один з одним) та питань повторення (скільки кожного елемента повинно виникати, як вони відокремлюються?), Вам також потрібно буде подумати про порядок: чи завжди щось повинно з’являтися перед іншою справою? Якщо одна річ включена, чи повинна бути виключена інша?

На даний момент у вас може виникнути спокуса граматично застосувати деякі правила, як правило, наприклад, якщо 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.