Язык Форпост
(Пётр Советов)
Вступление
Описание языка
Особенности
реализации на Си
Справочник
по словам ядра
Вступление
Форпост это встраиваемый стековый язык, с простой, компактной и
эффективной
реализацией на 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 # исполняемые слова
Типы данных
Встретив в тексте программы некоторый литерал, интерпретатор Форопоста
заносит его на стек соответствующего типа:
- stack - стек целых
- fstack - стек чисел с плавающей точкой
- astack - стек массивов
Истина и ложь кодируются целыми -1 и 0.
В Форпосте дополнительно существует два стека:
- cstack - стек компилятора, который может содержать данные первых
трех типов
- rstack - стек возвратов из исполняемых слов, явно программистом
не используемый
Массивы
Единственной составной структурой данных в Форпосте является массивы,
ими представляются:
- Текстовые строки
- Исполняемые слова
- Пользовательские типы данных
При дублировании массивов на стеке
копируются только их адреса, сами же массивы постоянно занимают место в
памяти Форпоста. Индексируются массивы начиная с нуля. Выборка
элементов равносильна их
выполнению. Присвоить имя массиву можно с помощью точки с запятой ;, так же
определяются и новые слова.
Примеры
{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 } ;
Особенности реализации
на Си
Базовый Форпост состоит из следующих файлов:
- forpost.c - виртуальная машина, интерпретатор
- core.c - слова ядра
- forpost.h - декларации для forpost.c и core.c
- dict.c - словарь примитивов
Эти три функции необходимы для вызова Форпоста из внешней
программы:
- forpost_open() - подготовка интерпретатора к работе
- interpret(s, size) - интерпретация строки s длины size
- forpost_close() - освобождение ранее выделенной памяти
Виртуальная машина
Все ячейки памяти Форпоста имеют одинаковую структуру.
typedef struct
cell {
op tag;
union {
int i;
FLOAT f;
struct cell *a;
} data;
} cell;
В cell имеется две части:
- tag - адрес функции, связанной с данной ячейкой
- data - объединение, способное хранить данные различных типов
Исполнение Форпост-кода происходит следующим образом:
for(;;)
(ip->tag)();
Поле tag может хранить адрес примитива, а также адреса следующих
функций:
- tX - исполнение слова по адресу из ip->data.a, предварительно
занести на rstack адрес ip+1
- tR - возврат из слова: снять с rstack элемент и исполнить его
- tJ - безусловный переход по адресу из ip->data.a
- tI - целочисленный литерал, значение из ip->data.i
- tF - литерал с плавающей точкой, значение из ip->data.f
- tA - литерал массива, значение из ip->data.a
- tS - останов, выход на текстовый интерпретатор
Примитивы должны изменять указатель 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