воскресенье, 22 января 2012 г.

ТЯП часть 4. Генерация кода

Немного отсебятины: мы написали почти рабочую генерацию за ~ 3 дня(причём после экзамена) и написана она весьма криво. Так что если будете заимствовать, делайте это с умом.


Что нужно сделать на последнем этапе: из того, что есть(атрибутированное дерево + таблицы, полученные на семантике) необходимо написать байт-код для каждого класса.
Мы просто скопировали наш проект с семантики на qt и постепенно начали добавлять функции для генерации кода.


Сначала я по готовому примеру сделал генерацию скелета файла(смотрим внимательно в методичку, потом genFuncs.cpp и .h). Заполнение констант задача весьма тривиальная, поэтому всё сводилось к написанию кода для методов, а в частности аттрибута Code.


Сразу хочу сказать, что в методичке было написано следущее: все команды длиной > 1 байта необходимо переворачивать(менять порядок байтов). Мы этого не делали, но запись в файл осуществляли немного изменённым способом.


Внимание!
Часто возникают ошибки с непонятно откуда взявшимися байтами! 90% из них связано с текстовым режимом записи в файл. Для правильной генерации используйте только бинарный режим! В нашем проекте этот вопрос решён использованием QDataStream.


Для просмотра нагенерированного кода мы использовали утилиту JBE(Java ByteCodeEditor).


Проблемы начались в принципе сразу).
Во-первых, запускать пустой
void main()
{
}
машина отказывалась, хотя код компилировался.
Выяснилось, что
а) Главная функция должна быть в классе(на этапе семантики мы добавили все свободные функции в универсальный класс)
б)  Главная функция  должна иметь имя main(очевидно)
в)  Главная функция должна иметь дескриптор "([Ljava/lang/String;)V" , т.е. должна принимать массив("[") строк("Ljava/lang/String;") и возвращать void("V")).
г) Главная функция должна быть статической(вспоминаем правила работы с локальными переменными в статических методах из предыдущей статьи). 


Итак, преобразовав функцию в:
void main(NSString arr[10])
{
}
код заработал.


В коде метода я просто вписывал _RETURN(всем советую проверять типы возвращаемого значения на этапе генерации и, если там void, записывать _RETURN самостоятельно; в противном случае java при работе выдаст ошибку).


Затем мы попробовали присваивать переменным значения и всё вроде бы работало и компилировалось, но хотелось на всё это посмотреть. Наступила пора писать методы для вывода.


В общем случае все эти методы объединяются в библиотеку времени исполнения, так называемую RTL. 
У нас она состояла из одного класса, в котором было несколько методов(ввод\вывод числа, вывод строки). 
Подробнее об RTL должны написать мои однокурсники.


Класс для методов ввода\вывода пишется на Java (в NetBeans, например), затем компилируется через javac.exe <имя .java> и мы получаем class-файл, неотличимый от нашего.
Мы назвали его ___BaseIO___.


Чтобы вызвать метод ввода-вывода(а методы у нас статические), нам нужно просто указать имя класса, у которого будет вызываться метод и сам метод(всё через константы). Так как мы с самого начала не писали специальных условий для функций ввода вывода, нам пришлось дописать эту часть на этапе семантики - как только мы встречали функцию, у которой имя совпадало с geti\puti\puts, мы писали в таблицу нужные константы, а на этапе генерации кода обрабатывали их как обычные вызовы методов.
Если всё прошло хорошо, то вывод работает)


Дальше выяснился ещё один момент, весьма неприятный.
Чтобы создать объект и работать с ним, нужно вызывать метод <init>.
Он работает по сути как конструктор, при этом вызывая родительский метод инициализации.
В Java все объекты без родителя наследуются от java/lang/Object.
Так как мы не поддерживали разные конструкторы, то пришлось опять лезть в семантику и всё там ломать. Пришлось перед семантикой добавлить интерфейс метода <init> и его реализацию - [super <init>] для каждого класса.  Здесь стоит отметить, что чтобы вызвать родительский метод, нужно использовать не обычный код вызова методов INVOKE_VIRTUAL, а INVOKE_SPECIAL.
Так же необходимо было записать данные об этом методе в таблицу констант(вызывается ведь метод parent'а).


Сама идея лезть в семантику не ахти, потому что у нас она проверяла методы\поля не только у себя, но и у родителя, а откуда она знает, какие методы и поля у Object'а?
Ну кое-как мы её всё-таки перестроили.
На этапе генерации кода метод <init> уже обрабатывался как обычно.


Что здесь стоит отметить:
Если у вас в коде встречается строчка new Object(), то
а) необходимо создать объект
б) необходимо вызвать у объекта метод <init> специальной командой INVOKE_VIRTUAL!


Наверное о генерации можно сказать многое, но я что-то уже устал и скажу вот что:
в день сдачи, утром всё вылетало и ничего не работало) За полдня мы всё исправили и в итоге не работали только массивы объектов и ещё какая-то ерунда.


Так что всё в ваших руках и не затягивайте с этим делом, потом будет очень неприятно!
Всем кто осилил - спасибо!
Надеюсь, что писал это не зря.


Ссылка на репозиторий
http://code.google.com/p/vstu-objective-c/
Папка с генерацией 
trunk/Gen
RTL
trunk/Gen/SemanticAnalyzer/classes/___BaseIO___.java
Файлы по генерации 
genfuncs.h
genfuncs.cpp

Комментариев нет:

Отправить комментарий