Язык Форпост

(Пётр Советов)

Вступление
Описание языка
Особенности реализации на Си
Справочник по словам ядра

Вступление

Форпост это встраиваемый стековый язык, с простой, компактной и эффективной реализацией на ANSI C. Программист, использующий Форпост, вполне в состоянии изучить во всех подробностях небольшое внутреннее устройство языка и, при желании, модифицировать его для своих нужд. Обучение Форпосту занимает минимум времени, поскольку язык основан всего на нескольких общих и ясных правилах. С другой стороны, не смотря на простоту и минимальность конструкции, в языке широко используются гетерогенные массивы и функции высшего порядка, а также соблюдается эквивалентность кода и даннных. Интерпретатор Форпоста является безопасным, в том смысле, что ошибки программиста не приводят к краху всей системы. Существует автономная версия интерпретатора, благодаря чему Форпост может использоваться и как язык общего назначения.

Пример программы

"../tools/tools.fp" load # загрузить библиотеку tools.fp, для определения print и cr

"hello, world" print cr # занести текст на стек, напечатать, перейти на новую строку


Среди других примеров, в дистрибутиве Форпоста находится простой почтовый клиент, работающий через сокеты по протоколам smtp и pop3.

Описание языка

Язык Форопост является стековым, подобно языкам Форт и Постскрипт. В стековом языке данные явно и последовательно укладываются в стек, выбираются оттуда исполняемыми словами, а результаты выполнения вновь оказываются на стеке. В арифметических выражениях это соответствует обратной польской форме записи(RPN).

Синтаксис

Некоторое количество литералов различных типов и исполняемых слов, последовательно обрабатываемых интерпретатором составляют программу на Форпосте. Вид числовых литералов соответствует соглашениям языка Си. Строки выделяются двойными кавычками. Массивы выделяются фигурными скобками. Слова разделяются пробельными символами. Комментарии начинаются с символа решётки и действуют до завершения строки.

Примеры

-1 0xFF 3.14 # числовые литералы
"hello, world" # строка
{ 1 2 3 4 5 } # массив
print cr # исполняемые слова

Типы данных

Встретив в тексте программы некоторый литерал, интерпретатор Форопоста заносит его на стек соответствующего типа:
Истина и ложь кодируются целыми -1 и 0.

В Форпосте дополнительно существует два стека:

Массивы

Единственной составной структурой данных в Форпосте является массивы, ими представляются:
При дублировании массивов на стеке копируются только их адреса, сами же массивы постоянно занимают место в памяти Форпоста. Индексируются массивы начиная с нуля. Выборка элементов равносильна их выполнению. Присвоить имя массиву можно с помощью точки с запятой ;, так же определяются и новые слова.

Примеры

{1 2 3 4} @ + + + # сумма элементов
{1 2 {3 4}} 2 :@ @ + # сумма элементов внутреннего массива
"hello" { "hello, world" print cr } ; # определение нового слова

"p" { {"post" "script"} } ;
"f" { "for" "th" } ;
f 0 :@  p 1 :a!
p @ print print # :-)

Управляющие конструкции

Помимо вполне традиционных if и ifelse в Форпосте имеется слово немедленного выхода из n вложенных массивов break, а также слово recurse для рекурсии, которая сводится к простому безусловному переходу на начало массива, если recurse занимает в нем последний элемент.

Пример(определение факториала)

"do" # n {a}, исполнить n раз массив a
{ dup {adup >c @ c> 1 -} {drop adrop  2 break} ifelse recurse } ;

"i" # n - 1 2 .. n, занести на стек n элементов, от 1 до n
{ 1 swap 1 max {dup 1 +} do drop } ;

"fact" # n - 1*2*..n, факториал
{ i dup 1 - {*} do } ;

Особенности реализации на Си

Базовый Форпост состоит из следующих файлов:
Эти три функции необходимы для вызова Форпоста из внешней программы:

Виртуальная машина

Все ячейки памяти Форпоста имеют одинаковую структуру.

typedef struct cell {
  op tag;
  union {
    int i;
    FLOAT f;
    struct cell *a;
  } data;
} cell;

В cell имеется две части:
Исполнение Форпост-кода происходит следующим образом:

for(;;) (ip->tag)();

Поле tag может хранить адрес примитива, а также адреса следующих функций:
Примитивы должны изменять указатель ip самостоятельно.

При создании массива длины n память в словаре выделяется на n+2 элемента. Сначала записывается размер массива, далее идут ячейки с данными, напоследок записывается элемент с tag = tR.

Справочник по словам ядра

"name" {code} ;
определить слово name c действием code

n array {a}
создать массив a из n нулевых элементов

{a} length n
возвратить длину n массива a

{a} @
исполнить a

{a} i :@
исполнить элемент массива a с индексом i

n {a} i :!
a[i]  =  n, записать n элементом массива a с индексом i

{x} {a} i :a!
a[i]  = {x}

{x} {a} i :x!
a[i]  = {x}, x записывается как исполняемое слово

{a} i :a? ?
истина, если элемент массива сам является массивом

n dup n n
дубль n

n drop
убрать n

n1 n2 swap n2 n1
поменять n1 и n2 местами

n1 n2 over n1 n2 n1
добавить сверху копию n1

n1 n2 n3 rot n2 n3 n1
переместить n1 наверх

n1 n2 2dup n1 n2 n1 n2
дубль n1 и n2

n1 n2 2drop
убрать n1 и n2

{a} adup {a} {a}
дубль a

{a} adrop
убрать a

{a1} {a2} aswap {a2} {a1}
поменять a1 и a2 местами

{a1} {a2} aover {a1} {a2} {a1}
добавить сверху копию a1

{a1} {a2} {a3} arot {a2} {a3} {a1}
переместить a1 наверх

n >c
поместить n на cstack

{a} a>c
поместить {a} на cstack

n :c
исполнить элемент cstack с индексом n

c>
снять верхний элемент с cstack и исполнить его

cdrop
убрать верхний элемент cstack

с= ?
истина, если оба верхних элемента, снятые с cstack, равны

{a} i :>c
 занести элемент массива a с индексом i на cstack

{a} i :c!
снять элемент с cstack и записать его элементом массива a с индексом i

{a1} {a2} n copy
скопировать n элементов из a1 в a2

n {a} if
исполнить a, если n не нуль

n {a1} {a2} ifelse
исполнить a1, если n не нуль, иначе исполнить a2

n break
убрать c rstack n верхних элементов

n1 n2  + n3
n3 = n1 + n2

n1 n2 - n3
n3 = n1 - n2

n1 n2 * n3
n3 = n1 * n2

n1 n2 / n3
n3 = n1 / n2

n1 n2 mod n3
n3 = n1 % n2

n1 n2 u/mod r q
беззнаковое деление, с остатоком r и частным q

n negate n
n = -n

n abs n
n = abs(n)

n1 n2 < ?

n1 n2 = ?

n1 n2 > ?

n1 n2 u< ?
беззнаковое сравнение

n not ?
истина, если n равно нулю

n1 n2 min n1|n2
наименьшее значение

n1 n2 max n1|n2
наибольшее значение

n1 n2 and n3
n3 = n1 & n2

n1 n2 or n3
n3 = n1 | n2

n1 n2 xor n3
n3 = n1 ^ n2

n invert n
n = ~n

n1 n2 lshift n3
n3 = n1 << n2

n1 n2 rshift n3
n3 = n1 >> n2

n emit
отобразить символ с кодом n

"text" n evaluate
интерпретировать текст длины n

"filename" load
интерпретировать файл filename

{a} abort
завершить интерпретацию текущей программы, очистить стеки, исполнить a