1.3.3. Фази аналізу
У процесі трансляції змінюється внутрішнє подання вхідної програми.Для ілюстрації розглянемо інструкцію (оператор)
position: = initial + rate * 60 (1.1)
На рис. 1.8 показано внутрішнє подання інструкції після кожної фази.
При лексичному аналізі символи вхідної програми зчитуються і групуються в потік токенів, в якому кожен токен представляє логічно пов'язану послідовність символів – ідентифікатор, ключове слово (if, whi1е, тощо), символ пунктуації або многосімвольний оператор типу : = . Послідовність символів, що формує токен, називається лексемою токена. Деякі токени доповнюються "лексичним значенням". Наприклад, коли знайдено ідентифікатор rаtе, лексичний аналізатор не тільки генерує токен, скажімо id1, але й вносить лексему rаtе в таблицю символів, якщо її там немає.
Лексичне значення, зв’язане з цією появою токена id, вказує на запис rаtе в таблиці символів.
У цьому розділі ми будемо використовувати позначення id1, id2 і id3 для position, initial і rate, щоб підкреслити відмінність внутрішнього подання ідентифікатора від послідовності символів, що формують його. Інструкція (1.1) після лексичного аналізу виглядає так:
id1 := id2 + id3 * 60 (1.2)
Потрібно також створити токени для многосімвольного оператора : = і числа 60, але про це – пізніше. Детальніше лексичний аналіз розглядається в розділі 2 "Лексичний аналіз".
Друга і третя фази, синтаксичний і семантичний аналіз, також були рарозглянуті в розділі 1.2. При синтаксичному аналізі на потік токенів накладається ієрархічна структура, яку можна зобразити за допомогою синтаксичного дерева, наведеного на рис. 1.9а. Типова структура даних для такого дерева показана на на рис. 1.9б. Її внутрішній вузол являє собою запис з полем для оператора і двома полями з покажчиками на дочірні записи. Листя є запис з двома або більше полями (для ідентифікації токена в листі і запису інформації про токен). Додаткова інформація про мовні конструкції може міститися в додаткових полях записів для вузлів.
Рис. 1.9. Структура даних (а) для дерева (б), що на рис. 1.3
Синтаксичний і семантичний аналіз будуть детальніше розглядатися у відповідних розділах.
1.3.4. Генерація проміжного коду
Після синтаксичного і семантичного аналізу деякі компілятор генерують явне проміжне представлення вихідної програми, яку можна розглядадти як програму для абстрактної машини. Таке представлення має легко створюватися і транслюватися в цільову програму.
Проміжне подання може мати ряд форм. Розглянемо проміжну форму, яка називається "трехадресним кодом". Цей код схожий на мову асемблера для машини, в якій кажен осередок пам'яті може працювати в якості регістру. Трехадресний код складається з послідовності інструкцій, кожна з яких має не більше трьох операндів. Вхідна програма (1.1) в трьохадресном коді може виглядати так:
temp1 := inttoreal(60)
temp2 := id3 * temp1 (1.3)
temp3 := id2 + temp2
id1 := temp3
Дана проміжна форма має ряд специфічних властивостей. По-перше, кожна трехадресная інструкції, крім присвоєння, має не більше одного оператора. Тому при генерації цих інструкцій компілятор повинен визначити порядок виконання операцій; так, у вхідній програмі (1.1) множення передує додаванню. По-друге, компілятор повинен згенерувати тимчасове ім'я для зберігання результатів, обчислюваних кожною інструкцією. По-третє, деякі трехадресні інструкції мають менше трьох операндів (наприклад, перша і остання інструкції в (1.3)).
У главі 7 "Генерація проміжного коду" будуть розглянуті основні положення проміжного представлення, використовувані в компіляторах. Взагалі кажучи, ці представлення повинні не тільки обчислювати вирази, але й керувати потоком інструкцій і викликів процедур. У розділах "Синтаксично керована трансляція" і "Генерація проміжного коду" будуть представлені алгоритми генерації проміжного коду для типових конструкцій мов програмування.
1.3.5. Оптимізація коду
При оптимізації коду здійснюються спроби поліпшити проміжний код, щоб отримати більш ефективний машинний код. Деякі оптимізації тривіальні. Наприклад, природний алгоритм генерує проміжний код (1.3), використовуючи інструкції для кожного оператора в дереві, створеному в результаті семантичного аналізу, хоча ті ж обчислення можна виконати за допомогою двох інструкцій:
temp1 := id3* 60.0
id1 := id2 + temp1 (1.4)
Перетворення, що оптимізує, може бути виконане в процесі генерації коду. По-перше, компілятор може прийняти рішення про перетворення цілого числа 60 в дійсне ще під час компіляції, усуваючи необхідність в операції inttoreal. По-друге, змінна temp3 використовується лише для передачі свого значення в id1, тому цілком коректно використовувати id1 замість temp3 і видалити останню інструкцію з (1.3), отримуючи код (1.4).
Ступінь оптимізації коду у різних компіляторів значно відрізняється. У деяких компіляторах, званих "оптимізуючими", більша частина часу роботи йде саме на оптимізацію. Однак існують прості прийоми оптимізації, які істотно зменшують час роботи цільової програми, не сильно сповільнюючи роботу компілятора.
