My favorites | Sign in
Project Home Downloads Wiki Issues Source
Search
for
DEmbro  
Updated Jul 28, 2011 by vite...@gmail.com

Текущие наработки

Игра Snake Journey совместима с дембро ревизии 507.

Игра Maze Journey совместима с DEmbro ревизии 484.

Текущая нестабильная версия http://code.google.com/p/dforth/source/browse/#svn/trunk/release Стабильной следует считать ревизию 243, хотя код, написанный под DEmbro той версии, станет некорректным в готовящихся версиях. (В основном, в связи с вынесением строк в отдельный стек.)

Основные разделы

В настоящий момент описание DEmbro ещё не разбито на файлы, и главное описание находится ниже на этой странице.

  1. FAQ
  2. Стандарт DEmbro v0.12
  3. Язык
    1. Основы
    2. Синтаксис
    3. Работа со стеком
    4. Функции, переменные и константы
    5. Арифметика
    6. Операторы ветвлений
    7. Строки
    8. Стандартный ввод-вывод
    9. Работа с файлами
    10. Выразимость
    11. Тип command
    12. Использование словарей
    13. Взаимодействие с внешним кодом
    14. Scattered Colons (расширяемые команды)
    15. Системные команды
    16. Расширенный набор команд для работы со стеком
    17. Трюки
  4. Использование в качестве скриптового движка
  5. TODO

Идеология

  1. Упор на практическое применение
  2. Маленький размер скомпилированного файла, маленький размер компилятора
  3. Быстрая скорость интерпретации, компиляции и, по возможности, исполнения
  4. Дух классического форта

Рекомендуемая литература

DEmbro является forth-подобным языком программирования, поэтому чтобы многое понять рекомендую почитать кое-что о форте.

  1. Л. Броуди «Начальный курс программирования на языке форт»
  2. С.Н. Баранов, Н.Р. Ноздрунов «Язык форт и его реализации»
  3. Л. Броуди. «Способ мышления - ФОРТ. Язык и философия для решения задач.»
  4. Стандарт Forth 94 (на русском)
  5. Стандарт Forth 83 (на английском)
  6. winglion ru набросок стандарта от mOleg

Синтаксис

Программа представляет из себя последовательность команд, каждая команда отделена от других пробелами (пробелом считается символ от 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, шитый код — готовый код для выполнения на виртуальной машине.

Есть три режима:

  1. Интерпретация — входящий исходный код сразу выполняется (source -> действия)
  2. Компиляция — входящий исходный код преобразуется в шитый код (source -> embro)
  3. Выполнение — выполняется скомпилированный ранее шитый код (embro -> действия)

Каждое слово в зависимости от текущего режима выполняет некоторое действие.

Если не оговорено иное, то описание к командам следует понимать как описание её действия при интерпритации и выполнении, а при компиляции просто генерируется вызов её выполняющей части.

Типы

Название Зависимость от платформы Размер Комментарий
int Да Ячейка Целое знаковое число
uint Да Ячейка Целое беззнаковое число
ptr Да Ячейка Указатель
int8 Нет 8 бит Целое знаковое число
int16 Нет 16 бит Целое знаковое число
int32 Нет 32 бит Целое знаковое число
int64 Нет 64 бит Целое знаковое число
uint8 Нет 8 бит Целое беззнаковое число
uint16 Нет 16 бит Целое беззнаковое число
uint32 Нет 32 бит Целое беззнаковое число
uint64 Нет 64 бит Целое беззнаковое число
extended Да Машинное число с плавающей точкой максимальной точности
float Нет 32 бит Знаковое число с плавающей точкой
double Нет 64 бит Знаковое число с плавающей точкой
bool Да Ячейка Булев тип
flag Да Ячейка Флаг
pchar Да Ячейка Указатель на последовательность символов с завершающим нулём
str Да Ячейка Строка, блочный тип
type Да Ячейка Идентификатор типа
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 типом вызова.

Comment by bsi...@gmail.com, Aug 2, 2011

Хотелось бы видеть более конкретные цели.

А то тут получается, что клон форта с набором велосипедов для автора. И стороннему разработчику оч долго в такое въезжать с практически никаким результатом для себя.

И для понимания плюсов хорошо бы видеть примеры, описывающие ключевые особенности и плюсы языка.

Comment by project member vite...@gmail.com, Aug 23, 2011

Добрый день!

Основная цель -- сделать полезный с практической точки зрения язык, с последующим его использованием. Что же касается подробностей того, как DEmbro будет выглядеть ближе к концу, то они хранятся у меня в голове вследствие их неокончательности и ненужности другим людям, поэтому, увы, выдать написанный Стандарт я сейчас не могу, но, быть может, Вас интересуют какие-то конкретные моменты?

Стороннему разработчику долго въезжать, безусловно, -- но его никто не принуждает, он вправе выбрать что-то более простое и понятное для себя.

Примеры, может быть, будут.

PS. Странно, что о Вашем комментарии мне не пришло автоматическое оповещение на почту...


Sign in to add a comment
Powered by Google Project Hosting