- •Глава 4. Синтаксичний аналіз
- •4.1. Граматики та роль синтаксичного аналізатора
- •4.2. Контекстно-вільні граматики
- •Угоди по позначеннях
- •4.3. Дерева розбору й приведення
- •4.4. Регулярні вирази й контекстно-вільні граматики
- •4.5. Перетворення граматик для спрощення синтаксичного аналізу
- •4.5.1 Усунення неоднозначності
- •4.5.2 Усунення лівої рекурсії
- •4.5.3 Ліва факторизація
4.3. Дерева розбору й приведення
Дерево розбору може розглядатися як графічне подання породження, з якого вилучена інформація про порядок заміщення. Згадаємо, що кожен внутрішній вузол дерева розбору позначається деяким нетерміналом А, а дочірні вузли зліва направо — символами із правої частини продукції, використаної в породженні для заміни А. Листи дерева розбору позначені нетерміналами або терміналами й, будучи прочитані зліва направо, утворять сентенціальну форму, називану кроною, або границею дерева. Наприклад, дерево розбору для -(id+id), отримане породженням (4.4), показане на рис. 4.8.
id id
Рис. 4.8. Дерево розбору для -(id+id)
Для того щоб побачити взаємозв'язок між породженням і деревами розбору, розглянемо будь-яке приведення α1 → α 2 → ... → α n, де α i — окремий нетермінал А. Для кожної сентенціальною форми αi у породженні будуємо дерево розбору, результатом якого є α i. Цей процес являє собою індукцію по і. Базисом служить дерево для α i = А, що являє собою єдиний вузол, позначений як А. Для виконання індукції припустимо, що ми вже побудували дерево розбору, що має крону αi-1 = Х1Х2 ... Хk (згадаємо, що Хi, може означати термінал або нетермінал). Припустимо, що αi-1 породжує αi заміною нетермінала Хj на β = Y1Y2…Yr. Таким чином, на і-м кроці породження до αi-1 застосовується продукція Xj ::= β , породжуючи αi = Х1Х2 ... Хj-1β Хj+1… Хk .
Для моделювання цього кроку знаходимо j-е лівt листя у поточному дереві розбору, що помічено Хj. Ми даємо цьому листю r дочірніх вузлів, позначених Уь Y2, ...,Yr зліва направо. У спеціальному випадку r = 0, тобто β = ε, у j-го листя з'являється один дочірній вузол, позначений як ε.
Приклад 4.6. Розглянемо породження (4.4).
E → -E → -(E) → -( E + E) → -(id + E) → -(id + id)
Послідовність дерев розбору, побудована на його основі, показана на рис. 4.3. Перший крок цього породження – E → -E. Для моделювання цього кроку ми додаємо до кореня початкового дерева два дочірніх вузли, позначених як "-" й "E".
Другий крок являє собою -E → -(E). Відповідно, ми додаємо три дочерніх вузла — "(", "Е" й ")" — до листя E у другому дереві для одержання третього дерева, що дає -(E). Продовжуючи побудови, ми одержимо шосте дерево як повне дерево розбору.
Як згадувалося, дерево розбору ігнорує порядок, у якому здійснювалося заміщення символів у сентенціальній формі. Наприклад, якщо породження (4.4) змінити відповідно на (4.5), остаточне дерево розбору буде таким же, як на рис. 4.9. Варіації порядку використання продукцій можна уникнути, розглядаючи тільки ліві (або праві) породження. Неважко побачити, що кожному дереву розбору відповідають єдине ліве і єдине праве породження - однак варто усвідомлювати, що речення може мати не одне дерево розбору й навіть не одне ліве або праве породження.
Рис. 4.9. Побудова дерева розбору з породженням (4.4)
Приклад 4.7. Звернемося знову до граматики арифметичних виразів (4.3). Речення id+id*id має два різних лівих породження
-
E
→ E+E
E
→ E*E
→ id+E
→ E+E*E
→ id+ E*E
→ id+ E*E
→ id+ id*E
→ id+ id*E
→ id+ id*id
→ id+ id*id
с двома відповідними їм деревами розбору, показаними на рис. 4.10.
При цьому, дерево розбору на рис. 4.10а відбиває звичайні пріоритети операцій ‘+’ і ‘*’ на відміну від дерева на рис. 4.10б. Звичайно пріоритет множення вище, і вираз типу а+b*c трактується як а+(b*с), а не як (а+b)*с.
E E
id id id id
а) б)
Рис. 4.10. Два дерева розбору для id + id * id
Граматика, що дає більше одного дерева розбору для деякого речення, називається неоднозначної (ambiguous). Інакше кажучи, неоднозначна граматика— це та, котра для одного й того ж самого речення дає не менш двох лівих або правих породжень. Для більшості типів синтаксичних аналізаторів граматика повинна бути однозначної, оскільки, якщо це не так, ми не в змозі визначити дерево розбору для речення єдиним образом. Для деяких додатків існують методи, що допускають використання ряду неоднозначних граматик разом із правилами усунення неоднозначностей (disambiguating rules), які "відкидають" небажані дерева розбору, залишаючи для кожного речення по одному дереву.
Граматикою можна описати більшу частину синтаксису (але не весь) мов програмування. Деяка частина синтаксичного аналізу виконується лексичним аналізатором у процесі одержання послідовності токенів із вхідного потоку символів. Деякі обмеження, що накладають на вхідний потік (наприклад, вимога оголошення ідентифікаторів до їхнього використання), не можуть бути описані в рамках контекстно-вільної граматики. Отже, послідовності токенів, що допускаються синтаксичним аналізатором, утворюють надмножину мови програмування; наступні фази компіляції повинні аналізувати вихід синтаксичного аналізатора на його відповідність правилам, які не перевіряються цим аналізатором (наприклад, перевірка типів).
