Міністерство освіти і науки, молоді та спорту України
Національний університет “Львівська політехніка”
Кафедра СКС
Курсовий проект
з предмету ” Системне програмування ”
на тему: "Розробка та реалізація компонент системного програмного забезпечення "
Львів 2012
Анотація
В даному курсовому проекті розроблено компілятор з СІ-подібної вхідної мови програмування . Оболонка компілятора розроблена в середовищі програмування Visual C++ і в опис проекту не входить. Сам компілятор написанний на мові С++, та поданий у пояснювальній записці, а також разом з оболонкою в електронному варіанті. В пояснювальній записці подано детальний опис мови, огляд існуючих методів розробки компіляторів, а також описано процес розробки програми компілятора на рівні блок-схем і тексту програми. До проекту додано результати тестування програми.
Зміст
Ст.
1. Завдання …………………………………………………………………………………….4
2. Вступ………………………………………………………………………………………....5
3. Способи побудови компіляторів…………………………………………………………...6
3.1. Загальна схема компілятора………………………………………………………6
3.2. Лексичний аналізатор……………………………………………………………..7
3.3. Синтаксичний аналізатор……………………………………………………...….7
4. Формальний опис вхідної мови……………………………………………………..……..8
5. Таблиця лексем………………………………………………………………........….....…..9
6. Опис тексту програми………………………………………………………………...……10
7. Результати тестування…………………………………………………………………......12
8. Висновки………………………………………………………………………………..…..15
9. Список використаної літератури……………………………………………………..…...16
Додаток1……………………………………………………………………………………..17
Додаток2……………………………………………………………………………………..37
Додаток3……………………………………………………………………………………..38
1. Завдання
Розробити компілятор з вхідної мови програмування короткий опис якої подано нижче (у відповідності з заданим варіантом) з виводом необхідної проміжної інформації на кожному кроці. Розробити інтерфейс користувача (інтегроване середовище програмування вхідною мовою).
Варіант № 14
Мова високого рівня є CІ-подібною та повинна підтримувати:
початок блоку {
кінець блоку }
оператор вводу SCAN
оператор виводу PRINT
умовний оператор IF THEN ELSE
оператор циклу WHILE(…)
процедури і функції PROCEDURE
- оператор присвоєння ::
арифметичні операції + , - , / , * , POW, MOD
операції порівняння = ,<> , < , > , <= , >=
логічні операції NOT, OR, AND
ключові слова великими буквами
ідентифікатори великими і малими буквами до 6-ти символів
коментар // …
робота з типами даних – INTEGER, CHAR, REAL, STRING
Вимоги до синтаксису мови, такі ж як у мові програмування С.
Робота програми-компілятора повинна складатись з двох етапів – лексичного та синтаксичного аналізу.
2. Вступ
Компілятори становлять істотну частину програмного забезпечення ЕОМ. Це пов’язано з тим, що мови високого рівня стали основним засобом розробки програм. Тільки дуже незначна частина програмного забезпечення, що вимагає особливої ефективності, програмується за допомогою ассемблерів, що на сьогоднішній день на стільки складно для людини (у сучасних процесорів дуже велика система команд та багато аспектів з їх застосування), що й таке програмування застосовується дуже рідко та із великою обережністю написання коду.
На сьогодні існує досить багато мов програмування. Нарівні з традиційними мовами, такими, як Фортран, широке поширення отримали так звані «універсальні мови» (Паскаль, С, Модула-2, Ада та інші), а також деякі спеціалізовані (наприклад, мова обробки облікових структур Лісп). Крім того, велике поширення отримали мови, пов’язані з вузькими предметними областями, такі, як вхідні мови пакетів прикладних програм.
Для деяких мов є досить багато реалізацій. Наприклад, реалізацій Паскаля, Модули-2 або С для ЕОМ типу IBM/PC на ринку десятки. З іншого боку, постійно зростаюча потреба в нових компіляторах пов’язана з бурхливим розвитком архітектури ЕОМ. Цей розвиток йде у різних напрямах. Удосконалюється стара архітектура, як в концептуальному відношенні, так і по окремих, конкретних лініях. Це можна проілюструвати на прикладі мікропроцесора Intel 80х86. Послідовні версії цього мікропроцесора 8086, 80186, 80286, 80386, 80486, 80586 відрізняються не тільки технічними характеристиками, але і, що більш важливо, новими можливостями і, значить, зміною (розширенням) системи команд. Отже, це вимагає нових компіляторів (або модифікації старих).
Також бурхливо розвивається різна паралельна архітектура – векторні системи, багатопроцесорні системи, а також системи з широким командним словом (VLIW), варіантом яких є суперскалярні ЕОМ. На ринку вже є десятки типів ЕОМ з паралельною архітектурою, починаючи від супер-ЕОМ (Cray, CDC та інші), через робочі станції (наприклад, IBM/RS-6000) і закінчуючи персональними (наприклад, на основі мікропроцесора і860). Отже, для кожної з машин створюються нові компілятори для багатьох мов програмування. Тут необхідно також відмітити, що нова архітектура вимагає розробки абсолютно нових підходів до створення компіляторів, так що поряд з розробкою компіляторів ведеться і велика наукова робота по створенню нових методів трансляції.
3. Способи побудови компіляторів
3.1. Загальна схема компілятора
Процедурно-орієнтовані мови програмування, такі як FORTRAN, Pascal, C, C++ та ін. засновані на принципі послідовного виконання операторів, команд або директив. Їх базові оператори, операції, команди та директиви можна класифікувати по трьох основних групах:
Безумовні оператори (statements) обрахунків та перетворень;
Оператори обробки розгалужень та передачі управління;
Оператори організації циклічної обробки.
В загальному випадку віртуальна машина складається з інформаційного, операційного, управляючого та комунікаційного компонентів.
Будь-яка віртуальна машина повинна мати:
Пам’ять різних видів та типів для збереження кодів програм і даних (інформаційний компонент) і механізми доступу до неї;
Вказівник поточної операції (основа управляючої компоненти), що змінюється при підрахунку номера оператора;
Блок виконання операцій (operators), який реалізує функції операційних та управляючих компонентів. Разом з інтерфейсним обладнанням він реалізує комунікаційний компонент, який забезпечує зв’язок віртуальної машини із зовнішнім світом.
Можна виділити сім різних логічних задач:
Інтерпретація – визначення точного змістового навантаження, створення матриці та таблиць з допомогою програм інтерпретації;
Машинно-незалежна оптимізація – створення оптимальнішої матриці;
Лексичний аналіз – розпізнавання базових елементів та створення стандартних символів;
Синтаксичний аналіз – розпізнавання базових синтаксичних конструкцій з використанням редукцій;
Розподіл пам’яті – модифікація таблиць ідентифікаторів та літералів. В матриці розміщується інформація, з допомогою якої генерується код, який розподіляє динамічну пам’ять;
Генерація коду – використання макропроцесора для отримання більш оптимального вихідного коду;
Компонування кінцевого вихідного коду – розв’язання проблеми символічних адрес та генерування програми на машинній мові.
Фази з першої по четверту машинно-незалежні і визначаються тільки мовою. Фази з п’ятої по сьому – машинно-залежні і не залежать від мови. З метою забезпечення вищої ефективності ці фази можуть не розділятись так чітко.
Ми повинні оцінювати компілятор не тільки по об’єктному коду, що генерується, але також і по об’єму оперативної пам’яті, що він займає і по часу, що потрібен для трансляції. На жаль, ці критерії оптимальності часто досить суперечливі. Крім того, оптимальність коду обернено пропорційна складності, розміру та часу роботи самого компілятора. Насправді необхідні компроміси.
Компілятором використовуються бази даних, що забезпечують зв’язки між фазами: вхідний код, таблиця стандартних символів, таблиця термінальних символів, таблиця ідентифікаторів, таблиця літералів, редукції, матриця, кодові продукції, код компоновки, кінцевий об’єктний код.
Лексичний аналiз
Основна задача лексичного аналізу – розбити вихідний текст, що складається з послідовності одиночних символів, на послідовність слів, або лексем, тобто виділити ці слова з безперервної послідовності символів. Всі символи вхідної послідовності з цієї точки зору розділяються на символи, що належать яким-небудь лексемам, і символи, що розділяють лексеми. В цьому випадку використовуються звичайнi засоби обробки рядків. Вхiдна програма проглядається послiдовно з початку до кінця. Базовi елементи, або лексичнi одиницi, роздiляються пробiлами, знаками операцiй i спецiальними символами (новий рядок, знак табуляції), i таким чином видiляються та розпізнаються iдентифiкатори, лiтерали i термiнальнi символи (операцiї, ключові слова).
При виділенні лексеми вона розпізнається та записується у таблицю лексем за допомогою відповідного номера лексеми, що є унікальним для кожної лексеми із усього можливого їх набору. Це дає можливість наступним фазам компiляції звертатись лексеми не як до послідовності символів, а як до унікального номера лексеми, що значно спрощує роботу синтаксичного аналізатора: легко перевіряти належність лексеми до відповідної синтаксичної конструкції та є можливість легкого перегляду програми, як вгору, так і вниз, від текучої позиції аналізу. Також в таблиці лексем ведуться записи, щодо рядка відповідної лексеми – для місця помилки – та додаткова інформація.
При лексичному аналiзі виявляються i вiдзначаються лексичнi помилки (наприклад, недопустимi символи i неправильнi iдентифiкатори). Лексична фаза вiдкидає також i коментарi, оскiльки вони не мають нiякого впливу на виконання програми, отже ж й на синтаксичний розбір та генерацію коду.
Лексичний аналізатор (сканер) не обов’язково обробляє всю програму до початку всіх інших фаз. Якщо лексичний аналіз не вділяється як окрема фаза компіляції, а є частиною синтаксичного аналізу, то лексична обробка тексту програми виконується по мірі необхідності по запиту синтаксичного аналізатора.
Синтаксичний аналiз
Синтаксичний аналіз – це процес, в якому досліджується послідовність лексем, яку повернув лексичний аналізатор, і визначається, чи відповідає вона структурним умовам, що сформульовані у визначені синтаксису мови.
Синтаксичний аналізатор – це частина компілятора, яка відповідає за виявлення основних синтаксичних конструкцій вхідної мови. В задачу синтаксичного аналізатора входить: знайти та виділити основні синтаксичні конструкції мови в послідовності лексем програми, встановити тип та правильність побудови кожної синтаксичної конструкції і представити синтаксичні конструкції у вигляді, зручному для подальшої генерації тексту результуючої програми. Як правило, синтаксичні конструкції мови програмування можуть бути описані за допомогою контекстно-вільних граматик. Розпізнавач дає відповідь на питання, належить чи ні, ланцюжок вхідних лексем заданій мові. Але, задача синтаксичного аналізу, не обмежується тільки такою перевіркою. Синтаксичний аналізатор повинен мати, деяку результуючу мову, за допомогою якої він передає наступним фазам компіляції, інформацію про знайдені і розібрані синтаксичні конструкції, якщо відокремлюється фаза генерації об’єктного коду.
4. Формальний опис вхідної мови
На етапі синтаксичного аналізу сканується таблиця лексем, та із послідовності лексем розпізнаються окремі синтаксичні конструкції, які описані правилами граматики, які в свою чергу можуть бути описані, наприклад, формулами Бекуса-Наура, синтаксичними діаграмами та ін.
Для даної мови програмування синтаксичний аналізатор (парсер) розпізнає конструкції за формулами Бекуса-Наура, що подані нижче.
<program>
::=
[<procedure_declaration>] | [<variable_ declaration>] MAIN( ) <block>
<procedure_
declaration>
::=
PROCEDURE <procedure_name>([VAR][<variable_declaration>{,|;
[VAR]<variable_declaration>}] );
<variable_ declaration>
::=
[CONST] <type> <ident>{,<ident>};
<block>
::=
{ < statement >{<statement >}}
<statement>
::=
<variable_declaration> | <operator>
<procedure_name>
::=
<ident>
<type>
::=
INTEGER | CHAR | REAL | STRING
<ident>
::=
<letter> [<letter>|<digit>][<letter>|<digit>][<letter>|<digit>]
[<letter>|<digit>][<letter>|<digit>]
<letter>
::=
a|b|c|d|e|f|g|h|i|j|k|l|n|m|o|p|q|r|s|t|u|v|w|x|y|z|A|B|C|D|E|F|G|H|I|J|K|L|N|M|O|P|Q|R|S|T|U|V|W|X|Y|Z
<digit>
::=
0|1|2|3|4|5|6|7|8|9
<operator>
::=
<input> | <if_then_else> | <assign> | <while> | <output> | <procedure>
<input>
::=
SCAN(“<format>{,<format>} “,<ident>{,<ident>} );
<if_then_else>
::=
IF (<bool_expression>) THEN <block> ELSE <block>
<assign>
::=
<ident> :: <expression>;
<expression>
::=
<num_expression> | <bool_expression>
<num_expression>
::=
<num_operand>{<num_operation> <num_operand>};
<bool_expression>
::=
<num_operand> | <ident> <bool_operation> <num_operand> | <ident>
<num_operand>
::=
(<num_expression>) | <ident> | <number>
<num_operation>
::=
+ | - | / | * | POW | MOD
<bool_operation>
::=
= | <> | < | > | <= | >= | NOT | OR | AND
<number>
::=
<digit>{<digit>}
<while>
::=
WHILE <bool_expression> <block>
<procedure>
::=
<ident>( [<ident>{,<ident>}|<number>{,<number>}] );
<output>
::=
PRINT ( “{<letter>|<number>|!|@|#|$|%|^|&|*|(|)|_|+|-|=|{|}|[|]|;|’|”|<|>|?|,|.|/|<format>}”|{<ident>[,<ident>]} );
<format>
::=
%d | %c | %f | %q
5. Таблиця лексем
Лексичний аналіз реалізує лексичний аналізатор – сканер, що розбирає рядки вхідної програми на лексеми.
Вихідним продуктом процесу лексичного аналізу є таблиця лексем, що містить запис, що складається із таких полів:
1.Номер рядка лексеми
2.Номер лексеми
3.Примітка, тип лексеми або її символьне представлення.
Номер лексеми, як і її, символьне значення (крім імен змінних та міток) є унікальним. В табл. наведено перелік усіх лексем для, вибраної для курсового проекту, мови, та відповідність номера лексеми до її символьного значення.
Табл. 1
Лексема
Індивідуальний номер
Лексема
Індивідуальний номер
INTEGER
0
(
21
CHAR
1
)
22
REAL
2
::
23
STRING
3
;
24
MAIN
4
,
25
SCAN
5
//
26
PRINT
6
.
27
IF
7
+
28
THEN
8
-
29
ELSE
9
*
30
WHILE
10
/
31
PROCEDURE
11
=
32
POW
12
<>
33
MOD
13
<
34
NOT
14
>
35
OR
15
<=
36
AND
16
>=
37
VAR
17
&
38
CONST
18
%
39
{
19
[
40
}
20
]
41
6. Опис тексту програми
Лексичний аналізатор займається виділенням лексем з коду вихідної програми. На першому етапі своєї роботи, компілятор загружає код вихідної програми в пам’ять. Змінна Prg_Text зберігає текст програми. Змінна sym_count є індексом, який вказує на символ що зчитується. Текст програми містить лексеми, що розділені розділяючими знаками – знаками арифметичних операцій, початком синтаксичних конструкцій та спеціальними символами. Спеціальні символи – пробіл, знак табуляції, можуть зустрічатись послідовно, розділяючи лексеми, в необмежених кількостях. Тому на початку сканування рядка та після знаходження кожної наступної лексеми потрібно відшукувати початок нової лексеми, пропускаючи спеціальні символи.
Після знаходження початку лексеми, усі її символи копіюються у буфер лексеми. Кінець копіювання визначається кінцем лексеми, а так як між лексемами обов’язково є розділювачі, то ознакою кінця лексеми є початок розділювача. Як вже сказано вище, розділювачами можуть бути спеціальні символи, які не представляють для наступних фаз компіляції ніякого інтересу, а отже синтаксичному аналізатору не обов’язково знати про їх існування, тому для синтаксичного аналізу вони не передаються . Інша справа, коли розділювачем виступає знак операції, або початок синтаксичної структури. Ці розділювачі є лексемами, тому в таблицю лексем вони заносяться обов’язково.
Лексему після її копіювання у буфер лексеми потрібно розпізнати, тобто визначити індивідуальний номер лексеми, та занести дані про неї у таблицю лексем. Розпізнавання здійснюється методом порівнювання вмісту буферу із наперед визначеним набором лексем. Для розпізнавання лексем, що є розділювачами, їх також потрібно помістити у буфер лексеми. В таблицю лексем також записується символьне представлення лексеми для ідентифікації ідентифікаторів з поміж множини імен змінних.
Виділення лексем відбувається за наступним алгоритмом: функція NextLex() використовує функції, які розрізняють перший символ лексеми:
isDigit() – повертає значення, відмінне від 0, якщо параметром є число;
isLetter() – повертає значення, відмінне від 0, якщо параметром є буква;
isIgnore() – пропускає символи пробілу, табуляції, нового рядка;
find_idkw() – функція пошуку ключових слів. Якщо її параметр зустрічається в таблиці внутрішніх лексем, то вона повертає числове значення лексеми. В іншому випадку повертає нельове значення. Таким чином ми маємо змогу відрізнити ключове слово від звичайної змінної.
Сканер має справу із необмеженим набором символів. Пошук лексем ведеться циклічно, доки не досягнуто кінця вхідного тексту. Лексичний аналізатор викликається із синтаксичного аналізатора. У додатку 1 подано блок-схему лексичного аналізу, що відображає роботу сканера.
На етапі синтаксичного аналізу із послідовності лексем розпізнаються окремі синтаксичні конструкції, які описані правилами граматики, які в свою чергу описані формулами Бекуса-Наура. З цих формул видно, що синтаксичний аналіз легко виконується за допомогою рекурсивного спуску, або ще як його називають – метод згори-вниз. Суть цього методу полягає у тому, що розбір синтаксису проходить від синтаксичних конструкцій “зовнішнього” рівня до конструкцій найнижчого рівня, які є “внутрішніми” по відношенню до перших, та аж до самої межі – лексем. Цей процес схожий на виділення конструкцій за деяким правилом граматики, яка сама деталізується іншими правилами граматики.
Лексеми проглядаються послідовно. Синтаксичний аналізатор працює надзвичайно просто – вибирає з вхідного потоку лексеми і, аналізуючи кожну лексему, виконує певний код.
Конструкції визначаються за лексемами або послідовністю декількох лексем, що є першими та унікальними для кожної інструкції. Після ідентифікації синтаксичної конструкції вона перевіряється на відсутність помилок.
Це відбувається до того моменту, поки змінна – вказівник, яка вказує на вихідний код програми, який знаходиться в пам’яті, не досягне кінця програми. Паралельно код, який виконується у відповідності із значенням біжучої лексеми, генерує код на мові С.
У додатку 2 зображено блок-схему обробки компілятором мовних конструкцій, які передбачені завданням курсової роботи. Компілятор циклічно перебирає лексеми вихідного тексту до того моменту, поки не зустріне лексему типу lexEOF.
Відповідно до кожної найденої лексеми компілятор генерує відповідний код на мові С.
Якщо розглядати точніше, то для кожної лексеми викликається відповідна функція, яка в свою чергу зчитує лексеми з вихідного потоку, перевіряє, чи послідовність цих лексем відповідає граматиці нашої мови, і як результат функція генерує потрібний код.
Після того, як синтаксис оператора визначено, іде інтерпретація його значення, тобто семантичний аналіз. Кожній синтаксичній одиниці відповідає певне значення, яке може бути виражене в формі реальних входів або в проміжній формі. Проміжною формою синтаксичної конструкції є дерево розбору.
Трансляція тексту відбувається за один прохід. Перевагою однопрохідних компіляторів є те, що не потрібно підтримувати зв’язок між проходами, але недоліком є те, що всі змінні, функції, і константи мусять обов’язково бути описані перед їх використанням.
Змінні, процедури і масиви зерігаються у відповідних структурах типу list_item.
struct list_item {
CString name; // ім’я змінної, процедури або масиву
lex_type l_type; // тип змінної, процедури або масиву
lex_type param_type[10]; // тип параметрів процедури
CString v[10]; // параметр-змінна чи параметр-значення
BYTE param_num; // кількість параметрів процедури
list_item* next; // вказівник на наступний елемент
CString var; // константа чи змінна
}
Структура rwtab містить таблицю ключових слів мови.
struct find_string rwtab[] = {
{"MAIN", kwMain},
{"IF", kwIf},
{"THEN", kwThen},
{"ELSE", kwElse},
{"PROCEDURE", kwProcedure},
{"INTEGER", kwInt},
{"CHAR", kwChar},
{"REAL", kwReal},
{"STRING", kwString},
{"VAR", kwVar},
{"PRINT",kwPrint},
{"SCAN",kwScan},
{"WHILE",kwWhile},
{"POW",kwPow},
{"MOD",kwMod},
{"NOT",kwNot},
{"OR",kwOr},
{"AND",kwAnd},
{"CONST",kwConst},
}.
6. Результати тестування
Для превірки працездатності програмного продукту нижче наведені тестові приклади, які демонструють роботу компілятора.
Для тестового файлу “test.k14” сформовано такі файли, що відображають представлення даних при роботі компілятора:
test.k14
PROCEDURE test(VAR INTEGER i){
i::i POW 4;
PRINT("i=%d\n",i);
}
MAIN( ) {
INTEGER a;
CHAR b;
REAL c;
STRING d;
PRINT("\nInput string ");
SCAN(&d);
a::10;
b::'a';
c::2.5;
test(a);
c::c*12+5.2;
PRINT("b=%c\n",b);
PRINT("c=%f\n",c);
PRINT("d=%q\n",d);
}
Вивід лексем: test.lex
[ 1, 9] lex: kwPROCEDURE lex value: PROCEDURE
[ 1,14] lex: lexId lex value: test
[ 1,15] lex: lexLftRndBr lex value: (
[ 1,18] lex: kwVAR lex value: VAR
[ 1,26] lex: kwINTEGER lex value: INTEGER
[ 1,28] lex: lexId lex value: i
[ 1,29] lex: lexRghRndBr lex value: )
[ 1,30] lex: lexBeg lex value: {
[ 2, 1] lex: lexId lex value: i
[ 2, 3] lex: lexAssign lex value: ::
[ 2, 4] lex: lexId lex value: i
[ 2, 8] lex: kwPOW lex value: POW
[ 2,10] lex: lexInt lex value: 4
[ 2,11] lex: lexSemicolon lex value: ;
[ 3, 5] lex: kwPRINT lex value: PRINT
[ 3, 6] lex: lexLftRndBr lex value: (
[ 3,14] lex: lexString lex value: i=%d\n
[ 3,15] lex: lexComma lex value: ,
[ 3,16] lex: lexId lex value: i
[ 3,17] lex: lexRghRndBr lex value: )
[ 3,18] lex: lexSemicolon lex value: ;
[ 4, 1] lex: lexEnd lex value: }
[ 5, 5] lex: kwMAIN lex value: MAIN
[ 5, 6] lex: lexLftRndBr lex value: (
[ 5, 8] lex: lexRghRndBr lex value: )
[ 5,10] lex: lexBeg lex value: {
[ 6, 8] lex: kwINTEGER lex value: INTEGER
[ 6,10] lex: lexId lex value: a
[ 6,11] lex: lexSemicolon lex value: ;
[ 7, 5] lex: kwCHAR lex value: CHAR
[ 7, 8] lex: lexId lex value: b
[ 7, 9] lex: lexSemicolon lex value: ;
[ 8, 5] lex: kwREAL lex value: REAL
[ 8, 8] lex: lexId lex value: c
[ 8, 9] lex: lexSemicolon lex value: ;
[ 9, 7] lex: kwSTRING lex value: STRING
[ 9, 9] lex: lexId lex value: d
[ 9,10] lex: lexSemicolon lex value: ;
[10, 6] lex: kwPRINT lex value: PRINT
[10, 7] lex: lexLftRndBr lex value: (
[10,24] lex: lexString lex value: \nInput string
[10,25] lex: lexRghRndBr lex value: )
[10,26] lex: lexSemicolon lex value: ;
[11, 5] lex: kwSCAN lex value: SCAN
[11, 6] lex: lexLftRndBr lex value: (
[11, 7] lex: lexAmp lex value: &
[11, 8] lex: lexId lex value: d
[11, 9] lex: lexRghRndBr lex value: )
[11,10] lex: lexSemicolon lex value: ;
[12, 2] lex: lexId lex value: a
[12, 4] lex: lexAssign lex value: ::
[12, 6] lex: lexInt lex value: 10
[12, 7] lex: lexSemicolon lex value: ;
[13, 2] lex: lexId lex value: b
[13, 4] lex: lexAssign lex value: ::
[13, 7] lex: lexChar lex value: 'a'
[13, 8] lex: lexSemicolon lex value: ;
[14, 2] lex: lexId lex value: c
[14, 4] lex: lexAssign lex value: ::
[14, 7] lex: lexReal lex value: 2.5
[14, 8] lex: lexSemicolon lex value: ;
[15, 5] lex: lexId lex value: test
[15, 6] lex: lexLftRndBr lex value: (
[15, 7] lex: lexId lex value: a
[15, 8] lex: lexRghRndBr lex value: )
[15, 9] lex: lexSemicolon lex value: ;
[16, 2] lex: lexId lex value: c
[16, 4] lex: lexAssign lex value: ::
[16, 5] lex: lexId lex value: c
[16, 6] lex: lexStar lex value: *
[16, 8] lex: lexInt lex value: 12
[16, 9] lex: lexPlus lex value: +
[16,12] lex: lexReal lex value: 5.2
[16,13] lex: lexSemicolon lex value: ;
[17, 6] lex: kwPRINT lex value: PRINT
[17, 7] lex: lexLftRndBr lex value: (
[17,15] lex: lexString lex value: b=%c\n
[17,16] lex: lexComma lex value: ,
[17,17] lex: lexId lex value: b
[17,18] lex: lexRghRndBr lex value: )
[17,19] lex: lexSemicolon lex value: ;
[18, 6] lex: kwPRINT lex value: PRINT
[18, 7] lex: lexLftRndBr lex value: (
[18,15] lex: lexString lex value: c=%f\n
[18,16] lex: lexComma lex value: ,
[18,17] lex: lexId lex value: c
[18,18] lex: lexRghRndBr lex value: )
[18,19] lex: lexSemicolon lex value: ;
[19, 6] lex: kwPRINT lex value: PRINT
[19, 7] lex: lexLftRndBr lex value: (
[19,15] lex: lexString lex value: d=%q\n
[19,16] lex: lexComma lex value: ,
[19,17] lex: lexId lex value: d
[19,18] lex: lexRghRndBr lex value: )
[19,19] lex: lexSemicolon lex value: ;
[20, 0] lex: lexEnd lex value: }
[20, 0] lex: lexEof lex value: -1
Виконання файлу test.exe
Input string test
i=10000
b=a
c=35.20000
d=test
Тест при створенні помилки
PROCEDURE test(VAR INTEGER i){
i::i POW 4;
PRINT("i=%d\n",i);
}
MAIN( ) {
INTEGER a;
CHAR b;
REAL c;
STRING d;
PRINT("\nInput string ");
SCAN(&d);
a::10;
b::'a';
c::2.5
test(a);
c::c*12+5.2;
PRINT("b=%c\n",b);
PRINT("c=%f\n",c);
PRINT("d=%q\n",d);
}
Повідомлення про помилку:
Рядок 15. Очікується “;”
8. Висновки
При виконанні курсового проекту, я ознайомився з принципами роботи компіляторів, їх структурою а також різними методами їх реалізації. Також був розроблений компілятор для мови програмування, що задовольняє вимоги індивідуального завдання на курсовий проект, із генерацію коду у вигляді програми на мові СІ. Для зручності користування компілятором, використовуючи Win32 API та бібліотеку MFC, був розроблений інтерфейс користувача.
9. Список використаної літератури
А.Ахо, Д.Ульман “Теория синтаксического анализа, перевода и компиляции”. т.1,2 - М.:Мир, 1978.
Д. Грис “Конструирование компиляторов для цифровых вычислительных машин”. - М.:Мир, 1975.
Кузьмин А.Я. Лексический анализ. – М.:ВШ.,1985.
Фролов А.В. Проэктирование компиляторов. –М.: Мир,1989.