Skip to main content

Блог инженера

Блог о минимализме, инжинерии и программировании.



MiniForth и расширение Форт на Си.

  | #Форт программирование unfinished

Расширение Форт программами на С. Исторически, Форт был “вещью в себе”. Он не нуждался ни в операционной системе, ни в способах расширения, кроме ассемблерных вставок. Эта простота обеспечивала успех Форт во времена ЭВМ и первых персональных компьютеров. И эта же черта - одна из причин упадка Форта сейчас. Без возможности вызова внешних библиотек Форт непригоден для практических задач.

На первый взгляд - всё просто исправить, вместо встроенного ассемблера добавить встроенный Си. Но на практике - это совсем не просто. Ассемблер - простой язык. По сути - это операции поиска и замены. Обычно, Форт-ассемблер даже не содержит переходов, вставки используются для вызова системных функций и подобных случаев. С Си всё гораздо сложнее. У программ на Си имеется структура. Для доступа к внешним библиотекам нужен, как минимумм, свой линковщик. Хорошая идея становится сложной. Я нашёл реализацию Форт, которая обходит эту сложность.

Типичная реализация Форт состоит из двух частей. “Внутреннего компилятора” - минимальной реализации Форт. Как правило, это очень простая система с базовым словарём и компилятором, без интерпретатора. Именно этот “внутренний комплиятор” переносился на новую систему, когда Форт использовался на ЭВМ. Т.к. он действительно очень прост, то его писали на ассемблере или даже машинных кодах. Это также называется программой самозагрузки “bootstrap”. Внутренний компилятор собирает слова Форт более высокого уровня, которые определены уже на основе слов из базового словаря. Они компилириются в шитый код Форт. Создаётся словарь Форт, соответсвующий реализации языка, ANSI или какому-то из более давних стандартов. Так устроен, например, pForth. Форт компилируется из программы на Си, но словарь собирается уже из слов на Форте, как описано выше. Расширять Форт программами на Си можно добавляя соответствующие слова в пользовательский словарь. В этом же pForth это сделано довольно просто, есть файл pfcustom.c, где можно добавлять определения слов на Си, расширяя тем самым базовый словарь Форт. Для взаимодействия со стеком форта предусмотрены специальные функции, которые получают значения со стека и возвращают на него же.

MiniForth устроен иначе. Специальная программа-транслятор переводит определения Форт на Си, объединяет “базовый словарь”, который изначально на Си и “расширенный словарь”, который определён на Форте с вставками на Си (или без них) в один Си файл и компилирует его внешним компилятором. Получается монолитный Форт, не разделённый на базовый Форт и базовый словарь. Только новые команды включаются в словарь. Таким образом, можно довольно гибко управлять тем, какие же слова попадут в ядро Форт. Довольно легко делать специализированные реализации Форт под отдельные задачи. Но главный плюс - возможность расширения. Добавить графические функции в MiniForth мне удалось в течение одного вечера. Большую часть времени я потратил на то, чтобы разобраться, как работает совмещение кода на Форте и Си. Есть небольшое количество примеров, но почти никакой документации по этому вопросу.

Эта публикация не претендует на то, чтобы стать отсутствующим руководством по расширению MiniForth, скорее справка для себя о том, как это работает. Поэтому структура отражает скорее очерёдность, в которой я разбирался с этим вопросом, а не логику работы. Какие-то важные детали, которые мне кажутся очевидными, могут быть упущены.

Расширение MiniForth при помощи Си

Делать расширения на Си можно только для основного словаря Форт. Таким образом, нельзя делать Си вставки в конструкцию CREATE DOES>, например. Идея расширения в использовании Си библиотек в Форт программах. Для своего случая использования графической библиотеки я сделал программу - “обёртку” для расширения языка. Например, в переменных Форт не получится хранить структуры, есть решаемая проблема с возвратом строк. В простейшем случае программа получает целые числа, возвращает на стек их же. Технически, такое целое число может быть указателем на структуру данных Си-программы, например. Я не пробовал такой вариант.

Создаём новый или добавляем в имеющийся словарь с расширением “mfc”. При этом можно комбинировать использование форт-слов и Си-кода. Признак Си-кода - заглавная буква C первым символом строки. Ниже - пример:

: INITGRAPH \ (Winows_width Windows_height width height )
C  mferr(mfin(4));
0 XPen !	
0 YPen !                          \ Move pen position to zero
C  InitGraph( mffth, mfthd, mfsec , mftos );
C  mf2drop, mf2drop; ;

Здесь мы создали определение нового Форт-слова INITGRAPH. Проверили, что на стеке имеется минимум четыре значения - параметра слова. Обнулили две переменные Форт. Вызвали функцию из программы - “обёртки” с параметрами, снятыми со стека Форт. Сбросили со стека четыре используемые значения. Обратите внимание на последнюю строку с двумя точками с запятой, это важно. Первая точка с запятой закрывает определение Си, а вторая - завершает определение форт-слова. Технически, в Си тоже можно завершать строчки точкой с запятой с пробелами перед ней. Но в данном случае это приведёт к тому, что во-первых, эта точка с запятой вызовет завершение форт-слова, а не Си-строки, во-вторых компиляция на Си может закончится с ошибкой. Причём это будет ошибка компиляции, она возникнет не при сборке файла dictionary.mtf, поскольку его транслируют в код на Си, а транслятор не проверяет правильности конструкций на Си. Ошибку выдаст компилятор. К счастью, транслятор переносит комментарии в транслированный код, так что выявить ошибку не так сложно.

Передача значений стека, как параметров функции

При вызове функций С из словарей Форт на значения стека ссылаются при помощи специальных параметров. Это не функции, а переменные, которые доступны в момент исполнения слова.

  • mftos - первый параметр на стеке;
  • mfsec - второй параметр на стеке;
  • mfthd - третий параметр на стеке;
  • mffth - четвёрный параметр на стеке.

Они используются так

mftos=mfsec;

Выше часть определения для слова DUP, например.

Проверка наличия необходимых параметров на стеке

Форт - бестиповый язык, поэтому всё, что можно проверить - это наличие необходимого количества данных на стеке. Проверка выполняется

mferr(mfin(N))

Где mferr - функция для проверки ошибок. Именно она выбросит сообщение “Stack underflow”. Параметр N - необходимое количество чисел на стеке. Функция проверена и работает.

Имеется функция mfout(N). Очевидно, её задача - проверить не возникнет ли переполнения стека, когда на него попадут параметры работы слова. Функция не проверена, но используется в словаре core.mfc.

Манипуляции стеком

Есть специальные операторы для манипуляции стеком.

mfup

Поднимает стек, т.е. добавляет новое значение. Полное определение слова DUP выглядит так:

mfup, mftos=mfsec;

Сброс параметров со стека для “разрушающих” слов Форт

  • mfdrop - вызов DROP из Си;
  • mf2drop - вызов 2DROP из Си.

Помимо способа добавить число на стек, как показано выше, есть функция mfpush(N), где N - значение, которое будет добавлено на стек.

Другие интерфейсные фукнции

Имеется несколько интерфейсных фукций, к которым нет хорошего описания и я не вполне понимаю, что они делают.

mfprint(N)

Очевидно, печатает в Форт какие-то диагностические и информационные сообщения. Однако, ‘printf’ тоже прекрасно работает, т.к. стандартный вывод связан с экраном Форт. Наверяка есть нюансы, но я их не знаю.

mfzero(N)

Очевидно, проверка на ноль. Используется для операций деления. Я не знаю, чем это отличается от прямого сравнения mftos == 0. Возможно, только удобством.

mfCell

Пока непонятная мне функция.

Компиляция MiniForth

Компиляция MiniForth рассмотрена в руководстве. Но все заботы, связанные с расширением словаря программисту нужно взять на себя. Во-первых, нужно добавить все заголовочные файлы в ‘mf3.h’. Во-вторых, вручную модифицировать командный файл для сборки MiniForth. По-умолчанию он собирает только сам MiniForth, все расширения нужно добавлять вручную.

Результат расширения MiniForth

Расширять MiniForth определениями на Си несколько проще, чем расширять pForth, например. Некоторая разница в том, что расширяя MiniForth можно активно использовать Форт-слова, а вызовы Си делать только в необходимых случаях. Т.е. можно хранить указатели на области памяти, сохранять промежуточные значения и т.п. в переменных и структурах данных Форт. В этом некоторая принципиальная разница между MiniForth и pForth.

Переносимость программ между MiniForth и pForth великолепная. После определения базовых слов для работы с графикой - слова более высокого уровня заработали без изменений.

About Mikhail Kiselev

Photo of Mikhail Kiselev

Приветствую в моём блоге! 😄 Меня зовут Михаил. Я инженер и программист. Живу в Сибири, работаю на Сахалине, путешествую где придётся. Я предпочитаю пост в блог посту в твиттер. Описание полезной технологии или гаджета предпочитаю описанию заката или посиделок в кафе.