Асемблер для платформи Java

[ виправити ] текст може містити помилки, будь ласка перевіряйте перш ніж використовувати.

скачати

Зміст.

Зміст. 1
Зміст. 2
Введення. 3
Постановка завдання. 4
Формат файлу класу. 5
Структура файлу класу. 5
Типи елементів Constant_pool 6
Формат структури field_info. 7
Формат структури method_info. 7
Формат атрибуту Code. 8
Робота JVM .. 10
Система команд JVM. 12
Синтаксис мови асемблера для платформи Java (мови JASM). 14
Тестові приклади. 18
1. 18
2. 19
3. 20
Проектування і реалізація компілятора. 25
Висновок. 30
Використана література. 31


Введення.

Мова програмування Java був розроблений у середині 90-х років на основі мови Oak, що призначався для програмування «прошивок» для різних електронних пристроїв. Однак, на відміну від свого попередника, мова Java набув широкого поширення, перш за все як мова, що використовувався в програмуванні для мережі Інтернет. В даний час область застосування Java значно розширилася, і ця мова часто застосовується і в звичайному прикладному програмуванні. Це зумовлено такими перевагами як кросплатформеність і безпека, які забезпечуються тим, що вихідний код на Java компілюється не безпосередньо в машинний код, а в, так званий, байт-код, який інтерпретується віртуальною машиною Java (JVM). У багатьох сучасних реалізаціях JVM байт-код перед виконанням перетвориться в машинні інструкції, що значно підвищує продуктивність, наближаючи її до продуктивності програм, написаних на C / C + +. Таким чином, Java, в сучасному стані цієї технології, поєднує переваги інтерпретованих і компільованих мов програмування.
Специфікація, що описує JVM, як абстрактну обчислювальну машину, надана компанією Sun у відкритий доступ. Це дозволяє створювати як власні реалізації JVM для різних платформ, так і альтернативні компілятори, генеруючі байт-код Java, в тому числі для мов програмування, відмінних від Java. Більшість літератури, присвяченої Java, майже не приділяє уваги пристрою JVM і описує лише сама мова Java. Проте, у ряді випадків, знання особливостей архітектури буває дуже корисним. У даній роботі я створив навчальну програму, яка може допомогти у вивченні архітектури JVM - нескладний асемблер для байт-коду Java.

Постановка завдання.

Потрібно вивчити архітектуру рівня команд платформи Java, формат файлу класу Java, і написати компілятор ассемблероподобного мови, що дозволяє створювати файли класів, коректно оброблювані реальної JVM. Цей компілятор повинен підтримувати всі команди байт-коду Java і найважливіші можливості JVM.

Формат файлу класу.

Основним форматом виконуваних файлів в архітектурі Java є формат файлу класу, описаний в The Java TM Virtual Machine Specification, виданої компанією Sun. Файл даного формату має ім'я, що збігається з ідентифікатором класу (за винятком вкладених класів) та розширення. Class.

Структура файлу класу.

Файл класу має наступну структуру:
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
     }
(Тут і далі u1, u2, u4 - цілі числа розміром 8, 16 і 32 біт з порядком байтів старший байт за молодшому адресою). Розглянемо послідовно всі поля.
· Magic - так зване магічне число, яке має в шістнадцятковій запису вид 0xCAFEBABE;
· Minor_version, major_version - версія формату файлу, по ній визначається сумісність даного файлу з конкретною версією JVM;
· Constant_pool_count - кількість елементів у Constant_pool плюс одиниця;
· Constant_pool - область констант - масив структур змінного розміру, які представляють ті чи інші константні значення. Звернення до області констант виробляються за індексом (індексація починається з одиниці; індекси, наступні за позиціями констант, що представляють числа типів long і double, не використовуються). Формати констант різних видів будуть розглянуті нижче;
· Access_flags - комбінація бітових прапорів, що визначають права доступу і деякі інші характеристики класу:
Прапор
Значення
Сенс
ACC_PUBLIC
0x0001
Доступний з-за меж пакету
ACC_FINAL
0x0010
Заборонено успадкування від даного класу
ACC_SUPER
0x0020
У методах даного класу потрібно використовувати прийняту в Java2 трактування команди invokespecial
ACC_INTERFACE
0x0200
Інтерфейс (є класом спеціального виду)
ACC_ABSTRACT
0x0400
Абстрактний клас
· This_class, super_class - індекси структур в області констант, що посилаються на даний клас і його клас-предок;
· Interfaces_count - число інтерфейсів, реалізованих даним класом;
· Interfaces - масив індексів структур в області констант, які посилаються на інтерфейси, реалізовані даним класом;
· Fields_count - кількість полів в даному класі;
· Fields - масив структур field_info, що описують поля класу. Формат структури field_info буде розглянуто нижче;
· Methods_count - кількість методів;
· Methods - масив структур method_info, що описують методи класу. Формат структури mettho_info буде розглянуто нижче. Конструктори і статичні ініціалізатор представляються методами зі спеціальними іменами <init> і <clinit>;
· Attributes_count - кількість атрибутів класу;
· Attributes - масив структур-атрибутів класу (поля, методи і байт-код методів також можуть мати свої атрибути). Кожна така структура на початку має дві обов'язкові поля, що описують тип атрибута і його розмір. До класу можуть бути застосовані такі стандартні атрибути: SourceFile - вказує на файл вихідного тексту, з якого було отримано даний файл класу, і Dedivcated - клас залишений для сумісності зі старим кодом і його використання не рекомендується. Можливе створення атрибутів нестандартних типів, але вони будуть ігноруватися середовищем виконання.

Типи елементів Constant_pool

Кожен елемент сonstant_pool починається з однобайтное поля, що визначає його тип. Розмір і зміст іншої частини структури залежить від типу. Існують наступні типи констант (елементів constant_pool):
· CONSTANT_Class - вказує на клас. Містить індекс константи типу CONSTANT_Utf8, що зберігає дескриптор класу;
· CONSTANT _Fieldref - вказує на поле класу. Містить індекси констант типу CONSTANT_Class і CONSTANT_NameAndType;
· CONSTANT _Methodref вказує на метод класу (не інтерфейсу). Містить індекси констант типу CONSTANT_Class і CONSTANT_NameAndType;
· CONSTANT _InterfaceMethodref вказує на метод інтерфейсу. Містить індекси констант типу CONSTANT_Class і CONSTANT_NameAndType;
· CONSTANT_String - вказує на рядок, містить індекс константи типу CONSTANT_Utf8;
· CONSTANT_Integer - містить ціле 32-розрядне число;
· CONSTANT_Float - містить дійсне число одинарної точності;
· CONSTANT_Long - містить ціле 64-розрядне число;
· CONSTANT_Double - містить дійсне число подвійної точності;
· CONSTANT_NameAndType - описує сигнатуру та ім'я методу або тип та ім'я поля. Містить індекси двох констант типу CONSTANT_Utf8, що зберігають відповідно ім'я і дескриптор типу (сигнатури);
· CONSTANT_Utf8 - містить рядок у форматі Utf8 (символи Unicode представляються комбінаціями від 1 до 3-х байт, причому символи з кодами, що не перевищують 127, представляються одним байтом).
Дескриптори - це рядки, що описують типи і сигнатури методів у компактному форматі. Примітивні типи позначаються однією літерою, типи масивів - відкривають квадратними дужками в кількості, що дорівнює розмірності масиву, перед позначенням базового типу. Класи описуються рядком, що містить ім'я класу з повним шляхом, при цьому замість точки роль роздільника назв пакунків і класу виконує слеш. У дескрипторах сигнатур методів у круглих дужках без роздільників перераховуються дескриптори типів параметрів; після закриває дужки знаходиться дескриптор типу значення. Для усунення неоднозначностей при цьому перед дескрипторами класів записується буква L, а після них - крапка з комою. Наприклад, (ILjava / lang / Object;) I - (int, Object): int (буквою I позначається тип int).

Формат структури field_info

Структура field_info має наступний формат:
field_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes [attributes_count];
     }
        
         Тут:
· Access_flags - комбінація бітових прапорів, що визначають права доступу та деякі інші характеристи ики поля:
Ім'я прапора
Значення
Сенс
ACC_PUBLIC
0x0001
Поле оголошено як public
ACC_PRIVATE
0x0002
Поле оголошено як private
ACC_PROTECTED
0x0004
Поле оголошено як protected
ACC_STATIC
0x0008
Поле є статичним
ACC_FINAL
0x0010
Поле оголошено як final і не може бути змінено після початкової ініціалізації
ACC_VOLATILE
0x0040
Поле оголошено як volatile
ACC_TRANSIENT
0x0080
Поле оголошено як transient - не збережуться при серіалізациі
· Name_index - індекс строковою константи-імені поля в Constant Pool;
· Descriptor_index - індекс строковою константи-дескриптора поля (описує тип) в Constant Pool;
· Attributes_count - число атрибутів поля;
· Attributes - атрибути поля. До полях можуть бути застосовані стандартні атрибути Dedivcated (див. вище), Synthetic (поле створено компілятором і не оголошено явно у вихідному тексті) і ConstantValue (ініціалізують значення для статичного поля).

Формат структури method_info

Структура method_info має наступний формат:
   method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
     }
 
Тут:
· Access_flags - бітові прапори, що визначають права доступу і деякі додаткові властивості методу:
Flag Name
Value
Interdivtation
ACC_PUBLIC
0x0001
Метод оголошений як public
ACC_PRIVATE
0x0002
Метод оголошений як private
ACC_PROTECTED
0x0004
Метод оголошений як protected
ACC_STATIC
0x0008
Метод є статичним
ACC_FINAL
0x0010
Метод є фінальним і не може бути заміщений
ACC_SYNCHRONIZED
0x0020
Метод оголошений як synchronized
ACC_NATIVE
0x0100
Метод є «рідним» і містить код, безпосередньо що виконується фізичним процесором
ACC_ABSTRACT
0x0400
Метод є абстрактним
ACC_STRICT
0x0800
Встановлює «суворий» режим роботи з речовими числами (тільки в Java 2).
· Name_index, descriptor_index, attributes_count - аналогічно field_info;
· Attributes - атрибути методу. Методи можуть мати такі стандартні атрибути:
o Dedivcated, Synthetic - аналогічно відповідним атрибутам полів;
o Exceptions - опис винятків, які може генерувати метод. Потрібно зазначити, що обов'язкове опис винятків не є необхідною вимогою для коректного виконання;
o Code - власне кажучи, байт-код методу.

Формат атрибуту Code.

Атрибут Code має наступну структуру:
Code _ attribute {
                u2 attribute_name_index;
u4 attribute_length;
u2 max_stack;
u2 max_locals;
u4 code_length;
u1 code[code_length];
u2 exception_table_length;
{ u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
} exception_table[exception_table_length];
u2 attributes_count;
attribute_info attributes[attributes_count];
     }
 
Тут:
· Attribute_name_index, attribute_length - стандартні для будь-якого атрибуту поля, які описують його тип і розмір;
· Max_stack - граничний розмір стека операндів для методу;
· Max_locals - гранична кількість локальних змінних методу (включаючи формальні параметри);
· Code_length - розмір байт-коду методу в байтах;
· Code - власне кажучи, байт-код;
· Exception_table_length - кількість захищених блоків;
· Exception_table - таблиця захищених блоків (обробників виключень). Кожна її запис має такі поля:
o start_pc - індекс початку захищеного блоку в масиві байт-коду,
o end_pc - індекс кінця захищеного блоку,
o handler_pc - індекс початку обробника,
o catch_type - тип оброблюваного вилучення (індекс у Constant Pool) або 0 для блоку try ... finally;
· Attributes_count - число атрибутів;
· Attributes - атрибути коду методу. Можуть використовуватися стандартні атрибути LineNumberTable і LocalVariableTable, що містять налагоджувальну інформацію.

Робота JVM

При запуску JVM в якості параметрів їй передаються ім'я класу, з методу main якого буде розпочато виконання програми, а також аргументи командного рядка програми. Спочатку завантажується вказаний клас. Інші класи, використовувані в програмі, завантажуються при першому зверненні до них. Процес завантаження класу складається з декількох етапів:
· Власне завантаження файлу класу (loading). За умовчанням здійснюється за допомогою класу ClassLoader із стандартної бібліотеки Java, проте можна використовувати для користувача завантажувач для зміни способу пошуку файлу;
· Зв'язування (linking). Складається з трьох стадій:
o перевірка (verification) на правильність формату файлу класу і коректність байт-коду (наприклад, на відсутність переходів на середину інструкції),
o підготовка (divparation) - виділення пам'яті для статичних полів класу і заповнення їх нульовими значеннями,
o дозвіл імен (resolution);
· Ініціалізація (initialization) статичних даних початковими значеннями. Включає виклик методу <clinit>, якщо він присутній у класі.
Програма, що виконується JVM, може мати декілька потоків виконання. Реалізація багатопоточності залежить від використовуваного апаратного забезпечення і може бути різною - різні потоки можуть виконуватися на різних процесорах або їм можуть виділятися кванти часу на одному процесорі. JVM має ряд засобів для синхронізації роботи потоків та захисту поділюваних ними даних. Найважливішим із них є механізм блокувань (locks), підтримуваний на рівні системи команд JVM. Кожен об'єкт має асоційований з ним «замок» (lock). Якщо один з потоків «закрив» цей «замок», то жоден інший потік не зможе також його «закрити» до тих пір, поки перший потік його не «відкриє».
JVM визначає кілька віртуальних областей пам'яті, які вона використовує при своїй роботі:
· Регістр PC (program counter), що вказує на поточну позицію виконання в методі. Кожен потік програми має свій регістр PC;
· Стік. Кожен потік має свій власний стек. При вході в метод на вершині стека створюється фрейм, що містить локальні змінні методу і його стек операндів. Розмір саме цих областей вказується полями max_locals і max_stack атрибуту методу Code;
· Купа - область пам'яті, в якій динамічно розміщуються об'єкти класів та масиви. Пам'ять з-під не використовуються більше об'єктів (на які немає посилань) автоматично звільняється так званим складальником сміття;
· Область методів. У неї при завантаженні класів поміщаються байт-код методів, різна інформація про методи і полях. Область методів також містить області констант часу виконання, які зберігають вміст constant pool із завантажених класів;
· Стеки для native-методів.
Розташування та представлення цих областей у фізичній пам'яті може бути різним у різних реалізаціях JVM.
JVM є стекової машиною. Більшість з команд JVM виконують одну з таких дій:
· Зчитують значення з змінної або поля і поміщають його на вершину стека,
· Зберігають значення з вершини стека у змінній або поле,
· Виконують ті чи інші дії над значеннями, взятими з вершини стека, і записують результуюче значення (якщо воно є) на вершину стека,
· Виконують перехід на команду з заданим зсувом щодо поточного значення регістра PC безумовно або залежно від значень, прочитаних з вершини стека.
Будь-яке читання з стека операндів призводить до видалення з нього прочитаного значення. Розмір стека операндів, що вказується як max_stack, розраховується наступним чином: значення типів long і double займають два осередки стека (8 байт), будь-які інші значення - одну (4 байти). Значення типів char, boolean, byte, short зберігаються в одній чотирибайтових осередку. Тут можна відзначити, що в переважній більшості випадків JVM не робить відмінностей між логічними значеннями і цілими числами типу int, для середовища виконання не існує окремого булевского типу (брехні відповідає нульове значення, істині - ненульовий, як правило, одиниця). Однак, в масивах типу boolean [] на кожен елемент виділяється один байт. Існує таке обмеження на байт-код: кожен раз, коли точка виконання досягає будь-якої конкретної позиції в методі, глибина стека повинна бути однаковою, крім того, тип верхніх значень в стеку повинен відповідати необхідному типу видобутих черговий командою значень.
В області локальних змінних на момент початку виконання методу в перших позиціях перебувають фактичні параметри методу, а у випадку методу примірника першу (нульову) позицію займає посилання this на поточний об'єкт. Ніякого розходження в процесі виконання методу між параметрами (навіть посиланням this) і, власне кажучи, локальними змінними не робиться. Так само як і в стеці, значення типу long і double в області локальних змінних займають дві чотирибайтових осередку, значення типів розміром менше 32-х розрядів розширюються до чотирьох байт. У коректному байт-коді повинні виконуватися, зокрема, такі умови: по-перше, типи значень у локальних змінних повинні відповідати необхідним для команд, які звертаються до цих змінним, по-друге, не допускається читання значення змінної до її ініціалізації (привласнення значення ).
Перед викликом методу його фактичні параметри повинні знаходитися на вершині стека; вони стають операндами відповідної команди виклику. При поверненні з методу, за винятком методів, які повертають void, яке значення поміщається на вершину стека.
У процесі виконання програми в результаті виникнення тієї чи іншої помилки або виконання команди athrow може бути згенеровано виняток. При цьому відбувається пошук відповідного обробника виключення (захищеного блоку) в поточному методі, якщо він не знайдений, то в методі, що викликала поточний і т. д. Якщо відповідний обробник знайдений, то управління передається в точку, яка визначається полем handler_pc відповідного запису таблиці exception_table в атрибуті Code методу. Посилання на об'єкт виключення при цьому поміщається на вершину стека. Об'єкт виключення обов'язково повинен належати класу Throwable або класу, похідного від нього.

Система команд JVM.

Перший байт кожної команди JVM містить код операції. Існує кілька типових форматів, які мають більшість команд:
· Тільки код операції,
· Код операції та однобайтовим індекс,
· Код операції та двобайтових індекс,
· Код операції та двухбайтное зсув переходу,
· Код операції та чотирибайтових зсув переходу.
Кілька команд використовують інші формати, серед них дві команди змінного розміру - tableswitch і lookupswitch. Крім того, існує спеціальний префікс wide, який змінює розмір деяких команд, замінюючи однобайтовим індекс локальної змінної двобайтових. У The Java Virtual Machine Specification для кожної команди встановлено своє мнемонічне позначення.
Існує багато груп аналогічних за виконуваному дії команд, які працюють з різними типами даних, наприклад, команди iload, lload, aload, fload, dload виконують функцію завантаження значень відповідних типів з локальних змінних на вершину стека. Реалізація таких команд може бути ідентичною, але він різняться при перевірці коректності байт-коду. Прийняті наступні позначення для типів даних, з якими працюють команди:
· I - int (також byte, short, char і boolean),
· L - long,
· F - float,
· D - double,
· A - посилання на об'єкт або масив.
Крім того, є кілька команд, які працюють з типами char, byte і short.
Можна виділити кілька груп команд за призначенням:
· Команди завантаження і збереження:
o Завантаження локальної змінної на стек: iload, iload _ <n>, lload, lload _ <n>, fload, fload _ <n>, dload, dload _ <n>, aload, aload _ <n>;
o Збереження значення з вершини стека в локальних змінних: istore, istore_ <n>, lstore, lstore_ <n>, fstore, fstore_ <n>, dstore, dstore_ <n>, astore, astore_ <n>;
o Завантаження констант на стек: istore, istore_ <n>, lstore, lstore_ <n>, fstore, fstore_ <n>, dstore, dstore_ <n>, astore, astore_ <n>;
· Арифметичні і логічні команди:
o складання: iadd, ladd, fadd, dadd;
o віднімання: isub, lsub, fsub, dsub;
o множення: imul, lmul, fmul, dmul;
o розподіл: idiv, ldiv, fdiv, ddiv;
o залишок: irem, lrem, frem, drem;
o зміна знака: ineg, lneg, fneg, dneg;
o зрушення і побітові операції: ior, lor, iand, land, ixor, lxor, ishl, ishr, iushr, lshl, lshr, lush;
o порівняння: dcmpg, dcmpl, fcmpg, fcmpl, lcm g;
o інкремент локальної змінної: iinc.
Усі ці команди, за винятком iinc, не мають параметрів. Вони витягають операнди з вершини стека і записують результат на вершину стека. Команда iinc має два операнди - індекс локальної змінної і величину, на яку значення даної змінної має бути змінена;
· Команди перетворення типів:
o розширює: i2l, i2f, i2d, l2f, l2d, f2d;
o звужуюче: i2b, i2c, i2s, l2i, f2i, f2l, d2i, d2l, d2f;
· Команди роботи з об'єктами і масивами:
o створення об'єкта: new;
o створення масиву: newarray (Примітивного типу), anewarray (посилального типу), multianewarray (багатовимірного);
o доступ до полів: getfield, putfield (Для полів примірники), getstatic, putstatic (для статичних полів);
o завантаження елемента масиву на стек: baload (Тип byte), caload (Тип char), saload (Тип short), iaload, laload, faload, daload, aaload;
o збереження значення з вершини стека в елемент масиву: bastore, castore, sastore, iastore, lastore, fastore, dastore, aastore;
o отримання розміру масиву: arraylength;
o перевірка типів: instanceof (Повертає на вершині стека логічне значення) і checkcast (генерує виключення у разі невідповідності типу посилання на вершині стека необхідному типу);
· Команди маніпуляцій зі стеком операндів:
o pop - видалення верхнього елемент стека;
o pop 2 - видалення двох верхніх елемента стека;
o dup, dup 2, dup _ x 1, dup 2_ x 1, dup _ x 2, dup 2_ x 2 - дублювання елементів на вершині стека;
o swap - зміна місцями двох верхніх елементів стека;
· Команди безумовної передачі керування:
o jsr, jsr _ w, ret - виклик підпрограм і повернення з них. Використовуються при компіляції блоку finally;
o goto, goto _ w - Безумовний перехід;
· Команди умовного переходу: ifeq, iflt, ifle, ifne, ifgt, ifge, ifnull, ifnonnull, if _ icmpeq, if _ icmpne, if _ icmplt, if _ icmpgt, if _ icmple, if _ icmpge, if _ acmpeq, if _ acmpne;
· Команди виклику методів:
o invokevirtual - звичайний виклик методу примірника з використанням механізму пізнього зв'язування;
o invokestatic - виклик статичного методу;
o invokeinterface - Виклик методу інтерфейсу в об'єкта, що реалізує даний інтерфейс;
o invokespecial - виклик методу примірника без використання механізму пізнього зв'язування. Використовується для виклику конструкторів, методів суперкласу і private-методів;
· Команди повернення з методу:
o return - повернення з методу, що повертає void;
o ireturn, lreturn, freturn , Dreturn, areturn - повернення значення відповідного типу;
· Команда генерації винятків: athrow;
· Команди синхронізації (механізм блокування):
o monitorenter - Встановити блокування (увійти в критичну секцію);
o monitorexit - звільнити блокування (вийти з критичної секції).

Синтаксис мови асемблера для платформи Java (мови JASM).

Файл вихідного тексту на мові асемблера для платформи Java (мові JASM) представляє собою текстовий файл, рядки якого розділені послідовністю символів з кодами 13 та 10. Ім'я файлу вихідного тексту і його розширення можуть бути будь-якими, однак рекомендується, щоб ім'я збігалося з ім'ям описаного у файлі класу, а розширенням було. Jasm небудь. Jsm. Файл вихідного тексту складається з пропозицій, розділених крапкою з комою. Остання пропозиція може не мати в кінці точки з комою. Коментарі відділяються знаком відсотка і поширюються до кінця рядка. Точки з комою і знаки відсотка всередині рядкових констант, обмежених подвійними лапками, не мають свого спеціального значення. Дві йдуть підряд подвійні лапки всередині строковою константи інтерпретуються як одна лапки в рядку. Будь-які послідовності пробільних символів (пропусків, табуляцій, перекладів рядка і т. д.) інтерпретуються як один пробіл, якщо з обох сторін від них знаходяться символи наступних видів: літери латинського алфавіту, цифри, знак підкреслення, або у протилежному випадку, ігноруються. Виняток становлять пробільні символи в строкових константах та коментарях. Верхній і нижній регістр літер в ідентифікаторах, іменах команд та інших лексемах розрізняється.
Кожен файл вихідного тексту компілюється в один файл класу. Файл вихідного тексту повинен мати таку структуру:
[Модіфікатори_доступа] {class | interface} <імя_класса>;
[Extends <базовий клас>;]
[Implements <інтерфейс_1>, <інтерфейс_2>, ... , <Інтерфейс_n>;]
[Fields;
<Опісанія_полей>
]
[Methods;
<Опісанія_методов>
]
Тут і далі в квадратні дужки поміщені необов'язкові елементи, у фігурні - альтернативні варіанти (розділені вертикальною рискою), в кутові - нетермінальні символи.
Модіфікатори_доступа - це слова public, final, abstract, super, відповідні прапорів прав доступу ACC_PUBLIC, ACC_FINAL, ACC_ABSTRACT, ACC_STATIC. Ці прапори встановлюються в одиницю тоді і тільки тоді, коли в оголошенні класу присутній відповідне ключове слово. Клас може мати кілька різних модифікаторів доступу, розділених пропуском (або будь-який інший послідовністю пробільних символів). Повторення однакових модифікаторів в заголовку одного класу не допускається. Коли клас не має прапора ACC_INTERFACE, у її декларації використовується слово class, інакше використовується ключове слово interface. Всі імена класів і інтерфейсів записуються із зазначенням повного шляху (пакетів, в яких ці класи містяться). Імена пакетів і класу відокремлюються крапкою, наприклад, java.lang.String. В аргументах команд, там, де це необхідно, замість повного імені поточного класу можна використовувати символ «@». Якщо базовий клас не зазначений (пропозиція extends відсутній), то за замовчуванням використовується java.lang.Object. Інтерфейси - предки описуваного інтерфейсу записуються в секції implements.
Для ідентифікаторів - назв пакунків, класів, полів і методів, а також міток, використовуються наступні правила: вони повинні складатися з літер латинського алфавіту будь-якого регістру (регістр має значення), знаків підкреслення і цифр, причому не повинні починатися з цифри. Настійно не рекомендується використання ідентифікаторів, що збігаються з ключовими словами мови Java, що може привести до некоректної компіляції, або інтерпретації файлів класів JVM. Два спеціальних імені <init> і <clinit> також розглядаються як ідентифікатори.
Найпростіший приклад опису класу, що не має полів і методів:
public abstract class some_package.SomeClass;
% Це коментар
extends
some_package.nested_package1.BaseClass;
implements% і це коментар
some_package.Interface1, some_package.nested_package2.Interface2;
Опис поля має наступний вигляд:

[Модіфікатори_доступа] <ім'я_поля>: <тіп_поля> [= <початкове значення>];
Тут модіфікатори_доступа - наступні слова: public, protected, private, final, static, transient, volatile, відповідні прапорів доступу поля ACC_PUBLIC, ACC_PROTECTED, ACC_PRIVATE, ACC_FINAL, ACC_STATIC, ACC_TRANSIENT, ACC_VOLATILE. Повторення однакових модифікаторів доступу в оголошенні одного поля і поєднання модифікаторів, відповідні забороненим сполученням прапорів доступу (див. The Java Virtual Machine Specification), викликають помилку часу компіляції. Поля інтерфейсу обов'язково повинні бути оголошені з модифікаторами public, static і final. Ім'я_поля - коректний ідентифікатор. Тіп_поля - ім'я класу або ім'я примітивного типу (імена примітивних типів збігаються з відповідними ключовими словами мови Java - byte, short, int, long, char, float, double, boolean). Початкове значення може бути поставлено лише для статичного поля, якщо вони зазначені, то у поля створюється атрибут ConstantValue. Початкове значення може бути целочисленной, речової, логічної або символьної константою для полів відповідних типів. Речова константа може бути записана в десятковій або експоненціальної формі, у форматі дійсних чисел, прийнятому в Java. Символьні константи полягають в апострофи. Крім того, може бути вказано код символу як звичайне ціле число. Логічні константи записуються у вигляді слів true і false. Приклади описів полів:
public final static COUNT: int = 10;
static c: char = 'A';
static c1: char = 13;
private volatile m_flag: boolean;
protected m_list: java.util.ArrayList;
Опис методу в загальному випадку має вигляд:
[<Модіфікатори_доступа>] <імя_метода> (<тіп_параметра_1>, <тіп_параметра_2>, ..., <тіп_параметра_n>): <тіп_возвращаемого_значенія> [throws <класс_ісключенія_1>, ... , <Класс_ісключенія_n>];
% Для методів з модифікатором abstract нижележащих частина опису
% Відсутня
maxstack <число>;
maxlocals <число>;
[<Метка_1>:]
<Команда_1>;
...
[<Метка_n>:]
<Команда_n>;
[
protected_blocks;
{<Класс_ісключенія> | finally} <мітка>: <мітка>> <мітка>;
...
{<Класс_ісключенія> | finally} <мітка>: <мітка>> <мітка>;
]
end;
Тут модіфікатори_доступа - ключові слова: public, protected, private, static, final, abstract, які відповідають наступним прапорів доступу методу: ACC_PUBLIC, ACC_PRIVATE, ACC_PROTECTED, ACC_STATIC, ACC_FINAL, ACC_ABSTRACT. Повторення однакових модифікаторів доступу в заголовку одного методу і поєднання модифікаторів, відповідні забороненим сполученням прапорів доступу (див. The Java Virtual Machine Specification), викликають помилку часу компіляції. Методи інтерфейсу обов'язково повинні бути оголошені з модифікаторами public і abstract. Імя_метода - коректний ідентифікатор, або <init> або <clinit> для конструкторів і статичних ініціалізаторов. Типи параметрів і тип значення повинні бути іменами класів, або іменами примітивних типів, прийнятими в мові Java (byte, short, int, long, char, float, double, boolean). Крім того, тип значення може бути вказаний як void. Після ключового слова throws в заголовку методу можуть бути перераховані через кому імена класів винятків, що генеруються методом. Для методів, які не є абстрактними, після заголовка обов'язково записуються пропозиції maxstack і maxlocals, в яких вказується розмір стека операндів і області локальних змінних методу (у чотирибайтових осередках). Потім слід код методу у вигляді послідовності команд, розділених крапками з комами. Кожній команді може передувати мітка, відокремлювана від неї двокрапкою. Мітка повинна бути коректним. Кожна команда може мати не більше однієї мітки, і кожна мітка повинна передувати тій чи іншій команді. Однак, є спеціальна псевдокоманди none, для якої не генерується якийсь код (порожня команда). Її можна використовувати, якщо необхідно розташувати більше однієї мітки в однієї команди або помістити мітку в кінець методу. Після ключового слова protected_blocks можуть бути перераховані захищені блоки (обробники винятків) методу. Опис кожного захищеного блоку складається з імені класу виключення або ключового слова finally і трьох міток, розділених символами ':' і '>'. Перша з них вказує на початок захищеного блоку, друга на його кінець, третя - на місце в коді методу, куди переходить управління при виникненні виключення або при виході із захищеного блоку в разі finally.
Використовувані в коді мнемонічні імена команд збігаються з прийнятими в The Java Virtual Machine Specification. Проте, як виняток, префікс wide не розглядається як окрема команда, замість цього команди, його мають, записуються як wide_ <імя_команди>. Формати запису команд:
· <Мнемонічне _ ім'я>; Таку форму мають наступні команди: aaload, aastore, aconst_null, aload_0, aload_1, aload_2, aload_3, areturn, arraylength, astore_0, astore_1, astore_2, astore_3, athrow, baload, bastore, caload, castore, d2f , d2i, d2l, dadd, daload, dastore, dcmpg, dcmpl, dconst_0, dconst_1, ddiv, dload_0, dload_1, dload_2, dload_3, dmul, dneg, drem, dreturn, dstore_0, dstore_1, dstore_2, dstore_3, dsub, dup, dup2 , dup2_x1, dup2_x2, dup_x1, dup_x2, f2d, f2i, f2l, fadd, faload, fastore, fcmpg, fcmpl, fconst_0, fconst_1, fconst_2, fdiv, fload_0, fload_1, fload_2, fload_3, fmul, fneg, frem, freturn, fstore_0 , fstore_1, fstore_2, fstore_3, fsub, i2b, i2c, i2d, i2f, i2l, i2s, iadd, iaload, iand, iastore, iconst_0, iconst_1, iconst_2, iconst_3, iconst_4, iconst_5, iconst_m1, idiv, iload_0, iload_1, iload_2 , iload_3, imul, ineg, ior, irem, ireturn, ishl, ishr, istore_0, istore_1, istore_2, istore_3, isub, iushr, ixor, l2d, l2f, l2i, ladd, laload, land, lastore, lcmp, lconst_0, lconst_1 , ldiv, lload_0, lload_1, lload_2, lload_3, lmul, lneg, lor, lrem, lreturn, lshl, lshr, lstore_0, lstore_1, lstore_2, lstore_3, lsub, lushr, lxor, monitorenter, monitorexit, nop, pop, pop2, return , saload, sastore, swap;
· <Мнемонічне _ ім'я> <мітка>; Таку форму мають команди переходу: goto, goto_w, if_acmpeq, if_acmpne, if_acmpge, if_acmpgt, if_icmple, if_icmplt, if_icmpne, ifeq, ifge, ifgt, ifle, iflt, ifne, ifnonull, ifnull, jsr, jsr_w;
· <Мнемонічне _ ім'я> <ціле число>; Число повинно задовольняти обмеженням конкретної команди: aload, astore, dload, dstore, fload, fstore, iload, istore, lload, lstore, ret, bipush, sipush, wide_aload, wide_astore, wide_dload, wide_dstore, wide_fload, wide_fstore, wide_iload, wide_istore, wide_lload, wide_lstore, wide_ret;
· <Мнемоніческое_імя> {<полное_імя_класса >|@}::< ім'я_поля>: <тіп_поля>; Тіп_поля - ім'я примітивного типу, прийняте в мові Java, або ім'я класу. Команди: getfield, putfield, getstatic, putstatic;
· <Мнемоніческое_імя> {<полное_імя_класса >|@}::< імя_метода> (<тіп_параметра_1>, ..., <тіп_параметра_ n>): <тіп_возвращаемого значення>; Тут типи параметрів і значення, що повертається - імена примітивних типів, прийняті в мові Java, імена класів, або (тільки для значення, що повертається) void. Команди: invokespecial, invokestatic, invokevirtual;
· <Мнемоніческое_імя> <полное_імя_класса>; Такий формат мають наступні команди: anewarray, checkcast, instanceof, new;
· <Мнемоніческое_імя> <целое_число_индекс_переменной> <целое_чісло>; Команди: iinc, wide _ iinc;
· <Мнемоніческое_імя> <тип> <константа>; - команди ldc, ldc _ w, ldc _2 w. Тут тип - int, float, string (для ldc, ldc_w), double, long (для ldc_2w). Константа повинна мати відповідний тип (цілі числа записуються звичайним способом, речові - у десятковій або експоненціальної формі, в форматі, прийнятому в Java, рядки записуються в подвійних лапках, при цьому дві подвійні лапки всередині рядка інтерпретуються як одна лапки в рядку);
· Invokeinterface <імя_інтерфейса>:: <імя_метода> (<тіп_аргумента_1>, ..., <тіп_аргумента_2>): <тіп_возвращаемого_значенія> <целое_чісло>; - типи - аналогічно іншим командам виклику методів;
· Multianewarray <полное_імя_класса> <чісло_ізмереній>;
· Newarray {boolean | char | float | double | byte | short | int | long};
· Tableswitch <Чісло_1>: <чісло_ n> default: <метка_0> <чісло_1>: <метка_1> ... <Чісло_ n>: <метка_ n>; Тут числа чісло_1 ... чісло_ n повинні бути послідовними цілими числами. При цьому числа, зазначені відразу після мнемонічного імені команди, повинні збігатися з кордонами діапазону чисел, для яких зазначені мітки переходу. lookupswitch default: <метка_0> <чісло_1>: <метка_1> ... <Чісло_ n>: <метка_ n>; Тут серед чисел, для яких зазначені мітки переходу, не повинно бути однакових. Ці числа повинні бути цілими, вони не зобов'язані бути впорядковані за зростанням, сортування відбувається при обробці команди компілятором.

Тестові приклади.

Для тестування компілятора використовувалися, зокрема, наступні приклади:

1.

% Файл Summator.jsm
public class Summator;
fields;
private m_i: int;
methods;
% Конструктор. Заносить у поле m_i ціле число, що міститься в рядку,
% Переданої в якості параметра. У разі, якщо рядок не містить
% Правильного запису цілого числа, або це число від'ємне,
% То виводиться повідомлення про помилку.
public <init> (java.lang.String): void;
maxstack 4;
maxlocals 2;
aload_0;% this
dup;
invokespecial java.lang.Object:: <init> (): void;
aload_1;% arg1
begin_try:
invokestatic java.lang.Integer:: parseInt (java.lang.String): int;
dup;
iconst_0;
if_icmpge end_try;
new java.lang.Exception;
dup;
invokespecial java.lang.Exception:: <init> (): void;
athrow;
end_try:
putfield @:: m_i: int;
return;
exception:
pop;
getstatic java.lang.System:: out: java.io.PrintStream;
ldc string "Invalid argument";
invokevirtual java.io.PrintStream:: println (java.lang.String): void;
return;
protected_blocks;
java.lang.Exception
begin_try: end_try> exception;
end;
% Повертає суму натуральних чисел від 1 до m_i.
public getSum (): int;
maxstack 3;
maxlocals 2;
iconst_0;
istore_1;
aload_0;% this
getfield @:: m_i: int;
loop:
dup;
iload_1;% result
iadd;
istore_1;% result
iconst_1;
isub;
dup;
iconst_0;
if_icmpgt loop;
pop;
iload_1;% result
ireturn;
end;
% Повертає значення поля m_i
public getI (): int;
maxstack 1;
maxlocals 1;
aload_0;% this
getfield @:: m_i: int;
ireturn;
end;

2.

% Файл Switches.jsm
public class Switches;
fields;
methods;
% Обидва методи функціонально еквівалентні наступної функції, написаної на Java.
% Static int function (int i) {
% Switch (i) {
% Case 1: return 2;
% Case 2: return -1;
% Default: return 0;
%}
%}
public static lookup (int): int;
maxstack 1;
maxlocals 1;
iload_0;
lookupswitch
default: l_def
1: l_1
2: l_2;
l_def:
iconst_0;
ireturn;
l_1:
iconst_2;
ireturn;
l_2:
iconst_m1;
ireturn;
end;

public static table (int): int;
maxstack 1;
maxlocals 1;
iload_0;
tableswitch 1:2
default: l_def
1: l_1
2: l_2;
l_def:
iconst_0;
ireturn;
l_1:
iconst_2;
ireturn;
l_2:
iconst_m1;
ireturn;
end;

3.

Наступний приклад являє собою програму, що складається з 5 класів.
%------------------------------------------------- ------------%
% Файл Figure.jsm
public interface Figure;
methods;
public abstract getArea (): double;
%------------------------------------------------- ------------%
%------------------------------------------------- ------------%
% Файл Circle.jsm
public class Circle;
implements Figure;
fields;
private m_radius: double;
methods;
public <init> (double): void;
maxstack 4;
maxlocals 3;
aload_0;
invokespecial java.lang.Object:: <init> (): void;
dload_1;
dconst_0;
dcmpg;
ifge l_endif;
new java.lang.IllegalArgumentException;
dup;
invokespecial java.lang.IllegalArgumentException:: <init> (): void;
athrow;
l_endif:
aload_0;
dload_1;
putfield @:: m_radius: double;
return;
end;
public getArea (): double;
maxstack 4;
maxlocals 1;
aload_0;
getfield @:: m_radius: double;
aload_0;
getfield @:: m_radius: double;
dmul;
ldc2_w double 3.14159265;
dmul;
dreturn;
end;
%------------------------------------------------- ------------%
%------------------------------------------------- ------------%
% Файл Rectangle.jsm
public class Rectangle;
implements Figure;
fields;
private m_a: double;
private m_b: double;
methods;
public <init> (double, double): void;
maxstack 4;
maxlocals 5;
aload_0;
invokespecial java.lang.Object:: <init> (): void;
dload_1;
dconst_0;
dcmpl;
iflt l_error;
dload_3;
dconst_0;
dcmpl;
ifge l_endif;
l_error:
new java.lang.IllegalArgumentException;
dup;
invokespecial java.lang.IllegalArgumentException:: <init> (): void;
athrow;
l_endif:
aload_0;
dload_1;
putfield @:: m_a: double;
aload_0;
dload_3;
putfield @:: m_b: double;
return;
end;
public getArea (): double;
maxstack 4;
maxlocals 1;
aload_0;
getfield @:: m_a: double;
aload_0;
getfield @:: m_b: double;
dmul;
dreturn;
end;
%------------------------------------------------- ------------%
%------------------------------------------------- ------------%
% Файл Square.jsm
public class Square;
extends Rectangle;
methods;
public <init> (double): void;
maxstack 5;
maxlocals 3;
aload_0;
dload_1;
dload_1;
invokespecial Rectangle:: <init> (double, double): void;
return;
end;
%------------------------------------------------- ------------%
%------------------------------------------------- ------------%
% Файл MainClass.jsm
public class MainClass;
methods;
public <init> (): void;
maxstack 1;
maxlocals 1;
aload_0;
invokespecial java.lang.Object:: <init> (): void;
return;
end;
public static main (java.lang.String []): void;
maxstack 8;
maxlocals 7;
iconst_3;
anewarray Figure;
astore_1;
aload_1;
iconst_0;
new Circle;
dup;
ldc2_w double 10;
invokespecial Circle:: <init> (double): void;
aastore;
aload_1;
iconst_1;
new Rectangle;
dup;
dconst_1;
ldc2_w double 2;
invokespecial Rectangle:: <init> (double, double): void;
aastore;
aload_1;
iconst_2;
new Square;
dup;
ldc2_w double 3;
invokespecial Square:: <init> (double): void;
aastore;
dconst_0;
dstore_2;
iconst_0;
istore 4;
l_50:
iload 4;
aload_1;
arraylength;
if_icmpge l_75;
dload_2;
aload_1;
iload 4;
aaload;
invokeinterface Figure:: getArea (): double, 1;
dadd;
dstore_2;
iinc 4, 1;
goto l_50;
l_75:
new java.io.BufferedReader;
dup;
new java.io.InputStreamReader;
dup;
getstatic java.lang.System:: in: java.io.InputStream;
invokespecial java.io.InputStreamReader:: <init> (java.io.InputStream): void;
invokespecial java.io.BufferedReader:: <init> (java.io.Reader): void;
astore 4;
l_50:
aload 4;
invokevirtual java.io.BufferedReader:: readLine (): java.lang.String;
invokestatic java.lang.Double:: parseDouble (java.lang.String): double;
dstore 5;
getstatic java.lang.System:: out: java.io.PrintStream;
dload 5;
dload_2;
dadd;
invokevirtual java.io.PrintStream:: println (double): void;
l_114:
goto l_127;
l_117:
astore 4;
getstatic java.lang.System:: out: java.io.PrintStream;
ldc string "Error";
invokevirtual java.io.PrintStream:: println (java.lang.String): void;
l_127:
return;
protected_blocks;
java.io.IOException l_75: l_114> l_117;
end;
%------------------------------------------------- ------------%
Дана програма функціонально еквівалентна наступному коду на Java (асемблерний варіант створений на основі дизасемблювати за допомогою утиліти javap Java-програми):
//------------------------------------------------ -----------//
public interface Figure {
double getArea ();
}
//------------------------------------------------ -----------//
//------------------------------------------------ -----------//
public class Circle implements Figure {
private double m_radius;
public Circle (double radius) {
if (radius <0)
throw new IllegalArgumentException ();
m_radius = radius;
}
public double getArea () {
return m_radius * m_radius * Math.PI;
}
}
//------------------------------------------------ -----------//
//------------------------------------------------ -----------//
public class Rectangle implements Figure {
private double m_a;
private double m_b;
public Rectangle (double a, double b) {
if (! ((a> = 0) & & (b> = 0)))
throw new IllegalArgumentException ();
m_a = a;
m_b = b;
}
public double getArea () {
return m_a * m_b;
}
}
//------------------------------------------------ -----------//
//------------------------------------------------ -----------//
public class Square extends Rectangle {
public Square (double a) {
super (a, a);
}
}
//------------------------------------------------ -----------//
//------------------------------------------------ -----------//
import java.io. *;
public class MainClass {
public static void main (String [] args) {
Figure [] figures = new Figure [3];
figures [0] = new Circle (10);
figures [1] = new Rectangle (1, 2);
figures [2] = new Square (3);
double sum = 0;
for (int i = 0; i <figures.length; i + +)
sum + = figures [i]. getArea ();
try {
BufferedReader br = new BufferedReader (new InputStreamReader (System.in));
double d = Double.parseDouble (br.readLine ());
System.out.println (d + sum);
} Catch (IOException exc) {
System.out.println ("Error!");
}
}
}
//------------------------------------------------ -----------//

Проектування і реалізація компілятора.

Для реалізації компілятора був використаний мова програмування Java (JDK версії 1.5). Це дозволяє запускати даний компілятор на будь-якій платформі, для якої існує віртуальна машина Java v 1.5.
При кожному запуску компілятора обробляється один файл вихідного тексту на мові асемблера для платформи Java. Компілятор приймає два аргументи командного рядка: ім'я файлу вихідного тексту і ім'я створюваного файлу класу (явне вказівку розширення. Class обов'язково). У випадку, якщо вихідний файл вже існує, він перезаписується без попередження. У випадку синтаксичної або іншої помилки на консоль виводиться відповідне повідомлення.
Можна виділити кілька основних етапів компіляції (проходів):
· Читання вихідного файлу. При цьому він розбивається на речення, розділені крапками з комами, також викидаються коментарі;
· Розбір вихідного тексту. При послідовному переборі списку пропозицій виділяються синтаксичні конструкції. При розборі використовується лексичний аналізатор, що розділяє пропозиції на лексеми. На підставі виділених синтаксичних конструкцій генерується внутрішнє представлення програми, що має вигляд деревоподібної структури даних, коренем якої є представлення класу в цілому, вузлами першого рівня - об'єкти, які відповідають методам, полях, елементам Constant Pool і т. д.;
· Заміна номерів міток відповідними зміщеннями в коді методів;
Генерація байт-коду методів як масивів байт;
Генерація файлу класу на підставі внутрішнього представлення програми.
Цей поділ є умовним і не означає суворої часовій послідовності. Третій і четвертий етапи, по суті справи, є частинами другого етапу.
Діаграма пакетів проекту зображена на рис. 1.

Рис. 1.
Діаграма пакетів
Кореневий пакет проекту має ім'я jasm. Він містить клас MainClass, метод main () якого є точкою входу в програму, і класи SyntaxErrorException і InternalCompilerErrorException, успадковані від java.lang.Exception і представляють помилки, які можуть виникнути в процесі компіляції. Пакет compiler містить класи, відповідальні за розбір вихідного тексту, причому класи, які працюють з кодом методів, містяться у вкладеному пакеті code_compiler. Пакет structures містить класи, з об'єктів яких складається внутрішнє проміжне представлення програми, а також деякі допоміжні класи. Він має три вкладених пакету: commands, consts і attributes, класи з яких описують, відповідно, команди байт-коду, елементи Constant Pool і атрибути полів і методів. У пакет commands у свою чергу вкладені пакет command_formats, що містить базові абстрактні класи для команд типових форматів, пакети, що містять класи, що представляють команди кожного з типових форматів, а також пакет special для класів, що представляють команди, що мають особливий формат.
Більшість класів з пакету structures входять в ієрархію, коренем якої є інтерфейс IStructure, що містить два методи int getLength () і byte [] getContent (), що дозволяють отримати, відповідно, розмір, який займе структура при запису у вихідний файл, і масив байт, якими вона представляється у вихідному файлі. Даний інтерфейс не використовується для поліморфного виклику методів, він грає лише роль структурування програми. Основні класи пакету structures зображені на діаграмі на рис. 2.

Рис. 2.
Класи пакету jasm. Structures.
Генерований клас як ціле представляється об'єктом класу ClassFile, який містить у своїх полях посилання на об'єкти класів ConstantPool, FiledInfo і MethodInfo, що описують область констант, поля і методи створюваного класу. Сам клас ClassFile інтерфейс IStructure не реалізує. Серед його членів слід відзначити метод writeToFile (), що створює файл класу на підставі інформації, що міститься в об'єкті.
Цей компілятор можна створювати атрибути методів Code, Exceptions і атрибут поля ConstantValue, які представляються класами, похідними від AttributeStructure. Відзначу, що об'єкт класу CodeAttribute містить байт-код методу вже у вигляді масиву байт, а не у вигляді об'єктів класів, що представляють окремі команди (похідних від Command), списки таких об'єктів використовуються лише в процесі обробки коду методу.
Абстрактний клас Command додатково до методів інтерфейсу IStructure містить абстрактний метод byte getByte (int n), який повинен повертати байт із заданим номером у команді. Ще один метод changeOffset має порожню реалізацію, але перевизначається в класах-нащадках, відповідних командам переходу. Він використовується для заміни номерів міток зміщеннями на третьому етапі компіляції. Безпосередніми спадкоємцями класу Command є класи, відповідні типовим форматам команд (абстрактні) і команд, що мають унікальні формати. Більшість команд представляються класами, який наслідував класи типових форматів. Імена класів команд мають вигляд C_xxx, де xxx - мнемонічне ім'я команди. Порожній команді none відповідає клас NoCommand.
Клас ConstantPool містить як загальний список для всіх типів констант, що зберігає об'єкти класу CpInfo (базовий тип для класів, що представляють різні види констант), так і списки для констант окремих типів, що містять індекси елементів в першому списку. Ці списки описуються класами, вкладеними в клас ConstantPool. Така структура використовується для того, щоб при додаванні константи можна було швидко перевірити, чи не є присутнім вже ідентичний елемент у ConstantPool (ця перевірка проводиться не для всіх типів констант). Для кожного типу констант в класі ConstantPool існує свій метод додавання, який повертає індекс доданого (або знайденого існуючого) елемента в загальному списку. Серед спадкоємців CpInfo є спеціальний клас CpNone, який відповідає порожній структурі, що вставляється після констант типу Long і Double тому що наступний за ними індекс вважається невживаною.
За процес компіляції відповідає пакет compiler, який містить такі класи:
· Source - виконує функцію виділення пропозицій у вихідному тексті. Конструктор цього класу приймає як параметр ім'я файлу, вміст якого розбивається на пропозиції і заноситься до колекції типу ArrayList <String>. Метод String nextStatement () при кожному виклику повертає чергову пропозицію;
· StringParser - виконує функцію розділення рядків на лексеми. Кожен об'єкт цього класу відповідає одній оброблюваної рядку.
· TokenRecognizer - має статичні методи, що дозволяють визначити, чи є деяка рядок ключовим словом або коректним ідентифікатором. Об'єкти цього класу ніколи не створюються;
· DescriptorCreator - містить статичні методи для створення дескрипторів типів, полів і методів з рядків, що містять запис типів і сигнатур, використовувану в мові. Екземпляри класу також не створюються;
· ClassHeaderParser, FieldDeclarationParser, MethodHeaderParser - використовуються при аналізі заголовка класу, описів полів і заголовків методів. Об'єкти цих класів зберігають у собі інформацію, витягнуту з аналізованого в конструкторі класу пропозиції;
· Code_compiler.CodeCompiler - здійснює аналіз коду методу і генерацію байт-коду (включаючи третій і четвертий етапи компіляції). Даний процес буде розглянуто детальніше нижче;
· Code_compiler.CommandCompiler - аналізує команди у вихідному коді методу і створює об'єкти класів-нащадків Command;
· Code_compiler.Label - представляє мітку в коді методу;
· Code_compiler.LabelTable - таблиця міток методу. Містить імена міток, номери відповідних їм рядків і зміщення команд.
· SourceAnalyser - займає центральне місце у процесі аналізу вихідного тексту. Конструктор даного класу приймає як параметр об'єкт класу Source. При виклику методу analyse () відбувається аналіз вихідного коду і генерується проміжне представлення програми у вигляді описаної вище структури. У процесі аналізу використовуються класи StringParser, ClassHeaderParser, FieldDeclarationParser, MethodHeaderParser, CodeCompiler та ін Даний метод повертає об'єкт класу ClassFile.
Клас MainClass містить єдиний метод main, що є точкою входу в програму. Тут спочатку створюється об'єкт класу Source, який передається для обробки об'єкту класу SourceAnalyser, потім у повернутого методом SourceAnalyser.analyse () об'єкта класу ClassFile викликається метод writeToFile, який і генерує файл класу, що є результатом роботи компілятора. Всі перераховані операції укладені в блок try / catch, що перехоплює будь-які виключення, у разі виникнення яких на консоль виводиться відповідне повідомлення і процес компіляції завершується. Діаграма, в спрощеному вигляді показує цей процес, зображена на рис. 3.

Рис. 3.
Обробка вихідного файлу.

Розглянемо докладніше процес компіляції коду методу. Після обробки заголовка методу за допомогою класу MethodHeaderParser, у разі, якщо метод не є абстрактним, у методі SourceAnalyser. Analyse () зчитуються пропозиції maxstack і maxlocals. Потім зчитуються і заносяться в масиви пропозиції, що містять команди і опису захищених блоків. Ці масиви, а також посилання на об'єкт ConstantPool, що представляє область констант класу, передаються в якості параметрів конструктору класу CodeCompiler. У створеного об'єкта CodeCompiler викликається метод compile (), який повертає об'єкт класу CodeAttribute, що описує атрибут Code, що містить байт-код методу. При цьому відбуваються такі процеси. У конструкторі класу CodeCompiler з рядків, що містять команди, виділяються імена міток, які зберігаються в об'єкті класу LabelTable. Потім обробляється список рядків, що описують захищені блоки. У методі CodeCompiler.compile () виконуються такі операції. Спочатку за допомогою об'єкту класу CommandCompiler для кожної команди створюється об'єкт відповідного класу. При цьому одночасно для команд, при яких є мітка, в об'єкті LabelTable зберігається інформація про усунення мітки щодо початку методу. Як в описах захищених блоків, так і в об'єктах, що відповідають командам переходу, на закінчення момент цього кроку замість зсувів переходу, містяться порядкові номери команд, при яких розташовані відповідні мітки. Заміна їх на дійсні зміщення проводиться на останньому кроці за допомогою методів LabelTable.changePC () і Command.changeOffset ().

Висновок.

Технологія Java орієнтована на використання однієї мови програмування. Система типів даних та інші особливості мови Java тісно пов'язані з функціонуванням JVM та форматом файлу класу. Однак, існує відкрита специфікація, що дозволяють створювати як власні реалізації JVM, так і альтернативні засоби розробки. З її використанням мною розроблена мова JASM, що представляє собою мову асемблера для платформи Java, який дозволяє створювати файли класів, які використовують значну частину можливостей JVM, і реалізований його компілятор.

Використана література.

1. Гріс, Д. Конструювання компіляторів для цифрових обчислювальних машин. М., «Світ», 1975.
2. Еккель, Б. Філософія JAVA. СПб. 3-тє вид.: Пітер, 2003.
3. Tim Lindholm, Frank Yellin. The Java Virtual Machine Specification Second Edition. Sun Microsystems Inc. 1999.
Додати в блог або на сайт

Цей текст може містити помилки.

Програмування, комп'ютери, інформатика і кібернетика | Курсова
113.6кб. | скачати


Схожі роботи:
Використання JAVA-технологій для розробки графічних додатків
Використання JAVA технологій для розробки графічних додатків
Використання скриптів Java у документі Опис основних тегів для введення скриптів у документ
Основні платформи ЕОМ
Серверні платформи RISCUNIX
Західно-Європейська та Скіфська платформи
Асемблер 2
Асемблер
Асемблер Завдання 4 - вар2
© Усі права захищені
написати до нас