number_flag – використовується для виділення символьних констант. Якщо прапорець встановлений в одиницю, то використання символьних констант дозволяється (наприклад після оператора присвоєння), якщо прапорець в нулі, то в цьому місці символьні константи використовувати не можна (наприклад після лексем відкриття і закриття блоку).
bool_flag – використовується для надання атрибутів ідентифікаторам при їх описі. При знаходженні відповідної лексеми прапорець встановлюється в одиницю, а прапорець цілих змінних скидається в нуль. Поки прапорець встановлений, змінним присвоюється атрибут «b». Скидається при знаходженні лексеми оголошення цілих змінних чи початку блоку програми.
int_flag – використовується для надання атрибутів ідентифікаторам при їх описі. При знаходженні відповідної лексеми прапорець встановлюється в одиницю, а прапорець булевих змінних скидається в нуль. Поки прапорець встановлений, змінним присвоюється атрибут «і». Скидається при знаходженні лексеми оголошення булевих змінних чи початку блоку програми.
io_flag – використовується для виділення рядкових констант для виводу. Алгоритм аналогічний як для коментарів, але з занесенням лексеми до таблиці лексем.
blok_flag – використовується для підрахунку відкритих і закритих блоків.
Синтаксичний розбір (розпізнавання) є першим етапом синтаксичного аналізу. Саме при його виконанні здійснюється підтвердження того, що вхідний ланцюжок символів є програмою, а окремі підланцюжки складають синтаксично правильні програмні об'єкти. Вслід за розпізнаванням окремих підланцюжків здійснюється аналіз їх семантичної коректності на основі накопиченої інформації. Потім проводиться додавання нових об'єктів в об'єктну модель програми або в проміжне представлення.
Розбір призначений для доказу того, що аналізований вхідний ланцюжок, записаний на вхідній стрічці, належить або не належить безлічі ланцюжків породжуваних граматикою даної мови. Виконання синтаксичного розбору здійснюється розпізнавачами, тому даний процес також називається розпізнаванням вхідного ланцюжка. Мета доказу в тому, щоб відповісти на питання: чи належить аналізований ланцюжок безлічі правильних ланцюжків заданої мови. Відповідь «так» дається, якщо така приналежність встановлена. Інакше дається відповідь «ні». Отримання відповіді «ні» пов'язано з поняттям відмови. Єдина відмова на будь-якому рівні веде до загальної відмови.
Щоб одержати відповідь «так» щодо всього ланцюжка, треба його одержати для кожного правила, що забезпечує розбір окремого підланцюжка. Оскільки безліч правил утворюють ієрархічну структуру, можливо з рекурсіями, то процес отримання загальної позитивної відповіді можна інтерпретувати як збір за певним принципом відповідей для листків, що лежать в основі дерева розбору, що дає позитивну відповідь для вузла, що містить цей листок. Далі аналізуються оброблені вузли, і вже в них одержані відповіді складаються в загальну відповідь нового вузла. І так далі до самої вершини. Даний принцип обробки сильно нагадує бюрократичну систему, використовувану в організаційному управлінні будь-якого підприємства. Так підіймається вгору інформація, підтверджуюча виконання вказівки начальника організації. До цього, тими ж шляхами, вниз спускалася і розділялася початкова вказівка.
Основним завданням семантичного аналізатора є перевірка типів. Також семантичний аналізатор повинен знаходити вирази, що використовуються без присвоєння та видавати попередження.
Сама програма перевірки типів базується на інформації про синтаксичні конструкції мови, представлення типів і правилах присвоєння типів конструкціям мови.
Коренем дерева є не термінальний символ <Program>. Безпосередньо його листками є термінал Program та нетермінали <Blok>, <Declaration>, <Ident>. Листками нетерміналу <Blok> є термінали Start і Finish та нетермінали <Statement>, <Equation>, з листком <Cycle> існує двосторонній зв’язок. Нетермінал <Declaration> має термінальний листок Var та нетермінальний <Type> (листки Integer і Bool) і зв’язок з нетерміналом <Ident>. <Ident> має три листки – <Small_letter>, <Big_letter> і <Number>. <Statement> має два листки – <Input> (листок Input) і <Output> (листок Output) і зв’язок з нетерміналом <Equation>. <Equation> має зв’язок з нетерміналом <Ident>, нетермінальні листки <Compare>, <Const>, <Operation_a>, <Operation_m>, <Operation_l> і термінальний листок»:=». <Cycle> має два термінальні листки – For і DownTo і нетермінальний – <Const>. Від <Const> відходять два зв’язки, один до нетерміналу <Number>, другий до термінального символу» –». <Compare> має чотири термінальні листки – «==», "!=», «Le» і «Ge». <Operation_a> має листки «+»,» –»; <Operation_m> має листки «Mul», «Div» і «Mod». <Operation_l> має листки»!!», «And» і «||». Листками нетерміналу <Small_letter> є усі малі літери латинської абетки, <Big_letter> – великі літери латинської абетки. <Number> має десять термінальних листків – «0», «1», «2», «3», «4», «5», «6», «7», «8», «9».
Дерево граматичного розбору розроблено згідно правил, даних у формі розширеної нотації Бекуса-Наура, та оформлено згідно правил ЄСКД. Граф схема дерева граматичного розбору (1 аркуш) міститься в додатках.
Другий блок описує частину програми, де з таблиці лексем вибирається чергова лексема. Далі лексема проходить перевірку на її приналежність до одного з класів. В третьому блоці перевіряється чи належить лексема до класу лексем блоку. Якщо так, то вибираються лексеми, що стоять до і після заданої і ланцюжок перевіряється на коректність. Такий алгоритм роботи усіх ділянок програми, на які описані блоками 4, 6, 8, 10, 12, 14, 16, 18, 20, 22. У таблиці наведено усі перевірки, що ведуть до розгалуження алгоритму.
Таблиця 3.1. Блоки галуження
Номер блоку | Перевірка, що здійснюється |
3 | Лексема блоку? |
5 | Лексема вводу / виводу? |
7 | Лексема присвоєння? |
9 | Лексема математичного, логічного, порівняльного оператора? |
11 | Лексема ідентифікатор? |
13 | Лексема циклу? |
15 | Лексема оголошення? |
17 | Лекскма аргументу виводу? |
19 | Синтаксична лексема? |
21 | Лексема рядкова константа? |
23 | Остання лексема? |
Після перевірки чи лексема є останньою здійснюється перехід до натупної лексеми, якщо твердження хибне, і закінчується робота синтаксичного аналізатора, якщо твердження істинне.
Граф-схема алгоритму синатксичного аналізу (2 аркуші) розроблена згідно усіх правил ЄСКД та поміщена у додатках.
На вхід синтаксичного аналізатора подається таблиця лексем, створена на етапі лексичного аналізу. Потім по черзі перебираємо лексеми та аналізуємо класи лексем, що слідують за ними. Якщо після чергової лексеми слідує лексема, що має некоректний клас (наприклад після лексеми класу вводу / виводу слідує лексема класу математичних операторів), то формується повідомлення про помилку. Також здійснюється перевірка чи не йдуть підряд дві лексеми однакового класу (за винятком синтаксичних та операції множення на від’ємне число), якщо це справджується то виводиться повідомлення про помилку.
При знаходженні оператора присвоєння, математичних та логічних операторів чи операторів порівняння здійснюється перевірка атрибутів усіх ідентифікаторів, що входять у даний вираз. Якщо у виразі використовуються дані різних типів, то формується повідомлення про помилку.
На цьому етапі використовуються прапорці вводу / виводу для виділення лексем, що є аргументами виводу.
При кожному знаходженні помилки лічильник помилок збільшується на одиницю.
Останньою стадією розробки компілятора є генератор коду, який дістає на вхід проміжне представлення вихідної програми і виводить еквівалентну цільову програму.
Традиційно, до генератора коду висуваються жорсткі вимоги. Вихідний код повинен бути коректним і високоякісним, що означає ефективне використання ресурсів цільової машини. Крім того, ефективно повинен бути розроблений і сам генератор коду.
Математично, проблема генерації оптимального коду є нерозв’язною. На практиці ми вимушені користуватись евристичними технологіями, які дають хороший, але не обов’язково оптимальний код. Вибір евристики дуже важливий, оскільки детально розроблений алгоритм розробки генератора коду може давати код, що працю в декілька раз швидше, ніж код отриманий недостатньо продуманим алгоритмом.
Хоча дрібні деталі генератора колу залежать від цільової машини і операційної системи, такі питання, як керування пам’яттю, вибір інструкцій, розподіл регістрів і порядок обчислень, властиві усім задачам, зв’язаним з генерацією коду.
Вхідний потік генератора коду являє собою проміжне представлення вихідної програми, отримане на початковій стадії компіляції, разом із таблицею символів, яка використовується для обчислення адрес часу виконання об’єктів даних, зазначених в проміжному представленні іменами.
Результатом генератора коду являється цільова програма. Подібно до проміжного коду, результат генератора коду може приймати різні види: абсолютний машинний код, переміщуваний машинний код, або асемблерна мова. Перевагою генерації абсолютної програми в машинному коді є те, що такий код поміщається у фіксоване місце пам’яті і негайно виконується. Невеликі програми при цьому швидко компілюються і виконуються.