Brainfuck, вероятно, это самый безумный язык
программирования, который встречается на
свете. Сам язык состоит всего из 8
операторов, которыми можно написать
практически любую программу, которую вы
хотите. Для работы вам понадобится:
компилятор или интерпретатор (приводятся в
конце статьи - компилятор на ассемблере,
интерпретатор Brainfuck-C); ASCII таблица; вероятно
калькулятор :).
Основы
Главная идея Brainfuck - манипулирование
памятью. Вам выдается 30.000 массив 1 байтовых
блоков, хотя на самом деле размер массива
зависит от компилятора или интерпретатора,
но стандартный размер - 30.000. Внутри этого
массива вы можете увеличивать указатель,
значение в ячейке и так далее. Для работы,
как я уже говорил, используется 8
операторов:
> = увеличение указателя памяти или
смещение право на 1 блок
< = уменьшение или смещение влево на 1 блок
+ = увеличение значения в ячейке памяти, на
которую ссылается указатель
- = соответственно уменьшение на единиц
[ = аналог цикла while(cur_block_value != 0)
] = если значение в ячейке на которую
указывает указатель не равно нулю, то
переход на [
, = аналог getchar(), ввод одного символа
. = аналог putchar(), вывод одного сивола на
кончоль
Вводные правил
- Любые символы кроме описанных выше
игнорируются
- Все значения в памяти устанавливаются
на 0 в начале работы программы
- Циклов может быть сколько угодно, однако
каждая [ должна заканчиваться]
И вернемся к нашим баранам. Напишем
простую программу:
[-]
Что делается? По идее открывается цикл,
уменьшается значение текущей ячейки на 1 и
цикл продолжается до тех пор, пока значение
не достигнет 0. Однако так как изначально
все значения в памяти и есть 0, цикл никогда
не начнется.
Напишем другую программу:
+++++[-]
На С это аналогично такой программе:
*p=+5;
while(*p != 0){
*p--;
}
В этой программе мы увеличили значение по
указателю на 5, потом открыли цикл, который
уменьшал значение до 0.
>>>>++
На выходе мы получим такой вид памяти.
Очевидно, что сначала мы увеличиваем
указатель на 4 и затем два раза увеличиваем
значение на 1 два раза. Разнообразим?
>>>>++<<+>>+
Получаем:
Опять же понятно, что сначала 2
записывается в четвертую ячейку, потом 1 во
вторую и затем еще раз 1 прибавляется к 4.
Давайте напишем программу, которая будет
уже выводить нечто в консоль, например
любимый всеми нами "Hello world". Выглядит
она так:
>+++++++++[<++++++++>-]<.>+++++++[<++++>-]<+.
+++++++..+++.[-]>++++++++[<++++>-] <.
>+++++++++++[<++++++++>-]<-.--------.+++
.------.--------.[-]>++++++++[<++++>- ]<+.[-]++++++++++.
Круто? Настоящий Brainfuck... Разберем что
происходит. Надо помнить, что мы имеем дело
с кодами ASCII для вывода, так что надо
оперировать именно такими кодами.
Первая часть:
>+++++++++[<++++++++>-]<.
Сдвигаемся вправо, оставляя первую ячейку
пустой, записываем туда 9, открываем цикл. В
цикле смещаемся влево, записываем туда 8,
смещаемся вправо и уменьшаем значение на единицу.
Цикл идет до тех пор, пока во второй ячейке
не станет 0. Не слишком трудные вычисления
покажут, что в конце работы программы в
первой ячейки памяти будет 72, во второй 0.
Это код буквы Н, поэтому выводим ее в
консоль. Продолжаем для каждого символа и в
конце концов получаем заветные Hello world!
Занятно, не правда ли? :)
Ввод
Как уже было сказано, ввод в языке
осуществляется оператором '.'. Он получает
символ и в виде десятичного ASCII кода
записывает в тот блок, на который указывает
в настоящее время указатель. Напомню
операторы:
> = увеличение указателя памяти или
смещение право на 1 блок
< = уменьшение или смещение влево на 1 блок
+ = увеличение значения в ячейке памяти, на
которую ссылается указатель
- = соответственно уменьшение на единиц
[ = аналог цикла while(cur_block_value != 0)
] = если значение в ячейке на которую
указывает указатель не равно нулю, то
переход на [
, = аналог getchar(), ввод одного символа
. = аналог putchar(), вывод одного символа на
консоль
Итак, поэкспериментируем:
,.,.,.
Такая программа прочтет три символа с
клавиатуры и тут же выведет их. Более
полноценно:
>,[>,]<[<]>[.>]
Эта программа работает как юниксовая
команда cat - читает из STDIN и выводит в STDOUT.
Разберем как она работает: >, - смещает
указатель вправо и читает код символа; [>,] -
цикл, в котором указатель смещается вправо
и читается символ до тех пор, пока строка не
закончится символом NULL (\0 или десятичный
нуль); <[<] - перемотка, смещение указателя
влево по блокам до тех пор, пока не
встретится ноль, а он у нас лежит, напомню, в
самой первой ячейке; >[.>] - соответственно
обратный процесс, смещение по блокам вправо
до нуля и вывод символов.
На С это выглядело бы так:
++p;
*p=getchar();
while(*p != 0){
++p;
*p=getchar();
}
--p;
while(*p != 0) --p;
++p;
while(*p != 0){
putchar(*p);
++p;
}
Собственно, теперь мы знаем как вводить
символы, как их выводить и как
манипулировать памятью - этого вполне
достаточно для написания программ :).
Фишки
Есть много вещей, которые сделают
программирование на Brainfuck проще. Дабы
каждый не открывал для себя Америку опишу
некоторые.
Перенос блоков памяти.
+++++[>>+<<-]
Устанавливаем в первую ячейку 5, затем
организуем цикл, в котором переносим
значение из первой ячейки в третью,
оставляя первую пустой.
Копирование из одного блока памяти в
другой.
+++++[>>+>+<<<-]>>>[<<<+>>>-]
Опять 5 в первую ячейку, копируем в третью
и четвертую, оставляя первую пустой. Затем
переносим значение из четвертой обратно в
первую. Данные скопированы!
Сложение двух ячеек памяти:
+++++>+++[<+>-]
Заносим 5 и 3, организуем цикл, в котором
добавляем к первой ячейке единицу и
вычитаем из второй единицу.
Вычитание:
+++++++>+++++[<->-]
Очевидно, что тут из 7 вычитается 5.
Умножение мы уже разбирали в примере
вывода, однако повторим еще раз:
+++[>+++++<-]
Деление делается аналогично, только на
основе вычитания :) Напомню, что
операторов в нем всего восемь:
> = увеличение указателя памяти или
смещение право на 1 блок
< = уменьшение или смещение влево на 1 блок
+ = увеличение значения в ячейке памяти, на
которую ссылается указатель
- = соответственно уменьшение на единиц
[ = аналог цикла while(cur_block_value != 0)
] = если значение в ячейке на которую
указывает указатель не равно нулю, то
переход на [
, = аналог getchar(), ввод одного символа
. = аналог putchar(), вывод одного символа на
консоль
В прошлых статьях мы прошлись по вводу и
выводу, рассмотрели разные особенности
языка. Однако как же реализовать условия?
Допустим, мы хотим ввести с клавиатуры код символа
(x) в первый блок памяти, определить
равен ли он 5 и если равен установить другое значение
памяти (y) в 3. На С все это выглядит
просто:
x=getchar;
if(x == 5)
{
y = 3;
}
В реальном BrainFuck это всего лишь такая
программа:
,[>>+>+<<<-]>>>[<<<+>>>-]>+<<[-----[>]>>[<<<+++>>>[-]]
Рассмотрим что же происходит в программе:
, - ввод символа в первую ячейку памяти
[>>+>+<<<-]>>>[<<<+>>>-] -
копирование из ячейки 1 в ячейку 3 используя
4 как временный буфер. Указатель остается на
4 ячейке.
Память на момент завершения выглядит так:
>+<< - запись
1 в 5 ячейку и возврат к 3.
Самая важная часть:
[-----[>]>>[<<<+++>>>[-]] - вычитаем из
значения 5 и если оно именно таким и было
пишем во вторую 3, возвращаемся к 5 ячейке и
записываем туда 0 и в результате цикл
будет работать всего один раз. Если
исходное введенное значение не равно 5,
указатель останавливается на ячейке 6.
Соответственно, память у нас может остаться
такой:
Первый вариант если х=5, второй если не
равен. Это первый пример, в котором исходное
значение не "разрушается". Можно все
сделать гораздо проще, но при этом потерять
введенное число, получив только результат
сравнения.
>>+[<<,-----[>]>>[<<+++>>[-]]]
Надеюсь вы разберетесь с нею сами :).
А теперь о программах для работы:
Удачи :)
Компилятор написанный на С для BrainFack /* * obfc.c * The "omin0us brainfuck compiler" * omin0us * * A simple BrainFuck to C interpretor and font end Compiler. * * This program is free software; you can * redistribute it and/or modify it under the * terms of the GNU General Public License as * published by the Free Software Foundation; * either version 2 of the License or (at * your option) any later version; * * This program is distributed in the hope * that it will be useful but WITHOUT ANY * WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR * A PARTICULAR PURPOSE; See the GNU General * Public License for more details; * * You may find a copy of the GNU General Public * License on my web site at: * http://dtors.ath.cx/gpl.txt * or from the Free Software Foundation's web site at: * http://www.fsf.org/licenses/gpl.html */
#include #include #include #define CC "/usr/bin/gcc"
void usage(char **argv); void generate(char *c_output_name);
FILE *outfile; FILE *infile;
int main(int argc, char **argv){ char c; char *output_name; /* name of the executable that will be made */ char *c_output_name; /* name of the C source file that is generated */ char compile_opts[256] = CC; /* this array will contain the compiler options */ int keep_file = 0; /* Keep the generated C source file? */ int verbose = 0; /* Use verbose output? */ int no_compile = 0; /* Don't compile, just generate C source file */
if(argc < 2){ usage(argv); exit(1); }
/* chk(argv); */
while(1){ c = getopt(argc, argv, "o:c:knvh"); if(c < 0) break;
switch(c){ case 'o': output_name = optarg; break; case 'v': verbose = 1; break; case 'h': usage(argv); break; case 'k': keep_file = 1; break; case 'c': c_output_name = optarg; break; case 'n': no_compile = 1; keep_file = 1; break; } }
if((optind >= argc) || (strcmp(argv[optind], "-") == 0)){ fprintf(stderr, "%s: no input file specified\n", argv[0]); usage(argv); exit(-1); } if(verbose)printf("[+] Opening %s...", argv[optind]); if((infile = fopen(argv[optind], "r")) == 0){ if(verbose)printf("failed\n"); fprintf(stderr,"%s: could not open %s\n", argv[0], argv[optind]); exit(-1); } if(verbose)printf("Ok\n");
if(!c_output_name) c_output_name = "bf.out.c";
if(!output_name) output_name = "bf.out";
if(verbose)printf("[+] Opening the output file..."); if((outfile = fopen(c_output_name, "w")) == 0){ if(verbose)printf("failed\n"); fprintf(stderr, "%s: error opening output file %s\n", argv[0], c_output_name); exit(-1); } if(verbose)printf("Ok\n"); if(verbose)printf("[+] Generating C source..."); generate(c_output_name); if(verbose)printf("Ok\n"); fclose(outfile); /*strcat(compile_opts, "/usr/bin/gcc");*/ strcat(compile_opts, " -o "); strcat(compile_opts, output_name); strcat(compile_opts, " "); strcat(compile_opts, c_output_name);
if(!no_compile){ if(verbose){ printf("[+] Compiling...\n"); printf("Compiler: " CC "\n"); printf("Compile Options: %s\n", compile_opts); } system(compile_opts); if(verbose)printf("Compiling Complete\n"); }
if(!keep_file){ if(verbose)printf("[+] Deleting intermediate file %s...", c_output_name); unlink(c_output_name); if(verbose)printf("Ok\n"); } else if(verbose)printf("[+] Keeping intermediate file %s...Ok\n", c_output_name); fclose(infile); return(0); }
void generate(char *c_output_name){ char c;
fprintf(outfile, "/*\n * This source was automatically generated with"); fprintf(outfile, "\n * obfc - The \"omin0us brainfuck compiler\"."); fprintf(outfile, "\n * omin0us "); fprintf(outfile, "\n * \n */\n"); fprintf(outfile, "#include \n"); fprintf(outfile, "main() {\nchar a[30000],*ptr=a;\n"); while((c=fgetc(infile)) != EOF){ switch(c){ case '>': fprintf(outfile, "ptr++;\n"); break; case '<': fprintf(outfile, "ptr--;\n"); break; case '+': fprintf(outfile, "++*ptr;\n"); break; case '-': fprintf(outfile, "--*ptr;\n"); break; case '[': fprintf(outfile, "while(*ptr){\n"); break; case ']': fprintf(outfile, "}\n"); break; case '.': fprintf(outfile, "putchar(*ptr);\n"); break; case ',': fprintf(outfile, "*ptr=getchar();\n"); break; } } fprintf(outfile,"exit(0);\n}\n"); }
void usage (char *argv[]){ printf("obfc - the \"omin0us brainfuck compiler\"\n"); printf("by omin0us printf("\n"); printf("Usage: %s [OPTIONS] [FILE]\n",argv[0]); printf("Available Options:\n"); printf("\t-o outfile\tspecify output file name\n"); printf("\t-k\t\tkeep the generated C source (normally bf.out.c)\n"); printf("\t-c c_outfile\tspecify C source file name. Only used option-\n"); printf("\t\t\tally in conjunction with -k option\n"); printf("\t-n\t\tDon't compile the C source, just output a copy of it\n"); printf("\t-v\t\tverbose output\n"); printf("\t-h\t\tdisplay help\n");
}
Интерпритатор написанный на BrainFuck
[/* bf2c.b * The omin0us Brainfuck to C interpretor * omin0us <omin0us208@gmail.com> * * NOTE: This was written just before the release of K-1ine #44 * and consequently was rushed to be finished. Currently * it does not take well to any characters of input besides * the 8 standard brainfuck operators and newline and EOF. * So consequently, it will only interpret un-commented code. * Check my web site <http://dtors.ath.cx> for a later release * that will probably have support for commented code. */]
>+++++[>+++++++<-]>.<<++[>+++++[>+++++++<-]<-]>>.+++++.<++[>-----<-]>-.<++ [>++++<-]>+.<++[>++++<-]>+.[>+>+>+<<<-]>>>[<<<+>>>-]<<<<<++[>+++[>---<-]<- ]>>+.+.<+++++++[>----------<-]>+.<++++[>+++++++<-]>.>.-------.-----.<<++[> >+++++<<-]>>.+.----------------.<<++[>-------<-]>.>++++.<<++[>++++++++<-]> .<++++++++++[>>>-----------<<<-]>>>+++.<-----.+++++.-------.<<++[>>+++++++ +<<-]>>+.<<+++[>----------<-]>.<++[>>--------<<-]>>-.------.<<++[>++++++++ <-]>+++.---....>++.<----.--.<++[>>+++++++++<<-]>>+.<<++[>+++++++++<-]>+.<+ +[>>-------<<-]>>-.<--.>>.<<<+++[>>++++<<-]>>.<<+++[>>----<<-]>>.++++++++. +++++.<<++[>---------<-]>-.+.>>.<<<++[>>+++++++<<-]>>-.>.>>>[-]>>[-]<+[<<[ -],[>>>>>>>>>>>>>+>+<<<<<<<<<<<<<<-]>>>>>>>>>>>>>>[<<<<<<<<<<<<<<+>>>>>>>> >>>>>>-]<<+>[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[-[- [-[-[-[-[-[-[-[-[-[-[-[<->[-]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]]<[ <<<<<<<<<<<<[-]>>>>>>>>>>>>[-]]<<<<<<<<<<<<[<+++++[>---------<-]>++[>]>>[> +++++[>+++++++++<-]>--..-.<+++++++[>++++++++++<-]>.<+++++++++++[>-----<-]> ++.<<<<<<.>>>>>>[-]<]<<<[-[>]>>[>++++++[>+++[>++++++<-]<-]>>++++++.------- ------.----.+++.<++++++[>----------<-]>.++++++++.----.<++++[>+++++++++++++ ++++<-]>.<++++[>-----------------<-]>.+++++.--------.<++[>+++++++++<-]>.[- ]<<<<<<<.>>>>>]<<<[-[>]>>[>+++++[>+++++++++<-]>..---.<+++++++[>++++++++++< -]>.<+++++++++++[>-----<-]>++.<<<<<<.>>>>>>[-]<]<<<[-[>]>>[>+++[>++++[>+++ +++++++<-]<-]>>-.-----.---------.<++[>++++++<-]>-.<+++[>-----<-]>.<++++++[ >----------<-]>-.<+++[>+++<-]>.-----.<++++[>+++++++++++++++++<-]>.<++++[>- ----------------<-]>.+++++.--------.<++[>+++++++++<-]>.[-]<<<<<<<.>>>>>]<< <[<+++[>-----<-]>+[>]>>[>+++++[>+++++++++<-]>..<+++++++[>++++++++++<-]>--- .<+++++[>----------<-]>---.<<<<<<.>>>>>>[-]<]<<<[--[>]>>[>+++++[>+++++++++ <-]>--..<+++++++[>++++++++++<-]>-.<+++++[>----------<-]>---.[-]<<<<<<.>>>> >]<<<[<+++[>----------<-]>+[>]>>[>+++[>++++[>++++++++++<-]<-]>>-.<+++[>--- --<-]>.+.+++.-------.<++++++[>----------<-]>-.++.<+++++++[>++++++++++<-]>. <+++++++[>----------<-]>-.<++++++++[>++++++++++<-]>++.[-]<<<<<<<.>>>>>]<<< [--[>]>>[>+++++[>+++++[>+++++<-]<-]>>.[-]<<<<<<<.>>>>>]<<<[<++++++++++[>-- --------------<-]>--[>]>>[<<<<[-]]]]]]]]]]]>>]<++[>+++++[>++++++++++<-]<-] >>+.<+++[>++++++<-]>+.<+++[>-----<-]>.+++++++++++.<+++++++[>----------<-]> ------.++++++++.-------.<+++[>++++++<-]>.<++++++[>+++++++++++<-]>.<+++++++ +++.
Компилятор написанный на Ассемблере для BrainFack
;; bf.asm: Copyright (C) 1999-2001 by Brian Raiter, under the GNU ;; General Public License (version 2 or later). No warranty. ;; ;; To build: ;;nasm -f bin -o bf bf.asm && chmod +x bf ;; To use: ;;bf < foo.b > foo && chmod +x foo
BITS 32
;; This is the size of the data area supplied to compiled programs.
%define arraysize30000
;; For the compiler, the text segment is also the data segment. The ;; memory image of the compiler is inside the code buffer, and is ;; modified in place to become the memory image of the compiled ;; program. The area of memory that is the data segment for compiled ;; programs is not used by the compiler. The text and data segments of ;; compiled programs are really only different areas in a single ;; segment, from the system's point of view. Both the compiler and ;; compiled programs load the entire file contents into a single ;; memory segment which is both writable and executable.
%defineTEXTORG0x45E9B000 %defineDATAOFFSET0x2000 %defineDATAORG(TEXTORG + DATAOFFSET)
;; Here begins the file image.
orgTEXTORG
;; At the beginning of the text segment is the ELF header and the ;; program header table, the latter consisting of a single entry. The ;; two structures overlap for a space of eight bytes. Nearly all ;; unused fields in the structures are used to hold bits of code.
;; The beginning of the ELF header.
db0x7F, "ELF"; ehdr.e_ident
;; The top(s) of the main compiling loop. The loop jumps back to ;; different positions, depending on how many bytes to copy into the ;; code buffer. After doing that, esi is initialized to point to the ;; epilog code chunk, a copy of edi (the pointer to the end of the ;; code buffer) is saved in ebp, the high bytes of eax are reset to ;; zero (via the exchange with ebx), and then the next character of ;; input is retrieved.
emitputchar:addesi, byte (putchar - decchar) - 4 emitgetchar:lodsd emit6bytes:movsd emit2bytes:movsb emit1byte:movsb compile:leaesi, [byte ecx + epilog - filesize] xchgeax, ebx cmpeax, 0x00030002; ehdr.e_type (0x0002) ; ehdr.e_machine (0x0003) movebp, edi; ehdr.e_version jmpshort getchar
;; The entry point for the compiler (and compiled programs), and the ;; location of the program header table.
dd_start; ehdr.e_entry ddproghdr - $$; ehdr.e_phoff
;; The last routine of the compiler, called when there is no more ;; input. The epilog code chunk is copied into the code buffer. The ;; text origin is popped off the stack into ecx, and subtracted from ;; edi to determine the size of the compiled program. This value is ;; stored in the program header table, and then is moved into edx. ;; The program then jumps to the putchar routine, which sends the ;; compiled program to stdout before falling through to the epilog ;; routine and exiting.
eof:movsd; ehdr.e_shoff xchgeax, ecx popecx subedi, ecx; ehdr.e_flags xchgeax, edi stosd xchgeax, edx jmpshort putchar; ehdr.e_ehsize
;; 0x20 == the size of one program header table entry.
dw0x20; ehdr.e_phentsize
;; The beginning of the program header table. 1 == PT_LOAD, indicating ;; that the segment is to be loaded into memory.
proghdr:dd1; ehdr.e_phnum & phdr.p_type ; ehdr.e_shentsize dd0; ehdr.e_shnum & phdr.p_offset ; ehdr.e_shstrndx
;; (Note that the next four bytes, in addition to containing the first ;; two instructions of the bracket routine, also comprise the memory ;; address of the text origin.)
db0; phdr.p_vaddr
;; The bracket routine emits code for the "[" instruction. This ;; instruction translates to a simple "jmp near", but the target of ;; the jump will not be known until the matching "]" is seen. The ;; routine thus outputs a random target, and pushes the location of ;; the target in the code buffer onto the stack.
bracket:moval, 0xE9 incebp pushebp; phdr.p_paddr stosd jmpshort emit1byte
;; This is where the size of the executable file is stored in the ;; program header table. The compiler updates this value just before ;; it outputs the compiled program. This is the only field in the two ;; headers that differs between the compiler and its compiled ;; programs. (While the compiler is reading input, the first byte of ;; this field is also used as an input buffer.)
filesize:ddcompilersize; phdr.p_filesz
;; The size of the program in memory. This entry creates an area of ;; bytes, arraysize in size, all initialized to zero, starting at ;; DATAORG.
ddDATAOFFSET + arraysize; phdr.p_memsz
;; The code chunk for the "." instruction. eax is set to 4 to invoke ;; the write system call. ebx, the file handle to write to, is set to ;; 1 for stdout. ecx points to the buffer containing the bytes to ;; output, and edx equals the number of bytes to output. (Note that ;; the first byte of the first instruction, which is also the least ;; significant byte of the p_flags field, encodes to 0xB3. Having the ;; 2-bit set marks the memory containing the compiler, and its ;; compiled programs, as writeable.)
putchar:movbl, 1; phdr.p_flags moval, 4 int0x80; phdr.p_align
;; The epilog code chunk. After restoring the initialized registers, ;; eax and ebx are both zero. eax is incremented to 1, so as to invoke ;; the exit system call. ebx specifies the process's return value.
epilog:popa inceax int0x80
;; The code chunks for the ">", "<", "+", and "-" instructions.
incptr:incecx decptr:dececx incchar:incbyte [ecx] decchar:decbyte [ecx]
;; The main loop of the compiler continues here, by obtaining the next ;; character of input. This is also the code chunk for the "," ;; instruction. eax is set to 3 to invoke the read system call. ebx, ;; the file handle to read from, is set to 0 for stdin. ecx points to ;; a buffer to receive the bytes that are read, and edx equals the ;; number of bytes to read.
getchar:moval, 3 xorebx, ebx int0x80
;; If eax is zero or negative, then there is no more input, and the ;; compiler proceeds to the eof routine.
oreax, eax jleeof
;; Otherwise, esi is advanced four bytes (from the epilog code chunk ;; to the incptr code chunk), and the character read from the input is ;; stored in al, with the high bytes of eax reset to zero.
lodsd moveax, [ecx]
;; The compiler compares the input character with ">" and "<". esi is ;; advanced to the next code chunk with each failed test.
cmpal, '>' jzemit1byte incesi cmpal, '<' jzemit1byte incesi
;; The next four tests check for the characters "+", ",", "-", and ;; ".", respectively. These four characters are contiguous in ASCII, ;; and so are tested for by doing successive decrements of eax.
subal, '+' jzemit2bytes deceax jzemitgetchar incesi incesi deceax jzemit2bytes deceax jzemitputchar
;; The remaining instructions, "[" and "]", have special routines for ;; emitting the proper code. (Note that the jump back to the main loop ;; is at the edge of the short-jump range. Routines below here ;; therefore use this jump as a relay to return to the main loop; ;; however, in order to use it correctly, the routines must be sure ;; that the zero flag is cleared at the time.)
cmpal, '[' - '.' jzbracket cmpal, ']' - '.' relay:jnzcompile
;; The endbracket routine emits code for the "]" instruction, as well ;; as completing the code for the matching "[". The compiler first ;; emits "cmp dh, [ecx]" and the first two bytes of a "jnz near". The ;; location of the missing target in the code for the "[" instruction ;; is then retrieved from the stack, the correct target value is ;; computed and stored, and then the current instruction's jmp target ;; is computed and emitted.
endbracket:moveax, 0x850F313A stosd leaesi, [byte edi - 8] popeax subesi, eax mov[eax], esi subeax, edi stosd jmpshort relay
;; This is the entry point, for both the compiler and its compiled ;; programs. The shared initialization code sets eax and ebx to zero, ;; ecx to the beginning of the array that is the compiled programs's ;; data area, and edx to one. (This also clears the zero flag for the ;; relay jump below.) The registers are then saved on the stack, to be ;; restored at the very end.
_start: xoreax, eax xorebx, ebx movecx, DATAORG cdq incedx pusha
;; At this point, the compiler and its compiled programs diverge. ;; Although every compiled program includes all the code in this file ;; above this point, only the eleven bytes directly above are actually ;; used by both. This point is where the compiler begins storing the ;; generated code, so only the compiler sees the instructions below. ;; This routine first modifies ecx to contain TEXTORG, which is stored ;; on the stack, and then offsets it to point to filesize. edi is set ;; equal to codebuf, and then the compiler enters the main loop.
codebuf: movch, (TEXTORG >> 8) & 0xFF pushecx movcl, filesize - $$ leaedi, [byte ecx + codebuf - filesize] jmpshort relay
;; Here ends the file image.
compilersizeequ$ - $$
|