|
DEmbro
Текущие наработкиИгра Snake Journey совместима с дембро ревизии 507. Игра Maze Journey совместима с DEmbro ревизии 484. Текущая нестабильная версия http://code.google.com/p/dforth/source/browse/#svn/trunk/release Стабильной следует считать ревизию 243, хотя код, написанный под DEmbro той версии, станет некорректным в готовящихся версиях. (В основном, в связи с вынесением строк в отдельный стек.) Основные разделыВ настоящий момент описание DEmbro ещё не разбито на файлы, и главное описание находится ниже на этой странице.
Идеология
Рекомендуемая литератураDEmbro является forth-подобным языком программирования, поэтому чтобы многое понять рекомендую почитать кое-что о форте.
СинтаксисПрограмма представляет из себя последовательность команд, каждая команда отделена от других пробелами (пробелом считается символ от 0 до 32). Для каждой команды форт-машина ищет исполнителя и передаёт ему выполнение. КомментарииКомментарии являются обычными командами. Строковые комментарии должны начинаться с «//» и заканчиваются концом строки. // это комментарий Блочные комментарии начинаются с «(». После этого игнорируются все символы до «)». Например ( это
комментарий)Стековая нотацияЧтобы проще понимать что происходит со стеком при выполнении некоторых команд, применяется стековая нотация, имеющая следующий вид: ( состояние-стека-до -- состояние-стека-после ) Состояние стека записывается в виде последовательности символов, самый правый символ отвечает за вершину стека. Каждый символ должен представлять из себя либо название типа, либо условное название величины. Для простых команд последовательность символов не разделяется пробелом, вот пример нотации команды SWAP (обмен местами двух верхних чисел) ( ab -- ba ) В случае сложного выражения, символы следует разделять пробелами — пример для команды ROTN: ( a1 a2 a3 ... an n -- a2 a3 ... an a1 ) Некоторые команды могут что-то делать в режиме компиляции. В этом случае нужно записывать изменения на стеке дважды — для случая исполнения и для случая компиляции: >resolve ( -- ) ( embroptr -- ) Некторые команды режима компиляции могут считывать из входного потока исходников символы. Для таких следует указывать что именно они считывают в двойных кавычках или фигурных скобках после описания входных параметров: // xxx следует понимать как следующее слово
compile ( "xxx" -- ) компилирует команду xxx в шитый код, вне зависимости от её флага immediate
// ccc следует понимать как входные символы, вплоть до первой встретившийся "
pchar" ( {ccc"} -- pchar )
int-variable ( int "xxx" -- ) аналогично create, но сразу выделяет память под x и инициализирует значением со стека Для наглядности, в документации можно описывать в более удобном виде: compile xxx ( -- ) компилирует команду xxx в шитый код, вне зависимости от её флага immediate pchar" ссс" ( -- pchar ) int-variable xxx ( int -- ) аналогично create, но сразу выделяет память под x и инициализирует значением со стека Первым способом я буду оформлять комментарии в коде, а последним — описывать команды в документации. ИсполнениеВходящий поток, исходник, source — последовательность символов, представляющая из себя исходник. Пробел — символ с кодом от 0 до 32. Слово — последовательность символов исходника без пробелов, ограниченная пробелами с обеих сторон. Embro, шитый код — готовый код для выполнения на виртуальной машине. Есть три режима:
Каждое слово в зависимости от текущего режима выполняет некоторое действие. Если не оговорено иное, то описание к командам следует понимать как описание её действия при интерпритации и выполнении, а при компиляции просто генерируется вызов её выполняющей части. Типы
КомандыБазовые команды для работы со стекамиМногие команды обладают префиксом типа, с которым они работают. Ниже символ % следует понимать как тип, который может иметь следующие значения ptr int int8 int16 int32 int64 uint uint8 uint16 uint32 uint64 single double extended bool embro type str pchar В версиях для int префикс int- можно опускать. Работа со стеком W. (Главный рабочий стек.) int-push xxx ( -- xxx ) помещает константу xxx типа % в стек (реализовано для int, bool) %-drop ( % -- ) удаляет с вершины стека переменную %-nip ( ab -- b ) удаляет второй по счёту элемент стека %-dup ( % -- %% ) дублирует верхнюю переменную на стеке %-over ( ab -- aba ) копирует предпоследний элемент стека на вершину %-tuck ( ab -- bab ) копирует верхний элемент стека ниже второго элемента стека %-swap ( ab -- ba ) меняет два верхних значения местами %-rot ( abc -- bca ) меняет три верхних элемента местами %-lrot ( abc -- bca ) то же самое (ещё не реализовано) %-rrot ( abc -- cab ) меняет три верхних элемента местами в другом порядке %-lrotn ( a1 a2 ... an n -- a2 a3 ... an a1 ) прокручивание произвольного кол-ва элементов %-rrotn ( a1 a2 ... an n -- a2 a3 ... an a1 ) %-pick ( a1 a2 ... an n -- a1 a2 ... an a1 ) копирует n-ный по счёту элемент стека на вершину Работа со стеком BW. (Стек блочных атомарных типов данных.) +x означает, что команда число ссылок на x увеличивает на 1 -x означает, что команда число ссылок на x уменьшает на 1 и удаляет из блок памяти, если нужно w>b ( W: a -- ) ( B: -- x) переносит значение с W в BW b>w ( W: -- a ) ( B: x --) переносит значение с BW в W binc ( B: x -- ) увеличивает число ссылок на блок x bdec ( B: x -- ) уменьшает число ссылок на x, освобождает если нужно bdrop ( B: x -- ) ( -x) удаляет с вершины стека переменную bnip ( B: xy -- y ) ( -x) удаляет второй по счёту элемент стека bdup ( B: x -- xx ) ( +x) дублирует верхнюю переменную на стеке bover ( B: xy -- xyx ) ( +x) копирует предпоследний элемент стека на вершину btuck ( B: xy -- yxy ) ( +y) копирует верхний элемент стека ниже второго элемента стека bswap ( B: xy -- yx ) меняет два верхних значения местами blrot ( B: xyz -- yzx ) меняет три верхних элемента местами brrot ( B: zxy -- zxy ) меняет три верхних элемента местами Арифметические команды (для арифметических типов)Если не указывать тип, то считается, что команда выполняется для int. %+ ( ab -- a+b )
%- ( ab -- a-b )
%* ( ab -- a*b )
%= ( ab -- a=b ) проверяет два значения на равенство, кладёт на стек bool
%<> ( ab -- a<>b ) ... на неравенство ...
%< ( ab -- a<b ) ... на меньше ...
%> ( ab -- a>b ) ... на больше ...
%<= ( ab -- a<=b ) ... на меньше или равно ...
%>= ( ab -- a>=b ) ... на больше или равно ...
%0= ( a -- a=0 ) проверяет a на равенство нулю
%0<> ( a -- a<>0 ) ... на неравенство ...
%0< ( a -- a<0 ) ... на меньше ...
%0> ( a -- a>0 ) ... на больше ...
%0<= ( a -- a<=0 ) ... на меньше или равно ...
%0>= ( a -- a>=0 ) ... на больше или равно ...
%-?dup ( a -- 0 | a a) дублирует a, если оно ненулевое
%0; ( a -- a | ) Если a не равно нулю, то ничего не делает. Иначе снимает
a со стека и выходит из функции.
%-within ( x a b -- bool ) проверяет лежит ли x в полуинтервале [a b) (в разработке)
%-max ( ab -- a | b ) из двух верхних элементов на стеке оставляет максимальный
%-min ( ab -- a | b ) из двух верхних элементов на стеке оставляет минимальный
%-minmax ( ab -- ab | ba) сортирует два элемента — на вершине стека оказывается максимум, а вторым по счёту минимум Для целых типовЦелые типы: int, int*, uint, uint* %-div ( ab -- a div b ) вычисляет целую чаcть при делении a на b %-mod ( ab -- a mod b ) вычисляет остаток при делении a на b %-divmod ( ab -- a div b, a mod b ) %-inc ( a -- a+1 ) быстрое увеличение на единицу %-dec ( a -- a-1 ) быстрое уменьшение на единицу 1+ ( a -- a+1 ) эквивалентно int-inc 1- ( a -- a-1 ) эквивалентно int-dec Для знаковых численных типов%-abs ( % -- |%| ) вычисляет модуль числа %-neg ( % -- -% ) обращает знак числа Для чисел с плавающей точкой%/ ( ab -- a/b ) делит одно число на другое %-cos ( a -- cos(a) ) %-sin ( a -- sin(a) ) %-tan ( a -- tan(a) ) %-atan ( a -- atan(a) ) %-atan2 ( yx -- atan2(y, x) ) Команды преобразованийДля преобразования из машинно-зависимых в машинно-независимые типы и обратно следует использовать следующие команды int8->int int16->int int32->int int->int8 int->int16 int->int32 uint8->uint uint16->uint uint32->uint uint->uint8 uint->uint16 uint->uint32 single->double single->extended double->single souble->extended extended->single extended->double При их выполнении возможна потеря точности (для чисел с плавающей точкой) и старших разрядов (для целых). Булевы/побитовые командыТипы bool и int на стеке имеют одинаковые размеры. Считается, что значение bool истинно тогда и только тогда, когда оно не равно нулю, если его представлять как int. true кладёт на стек число -1 (это int, у которого все биты установлены в 1). Операторы not, or, and, xor являются побитовыми для типа int. Поэтому, если их необходимо использовать в качестве булевых, то нужно, чтобы истина представлялась как -1. Вместо not можно использовать 0=, тогда последнее условие не обязательно. Чтобы привести bool на стеке к множеству {false, true}, можно воспользоваться командой 0<> false ( -- false ) кладёт на стек ложь true ( -- true ) кладёт на стек истину not ( b -- not b ) or ( ab -- a or b ) and ( ab -- a and b ) xor ( ab -- a xor b ) Операции над строкамиПрямая работа с памятью, переменными и шитым кодомНиже под % подразумевается один из следующих типов: арифметический, bool, ptr. Переменныеcreate xxx ( -- ) создаёт новую команду с именем xxx. При выполнении xxx на стек будет класться указатель на её область в шитом коде
allot ( i -- ) выделяет i байт в шитом коде последней определённой команды
here ( -- ptr) кладёт на стек текущий указатель вершины шитого кода
%, ( % -- ) выделяет нужный размер под % и записывает в область данных значение со стека
%-to xxx ( % -- ) перенести значение со стека в область данных команды xxx
%-variable xxx ( -- ) аналогично create, но сразу выделяет память под x и инициализирует значением со стека
%-value xxx ( % -- ) аналогично create, но при выполнении xxx на стек будет класться не указатель на область данных, а значение типа % в области данных
%-constant xxx ( % -- ) в настоящий момент аналогично value
does> ( -- ) immediate устанавливает код, который должен выполняться
для последнего слова, созданного командой `create`.
При вызове слова на стек будет положен указатель
на область данных слова и вызван код, указанный после
`does>` вполть до конца определения. Например, создать целочисленную переменную с начальным значением 3 можно вызовом create x 3 int, или, наглядней, 3 int-variable x По умолчанию, при выполнении слова, созданного командой create, на вершину стека кладётся адрес (ptr) переменной. Таким образом, распечатать значение x можно так (см. работу с указателями): x int@ . Записать новое значение 5 можно так: 5 x int! Можно определить переменную-указатель. В следующем коде переменная p является указателем на x или y и двойным разыменовыванием можно узнать значение: create x 5 int, create y 8 int, create p nil ptr, // дефолтное значение — nil x p ptr! // установили указатель на x p ptr@ int@ . // распечатает 5 y p ptr! // установили указатель на y p ptr@ int@ . // распечатает 8 В качестве альтернативы, можно использовать операторы to и value. 197 int-value x x . 127 int-to x x . Часто может возникнуть необходимость определения собственных определяющих слов. Для этого служит команда does>, которая применяется совместно с командой create: : name ... create ... does> ... ; Слово name при вызове выполнит код до does>. Код, указанный после does>, будет выполнен при запуске слова, которое созданно самым последним на помент вызова does>. Например, : printable-constant create , does> @ . ; создаёт определяющее слово, которое создаёт константы, которые при вызове печатают своё значение на экран. 189 printable-constant c c // напечатает 189 ptr%@ ( ptr -- % ) кладёт в стек значение по адресу ptr (команда разыменовывания) %! ( % ptr -- ) записывает значение % по адресу ptr ptr+% ( ptr1 -- ptr2) снимает со стека ptr, прибавляет к нему размер типа % и кладёт результат на стек cell+ ( ptr -- ptr ) увеличивает указатель на размер ячейки cells ( int -- int ) умножает число на размер ячейки %+! ( n ptr -- ) прибавляет к типу % по адресу ptr число n типа %. Только для арифметических типов %-inc! ( ptr -- ) увеличивает число типа % по адресу ptr на 1 %-dec! ( ptr -- ) уменьшает число типа % по адресу ptr на 1 1+! ( ptr -- ) увеличивает число типа int по адресу ptr на 1 1-! ( ptr -- ) уменьшает число типа int по адресу ptr на 1 malloc ( i -- ptr ) Выделяет i байтов в куче, возвращает указатель на область free ( ptr -- ) Освобождает выделенную ранее область embroДля embro-указателей работают все команды со стеком Команды контроля выполненияbranch ( -- ) совершает скачек на определённый адрес в коде. При этом адрес берётся из последующего после команды числа в шитом коде. ?branch ( f -- ) если f ложно, то действует как branch, в противном случае переходит к следующей команде >mark ( -- ) ( -- embroptr ) при исполнении ничего не делает, при компиляции кладёт текущий адрес на стек и выделяет в шитом коде место под запись адреса >resolve ( -- ) ( embroptr -- ) при исполнении ничего не делает, при компиляции извлекает со стека адрес в шитом коде и записывает по нему адрес текущего положения в шитом коде <mark ( -- ) ( L: -- embroptr ) при исполнении ничего не делает, при компиляции кладёт текущий адрес на стек <resolve ( -- ) ( L: embroptr -- ) при исполнении ничего не делает, при компиляции извлекает со стека адрес в шитом коде и записывает его в шитый код : ( "name" -- ) создаёт новое определение с именем xxx, переводит машину в режим компиляции ; завершает описание определения, переводит машину в режим интерпретации immediate делает последнюю определённую команду командой немедленного исполнения exit выйти из текущей функции, работает только внутри определения функции recurse рекурсивно вызвать текущую функцию, работает только внутри определения функции Этих команд достаточно, чтобы реализовать циклы и условные переходы. Внутри DEmbro подобные конструкции имеются, определены они следующим образом: : if compile ?branch >mark ; immediate : else compile branch >mark embro-swap >resolve ; immediate : then >resolve ; immediate : begin <mark ; immediate : while compile ?branch >mark embro-swap ; immediate : repeat compile branch <resolve >resolve ; immediate : until compile ?branch <resolve ; immediate Действия этих слов в режиме интерпретации не определены. <flag> if ... [else ...] then Снимает bool со стека, если оно true, то выполняет код сразу после if, в противном случае переходит к коду, который в else. begin ... <flag> until begin в режиме исполнения ничего не делает, until снимает bool со стека и если он равен false, то исполнение возвращается к begin begin ... <flag> while ... repeat begin в режиме исполнения ничего не делает, while снимает bool со стека, и если он равен false, то переходит к коду после repeat. repeat переходит к begin Примеры использования: : напечатать-числа-от-1-до-10 1 begin dup . 1 + dup 10 > until ; : рекурсивный-факториал ( n -- n!) dup 2 < if drop 1 else dup 1 - recurse * then ; Метакомандыcompile xxx ( -- ) компилирует команду xxx в шитый код, вне зависимости от её флага immediate
evaluate ( str -- ) интерпретирует код, находящийся в строке на стеке
evaluate-file ( str -- ) интерпретирует код, находящийся в файле, путь до которого указан в строке. Может использоваться для подключения библиотек
sys-version ( -- i ) кладёт число-номер версии форт-машины
state ( -- ptr) кладёт на стек указатель на значение переменной state
0 — DEmbro-машина находится в режиме интерпретации
1 — DEmbro-машина находится в режиме компиляции
[ ( -- ) immediate Переводит DEmbro-машину в режим интерпретации
] ( -- ) переводит DEmbro-машину в режим компиляции
%-literal ( C: % -- ) ( E: -- % ) immediate в режиме компиляции считывает
число со стека и компилирует код, который
кладёт это число на стек
last ( -xt) кладёт на стек последнюю созданную командуПример использования оследних трёх команд: : test1 120 str" Вызвано test1" ; : test2 [ test1 ] literal ; // после выполнения этой строки будет выполнена test1 test2 . // распечатает 120 Работа с типамиtypeof xxx ( -- type ) кладёт на стек тип, указанный в слове xxx type-size ( type -- i ) кладёт на стек размер типа t_% ( -- % ) то же, что и typeof % Размер типа - это кол-во байт, которые он займёт при хранении на стеке. Примеры работы с типами: // каждая строка печатает размер определённого типа typeof int type-size . typeof int64 type-size . t_ptr type-size . t_type type-size . Работа с консолью%. ( % -- ) выводит на консоль значение на вершине определённого типа и пробел после этого типа %$ ( -- % ) ожидает пока пользователь введёт в консоли значение нужного типа, кладёт его на стек (пока что работает только для арифметических типов) emit ( i -- ) берёт со стека целое число и выводит символ с кодом этого числа space ( -- ) печатает на консоль пробел spaces ( n -- ) печатает n пробелов cr ( -- ) переводит вывод на новую строку Взаимодействие с внешним кодом (в разработке)Команды по работе с внешними библиотеками (с dll в частности) lib-load ( pchar -- lib false | err true ) загружает библиотеку, путь до которой прописан в pchar. Каждую созданную библиотеку нужно высвободить вызовом lib-unload. Если при загрузке произошла ошибка, на вершине будет лежать true, а в err будет код ошибки. Даже если загрузить не удалось, вызывать lib-unload всё равно необходимо. lib-fun ( lib pchar -- lib ptr ) возвращает адрес функции в библиотеке с именем lib. Если такой нет, вернёт nil. lib-unload ( lib -- ) выгружает библиотеку из памяти, функции перестают быть доступными alien-fun xxx ( -- sys void ) указывает на то, что дальше будет идти описание прототипа внешней функции
alien-end ( sys void type1 type2 ... typen type-return conv -- ) создаёт определение с именем xxx
Среди typei не должно быть типа void. conv указывает на тип вызова, см. команды, начинающиеся на conv-.
После этого при вызове xxx будет производится обычный вызов функции:
1) Указатель функции будет взять из данных команды. Обратите внимание на то,
что изначально там находится nil, поэтому нужно вручную выставить указатель на
внешнюю функцию, например, оператором ptr-to.
2) параметры type1, ..., typen будут сняты со стека и переданы в функцию
3) после выполнения на стек будет положен type-return параметр
stdcall ( -- conv-stdcall ) поместить на стек идентификатор вызова stdcall
cdecl ( -- conv-cdecl ) поместить на стек идентификатор вызова cdeclДве предыдущие команды предназначены для использования в следующем виде: alien-fun xxx ... <return-type> <conv> alien-end Пример: alien-fun fun1 typeof ptr typeof ptr typeof int conv-stdcall alien-end Этот код создаст команду fun1, которая при вызове считает из данных указатель на внешнюю функцию, считает со стека два параметра ptr, ptr, вызовет с этими параметрами внешнюю функцию и поместит на стек возвращаемое значение (типа int). Вызов будет произведён в соответствии с stdcall типом вызова. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Хотелось бы видеть более конкретные цели.
А то тут получается, что клон форта с набором велосипедов для автора. И стороннему разработчику оч долго в такое въезжать с практически никаким результатом для себя.
И для понимания плюсов хорошо бы видеть примеры, описывающие ключевые особенности и плюсы языка.
Добрый день!
Основная цель -- сделать полезный с практической точки зрения язык, с последующим его использованием. Что же касается подробностей того, как DEmbro будет выглядеть ближе к концу, то они хранятся у меня в голове вследствие их неокончательности и ненужности другим людям, поэтому, увы, выдать написанный Стандарт я сейчас не могу, но, быть может, Вас интересуют какие-то конкретные моменты?
Стороннему разработчику долго въезжать, безусловно, -- но его никто не принуждает, он вправе выбрать что-то более простое и понятное для себя.
Примеры, может быть, будут.
PS. Странно, что о Вашем комментарии мне не пришло автоматическое оповещение на почту...