- •2 Лексичний аналіз
- •2.1. Роль лексичних аналізаторів
- •2.1.1 Завдання лексичного аналізу
- •2.1.2 Токени, шаблони, лексеми
- •2.1.3 Атрибути токенів
- •2.2. Алфавит, рядки і мови
- •2.2.1 Алфавит та рядки над алфавитом
- •2.2.2 Рядки й мови
- •2.2.3 Операції над мовами
- •2.3 Регулярні вирази
- •2.2.4 Регулярні визначення
- •2.2.5 Скорочення
- •2.2.6 Нерегулярні множини
- •2.4. Розпізнавання токенів
- •Id → letter (letter | digit)*
- •2.3.1 Діаграми переходів
- •2.3.2 Реалізація діаграми переходів
2.3.1 Діаграми переходів
Як проміжний крок при створенні лексичного аналізатора спочатку побудуємо стилізовані блок-схеми, називані діаграмами переходів (transition diagram). Діаграма переходів зображує дії, виконувані лексичним аналізатором при виклику його синтаксичним аналізатором для одержання чергового токена, як показано на рис. 2.1, Припустимо, що вхідний буфер і покажчик початку лексеми вказує на символ, що випливає за останньою знайденою лексемою. У цьому випадку ми використаємо діаграму переходів для відстеження інформації про символи, які були скановані при переміщенні покажчика forward. Щоб зробити це, ми переміщаємося при читанні символів від позиції до позиції по діаграмі переходів.
Позиції в діаграмі переходів зображуються кружками й називаються станами (states). Стани з'єднані стрілками, називаними дугами (edges). Дуги, що вихідять із стану s, мають мітки, що вказують вхідні символи, які можуть з'явитися у вхідному потоці по досягненні стану s. Мітка other означає появу будь-якого символу, не зазначеного іншими вихідними дугами.
У цьому розділі будемо вважати діаграми переходів детермінованими, тобто жоден символ не може бути міткою двох вихідних з одного стану дуг (далі цю умову буде послаблено).
Один зі станів має мітку start— це початковий стан діаграми переходів у момент початку розпізнавання токена. Деякі стани мають дії, виконувані при досягненні цих станів. При переході в деякий стан ми зчитуємо наступний вхідний символ й, якщо є вихідна дуга з міткою, що відповідає цьому символу, переміщаємося по ній у наступний стан. Якщо такої дуги немає, то вхідний символ некоректний, тобто відбулася помилка.
На рис. 2.11 показана діаграма переходів для шаблонів “>=” й “>”.
Рис. 2.11. Діаграма переходів для оператора порівняння ‘>=’ та ‘>’
Діаграма працює в такий спосіб. Робота починається в стані 0, у якому зчитується наступний символ із вхідного потоку. Дуга, позначена >, веде в стан 6, якщо цей символ — ">". У противному випадку ми не можемо розпізнати > або >=. По досягненні стану 6 зчитуємо наступний вхідний символ. Дуга, позначена як =, приводить зі стану 6 у стан 7, якщо цей символ — "=". У противному випадку по дузі other попадаємо в стан 8. Подвійний кружок, що зображує стан 7, показує, що це - припустимий або заключний стан, у якому знайдений токен “>=”.
Помітимо, що символ > з іншим додатковим символом приводить в інший стан, що допускає, 8. Оскільки цей інший символ не є частиною токена, він повинен бути повернутий у вхідний потік, що й зазначено зірочкою біля цього стану.
Загалом кажучи, може існувати ряд діаграм переходів, кожна з яких визначає групу токенів. Якщо при переміщенні по діаграмі переходів відбувається збій, ми повертаємо покажчик forward до того місця, де він був у стартовому стані даної діаграми, і переходимо до наступної діаграми. Оскільки покажчик на початок лексеми й покажчик forward у стартовому стані вказують на те саме місце, таке повернення здійснюється дуже просто — поверненням у позицію, що визначається покажчиком lexeme_beginning. Якщо збій відбувається у всіх діаграмах, викликається програма відновлення після помилки.
Приклад 2.7
Діаграма переходів для токена relop показана на рис. 2.12. Звернимо увагу на те, що рис. 2.11 являє собою частину цієї більш складної діаграми переходів.
Рис. 2.12. Діаграма переходів для операторів відношення (порівняння)
Приклад 2.8
Оскільки ключові слова є не що інше, як послідовності букв, вони становлять собою виняток із правила визначення ідентифікатора як послідовністі букв і цифр, що починається з букви, є ідентифікатором. Але замість кодування виключень у діаграмі переходів можна розглядати ключові слова як спеціальні ідентифікатори. Тоді по досягненні заключного стану на рис. 2.13 виконується деякий код, що визначає, чим є лексема, що привела у цей стан, - ключовим словом або ідентифікатором.
Рис. 2. 13. Діаграма переходів для ідентифікаторів і ключових слів
Найпростіша технологія відділення ключових слів від ідентифікаторів складається у відповідній ініціалізації таблиці символів, у якій зберігається інформація про ідентифікатори. Для токенів на рис. 2.10 треба внести в таблицю символів рядка if, then й else до початку роботи із вхідним потоком. При розпізнаванні такого рядка повертається токен ключового слова. Інструкція повернення, пов'язана з припустимим станом (рис. 2.13), використає функції gettoken() і install_id() для одержання токена й атрибута-значення відповідно. Процедура install_id() має доступ до буфера, де перебуває лексема ідентифікатора. Програма перевіряє таблицю символів й, якщо лексема знайдена в ній і позначена як ключове слово, installed() повертає 0. Якщо лексема знайдена і є змінної програми, install_id()повертає покажчик на запис у таблиці символів. Якщо лексема в таблиці символів відсутня, вона вважається змінною, і функція install_id() повертає покажчик на знову створений запис у таблиці символів для цієї лексеми.
Процедура gettoken() точно так само переглядає таблицю символів у пошуках лексеми. Якщо лексема являє собою ключове слово, повертається відповідний токен; у противному випадку повертається токен id.
Помітимо, що діаграма переходів не змінюється при необхідності розпізнавання додаткового ключового слова — ми просто додаємо нове ключове слово в таблицю символів при ініціалізації.
Технологія розміщення ключових слів у таблиці символів відіграє важливу роль при створенні лексичного аналізатора вручну. Без цього в лексичному аналізаторі для типової мови програмування кількість станів дорівнює декільком сотням, у той час як при використанні описаного способу звичайно вистачає менш сотні станів.
Рис. 2.14.Діаграми переходів для беззнакових чисел в Pascal
Приклад 2.9
Число проблем зростає при побудові розпізнавальника беззнакових чисел, що задають регулярним виразом
num → digit* (. digit+)? (E(+ | -)? digit+)?
Звернемо увагу на те, що визначення має вигляд digits fraction? exponent?, де fraction й exponent— необов'язкові частини.
Лексема для даного токена повинна бути максимально можливої довжини (правило пошуку лексеми найбільшої довжини — загальноприйняте правило роботи лексичного аналізатора, що дозволяє уникнути багатьох неприємностей). Наприклад, лексичний аналізатор не повинен зупинятися після того, як зустріне 12 або навіть 12.3, якщо уведено число 12.3Е4. Починаючи зі станів 25, 20 й 12 на рис. 2.14, що допускають стани при одержанні у вхідному потоці рядка 12.3Е4, за яким слідує нецифровий символ, досягаються після зчитування 12, 12.3 й 12.3Е4 відповідно. Діаграми переходів зі стартовими станами 25, 20 й 12 описують відповідно digits, digits fraction й digits fraction? exponent, так що випробовувати їх треба у зворотному порядку — 12, 20, 25.
По досягненні кожного з припустимих станів 19, 24 й 27 відбувається виклик процедури install_num(), що вносить лексему в таблицю чисел і повертає покажчик на створений запис. Лексичний аналізатор повертає токен num із цим покажчиком як лексичне значення.
Інформація про мову, що втримується поза регулярними визначеннями токенів, може використатися для точної вказівки помилок у вхідному тексті. Наприклад, при уведенні 1,<х збій відбудеться в станах 14 й 22 на рис. 2.14 при одержанні чергового вхідного символу "<". Замість того, щоб повернути число 1, можна повідомити про помилку й продовжити роботу так, ніби був уведений рядок 1.0<х. Таким чином, знання особливостей мови забезпечує можливість відновлення при деяких помилках, які інакше привели б до аварійного завершення трансляції.
Є ряд способів уникнути зайвих перевірок у діаграмах переходів на рис. 2.14. Один з підходів полягає в тому, щоб переписати діаграми переходів, об'єднавши їх в одну (що взагалі ж є нетривіальним завданням). Інший шлях - змінити реакцію на помилки в процесі роботи з діаграмою. Підхід, розглянутий пізніше в цій главі, дозволяє пройти по ряду станів, що допускають, повертаючись назад до останнього пройденого припустимого стану перед збоєм.
Приклад 2.10
Послідовність діаграм переходів для всіх токенів із приклада 2.6 здійснюється при об'єднанні діаграм переходів, зображених на рис. 2.12-2.14. Стартові стани з меншими номерами випробовуються перед станами з більшими номерами.
У цьому випадку залишається тільки проблема, пов'язана із проміжками. Обробка регулярного визначення ws, що представляє проміжки, відрізняється від обробки шаблонів тим, що синтаксичному аналізатору не повертається який-небудь токен. Діаграма переходів, що розпізнає проміжки, виглядає в такий спосіб.
По можливості варто починати перегляд із токенів, що часто зустрічаються, оскільки робота з деякою діаграмою переходів починається тільки після завершення роботи з її попередниками. У зв’язу з тим, що проміжки зустрічаються досить часто, розміщення діаграми переходів для проміжків перед іншими повинне підвищити загальну ефективність.
