разработка компилятора Forth для Windows на Delphi
Пользователи, просматривающие топик: none
|
Зашли как: Guest
|
Имя |
Сообщение |
<< Старые топики Новые топики >> |
|
|
разработка компилятора Forth для Windows на Delphi - 2007-03-21 14:42:32.650000
|
|
|
kufal
Сообщений: 1
Оценки: 0
Присоединился: 2007-03-21 14:33:30.190000
|
Компилятор Forth для Windows (разработка на Delphi) Книги и брошюры которые нужно полистать и почитать что бы понять как работает компилятор и разобраться в исходном коде на Delphi: 1. Г. Шилдт "Теория и практика C++" Глава 10. Реализация языковых интерпретаторов на C++. Здесь можно прочитать как сделать интерпритатор для языка BASIC, что такое токен и как делается разбор выражений (это будет интересно если делать компилятор для языков BASIC, C, Pascal в Forth используется польская форма записи выражений) 2. Д. Хендрикс "Компилятор языка Си для микроЭВМ" Это не совсем компилятор - это конвертор с языка Си на язык Ассемблера, потом конечно это можно откомпилировать в коды для микропроцессора, тем более в книге есть исходный код компилятора, но сделано это не для i386. 3. В. Юров "Assembler. Специальный справочник" Здесь есть информация о структуре EXE-файла, но лучше использовать её просто для знакомства, а самое главное что здесь есть это - "команды микропроцессора Pentium III". 4. А. Ю. Бураго, В. А. Кириллин, И. В. Романовский "Форт - язык для микропроцессоров" Именно по мотивам этой брошюры сделан компилятор Forth (избранные главы смотрите в приложении) 5. Статья из журнала "Системный Администратор" 06.2004, Крис Касперски "Путь Воина - Внедрение в PE/COFF - файлы" Очень полезная статья о структуре EXE-файла. 6. Книжка о Delphi. Арифметический стек В качестве арифметического стека - очень хорошо подходит обычный стек с его командами push и pop. Пример: Фрагмент программы на Forth: 1 2 3 4 + * swap drop вот как это будет выглядить на ассемблере: push 1 ; помещаем в арифметический стек число 1 push 2 ; помещаем в арифметический стек число 2 push 3 ; помещаем в арифметический стек число 3 push 4 ; помещаем в арифметический стек число 4 pop eax ; выполнение слова "+", обычное сложение, pop ebx ; т.е. снимаем со стека два числа, add eax, ebx ; складываем их и полученный результат push eax ; помещаем на вершину стека pop eax ; "*" - умножение, выполняется аналогично сложению, pop ebx ; надо учитывать что результат imul ebx ; помещается в edx:eax - и результат push eax ; будет правилен только если он уместился целиком в регистр eax pop eax ; меняет местами два последних значения на стеке (слово swap) pop ebx push eax push ebx pop eax ; снимает значение с вершины стека (слово drop) А теперь запишем все эти команды ассемблера в машинных кодах (можно для этого воспользоваться справочником Юрова) или подсмотреть в отладчике среды Delphi asm push 1 push 2 end; поставить точку останова (выбрать строку и нажать F5), запустить программу на выполнение (клавиша F9), после того как выполнение остановиться на точки останова, посмотреть это всё в машинных кодах (Ctrl+Alt+C). 68 01 00 00 00 - push 1 68 02 00 00 00 - push 2 68 03 00 00 00 - push 3 68 04 00 00 00 - push 4 5b - pop ebx 58 - pop eax 01 d8 - add eax, ebx 50 - push eax … ну и все остальные команды (слова Forth) которые работают каким-либо образом с арифметическим стеком - выполняются аналогично - значения с вершины стека помещаются в какой-либо регистр (eax, ebx, edx, …) потом над этими регистрами производятся какие-либо действия, затем результат(ы) из регистров помещаются обратно на стек (если это необходимо). пример: Компиляция слов работающих с арифметическим стеком while u1.token_type <> u1.FINISH do begin u1.GetToken1; … if u1.token_type = u1.NUM then begin tmp:=$68; fs.Write(tmp, 1); tmp:=StrToInt(u1.token) ; fs.Write(tmp, 4); {push NUM} continue; end; if u1.token = 'drop' then begin tmp:=$58; fs.Write(tmp, 1); {pop eax} continue; end; if u1.token = 'dup' then begin tmp:=$58; fs.Write(tmp, 1); {pop eax} tmp:=$50; fs.Write(tmp, 1); {push eax} tmp:=$50; fs.Write(tmp, 1); {push eax} continue; end; if u1.token = 'swap' then begin tmp:=$58; fs.Write(tmp, 1); {pop eax} tmp:=$5b; fs.Write(tmp, 1); {pop ebx} tmp:=$50; fs.Write(tmp, 1); {push eax} tmp:=$53; fs.Write(tmp, 1); {push ebx} continue; end; if u1.token = '-' then begin tmp:=$5b; fs.Write(tmp, 1); {pop ebx} tmp:=$58; fs.Write(tmp, 1); {pop eax} tmp:=$29; fs.Write(tmp, 1); tmp:=$d8; fs.Write(tmp, 1); {sub eax, ebx} tmp:=$50; fs.Write(tmp, 1); {push eax} continue; end; if u1.token = '+' then begin tmp:=$5b; fs.Write(tmp, 1); {pop ebx} tmp:=$58; fs.Write(tmp, 1); {pop eax} tmp:=$01; fs.Write(tmp, 1); tmp:=$d8; fs.Write(tmp, 1); {add eax, ebx} tmp:=$50; fs.Write(tmp, 1); {push eax} continue; end; if u1.token = '*' then begin tmp:=$5b; fs.Write(tmp, 1); {pop ebx} tmp:=$58; fs.Write(tmp, 1); {pop eax} tmp:=$f7; fs.Write(tmp, 1); tmp:=$eb; fs.Write(tmp, 1); {imul ebx} tmp:=$50; fs.Write(tmp, 1); {push eax} continue; end; if u1.token = 'negate' then begin tmp:=$58; fs.Write(tmp, 1); {pop eax} tmp:=$f7; fs.Write(tmp, 1); tmp:=$d8; fs.Write(tmp, 1); {neg eax} tmp:=$50; fs.Write(tmp, 1); {push eax} continue; end; if u1.token = '/' then begin tmp:=$31; fs.Write(tmp, 1); tmp:=$d2; fs.Write(tmp, 1); {xor edx, edx} tmp:=$5b; fs.Write(tmp, 1); {pop ebx} tmp:=$58; fs.Write(tmp, 1); {pop eax} tmp:=$f7; fs.Write(tmp, 1); tmp:=$fb; fs.Write(tmp, 1); {idiv ebx} tmp:=$50; fs.Write(tmp, 1); {push eax} continue; end; if u1.token = 'mod' then begin tmp:=$31; fs.Write(tmp, 1); tmp:=$d2; fs.Write(tmp, 1); {xor edx, edx} tmp:=$5b; fs.Write(tmp, 1); {pop ebx} tmp:=$58; fs.Write(tmp, 1); {pop eax} tmp:=$f7; fs.Write(tmp, 1); tmp:=$fb; fs.Write(tmp, 1); {idiv ebx} tmp:=$52; fs.Write(tmp, 1); {push edx} continue; end; if u1.token = '/mod' then begin tmp:=$31; fs.Write(tmp, 1); tmp:=$d2; fs.Write(tmp, 1); {xor edx, edx} tmp:=$5b; fs.Write(tmp, 1); {pop ebx} tmp:=$58; fs.Write(tmp, 1); {pop eax} tmp:=$f7; fs.Write(tmp, 1); tmp:=$fb; fs.Write(tmp, 1); {idiv ebx} tmp:=$52; fs.Write(tmp, 1); {push edx} tmp:=$50; fs.Write(tmp, 1); {push eax} continue; end; if u1.token = '1+' then begin tmp:=$ff; fs.Write(tmp, 1); tmp:=$04; fs.Write(tmp, 1); tmp:=$24; fs.Write(tmp, 1); {inc [esp]} continue; end; if u1.token = '1-' then begin tmp:=$ff; fs.Write(tmp, 1); tmp:=$0c; fs.Write(tmp, 1); tmp:=$24; fs.Write(tmp, 1); {dec [esp]} continue; end; if u1.token = 'and' then begin tmp:=$5b; fs.Write(tmp, 1); {pop ebx} tmp:=$58; fs.Write(tmp, 1); {pop eax} tmp:=$23; fs.Write(tmp, 1); tmp:=$c3; fs.Write(tmp, 1); {and eax, ebx} tmp:=$50; fs.Write(tmp, 1); {push eax} continue; end; if u1.token = 'or' then begin tmp:=$5b; fs.Write(tmp, 1); {pop ebx} tmp:=$58; fs.Write(tmp, 1); {pop eax} tmp:=$0b; fs.Write(tmp, 1); tmp:=$c3; fs.Write(tmp, 1); {or eax, ebx} tmp:=$50; fs.Write(tmp, 1); {push eax} continue; end; if u1.token = 'not' then begin tmp:=$58; fs.Write(tmp, 1); {pop eax} tmp:=$f7; fs.Write(tmp, 1); tmp:=$d0; fs.Write(tmp, 1); {not eax} tmp:=$50; fs.Write(tmp, 1); {push eax} continue; end; if u1.token = 'xor' then begin tmp:=$5b; fs.Write(tmp, 1); {pop ebx} tmp:=$58; fs.Write(tmp, 1); {pop eax} tmp:=$33; fs.Write(tmp, 1); tmp:=$c3; fs.Write(tmp, 1); {xor eax, ebx} tmp:=$50; fs.Write(tmp, 1); {push eax} continue; end; … end; Стек возвратов Адрес вершины стека возвратов храниться в регистре EBP. Сам стек возвратов находится в самом конце памяти выделяемой для загрузки exe-файла, это последняя секция (.kf) неинициализированных данных (размер этой секции на размер exe-файла не влияет, поэтому размер стека возвратов можно изменять – изменяя размер секции .kf . Слова языка Forth которые работают со стеком возвратов : >r – снимает значение с арифметического стека и ложит это значение на стек возвратов if u1.token = '>r' then begin tmp:=$58; fs.Write(tmp, 1); {pop eax} tmp:=$83; fs.Write(tmp, 1); tmp:=$ed; fs.Write(tmp, 1); tmp:=$04; fs.Write(tmp, 1); {sub ebp,4} tmp:=$89; fs.Write(tmp, 1); tmp:=$45; fs.Write(tmp, 1); tmp:=$00; fs.Write(tmp, 1); {mov [ebp],eax} continue; end; r> - снимает значение с вершины стека возвратов и кладет его на вершину арифметического стека if u1.token = 'r>' then begin tmp:=$8b; fs.Write(tmp, 1); tmp:=$45; fs.Write(tmp, 1); tmp:=$00; fs.Write(tmp, 1); {mov eax,[ebp]} tmp:=$83; fs.Write(tmp, 1); tmp:=$c5; fs.Write(tmp, 1); tmp:=$04; fs.Write(tmp, 1); {add ebp, 4} tmp:=$50; fs.Write(tmp, 1); {push eax} continue; end; и r@ - копирует значение со стека возвратов (оставляя стек возвратов нетронутым) на вершину арифметического стека if u1.token = 'r@' then begin tmp:=$8b; fs.Write(tmp, 1); tmp:=$45; fs.Write(tmp, 1); tmp:=$00; fs.Write(tmp, 1); {mov eax,[ebp]} tmp:=$50; fs.Write(tmp, 1); {push eax} continue; end; Описание новых слов В Forth для описания слова используется следующий синтаксис : : <имя нового слова> (<слово>) ; например так : : сто 100 ; : view_here here . ; слово «сто» - если оно встретится в тексте программы при выполнении положит на вершину арифметического стека число 100, а второе слово выведет на экран текущее значение вершины кодофайла ( here – кладёт на вершину стека это значение, а слово «.» (точка) – снимает значение с вершины стека и выводит это значение на экран ). При выполнении новых слов используется стек возвратов – перед выполнением слова в этот стек помещается адрес - по которому будет сделан переход после выполнения слова (т.е. слово ; (точка с запятой) или exit снимут с вершины стека возвратов значения адреса и передадут туда управление). Поэтому компиляция начала определения нового слова будет выглядеть так : if u1.token = ':' then begin u1.GetToken1; u2.Add(u1.token, fs.Position - _fCode + _BaseOfCode ,0); begin tmp:=$58; fs.Write(tmp, 1); {pop eax} tmp:=$83; fs.Write(tmp, 1); tmp:=$ed; fs.Write(tmp, 1); tmp:=$04; fs.Write(tmp, 1); {sub ebp,4} tmp:=$89; fs.Write(tmp, 1); tmp:=$45; fs.Write(tmp, 1); tmp:=$00; fs.Write(tmp, 1); {mov [ebp],eax} end; continue; end; здесь модуль u1 с его функцией Add используется для запоминания начала адреса в памяти определяемого слова. А это окончание определения нового слова (или слово exit) : if (u1.token = ';') or (u1.token = 'exit') then begin tmp:=$8b; fs.Write(tmp, 1); tmp:=$45; fs.Write(tmp, 1); tmp:=$00; fs.Write(tmp, 1); {mov eax,[ebp]} tmp:=$83; fs.Write(tmp, 1); tmp:=$c5; fs.Write(tmp, 1); tmp:=$04; fs.Write(tmp, 1); {add ebp, 4} tmp:=$ff; fs.Write(tmp, 1); tmp:=$e0; fs.Write(tmp, 1); {jmp eax} continue; end; Вот что компилируется и как если токен не известен компилятору(т.е. это не стандартное слово которые он знает, а новое слово которое определено раньше по тексту программы): if u2.Find(u1.token, _adr, _size) then begin if _size = 0 then begin tmp:=$e8; fs.Write(tmp, 1); tmp:=dword((_adr - _BaseOfCode) - (fs.Position - _fCode + 4)) ; fs.Write(tmp, 4); {call смещ32} end; continue; end; здесь модуль u1 с его функцией Find используется для поиска начала адреса в памяти ранее определенного слова по имени. Управляющие конструкции В Forth слова управляющих конструкций т.к. if, then, else, begin и др. – имеют признак немедленного исполнения и активно используют арифметический стек, т.к. в компиляторе во время компиляции EXE-файла, арифметического стека не существует – для его эмуляции используется модуль u3 с функциями procedure PUSH(i:integer) и function POP:integer. if u1.token = 'if' then begin tmp:=$58; fs.Write(tmp, 1); {pop eax} tmp:=$0b; fs.Write(tmp, 1); tmp:=$c0; fs.Write(tmp, 1); {or eax, eax} tmp:=$0f; fs.Write(tmp, 1); tmp:=$84; fs.Write(tmp, 1); u3.PUSH(fs.Position); u3.PUSH(2); tmp:=$0; fs.Write(tmp, 4); {jz metka32} continue; end; Слово if компилируется с командой условного перехода (jz), адрес (на самом деле это не реальный адрес, а fs.Position, но этого достаточно) по которому находится смещение этой команды (по началу это 0) – запоминаем с помощью PUSH. if u1.token = 'then' then begin if u3.POP = 2 then begin tmp := u3.POP; pdword(dword(fs.Memory) + tmp)^ := fs.Position - tmp - 4; end; continue; end; Слово then устанавливает по тому адресу который был запомнен словом if реальное смещение которое и высчитывает, не компилируя нового кода. if u1.token = 'else' then begin if u3.POP = 2 then begin tmp:=$e9; fs.Write(tmp, 1); tmp:=$0; fs.Write(tmp, 4); {jmp смещ32} tmp := u3.POP; pdword(dword(fs.Memory) + tmp)^ := fs.Position - tmp - 4; u3.PUSH(fs.Position - 4); u3.PUSH(2); end; continue; end; Пример: Слово abs – берет с арифметического стека число и кладёт туда модуль этого числа : abs dup 0 < if negate then ; Создание EXE-файла. Заголовок. Особенности и тонкости структуры исполняемого файла хорошо описаны у Криса Касперски, образ файла создаем в памяти - пригодится для этого класс – TMemoryStream, в последующим туда же будет происходить и компиляция. Для заголовка exe-файла понадобятся структуры модуля Windows : TImageDosHeader, TImageNtHeader и структура TImageSection. procedure CompileForth(prg : pchar; fn_exe : string); var fs : TMemoryStream; headDos : TImageDosHeader; inh : TImageNtHeaders; ish : TImageSectionHeader; // … fs := TMemoryStream.Create; fs.SetSize(_fData + _fszData); FillChar((fs.Memory)^, fs.Size, #$90); FillChar(headDos, sizeof(headDos),#0); headDos.e_magic := IMAGE_DOS_SIGNATURE; {MZ} he
|
|
|
RE: разработка компилятора Forth для Windows на Delphi - 2007-04-22 19:50:09.250000
|
|
|
NightmareZz
Сообщений: 1087
Оценки: 0
Присоединился: 2006-10-15 11:16:16.833333
|
Чо это было? 8|
|
|
|
RE: разработка компилятора Forth для Windows на Delphi - 2007-04-22 19:52:35.673333
|
|
|
12yearsold_ksakep
Сообщений: 771
Оценки: 0
Присоединился: 2007-02-12 21:40:30.126666
|
автор попробуй в гугле
|
|
|
RE: разработка компилятора Forth для Windows на Delphi - 2007-04-22 20:00:55.743333
|
|
|
Technologist
Сообщений: 3590
Оценки: 0
Присоединился: 2006-10-28 20:28:06.943333
|
quote:
Компилятор Forth для Windows (разработка на Delphi) Блеванул, спасибо :'( quote:
Книги и брошюры которые нужно полистать и почитать что бы понять как работает компилятор и разобраться в исходном коде на Delphi: 1. Г. Шилдт "Теория и практика C++" Ты дурак или как? quote:
6. Книжка о Delphi. Исчерпывающее определение. З.Ы.: кто жаждит сделать свой компилятор, читайте: Альфред Ахо, Рави Сети, Джеффри Ульман: "Компиляторы. Принципы, технологии, инструменты"
|
|
|
RE: разработка компилятора Forth для Windows на Delphi - 2007-04-22 20:25:51.590000
|
|
|
NightmareZz
Сообщений: 1087
Оценки: 0
Присоединился: 2006-10-15 11:16:16.833333
|
quote:
ORIGINAL: Technologist quote:
Компилятор Forth для Windows (разработка на Delphi) Блеванул, спасибо :'( Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Delphi Умри мучительной смерью :D:D:D:D
|
|
|
RE: разработка компилятора Forth для Windows на Delphi - 2007-04-22 20:47:17.153333
|
|
|
12yearsold_ksakep
Сообщений: 771
Оценки: 0
Присоединился: 2007-02-12 21:40:30.126666
|
мхахаха сползаю под стол :D
|
|
|
RE: разработка компилятора Forth для Windows на Delphi - 2007-04-22 20:53:47.673333
|
|
|
Technologist
Сообщений: 3590
Оценки: 0
Присоединился: 2006-10-28 20:28:06.943333
|
quote:
ORIGINAL: 12yearsold_ksakep мхахаха сползаю под стол :D Под рабочий стол с новыми обоями? :D
|
|
|
RE: разработка компилятора Forth для Windows на Delphi - 2007-04-22 20:56:52.413333
|
|
|
12yearsold_ksakep
Сообщений: 771
Оценки: 0
Присоединился: 2007-02-12 21:40:30.126666
|
не на обои - слишком готично :D
|
|
|
RE: разработка компилятора Forth для Windows на Delphi - 2007-04-22 20:57:31.770000
|
|
|
NightmareZz
Сообщений: 1087
Оценки: 0
Присоединился: 2006-10-15 11:16:16.833333
|
Это прям культ личности. Улыбает :D
|
|
|
RE: разработка компилятора Forth для Windows на Delphi - 2007-04-22 21:08:00.263333
|
|
|
Technologist
Сообщений: 3590
Оценки: 0
Присоединился: 2006-10-28 20:28:06.943333
|
quote:
ORIGINAL: NightmareZz Это прям культ личности. Улыбает :D 1) Это АнтиКульт 2) На личность ты не тянешь 3) Улыбает? Тебе кирпич на голову не падал?
|
|
|
RE: разработка компилятора Forth для Windows на Delphi - 2007-04-22 21:10:03.546666
|
|
|
rgo
Сообщений: 7170
Оценки: 281
Присоединился: 2004-09-25 05:14:25
|
это у дельфистов привычка такая всё через if'ы? или это принципиальная позиция? почему бы не сделать примерно так:struct token_trans {
char *token;
int n_bytes;
char bytes[MAX_BYTES]
} tok_table[] = {
{.token = "drop", .n_bytes = 1, .bytes = {0x58}},
{.token = "dup", .n_bytes = 3, .bytes = {0x58, 0x50, 0x50}},
/* так продолжаем пока все не перечислим */
};
#define N_TOKENS (sizeof (tok_table) / sizeof (tok_table[0]))
enum token_type
{
TOKEN_INTEGER, TOKEN_KEYWORD, TOKEN_MAX
};
struct token
{
char *str;
int int_val;
enum token_type type;
};
/* и код который будет транслировать */
enum parser_error ret = ERROR_NO_ERROR;
struct token *tok = NULL;
for (tok = first_token (); tok != NULL; tok = next_token ()) {
switch (tok->type) {
case TOK_INTEGER:
fs_write8 (1, 0x68);
fs_write32 (tok->int_value);
break;
case TOK_KEYWORD: {
struct token_trans *t;
for (t = tok_table; t < tok_table + N_TOKENS; t ++)
if (!strcmp (t->token, tok->str)) {
fs_write (t->bytes, t->n_bytes);
goto after_switch;
}
ret = ERROR_UNKNOWN_KEYWORD;
goto finish;
}
default:
ret = ERROR_UNKNOWN_TOKEN;
goto finish;
}
after_switch:
free_token (tok);
tok = NULL;
}
finish:
if (token)
free (token);
return ret; дальше простор для оптимизации, например, вместо линейного поиска по массиву tok_table, можно использовать бинарный, или вообще какой-нибудь map приделать. вместо описания tok_table на C, можно написать скриптец, который будет в С компилировать нечто в стиле:drop:
pop eax
dup:
pop eax
push eax
push eax
... вообще полезно, разделять данные и код. очень упрощает последующую работу с программой.
|
|
|
|
|