
8.3. Рекурсія в синтаксичних правилах
8.3.1. Синтаксичні правила
Ще один приклад рекурсивних означень — це синтаксичні правила, які описують структуру виразів у різних мовах.
Чітко визначити поняття мови, мабуть, неможливо. Неформально, мова — це система позначень для передачі певного змісту. Мова має алфавіт — це скінченна множина символів — і правила утворення лексем, які є найпростішими позначеннями певного змісту. Складніші записи мови утворюються з простіших і мають певний синтаксис, тобто структуру. Записам мови відповідає позначений ними зміст — їх семантика.
У кожній мові є певна система понять, наприклад, у мовах програмування: “інструкція”, “інструкція присвоювання”, “вираз”, “ім’я” тощо. Представники понять, тобто конкретні інструкції, вирази або імена, — це деякі записи в алфавіті мови, що мають певну структуру і утворюють множину записів (ланцюжків або слів) — формальну мову. Структуру ланцюжків описують за допомогою синтаксичних правил. Наприклад, кожен вираз присвоювання складається з імені, знака “=” та виразу.1 Ця фраза описує структуру представників поняття “вираз присвоювання”, тобто, по суті, є синтаксичним правилом.
Існує декілька форм запису синтаксичних правил. Найбільш поширені контекстно-вільні граматики Хомського (КВ-граматики) та їх різновиди — системи форм Бекуса-Наура (БНФ) та синтаксичні діаграми. Кожна з них дозволяє описати практично весь синтаксис мов програмування. Розглянемо синтаксичні правила у вигляді БНФ.
Надамо синтаксичним правилам чіткішу форму. Поняття мови позначимо словом у кутових дужках < >, наприклад, <вираз> або <ім’я>. Слова (лексеми) самої мови запишемо в 'апострофах' або виділимо напівжирним шрифтом, наприклад '=' або while. Порожню послідовність символів позначимо кутовими дужками < >.
Позначення поняття мови називається нетермінальним символом, або нетерміналом.
Термінальними символами, або терміналами, називаються символи або послідовності символів алфавіту мови.
Термінали та нетермінали розглядаються як неподільні записи. “Термінальний” означає “остаточний” (термінали — “остаточні” символи мови). “Нетермінальний” (“неостаточний”) символ позначає поняття мови й не є символом самої мови.
Послідовність (можливо порожня), складена з терміналів, нетерміналів та певних спеціальних символів (метасимволів), називається метавиразом.
Приклад. Структуру виразів присвоювання опишемо таким метавиразом.
<ім’я> '=' <вираз>
Метасимволи розглянемо нижче. Термінали та нетермінали в метавиразах для наочності інколи відокремлюють пропусками. Ці пропуски ніяк не пов’язані з пропусками, які додаються між лексемами в конкретних мовах, як, наприклад, між суміжніми константами або іменами в мові С++. Правила запису пропусків між лексемами визначаються в конкретних мовах по-своєму, в синтаксичних правилах не відтворюються й тут не розглядаються.
Приклад. Перепишемо фразу «вираз присвоювання складається з імені, знака = й виразу» так.
<вираз присвоювання> має структуру <ім’я> '=' <вираз>
Замість слів має структуру поставимо знак “::=” і одержимо таку формулу.
<вираз присвоювання> ::= <ім’я> '=' <вираз>
Взагалі, довільна формула вигляду
<поняття> ::= < метавираз>
означає: представники поняття, вказаного ліворуч, мають структуру, описану метавиразом праворуч.
Синтаксичне правило вигляду <поняття>::=<метавираз> називається формою Бекуса–Наура (за прізвищами авторів) або, скорочено, БНФ. Нетермінал ліворуч від метасимволу “::=” називається лівою частиною БНФ, або головою, метавираз праворуч — правою частиною, або тілом.
Отже, БНФ <вираз присвоювання> ::= <ім’я> '=' <вираз> задає загальну структуру кожного з конкретних виразів присвоювання. Структуру представників понять <вираз> та <ім’я> уточнимо за допомогою інших БНФ, які разом з наведеною БНФ утворять систему БНФ.
Приклад. Вирази спрощеного вигляду утворюються з імен, дужок та знаків операцій + і *. Структура виразів описується рекурсивно: виразом є ім’я, вираз у дужках або два вирази зі знаком операції між ними. Запишемо ці альтернативні варіанти структури виразів разом, відокремивши метасимволом “|”.
<вираз> ::= <ім’я> | '(' <вираз> ')' | <вираз> '+' <вираз> | <вираз> '*' <вираз>
Пригадаймо: “ім’я — це послідовність букв і цифр, яка починається з букви”. У цій фразі з’явилися два нових поняття — <буква> та <послідовність букв і цифр>. Позначимо їх нетерміналами <Б> та <ПБЦ> відповідно й перепишемо фразу у вигляді такої БНФ.
<ім’я> ::= <Б><ПБЦ>
<ПБЦ> означається рекурсивно — це або порожня послідовність, або буква чи цифра, за якою записано послідовність букв та цифр. Припустимо, що буквами є лише A, B, C, а цифрами — 0 і 1. Позначимо цифри нетерміналом <Ц>. Отже, означимо <ПБЦ> такими БНФ.
<ПБЦ> ::= <> | <Б><ПБЦ> | <Ц><ПБЦ> <Б> ::= 'A' | 'B' | 'C' <Ц> ::= '0' | '1'
Ще один приклад демонструє рекурсивність основних структурних елементів програми — інструкцій.
Приклад. Інструкціями мови С++ є інструкція присвоювання, розгалуження та циклу (для спрощення інших не розглядаємо).
<інструкція> ::= <Інс-=> | <Інс-if> | <Інс-cycle>
Розкриємо загальну структуру цих інструкцій, не уточнюючи понять ім’я та вираз.
<Інс-=> ::= <ім’я> = <вираз> ';' <Інс-if> ::= if '(' <вираз> ')' <інструкція>
[ else <інструкція> ] <Інс-cycle> ::= <Інс-while> | <Інс-do> | <Інс-for> <Інс-while> ::= while '(' <вираз> ')' <інструкція> <Інс-do> ::= do <інструкція> while '(' <вираз> ')' ';' <Інс-for> ::= for '(' <вираз> ';' <вираз> ';' <вираз> ')'
<інструкція>
Отже, поняття “інструкція” означається за допомогою його окремих різновидів. Водночас, у структурі інструкцій усіх типів, окрім присвоювання, присутнє поняття “інструкція”. Отже, всі ці поняття, окрім “інструкція присвоювання”, є рекурсивними. Проте й присвоювання містять вирази, також рекурсивні.