буфер ЦК:
Код:
0x10000 CONSTANT _Msz
CREATE M _Msz ALLOT
0 VALUE THERE
По умолчанию выделяем буфер размером 64К -- больший размер нам потребуется нескоро, учитывая что вкомпилировать в программу данные для нее нежелательно, лучше данные хранить отдельно в файловой системе платформы или структурах данных, построенных на базе блоков внешней памяти XMEM.
слова для чтения/записи буфера
Код:
: b! ( byte addr -- ) M + C! ;
: w! ( n16 addr -- ) M + W! ;
: d! ( n32 addr -- ) M + ! ;
слова выполняющие компиляцию
Код:
: b, ( byte -- ) THERE b! THERE 1 + TO THERE ;
: w, ( byte -- ) THERE w! THERE 2 + TO THERE ;
: d, ( byte -- ) THERE d! THERE 4 + TO THERE ;
Так как наш ЦК должен уметь компилить и 16-битный, и 32-битный байткод, должна быть определена константа, задающая режим работы.
Это сделано в файлах MODE16.4th и MODE32.bat:
Код:
2 CONSTANT _cell
Для чтения/записи/компияции cellов используются слова
Код:
: cell! ( n addr -- ) _cell 2 = IF w! ELSE d! THEN ;
: cell, ( n -- ) _cell 2 = IF w, ELSE d, THEN ;
Самая основа для ЦК есть, теперь нужно определить слово для сохранения байткода из буфера в файл:
Код:
: save" \ PROGRAM"
[CHAR] " WORD COUNT W/O CREATE-FILE DROP ( fh )
M ( addr ) THERE ( len ) ROT ( fh ) WRITE-FILE
BYE
;
В самом начале байт-кода находится стартовый jmp на точку входа, см. самый конец TC.4th:
Код:
-1 jmp \ jmp _entry
Слово { выполняет обновление этого стартового jmpа, устанавливая точку входа программы на последнее определенное через { } слово.
Для доступа к полям стартового кода определен набор констант (как минимум одна):
Код:
1 CONSTANT _entry \ адрес параметра стартового jmpа
Для определения команд ВМ используем CREATE-DOES слова, задающие безоперандные команды и команды с одним cellным операндом:
Код:
: 0op ( opcode:byte -- ) CREATE C, DOES> @ b, ;
: 1op ( opcode:byte -- ) CREATE C, DOES> @ b, cell, ;
Для команд с операндом на стеке при работе ЦК нужно значение параметра. Практически в таком виде используется только слово lit, а точнее его короткий синоним #.
Для описания структур управления, необходимых в нашем микроязыке, требуется набор команд ВМ, которые управляют выполнением программы:
Код:
0x00 0op nop \ команда-пустышка
0x01 1op jmp \ безусловный переход
0x02 1op ?jmp \ ( flag -- ) переход если flag=0
0x03 1op call \ (R: -- retaddr ) вложенный вызов в шитом коде
0x04 0op ret \ (R: retaddr -- ) возврат по адресу из стека возвратов
0x05 1op lit : # lit ; \ ( -- n ) литерал, на стек кладется константа
0x06 0op exec \ ( addr -- ) (R: -- retaddr ) вложенный вызов по адресу из стека данных
0x07 0op bye \ останов движка
Заодно определим и набор команд, реализующих циклы со счетчиками на уровне ВМ:
Код:
0x08 0op do \ ( n1 n2 -- ) начало цикла со счетчиком
0x09 0op loop \ конец цикла
0x0A 0op i \ ( -- n ) значение счетчика цикла с 0x вложением
0x0B 0op j \ ( -- n ) значение счетчика цикла с 1x вложением
0x0C 0op k \ ( -- n ) значение счетчика цикла с 2x вложением
Наконец слова определяющие слово (процедуру) в целевом коде:
Код:
: { THERE _entry cell! CREATE THERE , DOES> @ call ;
: } ret ;
и стартовый код, который компилируется в начало каждого файла байт-кода:
Код:
-1 jmp \ jmp _entry