Міністерство освіти і науки України
Національний університет "Львівська Політехніка"
Кафедра ЕОМ
/
Пояснювальна записка
до курсової роботи
з предмету: "Системне програмування"
на тему:"Розробка системних програмних модулів та компонент систем програмування. Розробка транслятора з вхідної мови програмування"
Львів-2013
Анотація
В даній курсовій роботі реалізується програма, яка транслює файл з деякої віртуальної мови, заданої завданням, в файл Асемблера з подальшою його компіляцією і створенням виконавчого файлу. Крім того, дана програма перевіряє на помилки (синтаксичні, семантичні, лексичні) вхідний файл і при їх присутності видає у файл повідомлення про помилки і зупиняє свою роботу до їх виправлення.
На прикладі даної програми нами вивчалася можливість створення достатньо потужних трансляторів в мову низького рівня (наприклад, Асемблер), - завдання, реалізація якого є достатньо потрібною на сучасному етапі розвитку програмування.
В ході виконання курсової роботи студенти повинні навчитися самостійно працювати з літературою, розробляти типові елементи системних програм, програмуючи роботу з таблицями, словниками, інформаційними базами, виконуючи лексичний та синтаксичний аналіз, а також семантичну обробку, здійснювати їх програмну реалізацію та відлагодження на сучасних обчислювальних системах.
Зміст
Завдання на курсову роботу 5
Вступ 6
1. Огляд методів та способів проектування трансляторів 7
1.1. Засоби побудови компіляторів 7
2. Формальний опис вхідної мови програмування 9
2.1. Деталізований опис вхідної мови в термінах розширеної нотації Бекуса-Наура 9
2.2. Опис термінальних символів та ключових слів 9
3. Розробка транслятора вхідної мови програмування 11
3.1. Вибір технології програмування 11
3.2. Проектування таблиць транслятора та вибір структур даних 11
3.3. Розробка лексичного аналізатора 12
3.3.1. Опис лексичного аналізатора 12
3.3.2. Структури даних, що використовуються при лексичному аналізі 14
3.4. Розробка синтаксичного та семантичного аналізатора 15
3.4.1. Опис синтаксичного аналізатора 15
3.4.2. Повне дерево граматичного розбору 17
3.5. Розробка генератора коду 18
3.5.1. Опис генератора коду 18
4. Опис програми(в тому числі граф-схем) 19
4.1. Опис інтерфейсу та інструкція користувачеві 23
5. Відлагодження та тестування програми 23
5.1. Виявлення лексичних помилок. 23
5.2. Виявлення синтаксичних помилок. 24
5.3. Виявлення семантичних помилок. 24
5.4. Загальна перевірка коректності роботи транслятора. 24
Висновки 25
Література 26
Додаток А. Текст програми-транслятора на мові С++ 27
Додаток Б - Результати відлагодження та тестування програми 57
Текст програми з лексичними помилками в вхідній програмі 57
Вмістиме файлу з повідомленнями про помилку 57
Текст програми з синтаксичнимипомилками в вхідній програмі 58
Повідомлення про помилку при виконанні синтаксичного аналізу 58
Текст коректної до вхідної мови програми: 59
Текст асемблерної програми: 59
Результати виконання програми: 63
ДОДАТОК В - Граф-схема алгоритму виконання програми через використання функцій. 64
Завдання на курсову роботу
Для отримання виконавчого файлу на виході розробленого транслятора скористатися програмами tasm.exe і tlink.exe.
Мова розробки транслятора: ANSI C або C++.
Реалізувати оболонку або інтерфейс командного рядка.
На вхід розробленого транслятора має подаватися текстовий файл, написаний на заданій мові програмування.
На виході розробленого транслятора мають створюватись чотири файли: файл з повідомленнями про помилки (або про їх відсутність), файл на мові асемблера, об’єктний та виконавчий файли.
Назва вхідної мови програмування утворюється від першої букви у прізвищі студента та номера його варіанту. Саме таке розширення повинні мати текстові файли, написані на цій мові програмування.
Назва виконавчого файлу транслятора утворюється від назви мови програмування додаванням літери “с” (від compiler). Для мого варіанту це буде CWork_M14
Блок тіла програми: # PROGRAM<name>; VARIABLE…; START - STOP
Оператори уводу-виводу: SCAN PRINT
Оператор присвоєння: <-
Оператор: IF [–ELSE] (СІ)
Регістр ключових слів: Up
Регістр ідентифікаторів: Low-Up8перший символ Low
Арифметичні операції: ADD; SUB; MUL; DIV; MOD
Операції порівняння: ==; !=; !>; !<
Логічні операції: NOT; AND; OR
Типи даних: INTEGER16_t
Коментар: \\
Вступ
Транслятором називається програма перекладу (трансляції) початкової програми, записаною вхідною мовою, в еквівалентну їй об`єктну програму. Якщо мова високого рівня є вхідною, а мова асемблера чи машинна – вихідною, то такий транслятор називається компілятором.
Компілятори дозволяють створювати об`єктні модулі, які пізніше, після етапу зв`язування відлагоджувачем, перетворюються у виконавчі файли.
Потреба різноманіття компіляторів дуже важлива, це прямо залежить від вхідної мови, яку він перекладає. Кожна з них реалізує клас задач, необхідних для користувача.
Кажучи простіше, існує виконавець – автомат або персональна ЕОМ, що вміє реалізувати скінчений набір дій. Наказ на виконання дії з вказаного набору, що виражається певним, раніше обумовленим способом, називається розпорядженням, а вся сукупність допустимих наказів – системою розпорядження виконавця.
Даючи завдання виконавцю на деяку роботу, ми звичайно даємо йому не одне розпорядження, а деяку скінчену послідовність розпоряджень, задаючи також порядок, у якому вони виконуються. Така послідовність розпоряджень з вказанням порядку їх виконання називається програмою.
Саме програма на певній мові програмування є вхідним даним для компілятора, що разом з відлагоджувачем перекладає послідовність команд у спеціальні машинні коди і здійснює виконання вхідної послідовності команд запуском відповідного виконавчого файлу.
Метою виконання курсової роботи є закріплення теоретичних знань та практичних навичок системного програмування, набутих при вивченні дисципліни “Системне програмування”.
Огляд методів та способів проектування трансляторів
Транслятором називається програма перекладу (трансляції) початкової програми, записаної вхідною мовою, в еквівалентну їй об`єктну програму. Якщо мова високого рівня є вхідною, а мова асемблера чи машинна – вихідною, то такий транслятор називається компілятором. Програма, записана мовою високого рівня, найчастіше має два етапи – компіляції та виконання. На першому початкова програма перекладається машиною А в об`єктну програму мовою машини, на другому вона заноситься в пам`ять машини В. При її виконанні, за необхідності вводяться початкові дані та виводяться результати. Якщо А і В –різні машини, то говорять, що на ЕОМ А виконується крос-компіляція, і такий транслятор називається крос-компілятором.
Інтерпретатором є різновид транслятора для перекладу початкової програми в програму, записану простою проміжною алгоритмічною мовою, та для її виконання. Деякі програми в проміжну форму не перекладається, а відразу виконуються. Інтерпретатор простіший від компілятора, але працює повільніше.
Транслятор з різними вхідною та об`єктною мовами високого рівня називається препроцесором. Як частина компілятора, він при бажанні може використовуватися самостійно для розширення можливостей конкретної мови програмування (наприклад мови Сі).
Засоби побудови компіляторів
Існують спеціальні засоби (компілятори компіляторів, генератори компіляторів тощо) для полегшення конструювання компіляторів. Ідеальною вважається ситуація, коли є програма (або система програм), яка автоматично будує транслятор чи компілятор, маючи на вході описи лексики, синтаксису вхідної мови, результатів перетворень синтаксичних конструкцій та опис цільової машини.
Для полегшення побудови трансляторів розроблені такі основні інструментальні засоби: генератор лексичних аналізаторів, в якому інформація про лексеми задається регулярними виразами, а можливі дії обробки – операторами мови Сі; генератор, який за граматикою початкової мови створює СА з діями обробки; засоби генерації коду – спеціальні мови, зручні для опису процесу генерації машинного коду.
Основна проблема при реалізації мови програмування полягає в урахуванні суперечливих умов. Тому при складанні транслятора доводиться йти на компроміси, беручи до уваги, як буде використовуватися транслятор; що важливіше – швидкість компіляції чи якість об’єктної програми; створюється транслятор для нової ЕОМ чи ні; які інструментальні засоби можна залучати. Проект повинен давати можливість просто вносити зміни.
В реалізації мов високого рівня часто використовується специфічний тільки для компіляції засіб “розкрутки”. З кожним транслятором завжди зв`язані три мови програмування: Х – початкова, Y – об`єктна та Z – інструментальна. Транслятор перекладає програми мовою Х в програми, складені мовою Y, при цьому сам транслятор є програмою написаною мовою Z (позначатимемо компілятор як ).
Припустимо, що треба реалізувати нову мову високого рівня L для двох ЕОМ подібної організації А та В, і обидві ЕОМ не мають якихось інструментальних засобів для прискорення процесу створення компілятора. Покажемо, що треба зробити, щоб побудувати . Для цього виділимо в мові L підмножину S, достатню, щоб створити компілятор для перекладу з мови L на мову А, і побудуємо та відтестуємо . Потім напишемо на підмножині S компілятор для перекладу з мови L на мову А. Пропустивши програму через компілятор одержимо необхідний компілятор : ((
Реалізація після цього мови L для ЕОМ В значно простіша – треба переписати компілятор в компілятор ; транслювати програму в програму компіля-тором ((.
Повторно транслювати програму компілятором і одержати потрібний компілятор :((
Формальний опис вхідної мови програмування
Деталізований опис вхідної мови в термінах розширеної нотації Бекуса-Наура
<програма>::=#PROGRAM<ідентифікатор>;VARIABLE<оголошення>;START<тіло>STOP
<ідентифікатор> ::= <БУКВА> [{БУКВА}]
<БУКВА>::= 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
<число>::=[-]< цифра >[{ цифра }]
<цифра> ::= 0|1|2|3|4|5|6|7|8|9
<оголошення>::= <ідентифікатор>[{,< ідентифікатор >}]<тип>
<тип> ::= INTEGER16_t;
<ввід> ::= SCAN(<ідентифікатор>);
<вивід> ::= PRINT(<ідентифікатор>|<вираз>)
<вираз> ::= [(]<ідентифікатор>|<число>[)]{[операція] [(]<ідентифікатор>|
|<число>[)]}
<присвоєння > ::=<ідентифікатор>::= <вираз>;
<операція> ::= ADD | SUB | MUL | DIV | MOD | == | != | !< |! > | AND | OR | NOT
<дія> -><присвоєння> | <ввід> | <вивід> | <розгалуження>
<розгалуження> -> IF (<вираз>) THEN START <дія>;[{<дія>;}] STOP; [ELSE START <дія>;[{<дія>;}]STOP;] <тіло> -><дія>;[{дія;}].
Отже, вхідну мову можна описати за допомогою 15 виразів.
Опис термінальних символів та ключових слів
Визначимо окремі термінальні символи та нерозривні набори термінальних символів (ключові слова):
;
,
<-
ADD
SUB
MUL
DIV
MOD
(
)
INTEGER16_t
#PROGRAM
VARIABLE
START
STOP
SCAN
PRINT
IF
ELSE
\\
AND
OR
!!
==
!=
!>
!<
-
До термінальних символів віднесемо також усі цифри (0-9), латинські букви (a-z, A-Z), символ підкреслення, символи табуляції, символ переходу на нову стрічку та пробілу. Отже, всього: 28 + 10 + 52 + 4 = 94 термінальних виразів.
Розробка транслятора вхідної мови програмування
Вибір технології програмування
Дуже важливим кроком розробки транслятора є вибір технології програмування від якого залежить подальше виконання курсової роботи.
Основна проблема при реалізації мови програмування полягає в урахуванні суперечливих умов. Тому при складанні транслятора доводиться йти на компроміси, беручи до уваги, як буде використовуватися транслятор; що важливіше – швидкість компіляції чи якість об’єктної програми; створюється транслятор для нової ЕОМ чи ні; які інструментальні засоби можна залучати. Проект повинен давати можливість просто вносити зміни.
В реалізації мов високого рівня часто використовується специфічний тільки для компіляції засіб “розкрутки”. З кожним транслятором завжди зв`язані три мови програмування: Х – початкова, Y – об`єктна та Z – інструментальна. Транслятор перекладає програми мовою Х в програми, складені мовою Y, при цьому сам транслятор є програмою написаною мовою Z
Проектування таблиць транслятора та вибір структур даних
Використання таблиць значно полегшує створення трансляторів, тому у даному випадку використовуються кілька таблиць:
1) Таблиця лексем з елементами, які мають таку структуру:
enum eTypesOfTokens
{
ltProgram, // #PROGRAM
ltVar, // var
ltType, // INTEGER16_t
ltBegin, // begin
ltEnd, // end
ltRead, // input
ltWrite, // output
ltIf, // if
ltElse, // else
ltNewValue, // <-
ltAdd, // +
ltSub, // -
ltMul, // *
ltDiv, // /
ltMod, // %
ltEqu, // ==
ltNotEqu ,// !=
ltLess, // <
ltGreate, // >
ltNot, // !
ltAnd, // and
ltOr, // or
ltEOF, // EOF
ltEndGroup, // ;
ltComma, // ,
ltIdentifier, //
ltNumber, //
ltLBraket, // (
ltRBraket, // )
ltUnknown
};
2) Таблиця ідентифікаторів з елементами типу Identificator та додатковою цілочисельною змінною цілочисельного типу в якій зберігається кількість визначених змінних. Структура елементів така:
typedef struct Ident
{
char name[50];
int value;
}Identifier;
Розробка лексичного аналізатора
Опис лексичного аналізатора
Основна задача лексичного аналізу – розбити вихідний текст, що складається з послідовності одиночних символів, на послідовність слів, або лексем, тобто виділити ці слова з безперервної послідовності символів. Всі символи вхідної послідовності з цієї точки зору розділяються на символи, що належать яким-небудь лексемам, і символи, що розділяють лексеми. В цьому випадку використовуються звичайні засоби обробки рядків. Вхідна програма проглядається послідовно з початку до кінця. Базов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якого впливу на виконання програми, отже ж й на синтаксичний розбір та генерацію коду.
Лексичний аналізатор (сканер) не обов’язково обробляє всю програму до початку всіх інших фаз. Якщо лексичний аналіз не виділяється як окрема фаза компіляції, а є частиною синтаксичного аналізу, то лексична обробка тексту програми виконується по мірі необхідності по запиту синтаксичного аналізатора.
В даному курсовому проекті реалізовано прямий лексичний аналізатор, який виділяє з вхідного тексту програми окремі лексеми і на основі цього формує таблицю.
Структури даних, що використовуються при лексичному аналізі
Рис. 3.1. Блок-схема роботи лексичного аналізатора
Розробка синтаксичного та семантичного аналізатора
Опис синтаксичного аналізатора
Синтаксичний аналіз – це процес, в якому досліджується послідовність лексем, яку повернув лексичний аналізатор, і визначається, чи відповідає вона структурним умовам, що сформульовані у визначені синтаксису мови.
Синтаксичний аналізатор – це частина компілятора, яка відповідає за виявлення основних синтаксичних конструкцій вхідної мови. В задачу синтаксичного аналізатора входить: знайти та виділити основні синтаксичні конструкції мови в послідовності лексем програми, встановити тип та правильність побудови кожної синтаксичної конструкції і представити синтаксичні конструкції у вигляді, зручному для подальшої генерації тексту результуючої програми. Як правило, синтаксичні конструкції мови програмування можуть бути описані за допомогою контекстно-вільних граматик. Розпізнавач дає відповідь на питання, належить чи ні, ланцюжок вхідних лексем заданій мові.
В даному трансляторі синтаксичний аналізатор працює паралельно почергово з генератором коду, частиною якого він і є. Принцип роботи такий: на основі нижче наведеного дерева граматичного розбору (рис.4) аналізується черговий набір послідовних лексем та викликається відповідна генерація асемблерного коду. Потім знову вище описані дії повторюються поки не обробляться всі лексеми.
Нижче показано блок-схему роботи синтаксичного аналізатора.
Рис.3.2. блок-схема роботи синтаксичного аналізатора
Повне дерево граматичного розбору
Рис.3.3 Повне дерево граматичного розбору
Розробка генератора коду
Опис генератора коду
Генерація коду – це машинно-залежний етап компіляції, під час якого відбувається побудова машинного еквівалента вхідної програми. Зазвичай входом для генератора коду служить проміжна форма представлення програми, а на виході може з’являтися об’єктний код або модуль завантаження.
У компілятора, реалізованого в даній курсовій роботі, вихідна мова - програма на мові Assembler. Ця програма записується у файл, що має таку ж саму назву, як і файл з вхідним текстом, але розширення “asm”. Генерація коду відбувається одразу ж після синтаксичного аналізу.
В даному трансляторі генератор коду послідовно викликає окремі функції, які записують у вихідний файл частини коду.
BeginASMFile();
Записує код заголовку програми.
CheckPresentInputOutput();
Визначає присутність операторів вводу/виводу для наступної функції.
PrintData(f);
Записує сегмент даних (визначені змінні та службові буфери та змінні),
BeginCodeSegment();
Записує код ініціалізації співпроцесора.
PrintCode(f);
Записує сегмент коду.
PrintEnding(f);
Записує завершення програми та необхідні процедури.
Опис програми(в тому числі граф-схем)
Дана програма написана мовою С++ з використанням визначень нових типів, перечислень та класів:
enum LexemType
{ program_,
variable_,
int_, // INTEGER16_t
start_,
stop_,
input_, //Оператор вводу
output_, //Оператор виводу
if_,
then_,
else_,
newval_, // <-
add_, // ADD
sub_, // SUB
mul_, // MUL
div_, // DIV
mod_, // MOD
equ_, // ==
notequ_, // !=
le_, // !<
ge_, // !>
not_, // NOT
and_, // AND
or_, // OR
eof_, //
EndGroup_, // ;
komma_, // ,
ident_,
number_,
LeftBraket_, // (
RightBraket_, // )
unknown_
};
typedef struct Lexem
{
char name[50];
int value;
LexemType type;
int line;
}Lexema;
typedef struct Ident
{
char name[50];
int value;
}Identifier;
typedef struct L //Структура-реєстр в якій зберігаються всі дані програми
{
Lexema LexArray[1000]; //Таблиця лексем
int length; //
Identifier IdTable[50]; //Таблиця ідентифікаторів
int idnum; //
int PFExpr[200]; //Буфер для виразу в постфіксній формі
}Data;
Data Reg;
typedef struct Stacks
{ int st[200];
int top;
}StackType;
typedef class stack
{
public:
StackType S;
void Init(StackType* s)
{ s->top = -1;
}
void Push(int i, StackType* s)
{
if(IsFull(s))
{
puts("Stack error (is full)");
exit(0);
}
else
{
++s->top;
s->st[s->top] = i;
}
}
int Pop(StackType* s)
{
int i;
if(IsEmpty(s))
{
puts("Stack error (is empty)");
exit(0);
}
else
{
i = s->st[s->top];
--s->top;
}
return i;
}
bool IsEmpty(StackType* s)
{
if(s->top == -1)
{
return true;
}
else
{
return false;
}
}
bool IsFull(StackType* s)
{
if(s->top == 199)return true;
else return false;
}
void prints(StackType s)
{
int i=0;
for(;i<10;++i)
{
printf("%d_",s.st[i]);
}
}
}Stack;
Stack s;
Stack if_s; //Стек для зберігання номерів IF
Stack start_s; //Стек для зберігання номерів START
В програмі також використовуються багато функцій тому вона є доволі структурованою (граф-схема алгоритму виконання програми розташована в додатку Д).
Головною функцією є int main(int argc, char* argv[]) з якої і починає своє виконання програма. В ній прямо чи опосередковано викликаються наступні функції:
int Balans(int, LexemType, LexemType, LexemType); - визначає баланс дужок чи інших лексем між двома лексемами певного типу; повертає суму помилок;
void BeginASMFile(); - записує до вихідного файлу стандартну „шапку”;
void BeginCodeSegm(); - записує до вихідного файлу стандартний початок сегменту коду;
void CheckPresentInputOutput(); - перевіряє наявність в коді операторів вводу-виводу;
void CodeGenerator(FILE*); - за таблицями ідентифікаторів та лексем генерує вихідний код на мові асемблера;
int ConvertToPostfixForm(int); - перетворює вирази в постфіксну форму;
int FindErrors();- функція пошуку помилок у вхідному файлі. Повертає їх кількість;
void GenASMCode(const char *, FILE*); - генерує код для окремих операцій;
Lexema* GetLexema(FILE*); - повертає в LexicalAnalis чергову лексему;
bool IsElsePresent(int);- перевіряє наявність else у розгалуженні;
int IsExpression(int, FILE*); - перевірка, чи є дана послідовність лексем виразом;
bool IsOperation(LexemType); - перевірка, чи є дана лексема операцією;
int LexicalAnalyze(FILE*); - виконує лексичний аналіз і створює масив лексем;
void MakeLexOutFile(); - видруковує в файл масив лексем;
void PrintData(FILE*); - видруковує в вихідний файл сегмент коду, який крім введених змінних за умови присутності операторів вводу/виводу (CheckPresentInputOutput();) і службову інформацію для них та додаткові буфери;
void PrintCode(FILE*); - видруковує в вихідний файл основний код програми;
void PrintAND(FILE*); - видруковує в вихідний файл процедуру and_;
void PrintOR(FILE*); - видруковує в вихідний файл процедуру or_;
void PrintNOT(FILE*); - видруковує в вихідний файл процедуру not_;
void PrintEQ(FILE*); - видруковує в вихідний файл процедуру eq_;
void PrintGE(FILE*); - видруковує в вихідний файл процедуру ge_;
void PrintLE(FILE*); - видруковує в вихідний файл процедуру le_;
void PrintMOD(FILE*); - видруковує в вихідний файл процедуру mod_;
void PrintEnding(FILE*); - видруковує в вихідний файл стандартне завершення *.asm-файлу;
void PrintInput(FILE*); - видруковує в вихідний файл процедуру вводу;
void PrintOutput(FILE*); - видруковує в вихідний файл процедуру виводу;
bool Prioritet(LexemType,StackType); - перевіряє пріорітет операцій (використовується при перетворенні виразу у посифіксну форму).
Опис інтерфейсу та інструкція користувачеві
Створений транслятор є DOS програмою, що запускається з командної стрічки з параметром, а саме - іменем вхідного файлу, в якому записана програма на вхідній мові. Файл повинен мати розширення .m14. Отже формат введення повинен бути таким:
CWork_M14 <ім’я програми>.m14
У випадку некоректного введення програма видасть повідомлення про помилку і завершить своє виконання.
Якщо коректно введене ім’я вхідного файлу та вдалось його відкрити, то транслятор почне його опрацювання. Початковою фазою обробки є лексичний аналіз (розбиття на окремі лексеми), наступною - перевірка на правильність написання програми (вхідної). У випадку, коли виявлені помилки, транслялятор видасть повідомлення про їхню присутність, вкаже в якому файлі можна ці помилки переглянути та завершить своє виконання. Якщо ж програма написана вірно, то транслятор перейде на наступну фазу семантичного розбору та генерації асемблерного коду та вкаже ім’я результуючого файлу з розширенням .asm та завершить своє виконання.
Для отримання виконавчого файлу необхідно скористатись програмами tasm.exe filename.asm для компіляції та tlink filename.obj для лінкування.
Відлагодження та тестування програми
Відлагодження та тестування транслятора проводиться з використанням кількох вхідних програм з навмисне введеними помилками та з коректною програмою для загальної перевірки роботи транслятора.
Виявлення лексичних помилок.
Виявлення лексичних помилок відбувається на стадії лексичного аналізу. При розборі вхідної програми на окремі лексеми лексичний аналізатор перевіряє чи відповідає отримана лексема лексиці заданої мови програмування. У випадку неспівпадіння лексемі присвоюється тип “неопізнаної лексеми”. Повідомлення про такі помилки можна побачити лише після виконання процедури перевірки таблиці лексем.
Приклад виявлення: (Додаток Б).
Виявлення синтаксичних помилок.
Виявлення синтаксичних помилок відбувається на стадії перевірки програми на коректність окремо від синтаксичного аналізу. При цьому перевіряється окремо кожне твердження яке може бути або виразом, або оператором (циклу, вводу, виводу), або оголошенням, та перевіряється структура програми в цілому.
Приклад виявлення: (Додаток Б).
Виявлення семантичних помилок.
Суттю виявлення семантичних помилок є перевірка числових констант на відповідність типу integer_4 тобто знаковому цілому числу з відповідним діапазоном значень.
Приклад виявлення: (Додаток Б).
Загальна перевірка коректності роботи транслятора.
Загальна перевірка полягає в транслюванні завідомо коректної вхідної програми з використаням всіх можливостей мови в асемблерний код та перевірці на правильність виконання програми попередньо зкомпільованої та злінкованої за допомогою tasm та tlink. Приклад перевірки: (Додаток Б).
Висновки
В процесі виконання курсової роботи було виконано наступне:
Складено формальний опис мови програмування m14, в термінах розширеної нотації Бекуса-Наура, виділено усі термінальні символи та ключові слова.
Створено транслятор мови програмування m14, а саме:
Розроблено прямий лексичний аналізатор, орієнтований на розпізнавання лексем, що є заявлені в формальному описі мови програмування.
Розроблено синтаксичний аналізатор на основі автомата з магазинною пам’яттю. Складено таблицю переходів для даного автомата згідно правил записаних в термінах Бекуса-Наура.
Розроблено генератор коду, відповідні процедури якого викликаються після перевірки синтаксичним аналізатором коректності запису чергового оператора, мови програмуваня m14. Вихідним кодом генератора є програма на мові Assembler(i8086).
Проведене тестування транслятора на тестових програмах за наступними пунктами:
На виявлення лексичних помилок.
На виявлення синтаксичних помилок.
Загальна перевірка роботи компілятора.
Тестування не виявило помилок в роботі компілятор, і всі помилки в тестових програмах мовою m14 були успішно виявлені і відповідно оброблені.
В результаті виконання даної курсової роботи було успішно засвоєно методи розробки та реалізації компонент системного програмного забезпечення.
Література
Ахо А., Ульман Дж. Теория синтаксического анализа, перевода и компиляции, Т.1. Синтаксический анализ. - М.: Мир, 1978.
Ваймгартен Ф. Трансляция языков программирования. – М.: Мир, 1977.
Грис Д. Конструирование компиляторов для цифровых вычислительных машин. – М.: Мир, 1975.
Искусственный интеллект: В 3-х книгах. Кн. 3. Программные и аппаратные средства: Справочник/Под ред. В.Н. Захарова, В.Ф., Хорошевского. - М.: Радио и связь. - 1990. - 368 с.
Шіхт І., Технологія програмування та створення програмних продуктів.-Блог
Додаток А. Текст програми-транслятора на мові С++
#include"variables_and_types.h"
#include"tokens.h"
#include"errors_check.h"
#include"listing.h"
int main(int argc,char* argv[])
{
printf("\n******************************************************************************\n");
printf("\tTRANSLATOR (m14 -> assembler)\tCopyright Panchyshyn!\n");
printf("******************************************************************************\n");
// Checking the number of arguments to be passed from the command line
if (argc !=3)
{
printf("Warning! Invalid parameter list!\n");
printf("Example: programName.exe inputFile.m14 outputFile.m14");
getchar();
exit(1);
}
// Obtaining and formation names of incoming and outgoing files
strcpy(inFileName, argv[2]);
printf("Start translating file: %s", inFileName);
if ((fin = fopen(inFileName, "r")) == 0)
{
printf("Error: ""Can not open file: %s\n", inFileName);
getchar();
exit(1);
}
strncpy(outFileName,inFileName,strlen(inFileName)-4);
strcat(outFileName,".asm");
if ((fout = fopen(outFileName, "w")) == 0)
{
printf("Error: ""Can not create file: %s""", outFileName);
getchar();
exit(1);
}
else
{
printf("ASM file %s successfully created\n", outFileName);
}
// Breaking into tokens and printing into file
nNumberTokens = AnalisisTokens(fin);
PrintTokensInFile();
printf("Breaking into tokens completed. A total: %d tokens. Report file: ""Report - Tokens.txt""\n", nNumberTokens);
nNumberErrors = ErrorChecking();
printf("Error checking is complete. There is(are) %d errors. Report file: ""Report - Error.txt""\n", nNumberErrors);
// If there are no errors, go to a translation stage
if(nNumberErrors != 0)
{
printf("Error checking is complete. There is(are) %d errors. Report file: ""Report - Error.txt""\n", nNumberErrors);
printf("Can not translate because of errors. Put changes into codefile and try again.\n");
}
else
{
printf("Error checking is complete. There are no errors.\n");
GenerateCode(fout);
printf("Done! Result file: %s\n", outFileName);
printf("******************************************************************************");
}
return 0;
}
#include<stdio.h>
#include<stdlib.h>
#include<conio.h>
#include<string.h>
#include<ctype.h>
#define MAX_TOKENS 1000
#define MAX_IDENT 100
#define MAX_BUF_SIZE 100
#define STACK_SIZE 200
#define MAX_