SourceForge Logo

Особенности SPF

Особенности SPF

Краткое вступление для тех, кто уже знаком с какой-либо Форт-системой и стандартом ANS'94.

Последнее обновление: $Date: 2009/06/03 12:35:40 $


[Русский] [Английский]


Содержание


Установил SPF4. И где здесь что-куда?

Первое и самое важное - расположение ваших рабочих файлов. В дистрибутиве SPF есть каталог devel, предназначенный для разработчиков (в том числе и вас). Создайте в ней ваш подкаталог, например, ~john. И теперь вы можете подключать ваши файлы написав сокращённый путь в виде ~john/prog/myprog.f. Это упрощает взаимный доступ к библиотекам и программам. Общепринято библиотеки класть в подкаталог lib, а примеры программ в prog. Альтернативный вариант -- держать свой код там где вам удобно и использовать относительные пути при подключении своих библиотек. Но лучше будет добавить свой рабочий каталог в пути поиска файлов для INCLUDED.

В каталоге devel собраны наработки других SP-Forth'еров, с кратким (очень кратким) их обзором вы можете ознакомиться в SPF_DEVEL, либо пройтись по файлам самому.

В каталоге samples/win/spfwc вы найдёте GUI фронтенд для SPF под Windows. Просто запустите compile.bat и используйте spf4wc.exe из корневого каталога (рядом с spf4.exe).


Как запускать и подключать файлы?

В командной строке скормить файл SPF'у можно просто указав путь к нему в параметрах запуска,

spf4.exe ~john/prog/myprog.f

Заметьте, что путь для включения могут быть как абсолютным, так и относительно каталога devel.

В консоли SPF (в режиме интерпретации) достаточно набрать имя файла:

~john/prog/myprog.f

В целях совместимости лучше подключать явно:

S" ~john/prog/myprog.f" INCLUDED

Но правильнее всего использовать REQUIRE.


REQUIRE

В SPF есть нестандартное слово REQUIRE ("word" "file" -- ), где word - некоторое слово определённое в библиотеке file. Если слово word присутствует в контекстном словаре, REQUIRE считает, что библиотека уже была подключена и не загружает её. Так избегается двойная загрузка библиотек. Если же найти word не удаётся - библиотека подключается обычным образом (через INCLUDED). Например:

REQUIRE CreateSocket ~ac/lib/win/winsock/sockets.f
REQUIRE ForEach-Word ~pinka/lib/words.f
REQUIRE ENUM ~nn/lib/enum.f

NB: В качестве word выбирайте всегда наиболее уникальное слово из подключаемой библиотеки.


Пути поиска файлов для INCLUDED

S" file.f" INCLUDED будет искать в перечисленных ниже местах в именно таком порядке

  • короткое имя файла file.f (т.е. в текущем каталоге)
  • PATH_TO_SPF.EXE/devel/file.f (т.о. удобно использовать чужие наработки),
  • PATH_TO_SPF.EXE/file.f (так подключаются стандартные либы и другие файлы из поставки SPF).

Если требуется указать больше путей поиска (например использовать форт код разделяемый между системами, или неважно - любой код вне дерева каталогов SPF который не может быть адресован относительно текущего файла), то можно либо переопределить FIND-FULLNAME (который VECT) либо использовать внешнюю либу - ~ygrek/spf/included.f.

TODO: Вынести отдельно

Тогда потребуется только прописать в spf4.ini

~ygrek/spf/included.f
with: my_path\
S" my path with spaces/" with

и все файлы будут искаться в my_path в дополнение к описанному выше алгоритму (my_path может быть как абсолютным так и относительно spf4.exe).


Модули

В SPF есть модули, которые позволяют скрывать некоторые внутренние слова библиотек выводя наружу только слова для взаимодействия.

MODULE: vasya-lib
\ внутренние слова
EXPORT
\ слова взаимодействия, видные снаружи, компилируются во внешний словарь.
DEFINITIONS
\ опять внутренние слова
EXPORT
\ ну вы поняли :)
;MODULE

Код MODULE: vasya-lib можно писать много раз - последующие вызовы будут докомпилировать слова в тот же модуль. На самом деле слово определённое через MODULE: это обычный словарь.


Регистро-зависимость

SPF регистрозависим, то есть для него слова CHAR , Char и char - три разных слова. Сделать SPF независимым от регистра слова можно подключив файл lib/ext/caseins.f. По умолчанию, регистро-независимость включается сразу после подключения lib/ext/caseins.f.

Чтобы регистро-независимость временно выключать/включать, используйте переменную CASE-INS:

REQUIRE CASE-INS lib/ext/caseins.f
2 dup * .
CASE-INS OFF \ вернуть обратно режим зависимости от регистра
2 DUP * .
CASE-INS ON  \ включить регистро-независимость
2 dup * .

Ввод чисел

SPF позволяет вводить шестнадцатеричные числа вне зависимости от текущей системы счисления (значения переменной BASE) так:

0x7FFFFFFF

Если в числе последний символ будет точкой, то оно воспримется как двойное (то есть - 64-битное, занимающее две ячейки на стеке):

9999999999. 1. D+ D.

Вещественные числа (числа с плавающей точкой)

Подключив либу lib/include/float2.f, можно вводить вещественные числа в формате [+|-][dddd][.][dddd]e[+|-][dddd]. То есть главным определителем что число вещественное, является наличие символа e.

Слова для работы с вещественными числам реализованы согласно стандарта ANS-94:

REQUIRE F. lib/include/float2.f
0.1e 0.2e F+ F.
FVARIABLE a
FPI a F!
a F@ F.

Для переноса целых двойных значений между стеком данных и float-стеком в SPF есть слова D>F ( D: d -- F: f ) и F>D ( F: f -- D: d ). При переносе нецелого значения на стек данных часть после запятой отбрасывается без округления. Есть также аналогичные слова для работы с одинарными значениями:

10 DS>F 3 DS>F F+ F>DS .

Так как для float-стека в SPF используется аппаратный стек сопроцессора, то его особенности нужно иметь в виду (закольцованность и ограниченность 8-ью ячейками).


Структуры

Структуры в SPF создаются через слово -- (оно же FIELD). Пример:

0
CHAR -- flag
CELL -- field
10 CELLS -- arr
CONSTANT struct

Слова flag, field и arr будут прибавлять к адресу своё смещение относительно начала структуры. А в struct записан общий размер всей структуры. То есть, можно:

struct ALLOCATE THROW VALUE s \ взяли память из кучи под один экземпляр struct
1 s flag С!  10 s field ! \ записали значения в поля структуры
s arr 10 CELLS DUMP \ вывели содержимое массива в структуре
s FREE THROW \ сняли экземпляр struct

Структуры можно наследовать:

0
CELL -- x
CELL -- y
CONSTANT point \ у point два поля

point
CELL -- radius
CONSTANT circle \ у circle три поля: x, y, radius

point
CELL -- w
CELL -- h
CONSTANT rect \ у rect четыре поля: x, y, w, h

Где FORGET?

FORGET нет. Но есть MARKER ( "name" -- )lib/include/core-ext.f).


Где NOT?

Слова NOT (лог. отрицание) в ядре SPF нет. Его можно добавить расширением ~profit/lib/logic.f. Также там определяются другие слова для работы логикой: >= (больше или равно) и <= (меньше или равно).


Где DEFER?

Для создания "векторов" (DEFERred words) в SPF используется слово VECT ( "word" -- ). Присвоение действия вектору делается словом TO ( xt "word" -- ).

Если вам нужно использовать именно DEFER и IS, то эти слова можно подключить дополнительно из файла lib/include/defer.f.

Вариант с размещением ячейки вектора в USER-области потока называется USER-VECT ( "word" -- ). Обратите внимание: так как вектор порождённый USER-VECT на равных правах со другими переменными расположенными в USER-блоке (USER , USER-VALUE) заполняется нулём (что является неправильным xt), то выставить вектор в начальное значение нужно вам (например через AT-THREAD-STARTING).


Как одним словом очистить стек?

Наберите lalala. Или bububu. Или лялятополя. Возникнет ошибка и стек сбросится. На самом деле стек сбросит слово ABORT, которое будет вызвано если интерпретатор не найдёт введённое слово. Ну а на самом-самом деле - это делается так: S0 @ SP!


Комментарии

В SPF есть комментарий до конца строки \. Есть и обычные, скобочные, комментарии, которые к тому же ещё и многострочные. То есть:

\ комментарий до конца строки
( комментарий
и даже в несколько строк )

Есть слово \EOF которое делает комментарием всё идущее после него в файле. Таким образом удобно отделять примеры использования библиотеки от самой библиотеки.

word1 word2
\EOF
комментарий до конца файла

В SPF/Linux дополнительно есть комментарий #!. Если файл с текстом программы на форте сделать исполняемым и дописать в начале строку

#! абсолютный_путь_к_spf

то можно будет запускать скрипт на исполнение не указывая путь к spf в коммандной строке. Коммандная оболочка (shell) сама запустит spf передав путь к файлу скрипта в качестве параметра. А интерпретатор SPF первую строчку пропустит как комментарий.


Строки

В SPF в основном используются строки со счётчиком на стеке - т.е. два значения (addr u). Для записи строковых литералов (строковых констант) используется слово S", которое в зависимости от текущего режима выполняет несколько разные действия:

  • В режиме интерпретации строка находится во временном текстовом буфере разбора (TIB), и соответственно, работает только в пределах одной строки.

  • В режиме компиляции строка вкомпилируется непосредственно в шитый код определяемого слова.

NB : В память после символов строки добавляется один нулевой байт, в счётчике символов он не учитывается - это сделано для удобства работы с функциями WinAPI.

Слово S" создаёт т. н. статическую строку, она находится или в буфере, или в словарной структуре SPF. Для работы со динамическими строками, которые резервируются в "куче" и снимаются оттуда есть библиотека ~ac/lib/str5.f. Пример её использования:

REQUIRE STR@ ~ac/lib/str5.f
"" VALUE r \ создаём пустую строку
" мама, мама, " VALUE m
" что я буду делать?" VALUE w
m r S+  w r S+
r STYPE
> мама, мама, что я буду делать?

Кроме конкатенации строк можно использовать и подстановку (в том числе и других строк):

" 2+2={2 2 +}" STYPE
> 2+2=4

Исчерпывающее описание и более подробные примеры см. в самой библиотеке.

Надо также заметить что в ядре SPF поддерживается соглашение об именовании слов начинающихся с S-, и слов кончающихся -ED.

Префикс S- означает что слово принимает строку с явно заданной длиной (например есть SFIND в дополнение к стандартному FIND, есть SLITERAL к LITERAL).

Суффикс -ED есть в словах CREATED, INCLUDED, REQUIRED, ALIGNED. Он обозначает что это слово, в отличие от своего "корня", будет ожидать параметры на стеке, а не выбирать их из входного потока (или из глобальной переменной, как это происходит в случае с ALIGN и ALIGNED).

Например, стандартный CREATE берёт свой параметр из входного потока, тогда как CREATED явно забирает параметр со стека данных в виде начала строки и её длины.


Многозадачность

Потоки создаются словом TASK: ( xt "name" -- ) и запускаются словом START ( u task -- tid ), xt это исполнимый токен который получит управление при старте потока и на стеке будет пользовательский параметр u. Возвращаемое значение tid используется для остановки потока снаружи словом STOP ( tid -- ). Слова SUSPEND ( tid -- ) и RESUME ( tid -- ) используются для приостановки исполнения потока "на паузу", и возобновления исполнения (тоже снаружи самого потока).

Приостановить текущий поток на заданное время (в миллисекундах) можно словом PAUSE ( ms -- ).

Пример:

REQUIRE { lib/ext/locals.f

:NONAME { u \ -- }
   BEGIN
   u .
   u 10 * 100 + PAUSE
  AGAIN
; TASK: thread

: go
  10 0 DO I thread START LOOP
  2000 PAUSE
  ( tid1 tid2 ... tid10 )
  10 0 DO STOP LOOP
;

go

Обычные переменные (VARIABLE, VALUE) будут разделять своё значение между потоками. Если же переменная должна быть локальной для потока - следует определять её словом USER ( "name" -- ) или USER-VALUE ( "name" -- ). USER-переменные при старте потока инициализируются нулём.


Словари

Словари создаются либо стандартным VOCABULARY ( "name" -- ) либо словом WORDLIST ( -- wid ). Точнее, WORDLIST это более общее понятие - просто список слов. Есть также слово TEMP-WORDLIST ( -- wid ) создающее временный словарь, который по окончании работы надо освободить из памяти словом FREE-WORDLIST, содержимое временного словаря не попадёт в образ системы при использовании слова SAVE. Слово {{ ( "name" -- ) сделает словарь name контекстным, а слово }} вернёт как было. Пример:

MODULE: my
: + * ;
;MODULE
{{ my 2 3 + . }}

напечатает 6, а не 5.


Локальные и временные переменные

Не входят в ядро, но подключаются:

REQUIRE { lib/ext/locals.f

\ пример простого использования
: test { a b | c d }  \ a b инициализируются со стека, c и d нулями
  a TO c
  b TO d
  c . d . ;
1 2 test
>1 2
> Ok

Подробное описание и примеры использования смотрите в самой библиотеке lib/ext/locals.f.

lib/ext/locals.f реализует синтаксис несовместимый со стандартом ANS-94. ANS-реализация локальных переменных есть в библиотеке ~af/lib/locals-ans.f:

REQUIRE LOCALS| ~af/lib/locals-ans.f

: plus  LOCALS| a b |
a b + TO a
a b * ;
2 3 plus .
>10
> Ok

Генерация исполняемых модулей (exe-файлов)

Слово SAVE ( a u -- ) сохраняет всю форт-систему, включая все словарные структуры (кроме временных!) в исполняемый модуль путь к которому задаётся строкой a u. Точка входа определяется value-переменной <MAIN> для консольного режима и переменной MAINX для GUI. Режим определяется value-переменными ?CONSOLE и ?GUI. SPF-INIT? контролирует интерпретацию командной строки и подключение spf4.ini:

0 TO SPF-INIT?
' ANSI>OEM TO ANSI><OEM
TRUE TO ?GUI
' NOOP TO <MAIN>
' run MAINX !
S" gui.exe" SAVE  

или

' run TO <MAIN>
S" console.exe" SAVE

Подключение dll-библиотек

FIXME переписать, чтобы было понятней, примеры

Для функций использующих формат вызова stdcall (например Win32 API) :

WINAPI: SevenZip 7-zip32.dll

Для формата вызова cdecl (например msvcrt.dll) и функций с переменным числом параметров используйте :

REQUIRE CAPI: ~af/lib/c/capi.f
2 CAPI: strstr msvcrt.dll

Если нужно подключить все функции из dll-файла то можно использовать для stdcall :

REQUIRE UseDLL ~nn/lib/usedll.f
UseDLL "имя_библиотеки"

или:

REQUIRE DLL ~ac/lib/ns/dll-xt.f
DLL NEW: "имя_библиотеки" 

Для cdecl :

REQUIRE USES_C ~af/lib/c/capi-func.f
USES_C "имя_библиотеки"

или:

REQUIRE SO ~ac/lib/ns/so-xt.f
SO NEW: "имя_библиотеки"

SPF/linux

Низкоуровневые слова DLOPEN DLSYM symbol-addr symbol-call

Использование. По-умолчанию подключены libc libdl и libpthread. Подключение дополнительных библиотек :

USE имя_so_файла

Вызов функции

(( H-STDOUT S" hello world!" )) write DROP

Обратите внимание, параметры передаются слева направо, а после вызова DROP убирает возвращаемое значение. Если на стеке уже есть часть параметров -- можно писать так :

H-STDOUT 1 <( S" hello world!" )) write DROP

т.е. число перед <( указывает сколько параметров внешней функции находится на стеке "за скобками".

Реализация (( )) в ядре не позволяет делать вложенные вызовы. Для обхода этого ограничения используйте ~ygrek/lib/linux/ffi.f.

В spf/linux работает также ~ac/lib/ns/so-xt.f (абсолютно точно так же как в Windows!).


Средства отладки

Слово STARTLOG включает запись всего консольного вывода (TYPE, . -- любые слова, выводящие в stdout) в лог-файл spf.log в текущем каталоге. ENDLOG соответственно выключает такое поведение.

Больше средств в devel


Оптимизатор

SPF - форт-система с подпрограммным шитым кодом, то есть компиляция проходит сразу в исполняемый код в виде цепочек CALL <адрес-cfa-слова>. Этот код можно запускать и непосредственно, но в системе по-умолчанию есть ещё и оптимизатор, который обрабатывает машкод для большего быстродействия, выполняя inline-подстановку и peephole-оптимизацию. Подробнее на ForthWiki: "Оптимизирующий компилятор".

Управление оптимизатором (значения по умолчанию работают в подавляющем большинстве случаев, вам скорее всего не нужны эти опции!)

  • DIS-OPT отключает макрооптимизацию
  • SET-OPT включает макрооптимизацию (по-умолчанию включена)
  • 0 TO MM_SIZE отключает inline подстановку оптимизатора (помните что inline подстановка для DO LOOP и некоторый других слов выполняется ядром spf и не зависит от этой опции)
  • TRUE TO ?C-JMP включает оптимизацию хвостовой рекурсии (экспериментальная, по-умолчанию выключена, иногда глючит)
  • FALSE TO VECT-INLINE? отключает прямую компиляцию векторных вызовов.

NB : Если вдруг ваша программа начинает выкидывать неожиданные фортели - отключите временно оптимизацию словом DIS-OPT, есть вероятность (очень маленькая!), что это может быть ошибка в оптимизаторе. Если это так - локализуйте её и напишите пожалуйста багрепорт.

Результат компиляции слова в виде машинного кода можно посмотреть вручную с помощью дизассемблера:

REQUIRE SEE lib/ext/disasm.f
SEE слово

или получить построчный листинг (форт-код и соответствующий машкод)

REQUIRE INCLUDED_L ~mak/listing2.f
S" файл, машкод которого хотим посмотреть"  INCLUDED_L
\ листинг будет лежать рядом с подключаемым файлом

Оптимизация условных переходов

Например типичная комбинация 10 > IF ... THEN падает не в последовательность действий: укладка на стек литерала 10, сравнение двух верхних значений и запись результата на стек и затем проверку флага с условным переходом; а в две машинные команды:

lib/ext/disasm.f
:NONAME DUP 10 > IF 1 . THEN ; REST

Вывод:

cmp eax, # A
jle @@1
...
@@1:

Похожим образом обрабатываются другие типичные комбинации 2DUP = IF ... THEN и им подобные:

lib/ext/disasm.f
:NONAME 2DUP = IF 1 . THEN ; REST

Вывод:

cmp eax, 0 [ebp]
jne @@1
...
@@1:

Оптимизируются последовательности логических операций (0= здесь аналогичен отрицанию, поэтому последовательность 0< 0= означает "больше или равно нулю"):

lib/ext/disasm.f
:NONAME DUP 0< 0= IF 1 . THEN ; REST

Вывод:

or eax, eax
jl @@1
...
@@1:

Компиляция слов порождаемых словами CREATE, VARIABLE, VALUE, USER

При компиляции переменных или констант, вместо записи простого вызова DOES-действия порождённого слова, запускается процедура специальной обработки которая разворачивает вызов в аналогичную последовательность машинного кода. Например:

lib/ext/disasm.f
10 CONSTANT c
:NONAME c ; REST

10 VALUE vl
:NONAME vl ; REST

VARIABLE vr
:NONAME vr @ ; REST

Вывод:

mov     -4 [ebp] , eax
mov     eax , # A
lea     ebp , -4 [ebp}
ret

mov     -4 [ebp] , eax
mov     eax , 572410  ( vl+5  )
lea     ebp , -4 [ebp]
ret

mov     -4 [ebp] , eax
mov     eax , 57243C  ( vr+5  )
lea     ebp , -4 [ebp]
ret

Поддержка ANS

Максимального соответствия ANS'94 можно добиться подключив lib/include/ansi.f. Там определяются дополнительные наборы слов, некоторые заглушки и т. д.

Также исправляется нарушающая стандарт оптимизация слов из ядра для работы с файлами - OPEN-FILE, CREATE-FILE и другие неявно требуют строку оканчивающуюся нулём, тогда как стандарт этого не требует. lib/include/ansi-file.f при получении строки без нуля в конце самостоятельно добавляет нулевой байт. При этом имя файла копируется во временный динамический буфер, который остаётся неосвобожденным для последующего использования. При явном указании имени файла с помощью строчных литералов S" и строчных библиотек ~ac/lib/str*.f необходимости в этом исправлении нет, т.к. строки всегда заканчиваются нулем. Но при использовании не-SPF-библиотек это может пригодиться.


Callback'и

Callback это форт-слово котороё вы передаёте внешнему коду для выполнения (отложенно). Для внешнего кода callback это адрес функции. Но внешний код не может напрямую вызывать форт-код т.к. форт-система по другому использует регистры процессора. Поэтому слово-callback надо оформить специальным образом.

Слово CALLBACK: ( "name" xt bytes -- ) принимает xt слова которое надо оформить callback'ом и число байт которые будут использованы под параметры на стеке при вызове. Совмещение формата вызова (cdecl,stdcall) целиком ложится на вас. При формате вызова cdecl (по умолчанию для C/C++) вы должны оставить на стеке все входные параметры (продубировать перед использованием например). При stdcall - xt должно снять все параметры. Также обратите внимание, что callback должен всегда возвращать одно дополнительное значение - результат, даже если в коде вызывающем callback-функцию она описана как void (это особенность работы CALLBACK:). Пример :

В cpp коде функция callback объявлена так :

typedef void (*TestCallback)(char*,int);

форт-код callback'а

:NONAME ( n str -- )
  2DUP \ дублируем параметры (т.к. cdecl)
  ASCIIZ> CR TYPE \ строка
  CR . \ число
  0 \ возвращаемое значение
; 2 CELLS \ 2 параметра - 8 байт
CALLBACK: Test \ слово Test это callback

При выполнении callback'а создаётся новая USER-область. Если надо использовать USER-область какого-то конкретного потока - передайте и установите в callback нужный TlsIndex (пример). Если callback гарантированно будет вызываться в контексте потока созданного форт-системой, то инициализацию USER-области можно не проводить -- используя облегчённую реализацию CALLBACK: -- ~af/lib/QuickWNDPROC.f

NB : Внешний код может вызывать callback по разному :

  • вызывать в том же потоке откуда его установил форт-код
  • вызывать его из другого потока
  • вызывать его из нескольких потоков сериализуя вызов (callback'и выполняются по-очереди, но в разных потоках)
  • вызывать его из нескольких потоков конкуретно - т.е. несколько callback'ов может выполняться одновременно в нескольких потоках
  • во время выполнения внешней функции вызванной из callback генерировать ещё один вызов callback'а (рекурсивно)

Слово NOTFOUND

Если во время цикла INTERPRET не будет найдено очередное слово из входного потока - в текущем словаре ищется и вызывается слово NOTFOUND ( a u -- ). Если NOTFOUND не обрабатывает данное слово - он должен вывалиться с исключением. Иначе считается, что слово воспринято и трансляция продолжается. По умолчанию через NOTFOUND реализовано распознавание чисел и доступ к вложенным словарям.

Правило хорошего тона - при переопределении NOTFOUND сначала вызвать его старый вариант, и если он не отвалится по исключению - выполнять свои действия. Пример:

: MY? ( a u -- ? ) S" !!" SEARCH >R 2DROP R> ;
: DO-MY ( a u -- ) ." My NOTFOUND: " TYPE CR ;

: NOTFOUND ( a u -- )
  2DUP 2>R ['] NOTFOUND CATCH 
  IF
    2DROP
    2R@ MY? IF 2R@ DO-MY ELSE -2003 THROW THEN
  THEN
  RDROP RDROP
;

Или так:

: NOTFOUND ( a u -- )
  2DUP MY? IF DO-MY EXIT THEN
  ( a u )
  NOTFOUND
;

Для подключения в цепочку NOTFOUND'ов удобно использовать расширение ~pinka/samples/2006/core/trans/nf-ext.f


Scattered colons

Расширяемые слова (описание техники: "Scattering a Colon Definition", на английском языке). Позволяют уже после определения слова добавлять в него новые действия. Слово ... подготавливает место для будущего расширения. ..: и ;.. добавляют действие как расширение.

: INIT ... orig ; 
\ если вызвать INIT здесь то выполнится orig
..: INIT extend1 ;.. 
\ если здесь - то extend1 и orig именно в таком порядке
..: INIT extend2 ;.. 
\ эквивалентно : INIT extend1 extend2 orig ;
\ и так далее

Подобного эффекта можно добиться и с помощью векторов, но так намного удобнее.

Через scattered colons в SPF реализованы слова AT-THREAD-STARTING и AT-PROCESS-STARTING, которые вызываются при старте потока и при старте процесса соответственно. Например библиотека lib/include/float2.f добавляет в AT-THREAD-STARTING действия по инициализации внутренних переменных.


Область видимости

Поиска слова во время интерпретации задаётся привычно стеком CONTEXT, но иногда возникает необходимость явно указать из какого словаря брать слово. Для этого используется синтаксис вида Словарь::слово. Пример:

MODULE: someWords
: TYPE 2DROP ;
;MODULE

ALSO someWords \ добавляем словарь someWords в контекст
S" foo" TYPE \ ничего не выводится, берётся TYPE из someWords
S" bar" FORTH::TYPE \ явное указание "обычного" TYPE из главного словаря

Для выполнения неких действий над словами через ' (взятие xt слова), POSTPONE (компилирование), TO (для выполнения вторичного действия слова) область видимости нужно указывать следующим образом:

Словарь::' word
Словарь::POSTPONE word
etc 

т.к. такие слова самостоятельно разбирают входной поток и word не попадает в INTERPRET и соответствующий NOTFOUND.


Обработка исключений

Обработка исключений в SPF происходит согласно ANS-94 с использованием THROW и CATCH.

Слово THROW ( n -- ) выбрасывает исключение с кодом n (кроме случая когда n равен нулю), т.е. выполнение текущего слова прерывается и происходит возврат до тех пор пока исключение не будет поймано.

Слово CATCH ( i*x xt -- i*x n | 0 ) выполняет xt и ловит исключения которые будут сгенерированы во время исполнения xt. Если исключений не было - на стеке ноль, в противном случае возвращается код исключения (тот n что был передан в слово THROW которое вызвало это исключение, или код системной ошибки), при этом устанавливается глубина стека данных равной той что была до выполнения xt (но сами данные на стеке могут быть испорчены если xt писало в стек на этой глубине).

Все исключения делятся на два типа, по способу возникновения - системные (защита памяти, деление на ноль, etc) и родные (слово THROW с ненулевым параметром). Все они ловятся одинаково, но в случае системных дополнительно печатается отчёт об исключении.

Многие слова возвращают ior (код возврата ввода/вывода), например операции с файлами (CREATE-FILE, OPEN-FILE, READ-FILE, WRITE-FILE, CLOSE-FILE и т.д.) и операции с памятью в куче (ALLOCATE, FREE, RESIZE). Этот ior соответствует коду ошибки (если таковая была) и его можно выбросить по THROW.

: file S" rewdsadwerdfstrg" R/O OPEN-FILE THROW ; \ пытаемся открыть несуществующий файл
: divide 
    ['] / CATCH  \ ловим системное исключения от деления
    IF ." Dont divide by " . DROP  \ исключение произошло - на стеке два числа параметра
    ELSE ." Result : " . \ результат успешного выполнения
    THEN CR ; 
: test 
   10 2 divide \ всё ок
   1 0 divide  \ системное исключение - отчёт SPF и наше сообщение из слова divide
   ['] file CATCH IF ." Caught exception" CR THEN \ ловим родное исключение
   \ не обязательно выбрасывать ior, можно проанализировать его на месте
   S" dsderewfdstrtr" R/O OPEN-FILE IF ." bad file" ELSE ." Good file" THEN CR ;
test

Коды ошибок, которые принимаются словом THROW и оставляются словом CATCH, интерпретируются согласно файлу spf.err, который находится в каталоге lib. Текстовые сообщения которые печатаются в отчёте об исключении берутся из этого файла.