Document: Дипломная работа Гасаненко М.Л. (ф-т ПМ-ПУ СПбГУ, 1992)
Document History:

??.06.1990 - работа сдана в печать, опубликована в 1993 г. как: Гасаненко М.Л. Новые синтаксические конструкции и бэктрекинг для языка Форт.//Проблемы технологии программирования - СПб: СПИИРАН, 1992, с.148-162. (Выпуск сборника планировался в 1991 г., осуществлен в 1993 г.; в рецензиях статей в самом этом сборнике указаны данные Л.:ЛИИАН, 1991.)
??.05.1992 - защищена дипломная работа
02.06.1999 - преобразование в HTML с сохранением особенностей оригинального текста.

Title
Содержание
Beginning of the text


A note for non-Russian speakers. The main ideas of this work have been published as: Gassanenko, M.L. BacFORTH: An Approach to New Control Structures. Proc. of the EuroForth'94 conference, 4-6 November 1994, Royal Hotel, Winchester, UK, p.39-41.

(c) М.Л. Гасаненко, 1999
(c) M.L.Gassanenko, 1999

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

Если появится возможность использования этой вещи в каких-либо проектах - я буду заинтересован в сотрудничестве.

Мои адреса:
    mlg@forth.org - писать следует сюда, но если меня там нет, можно попробовать mlg@iias.spb.su, mlg@post.tepkom.ru, gml@ag.pu.ru.
Не забывайте, что существуют отпуска и праздники!

Замечания 1999 года:


Слова ALLOC FREE PRED SUCC не обязательны в реализации.

Слово -- есть знак комментария \ , это связано с тем, что на ЭВМ серии ЕС символов {|}\ не было или на клавиатуре, или на АЦПУ.

Конструкция *> ... <*> ... <*> ... <* называлась по-разному. Одно время я использовал синтаксис { ... | ... | ... } , но сейчас стало принято заключать в фигурные скобки локальные переменные, поэтому я склоняюсь к синтаксису {| ... || ... || ... |} или даже .{ ... .|. ... .|. ... }.

Отложенный cut полезен как средство создания своих ориентированных на задачу операторов отсечения: без него или можно обойтись, или же он используется настолько часто, что следует ввести специальную конструкцию.


Title
Beginning of the text
Введение
Вспомогательные средства
Рекурсивный блок
Простейшая реализация откатов
Бoлeе общий случай (слова PRO и CONT )
Стековая нотация
START-EMERGE как ловушка неуспеха
Явное задание действий при откате
Рекурсивный блок с трассирующимся телом
Блок альтернатив
Структурный оператор CUT
Отложенный CUT
Отрицание по неудаче и квантор общности
Обратимые операции
Расширение ядра системы
Бэктрекинг в режиме текстовой интерпретации
RUSH - выход и исполнение
Переменные типа QUAN, допускающие обратимое присваивание
Реализация системы BacFORTH
Результаты и обсуждение
Заключение
Приложение 1
Приложение 2
Приложение 3
Литература



 

САНКТ-ПЕТЕРБУРГСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ

Факультет прикладной математики - процессов управления

Кафедра технологии программирования


ГАСАНЕНКО Михаил Леонидович

 
 
 
 
 

НОВЫЕ СТРУКТУРЫ УПРАВЛЕНИЯ В ЯЗЫКЕ FORTH

 
 
 

Зав. кафедрой канд. ф.-м. наук доцент СЕРГЕЕВ Сергей Львович

Научный руководитель доктор ф.-м. наук проф. ТУЗОВ Виталий Алексеевич
 
 
 

Санкт-Петербург
- 1992 -


 












 
 

Новые структуры управления в языке FORTH

M.Л.Гaсaнeнкo

Ленинградский Государственный Университет


Система BacFORTH является расширением стандарта Форт-83. Она предоставляет пользователю гораздо более мощный по сравнению с другими языками программирования набор управляющих конструкций.
Использование фортовского адресного интерпретатора нестандартным способом позволило реализовать бэктрекинг предельно просто и эффективно. Наряду со стандартными система включает следующие
структуры управления:

Конструкция START...DIVE...EMERGE (суперцикл, безымянный рекурсивный блок) позволяет программировать рекурсивные алгоритмы без использования вспомогательных рекурсивных процедур.

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

Перевод иностранных терминов

backtracking - 1. бэктрекинг (перебор с возвратом как метод);
               2. бэктрекинг (перебор с возвратом как процесс);
               3. бэктрекинг (как метод исполнения программы);
               4. откат, бэктрекинг (возврат при переборе).
backtrackable - трассирующийся ( в честь трассы, остающейся на стеке откатов ).
success - успех.
to succeed - вырабатывать успех (термин "успешное завершение" неудачен - вырабатывая успех, процедура откладывается, а не завершается).
failure - неуспех (неудача).
to fail - вырабатывать неуспех, завершаться неуспехом.

Введение

Как средство языка программирования бэктрекинг появился в языках ПЛЭНЕР [7] и Prolog [8,10].

Бэктрекинг представляет собой метод организации перебора вариантов в глубину и заключается в том, что в точке, где возможен выбор нескольких вариантов, выбирается один из них, на специальном стеке оставляется точка возврата и продолжается выполнение программы. Такая передача управления вперед называется успехом. Если впоследствии оказывается, что рассматриваемый вариант неудачен, то вырабатывается неуспех, смысл которого в том, что делается откат назад, к последней точке возврата, где выбирается следующий вариант. Если вариантов больше нет, то и там снова вырабатывается неуспех. Таким образом, успех влечет дальнейшую проработку варианта, а неуспех - отказ от этого варианта и возврат с целью рассмотреть другой. Разумеется, данный метод организации перебора может быть использован не только для организации поиска подходящих вариантов методом перебора. Бэктрекинг может намного увеличить выразительную силу языка программирования и является очень полезным средством.

Итак, допускается передача управления как вперед, так и назад. Передача управления вперед называется успехом, a назад - неуспехом. Вырабатывая успех, процедура может оставить на специальном стеке точку возврата. Этa точка возврата содержит информацию, необходимую для возобновления процедуры при откате. При откате управление передается на самую последнюю из имеющихся на стеке точку возврата.

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

Слова, оставляющие точку возврата, условимся называть трассирующимися (в честь трассы, остающейся на стеке откатов)

Трассирующаяся (backtrackable) процедура может вырабатывать значения, удовлетворяющие определенному условию (вырабатывая успех для каждого из этих значений), или проверять условие (вырабатывая успех, если оно истинно ). Процедуры первого типа будем называть генераторами, а второго - фильтрами.

В основу использования бэктрекинга в системах логического программирования [10] положена следующая интерпретация: каждой процедуре соответствует условие. Процедура вырабатывает успех, если условие выполняется. Если выполнение возможно для нескольких случаев, она вырабатывает успех для каждого случая (процедура вырабатывает успех несколько раз, каждый раз устанавливая одно из состояний, при котором условие выполняется. В Прологе значения остаются в переменных, фигурирующих в записи предиката, и присваивание им значений можно рассматривать как установление определенного состояния). Таким образом процедура может вырабатывать значения, удовлетворяющие условию, завершаясь успехом для каждого из них. Успех последовательности процедур понимается как признак того, что все условия, соответствующие процедурам, выполнены (логическое И). Операция ИЛИ реализуется как конструкция, завершающаяся успехом, когда любая из альтернатив-процедур (или альтернатив-последовательностей процедур) завершается успехом. Обзор по логическому программированию можно найти в работе Агафонова и др.[1]. Попытки дополнить возможностями Пролога другие системы программирования предпринимались неоднократно; упомянем здесь лищь работу Карлсона [5], где описывается использование метода успешных продолжений для построения интерпретатора хорновских клозов средствами Лиспа. В работах Чарльтона [11] и Родригеза [12,13,14] описывается нестандартное использование механизма интерпретации шитого кода для обхода дерева решений в задачах типа анализа образцов и при работе с системой правил в экспертной системе. Для всех этих работ характерно использование вспомогательного флажка, сигнализирующего об успехе проверки. В работах Родригеза [12,13] механизм откатов не используется, если одна из альтернатив отрабатывает успешно, то остальные не проверяются. В работах Чарльтона [11] и Родригеза [14] для реализации механизма откатов в качестве стека откатов используется фортовский стек данных, что не очень удобно. Ниже будет показано, что при реализации механизма откатов можно обойтись и без вспомогательных данных, используя только механизм интерпретации кода. Кроме того, описанные в [11,12,13,14] системы являются средствами решения конкретных задач.

В данной работе описывается язык BacFORTH, сочетающий в себе как общепринятые, так и новые структуры управления, в т.ч. средства организации перебора с откатами (бэктрекинга) и управления этим перебором.

Описание языка Форт можно найти в книге [2] или же [3,4]. Выбор этого языка объясняется тем, что это один из немногих машинно-независимых языков, дающих доступ к структуре исполнимого кода и механизму его исполнения.

Вспомогательные средства

При реализации описываемых здесь структур управления был использован механизм динамической памяти, аналогичный переменным CONTROLLED языка PL/1.

Изначально заводится обычная фортовская переменная. Если она используется как динамическая, то после размещения она будет содержать в поле параметров адрес отведенной для нее области, состоящей из двух ячеек, первая из которых будет содержать значение, а вторая - указатель на ранее отведенную область.
: @@ ( V -> N ) @ @ ;
: @! ( N V -> ) @ ! ;
Здесь N обозначает значение, а V -адрес ячейки (напр. PFA переменной).
ALLOC! ( N V -> ) разместить V с начальным значением N
FREE ( V -> ) освободить переменную V . Область памяти переносится в список свободных областей.
FFREE ( V -> ) освободить все отведенные для переменной области памяти.
PRED ( A1 -> A2 ) перейти от адреса отведенной для переменной области памяти (A1) к адресу ранее отведенной области (A2).
Это механизм можно использовать для:
1) отведения памяти под переменные при входе в процедуру;
2) реализации списков;
3) реализации стеков.

Слово @R определено на ассемблере. Его эквивалентное определение на Форте может выглядеть так:
: @R R> R> DUP 2+ >R @ SWAP >R ;
С помощью этого слова системное слово LIT можно определить следующим образом:
: LIT @R ;
Еще одно полезное слово - >R> - эквивалентно R> SWAP >R . Введен еще один стек - L-стек, и на ассемблере определены слова для работы с ним: L@ L> >L LDROP LP@ LP! , аналогичные словам для работы со стеком возвратов R@ R> >R RDROP RP@ RP! .

Cуперциклы вместо вспомогательных рекурсивных процедур

Описываемая конструкция START...DIVE...EMERGE (суперцикл, [безымянный] рекурсивный блок) позволяет программировать рекурсивные алгоритмы без использования вспомогательных рекурсивных процедур.

Слова START и EMERGE ограничивают блок, а слово DIVE обеспечивает его рекурсивный вызов. DIVE может встречаться несколько раз и находиться внутри структур IF...THEN, BEGIN...UNTIL и т.д. Конструкция START...EMERGE (без DIVE) эквивалентна использованию вспомогательной (нерекурсивной) процедуры ( после START на стеке возвратов остается адрес части, следующей за EMERGE, EMERGE компилирует EXIT ).

Kонструкция
START . . . DIVE . . . EMERGE
эквивалентна (на PL/1)
P:PROC; . . . CALL P; . . . END P; CALL P;
точно также, как цикл BEGIN ... UNTIL эквивалентен
L: . . . IF ... THEN GO TO L;

Помимо избавления от вспомогательных процедур, преимуществом этой конструкции является то, что предотвращается разнос связанных по логике программы операторов в разные части текста. (Действительно, удобнее, если действия, выполняемые при инициализации перебора и после его окончания записаны там же, где и тело.)

Для реализации суперцикла использованы следующие вспомогательные слова:
(START) - переносит скомпилированный за ним адрес на стек возвратов и обходит этот адрес.
(CALL) - кладет на стек возвратов адрес для возврата и совершает переход по адресу, скомпилированному за ним.

: (START) R> DUP @ >R 2+ >R ;
: (CALL) @R >R ;

Слова немедленного исполнения START , DIVE и EMERGE компилируют слова (START) , (CALL) , EXIT и адреса за ними следующим образом:


Cлово DIVE можно использовать внутри структур IF ... THEN , DO ... LOOP и др. сколько угодно раз. Слова START и EMERGE должны быть парными. Слово ?DIVE эквивалентно IF DIVE THEN , N?DIVE эквивалентно IFNOT DIVE THEN . Cлово немедленного действия DIVE# , за которым должно следовать число ( N ) позволяет вызвать N-й объемлющий блок ( DIVE# 1 эквивалентно DIVE ).

Пример.

Данная программа распечатывает двоичное дерево, узлы которого находятся в памяти и имеют следующий формат:
<значение>
<адр.левого поддер.>
<адр.правого поддер.>
0 в качестве адреса означает пустое поддерево.
: PRINT ( адр-корня -> )
    ?DUP IF ( не пусто ли дерево ? )
            START DUP 2+ @ ?DUP ?DIVE  -- распечатать левое поддерево,
                                       -- если оно не пусто
                  DUP @ .              -- распечатать значение
                  DUP 4 + @ ?DUP ?DIVE -- распечатать правое поддерево,
                                       -- если оно не пусто
            EMERGE
         THEN ." ;" CR ;
Когда дерево распечатано, печатается ';' и выполняется CR .

Использование конструкции START-EMERGE без DIVE эквивалентно использованию вспомогательной процедуры.
Например,
: 5. START @R EMERGE [ 5 , ] . ;
эквивалентно
: LIT @R ;
: 5. LIT [ 5 , ] . ;
( Напомним, что : @R R> R> DUP 2+ >R @ SWAP >R ; )

Простейшая реализация откатов

Как известно, шитый код представляет собой последовательность ссылок на процедуры. Интерпретатор по очереди вызывает процедуры, соответствующие ссылкам. В указателе интерпретации (IP) хранится указатель на следующую ссылку. При вызове высокоуровневой процедуры выполняется вызов фрагмента шитого кода, являющегося ее телом: текущее значение IP сохраняется на стеке возвратов, после чего IP устанавливается на начало шитого кода вызываемой процедуры. Для выхода из нее выполняется действие EXIT : IP загружается значением, снимаемым со стека возвратов.

Слово ENTER вызывает фрагмент шитого кода, адрес которого лежит на стеке:

: ENTER >R ;   ( ';' компилирует EXIT )

Будем рассматривать сразу два уровня шитого кода (ШК). Имеем две высокоуровневые процедуры: вызывающую и вызываемую. На вершине стека возвратов - адрес оставшейся части ШК вызывающей
процедуры. Основная идея реализации формулируется очень просто: успех - это вызов продолжения; неуспех - возврат из продолжения. (В роли продолжения выступает остаток шитого кода вызывающей процедуры.)
Если вызываемая процедура выполнит код R@ ENTER , она осуществит вызов остатка ШК вызывающей процедуры.

: SUCC COMPILE R@ COMPILE ENTER ; IMMEDIATE

Чтобы вызвать возврат из продолжения, т.е. из фрагмента ШК, процедура, скомпилированная в него, должна выполнить RDROP EXIT .

: FAIL COMPILE RDROP COMPILE EXIT ; IMMEDIATE

Обратите внимание на то, что слово
: FAILURE FAIL ;
эквивалентно EXIT .
 

Пример.

: //2 DUP 2 MOD 0= IF SUCC THEN FAIL ;
-- успех, если число делится на 2
: //3 DUP 3 MOD 0= IF SUCC THEN FAIL ;
-- успех, если число делится на 3

: 1-10     1 BEGIN     SUCC
                       DUP 10 < WHILE
                       1+
             REPEAT  DROP FAIL ;
: DIV2 1-10 //2 DUP . ;
: DIV3 1-10 //3 DUP . ;
: DIV6 1-10 //2 //3 DUP . ;

Слово 1SUCC иногда позволяет сэкономить одну ячейку в пространстве шитого кода и один вызов во время исполнения :
: 1SUCC COMPILE R> COMPILE ENTER ; IMMEDIATE

Пример.

: //3   DUP 3 MOD 0=
        IF     1SUCC
        ELSE   FAIL THEN ; -- FAIL после 1SUCC не нужен
С помощью слова 1SUCC можно выработать успех только один раз. При реализации слов SUCC и FAIL на ассемблере выигрыш практически не ощутим, и единственное полезное свойство 1SUCC - то, что на стеке возвратов остается только одно значение.

Итак,
        стек откатов = стек возвратов.

Бoлeе общий случай (слова PRO и CONT )

Если в определении процедуры использованы трассирующиеся (backtrackable) слова, или даже цикл DO...LOOP, то слова SUCC и FAIL не будут работать. Для этого случая предназначены слова PRO , переносящее адрес продолжения на L-стек, и CONT , вызывающее успех с использованием этого адреса. Под процедурой здесь будем понимать процедуру, в которую скомпилированы слова PRO и CONT .

CONT (continue) - обеспечивает успех процедуры. После отката будет выполнен код, скомпилированный за CONT . ( Oбратите внимание, что при откате управление передается на последнюю точку возврата, а она установлена внутри процедуры).

PRO (prologue,PROLOG) - обеспечивает работоспособность слова CONT . Обычно это первое слово в определении. Оно переносит адрес продолжения (по которому надо перейти при успехе) со стека возвратов на L-стек и оставляет точку возврата, которая при откате снимает адрес продолжения с L-стека и вызывает неуспех процедуры.

: PRO R> R> >L ENTER LDROP ;
-- : PRO R> R> >L >R SUCC LDROP FAIL ;
: CONT L> >R SUCC R> >L ;

Заметим, что словo
: NEVER-SUCCEEDS PRO ;
также эквивалентно EXIT .

Пример.

: 1-10  PRO 11 1 DO  I CONT DROP  LOOP ;
: //3   PRO DUP 3 MOD 0=  IF  CONT  THEN ;

Общие замечания

Действия трассирующегося (backtrackable) слова нельзя адекватно описать состояниями стека до и после исполнения. Поэтому будем описывать такие процедуры в виде:
cocтояние стека до исполнения -> cocтояние стека после успеха
cocтояние стека после неуспеха <- cocтояние стека перед откатом
Необходимо следить за балансом стека: cocтояние стека после неуспеха не должно зависеть от того, завершалась процедура успехом или нет.

Наряду с записью

до -> усп
после <- отк

будем использовать запись

( до -> усп / после <- отк )

или даже

( до <-> усп )
если соответствующие cocтояния стека совпадают.

Трассирующуюся процедуру, перебирающую значения, принадлежащие соответствующему множеству, можно считать абстрактным итератором. Может показаться необычным, что такой процедуре - всего одному слову - нужно уделять столько же внимания, сколько и циклу - структуре из нескольких слов. Практика программирования на языке BacFORTH показала, что легче всего использовать слова вида ( сост1 <-> сост2 ) или же ( сост1 -> сост2 / <- ). В конце концов, если манипуляции со стеком кажутся чересчур сложными, можно использовать обычные переменные или переменные типа QUAN, возможно, с еще одним полем кода для обратимого присваивания.

START-EMERGE как ловушка неуспеха

При использовании в виде

START <действия> EMERGE <действия-после-неуспеха>

после того, как <действия> завершатся неуспехом, будет выполнена часть, следующая за EMERGE. Скомпилированный код выглядит следующим образом:

После исполнения слова (START) адрес <действий-после-неуспеха> оказывается на стеке возвратов. Т.к. откат реализуется как возврат, после неуспеха <действий> управление попадет на <действия-после-неуспеха>.

Пример.

: DIV6A START 1-10 //2 //3 DUP .
EMERGE ." вот и все " CR ;
Слово DIV6A работает так же, как и DIV6 , но дополнительно печатает " вот и все ".

Явное задание действий при откате

Слова немедленного действия BACK и TRACKING используются в виде
BACK <действия-при-откате> TRACKING
и компилируют код

При исполнении слово (CALL) положит адрес <действий-при-откате>
на стек возвратов, устанавливая таким образом точку возврата.

При использовании в виде
BACK <действия> TRACKING R>
на стек данных будет положен адрес фрагмента шитого кода, который выполняет <действия> и завершается скомпилированым EXIT . Этот фрагмент можно выполнить с помощью слова ENTER .
 

Backtrackable Recursive Block

Рекурсивный блок с трассирующимся телом

Конструкция

START PRO ... DIVE ... CONT EMERGE

является аналогом прологовской рекурсии

P :- ... , P, ... .

Разница между START PRO ... DIVE ... CONT EMERGE и START ... DIVE ... EMERGE в том, что в первом случае часть после DIVE выполняется после успеха рекурсивного вызова, а во втором случае часть после DIVE выполняется после возврата.

Данная конструкция использует L-стек примерно так же, как START-DIVE-EMERGE использует стек возвратов.

Того же эффекта можно достичь, используя RECURSE в теле вспомогательной трассирующейся процедуры.

Блок альтернатив

Блок альтернатив выглядит таким образом:

Альтернативы блока по очереди выполняются, и блок вырабатывает успех каждый раз, когда одна из альтернатив вырабатывает успех. Kогда альтернатива завершается неуспехом, начинает выполняться следующая, и т.д.; после того, как завершается неуспехом последняя альтернатива, весь блок завершается неуспехом. ( Oбратите внимание, что при откате управление передается на последнюю точку возврата, которая может быть установлена внутри альтернативы). На использование других управляющих структур внутри альтернатив нет никаких ограничений.

Слова *> , <*> и <* компилируют следующий код:

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

В духе логического программирования можно трактовать текст
        *> A <*> B <*
как
        A or B.

Другой способ реализации блока альтернатив -

START PRO   START <альтернатива#1> CONT EMERGE
            START <альтернатива#2> CONT EMERGE
            . . . . .
            START <альтернатива#N> CONT EMERGE
EMERGE <общие_действия>

может быть неудобен прежде всего тем, что использует L-стек. Однако это может обернуться и преимуществом; кроме того, вместо внутренних блоков START-EMERGE можно использовать вспомогательные определения, что может быть удобно в некоторых задачах.

Структурный оператор CUT

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

CUT: <действия> -CUT

Слово -NOCUT может использоваться в качестве пары для CUT: в случае, если оказывается, что точки возврата уничтожать не надо. Возможна вложенность. Синтаксический контроль при компиляции отсутствует, соответствие определяется во время исполнения. Это позволяет использовать cut без ограничений внутри других структур управления, например так:

CUT: . . . IF -CUT ELSE -NOCUT THEN

Teкст " CUT: P -CUT " можно трактовать как " Exists x:P(x) ". Oбратите внимание, что после того, как P выработает успех, отката к P больше не будет.

Отложенный CUT

Конструкция выглядит так:

CUT: . . . CUT< . . . IF NOCUT! THEN . . . >CUT
CUT: . . . NOCUT< . . . IF CUT! THEN . . . >CUT

CUT: отмечает на стеке возвратов начало отсекаемой области.

CUT< отмечает на стеке возвратов конец (предположительно) отсекаемой области. При откате точки возврата (по умолчанию) будут уничтожены, если не будет выполнен оператор NOCUT!.
NOCUT< отмечает на стеке возвратов конец (предположительно) отсекаемой области. При откате точки возврата не будут уничтожены (по умолчанию), если не будет выполнен оператор CUT!.
CUT! указывает, что точки возврата на отмеченной области при откате следует уничтожить. (Точка возврата слова NOCUT< (или CUT< , без разницы) заменяется на точку возврата слова CUT< .)
NOCUT! аналогично указывает, что точки возврата на отмеченной области уничтожать не следует. В итоге точки возврата уничтожаются или нет в соответствии с последним CUT! или NOCUT! .

Слова ALWAYSCUT< и NEVERCUT< аналогичны словам CUT< и NOCUT< , но в отличие от последних на них слова CUT! и NOCUT! не действуют, т.к. они оставляют для них неверный адрес.

>CUT обозначает конец отложенного оператора cut.

Oператоры CUT могут быть вложенными, но есть ограничение: соответствующие слова CUT: , CUT< и >CUT должны быть вместе или внутри, или вне "обрубаемого" участка.

Как и в случае обычного cut, снтаксический контроль при компиляции отсутствует, и соответствие определяется во время исполнения.

Операторы CUT! и NOCUT! могут выдаваться по несколько раз, уничтожение точек возврата будет или не будет производиться в соответствии с последним из них.

Пример.

Процедура P вырабатывает успех, если A,B и C,D - одна и та же пара чисел, может быть в другом порядке

: P PRO ( A B C D <-> A B A B )
CUT: *> NOCUT<
<*> SWAP BACK SWAP TRACKING NEVERCUT< <*
2OVER 2OVER D= IF CUT! CONT THEN ;

Использование NOCUT< в 1-ой альтернативе предотвращает повторный успех, если на стеке лежит A A A A .

Отрицание по неудаче и квантор общности

Конструкция

NOT: P -NOT

вырабатывает успех, если P завершается неуспехом, ни разу не выработав успеха. Если P вырабатывает успех, все точки возврата, установленные P , уничтожаются и конструкция завершается неуспехом. На месте P может быть любой осмысленный текст.

В зависимости от того, проверяет ли P условие или перебирает элементы некоторого множества, успех " NOT: P -NOT " можно трактовать как " not P(x) " или " not Exists x:P(x) ".

Квантор общности, выраженный через отрицание, записывается таким образом:

ALL <перебор_элементов_множества> ARE <условие> OTHER <действия> WISE

и компилирует код:

Действия между OTHER и WISE , как правило, необходимы для баланса стека. Если процедура P генерирует значения, удовлетворяющие условию p, а процедура Q проверяет условие q, успех ALL P ARE Q ... OTHER ... WISE можно понимать как ДЛЯ_ВСЕХ x из {x:p(x)} q(x) .

Преобразование логического значения в успех/неуспех и наоборот. Цикл FOREVER

Слово ONTRUE завершается неуспехом, если значение на стеке - ложь

: ONTRUE IFNOT FAIL THEN ;

Слово ONFALSE завершается неуспехом если значение на стеке - истина

: ONFALSE IF FAIL THEN ;

Oбратите внимание, что эти слова не оставляют точек возврата.

Для этих слов существует также и альтернативный синтаксис:

ONTRUE ?
ONFALSE N?

Эти слова используются очень часто, в том числе и в качестве условного оператора до конца определения. Для этих же целей служат и слова
?/DROP ( a f -> a / <- )        ?/2DROP ( aa f -> aa / <- )
?/NIP ( a b f -> a b / b <- b ) ?/2NIP ( aa bb f -> bb <- bb )
N?/DROP ( a f -> a / <- )        N?/2DROP ( aa f -> aa / <- )
N?/NIP ( a b f -> a b / b <- b ) N?/2NIP ( aa bb f -> bb <- bb )
Приведем пример одного из них:

: ?/DROP IFNOT DROP FAIL THEN ;
: ?/DROP ONFALSE DROP FAIL ;

Конструкция PREDICATE . . . SUCCEEDS кладет на стек истину, если текст между PREDICATE и SUCCEEDS вырабатывает успех ( в этом случае точки возврата уничтожаются), и ложь в противном случае. Точно также конструкция PREDICATE . . . FAILS кладет на стек истину, если текст между PREDICATE и FAILS завершается неуспехом.

Oбратите внимание, что возможна следующая конструкция:
['] P PREDICATE EXECUTE SUCCEEDS IF . . .
Слово FOREVER при откате каждый раз вырабатывает успех, позволяя организовывать бесконечные циклы. Впрочем, из такого цикла можно выйти, используя CUT .

: FOREVER BEGIN SUCC AGAIN ;

Обратимые операции

Слова
BDUP ( A <-> A A ) ,          BDROP ( A <-> ) ,
BPRESS ( A B <-> B ) ,        BSWAP ( A B <-> B A ) ,
B2DUP ( A B <-> A B A B ) ,   B2DROP ( A B <-> ) ,
B2PRESS ( A B C D <-> C D ) , B2SWAP ( A B C D <-> C D A B )
отличаются от соответствующих стандартных тем, что при откате выполняют обратные действия.

Слово B! ( N V -> / <- ) работает как ! , но при откате восстанавливает значение переменной. Слово BC! - это обратимое C! .
Слово KEEP ( V -> / <- ) не меняет значения переменной, но восстанавливает его при откате.

Слова
DROPB ( -> / <- A ) ,         2DROPB ( -> / <- A B ) ,
PRESSB ( -> / B <- A B ) ,    2PRESSB ( -> / C D <- A B C D ) ,
SWAPB ( -> / B A <- A B ) ,   2SWAPB ( -> / C D A B <- A B C D ).
отличаются от соответствующих стандартных тем, что работают при откате.

B<smth> означает "backtrackable <smth>" , а <smth>B означает "<smth> when backtracking" ("<smth> при откате").

Кроме того, на практике полезны еще две операции, не имеющие в Форте прямых аналогов:
RESTB ( a -> a / a <- )
2RESTB ( a b -> a b / a b <- )

Пример.

Следующие определения почти эквивалентны: они различаются
только числом значений, оставляемых на стеке возвратов.

: SWAPB PRO BACK SWAP TRACKING CONT ;
: SWAPB PRO START CONT EMERGE SWAP ;
: SWAPB PRO CONT SWAP ;
: SWAPB SUCC SWAP FAIL ;
: SWAPB 1SUCC SWAP ;

Расширение ядра системы

Слово VOCABULARIES по очереди выдает PFA всех словарей, имеющихся в системе.
: VOCABULARIES ( -> voc-pfa / <- ) PRO
    VOC-LINK @ >R
    BEGIN
        R@ 8 - CONT -- выдать PFA словаря
        R> @ DUP >R -- перейти к следующему
    0= UNTIL
    RDROP ;
Слово NAMES выдает по очереди NFA всех имен в данном словаре.
: NAMES ( voc-pfa -> nfa / <- ) PRO
    @ ( 1-ое nfa ) >R
    BEGIN
        R@ CONT
        R> N>LINK @ DUP >R
    0= UNTIL
    RDROP ;
При использовании многониточного хеширования (multi-thread hashing), полезно также иметь слова
V>THREADS ( voc-pfa -> thread / <- ) и
T>NAMES ( thread -> nfa / <- ).
Определение слова NAMES может выглядеть как
: NAMES ( voc-pfa -> nfa / <- ) PRO V>THREADS T>NAMES CONT ;

Бэктрекинг в режиме текстовой интерпретации

В режиме текстовой интерпретации переменная >IN играет роль указателя интерпретации (IP). Для поддержки бэктрекинга в режиме текстовой интерпретации служит слово -> . Оно сохраняет >IN и вырабатывает успех; при откате оно восстановит значение >IN .

Входной поток :

    bслово -> слово2
    |      |  |      |
    a1     a2 a3     a4

: -> STATE @ ONFALSE -- во время компиляции ничего не делает
    PRO
        >IN @ >R     -- теперь >IN содержит a3
            BACK R> 3 - >IN ! -- записать a2 в >IN
            TRACKING
                    CONT ;
-- :d INTERPRET
-- BEGIN BL WORD FIND DUP IF
-- 0< STATE @ AND IF ,
-- ELSE EXECUTE THEN
-- ELSE DROP 1NUMBER [COMPILE] LITERAL
-- THEN ?STACK AGAIN ;
-- :d ("defined") использовано вместо : чтобы подчеркнуть, что
-- мы только предполагаем, что слово определено таким образом;
-- если мы даже введем такое определение, то система все еще
-- будет использовать старое определение.

Для выхода из INTERPRET мой Forth использует "пустое" слово, эквивалентное EXIT . Eго можно определить как

: X RDROP ; IMMEDIATE 0 LATEST 1+ C!

Как и EXIT , это слово всегда завершается неуспехом и вызывает откат.

Например, мы хотим знать, сколько слов в словаре FORTH.

0 ' FORTH >BODY NAMES -> DROP 1+ <return>
. <return>
252

Tаким образом можно тестировать слова "вручную", без вспомогательных тестовых процедур.

RUSH - еще одно полезное слово

Трассирующиеся слова очень чувствительны к содержимому стека возвратов: на вершине должен быть адрес куда-идти-если-успех, a вторым элементом должен быть адрес куда-вернуться. Поэтому, если мы определим
: WORK 0< STATE @ AND IF , ELSE EXECUTE THEN ;
: INTERPRET
  BEGIN BL WORD FIND DUP IF     WORK ( !!! )
                         ELSE   DROP 1NUMBER [COMPILE] LITERAL
                         THEN ?STACK
  AGAIN ;
то с таким интерпретатором трассирующиеся слова будут работать совсем не так, как того следовало бы ожидать. На самом деле адрес куда-идти-если-успех будет адресом скомпилированного в слове WORK слова EXIT. Если мы, например, хотим выполнить обратимое присваивание, то обратное присваивание будет выполнено немедленно, и никакого заметного результата мы не получим.

Во многих cлучаях слово RUSH эквивалентно EXECUTE EXIT (а именно, если ему передано CFA обычного фортовского слова без RDROP-ов). Как и EXIT , оно загружает (pop) IP со стека возвратов, после чего работает как EXECUTE . Это слово позволяет избавиться от лишнего значения на стеке возвратов. Определить его стандартными средствами Форта невозможно.

Таким образом, если мы определим
: WORK 0< STATE @ AND IF , ELSE RUSH THEN ;
тогда INTERPRET будет подерживать бэктрекинг.

Слово RUSH> работает только если cкомпилированo:
: RUSH> R> @ RUSH ;
( На EXIT , скомпилированный ; , управление никогда не попадает )
RUSH> <smth> эквивалентно ['] <smth> RUSH .
Oбратите внимание, что EXECUTE можно определить как
: EXECUTE RUSH ;
Кроме того, RUSH эквивалентно RUSH> EXECUTE и может быть определено как
: RUSH RDROP RUSH> EXECUTE ;
EXIT эквивалентно RUSH> NOOP .

Переменные типа QUAN, допускающие обратимое присваивание

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

Переменные типа QUAN были впервые описаны в работе [15], наиболее распространенная в СССР реализация описана в [2]. Одна из проблем, возникающих при реализации определяющих слов для слов с несколькими полями кода, состоит в том, что слово DOES> , изменяющее поле кода, должно вызывать выход из исполняемого фрагмента шитого кода определяющего слова. В [2] эта проблема решается с помощью вспомогательных определений, от которых можно избавиться, используя START-EMERGE.

Вариант 1.

: CF>> LATEST NAME> HERE OVER - OVER CFL + SWAP
        CFL ALLOT CMOVE> ;     -- добавить еще одно поле кода
: ,DOES> COMPILE CF>> [COMPILE] DOES> ; IMMEDIATE

: CFA CREATE IMMEDIATE 1- CFL * ,
      DOES> @ ' +
      STATE @ IF , ELSE RUSH THEN ;

: BQUAN CREATE 0 ,
    START DOES> PRO B! CONT EMERGE ( NOW )
    START ,DOES> CFL + EMERGE ( AT )
    START ,DOES> [ CFL 2 * ] LITERAL + ! EMERGE ( TO )
          ,DOES> [ CFL 3 * ] LITERAL + @ ; ( default )

2 CFA IS     3 CFA AT     4 CFA NOW

Использование этих переменных может выглядеть следующим образом:
BQUAN V 8 IS V <return>
: LLL 5 NOW V V . ; <return>
V . LLL V . <return>
8 5 8

Только что описанная реализация не слишком удобна из-за того, что поля кода приходится определять, начиная с последнего. Поэтому опишем еще одну реализацию аналогичного синтаксиса.

Bариант 2:

: COMPILE-DOES-INSTRUCTION DOES-CMD @ , ;
-- системно-зависимое слово. В моем Форте с ITC (косв. шитым кодом)
-- :d DOES> COMPILE (DOES>) DOES-CMD @ , ; IMMEDIATE
-- :d (DOES>) R> LATEST NAME> ! ;
: CFAS ( #cfas -> ) 1- CFL * ALLOT ;
: (#DOES>) ( cfa# -> ) 1- CFL * LATEST NAME> + R> SWAP ! ;
: #DOES> COMPILE (#DOES>) COMPILE-DOES-INSTRUCTION ; IMMEDIATE
-- 1 #DOES> эквивалентно DOES>

: CFA ?EXEC CREATE IMMEDIATE 3 CFAS ,
    START 1 #DOES> [ CFL 2* ] LITERAL +
                   @ 1- CFL * ' +
                   STATE @ IF , ELSE RUSH THEN
    EMERGE
    START 2 #DOES> CFL + @ EMERGE
    START 3 #DOES> EMERGE ;

2 CFA TO 3 CFA AT 4 CFA NOW

: BQUAN CREATE 4 CFAS 0 ,
START DOES> [ CFL 3 * ] LITERAL + @ EMERGE
    START TO TO #DOES> [ CFL 2 * ] LITERAL + ! EMERGE
    START TO AT #DOES> CFL + EMERGE
    START TO NOW #DOES> RUSH> B! EMERGE ;

Реализация системы BacFORTH

Изначально система была реализована на Форте-К580 на ТС7063 с прямым шитым кодом и адресным пространством 32К. Позднее система была перенесена автором на Форт для ЕС-ряд2, разработанный им же. Часть слов системы BacFORTH была включена в ядро Форт-системы, другая часть хранится в виде исходного текста и загружается при запуске (загрузка занимает около 5 сек. на ЕС1045 при не слишком большой загруженности машины). Эта Форт-система использует косвенный шитый код; она использует не блочную систему, а обычные файлы СВМ (форматов F и V) и работает с 32-разрядными значениями (что, в общем-то, является отступлением от стандарта 83 года). Слова SUCC и FAIL в этой версии реализованы как CODE-определения, а не как макрокоманды; на ассемблере же определены и такие часто использующиеся слова, как PRO , (START) и т.п. Текст ядра Форт-системы не приводится, поскольку это более 20000 строк листинга и более 5000 строк исходного текста на ассемблере ЕС.

Результаты и обсуждение

В данной работе описаны система BacFORTH, обладающая новыми структурами управления, и методика, позволяющая реализовать бэктрекинг предельно просто и эффективно.

Для языка BacFORTH характерно сочетание бэктрекинга с общепринятыми структурами управления. Здесь описана реализация бэктрекинга на основе языка Forth. Другим языком, допускающим столь же легкую реализацию по этой методике, является язык ассемблера (разумеется, с макропроцессором).

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

Несколько слов о языке Пролог. Несмотря на название, он не обеспечивает программирование в терминах логики. Процедуры Пролога соответствуют условиям только в первом приближении, выражая скорее, в зависимости от контекста, идею проверки условия или перебора значений, удовлетворяющих условию, чем самого условия. На примере отрицания хорошо видно, что Пролог, использующий бэктрекинг, не обеспечивает адекватной семантики для логических понятий. Таким образом, даже при условии соблюдения достаточно сильного предположения о том, что язык логики может быть языком мышления программиста (или быть близок к нему), Пролог вынуждает программиста пользоваться им как не слишком удобным императивным языком. Бэктрекинг является удобным средством для организации перебора вариантов, но плохо подходит на роль семантики языка предикатов. В этом плане Пролог можно сравнить с транслятором формул FORTRAN, который, во-первых, понимает не любые формулы, во-вторых, это и не формулы, поскольку результат зависит от того, как считать, а "действительные числа" не образуют поля и вообще операция сложения не коммутирует, и который имеет довольно слабо развитые структуры управления, так что выкрутиться можно, но сложно.

Предположим, что разработка программы осуществляется по следующей схеме:
1) Начальное осмысление задачи.
2) Составление черновика. Алгоритм записывается на некотором неформальном языке черновика, позволяющем быстро восстановить в памяти любой фрагмент программы. Результатом является черновик программы на языке черновика и на языке мышления в сознании программиста.
3) Адаптация к возможностям языка программирования и перевод с языка черновика и языка мышления на этот язык.
4) Отладка. На этом этапе часто требуется быстрый перевод с языка программирования на язык мышления и наоборот.
Облегчение такого перевода может быть достигнуто как минимум двумя способами :
1) путем сближения понятий, используемых в языке программирования, с используемыми программистом;
2) за счет концентрации в одном месте логически связанных операторов. Это позволит устанавливать соответствие между меньшим фрагментом программы и меньшим объемом информации, находящейся в сознании программиста.
BacFORTH позволяет сконцентрировать в одном месте операторы, ответственные за такие элементы алгоритма, как, например, перебор или проверка условия, делая программу более легко обозримой.

Сравним, например, запись на языке BacFORTH

    START
        перебор1
                BACK действия1 TRACKING
            перебор2
                проверка-условия
                    действия
    EMERGE

с эквивалентной записью в духе языка CLU [6]

    FOR i IN итератор1 DO
        FOR j IN итератор2 DO
            IF проверка-условия THEN
                действия
            END
        END
        действия1
    END

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

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

Конструкция START-DIVE-EMERGE также способствует концентрации логически связанных операторов в одном месте. Помимо избавления от вспомогательных процедур преимуществом этой конструкции является то, что предотвращается разнос связанных по логике программы операторов в разные части текста. (Действительно, удобнее, если действия, выполняемые при инициализации перебора и после его окончания записаны там же, где и действия, организующие перебор.) Использование конструкции START-EMERGE (без DIVE ) эквивалентно использованию вспомогательной процедуры.

В алголоподобных языках START-DIVE-EMERGE скорее всего больше походила бы на процедуру, чем на цикл. Впрочем, она, видимо, потеряет в этих языках половину своей выразительной силы, т.к. они позволяют делать только то, что было заранее предусмотрено их реализаторами.

Появление этой конструкции в этих языках позволило бы поднять вопрос об объявлении вредными (considering harmful) вспомогательных рекурсивных процедур, как это произошло с GOTO. START-DIVE-EMERGE не сможет заменить нескольких взаимно-рекурсивных процедур, но в большинстве случаев ее будет достаточно, точно так же как GOTO иногда оказываются полезными.

Предложены средства управления бэктрекингом, более мощные, чем в языке Пролог. Предложенный структурный оператор cut мощнее, обычного прологовского cut и remote cut в ESP [9]. Явное задание области отсечения делает его более удобным в пользовании, чем существующие аналоги.

Сам по себе оператор "отложенный cut" предназначен для случая, когда сложность программы начинает выходить за пределы способностей программиста. При программировании на BacFORTH он используется очень редко; при программировании же на Прологе, где приходится тащить за собой трассу из точек возврата, он был бы очень полезен, и это чувствуется даже в сравнительно несложных задачах. Отсутствие необходимости все время программировать только в режиме бэктрекинга является преимуществом перед языком Пролог.

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

Конструкции BACK-TRACKING и START-EMERGE в качестве ловушки неуспеха делают, по сути дела, одно и то же, но возможность использования любой из них, на выбор, облегчает программирование, играя для человека роль смыслового ударения.

Конструкция, выдающая логическое значение в зависимости от того, была ли процедура завершена успешно, представляется весьма полезной. Обратите внимание, что ее можно использовать в виде
['] <процедура> PREDICATE EXECUTE SUCCEEDS IF ...

Кроме того, в сочетании с этими средствами можно использовать и классические структуры управления, предоставляемые Фортом.

Очевидно, что полученные результаты укладываются в рамки императивного подхода.

BacFORTH полностью унаследовал гибкость Форта. Точку возврата, например, можно убрать словом RDROP. Ниже приведен несколько более сложный пример.
: 1-10 ( <-> I ) PRO 11 1 DO I CONT DROP LOOP ;
-- распечатать значения, выдаваемые итератором,
-- группируя в пары.
: PRINT-PAIRED ( iterator -> )
    BACK ." ( "
        START BACK ." , "
                    BACK ." ) ; ( " DIVE TRACKING R>
              TRACKING R>
        EMERGE
    TRACKING R>
    -- на стеке лежит адрес фрагмента шитого кода,
    -- распечатывающего разделитель ( ", " или ") ; ( " )
    -- и кладущего на стек адрес фрагмента кода, печатающего
    -- следующий разделитель.
    BACK DROP ." ) . " CR TRACKING -- по окончании вывести последний
    -- разделитель и сбросить со стека адрес фрагмента кода.
    SWAP EXECUTE -- запуск итератора
        SWAP ENTER SWAP -- распечатать разделитель
            DUP . ;

' 1-10 PRINT-PAIRED <return>
( 1 , 2 ) ; ( 3 , 4 ) ; ( 5 , 6 ) ; ( 7 , 8 ) ; ( 9 , 10 ) .

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

Слово PRO можно реализовать как слово немедленного действия, изменяющее поле кода. Вряд ли это будет лучше, т.к. в этом случае возможности использования этого слова будут ограничены. Например, START PRO . . . DIVE . . . CONT EMERGE эквивалентно прологовской рекурсии.

BacFORTH работает гораздо быстрее, чем Пролог. Язык Пролог, имея небольшой набор базовых выразительных средств, вынуждает выражать простые вещи через более сложные и более общие. Сравнение с МПрологом на ЕС1045 показало, что BacFORTH работает, в зависимости от задачи, в 8-35 раз быстрее. ( Список - не лучшая замена для массива, и вряд ли здесь что-нибудь поможет.)

BacFORTH может быть реализован на базе Форт-системы, использующей любую из разновидностей шитого кода.

Примечательно, что для реализации бэктрекинга потребовался всего один дополнительный стек.

Все программы, написанные в стандарте Форт-83, сохраняют работоспособность и в системе BacFORTH.

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

На сегодня с помощью этой методики была написана только одна серьезная программа - Форт-ассемблер для процессора Intel80386. В этом ассемблере процедуры-предикаты используются для анализа формата операндов и в словах, работающих с таблицей меток, используются итераторы. Полного набора управляющих структур системы BacFORTH для этой задачи не потребовалось.

Заключение

Получен мощный набор выразительных средств, соединяющий в себе возможности алголоподобных языков и языков логического программирования. Благодаря использованию принципа "успех как вызов продолжения" стоимость успеха и неуспеха ненамного больше стоимости вызова и возврата. Предложены средства управления бэктрекингом, более мощные, чем в языке Пролог. Конструкция START ... DIVE ... EMERGE позволяет записывать рекурсивные алгоритмы, не вводя вспомогательных рекурсивных процедур.

Система BacFORTH соединяет в себе классические структуры управления и механизм откатов (бэктрекинг).

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

Приложение 1.

Слово, приведенное здесь в качестве примера, было использовано в дизассемблере для К580. Оно изменяет поле кода слова-команды ассемблера таким образом, что это слово при вызове будет проверять, могло ли оно породить текущую машинную команду.

-- формат поля кода:       <call> <address>  - 1+2 bytes
-- формат таблицы адресов
-- для полей кода:         <asm-addr> <dasm-addr>
: N>DASM ( CFA -> CFA ) DUP 1+ @ ( cfa addr )
TBL 24 + TBL
DO
I @ OVER = IF -- адрес найден в таблице
DROP I 2+ @ OVER 1+ ! -- изменить поле кода
0 LEAVE THEN
4 +LOOP DROP ;
Аналогичное слово N>ASM выполняет обратную замену
: N>ASM ( CFA -> CFA ) DUP 1+ @ ( cfa addr )
    TBL 24 + TBL
    DO
            I 2+ @ OVER =      -- адрес найден в таблице
            IF
                    DROP I @ OVER 1+ ! -- изменить поле кода
                    0 LEAVE
            THEN
    4 +LOOP
    DROP ;
-- итого : 2 заголовка, 2 поля кода, 62 ссылки на процедуры в шитом коде.

На BacFORTH это могло бы выглядеть так:

: TBLSCAN ( <-> I ) -- просмотр таблицы
    TBL 24 + TBL
    DO I CONT DROP 4 +LOOP ;

: N>DASM ( CFA -> CFA ) DUP 1+ @ ( cfa addr )
    CUT: DROPB TBLSCAN ( cfa addr i )
        2DUP @ = ONTRUE             -- если адрес найден в таблице
            PRESS 2+ @ OVER 1+ !    -- изменить поле кода
                -CUT ;
: N>ASM ( CFA -> CFA ) DUP 1+ @ ( cfa addr )
    CUT: DROPB TBLSCAN ( cfa addr i )
        2DUP 2+ @ = ONTRUE          -- если адрес найден в таблице
            PRESS @ OVER 1+ !       -- изменить поле кода
                -CUT ;
-- итого : 3 заголовка, 3 поля кода, 51 ссылка на процедуру в шитом коде.

Приложение 2 - Задача о 8 ферзях.

: ARRAY ( lwb upb -> )
        CREATE SWAP DUP , - 1+ HERE OVER ERASE ALLOT
        DOES> DUP @ - + 2+ ;
1 8  ARRAY A -- можно ли поставить на I строку?
2 16 ARRAY B -- можно ли поставить
-7 7 ARRAY C -- на диагонали?
1 8  ARRAY X -- X[I] где стоит ферзь на I строке
: PRINT
        2 SPACES 9 1 DO I . LOOP CR
        9 1 DO  I .
                9 1 DO
                        J I X C@ =
                        IF ." Ф " ELSE ." . " THEN
                LOOP
                I . CR
        LOOP
        2 SPACES 9 1 DO I . LOOP CR ;

-- Собственно программа
: LOOK-OVER-LINES ( <-> I ) PRO
                  9 1 DO I CONT DROP LOOP ;
: MAY-BE-PLACED? ( J I <-> J I ) PRO
    DUP A C@ ONTRUE
        2DUP + B C@ ONTRUE
            2DUP - C C@ ONTRUE     CONT ;
: PLACE ( J I <-> J I ) PRO -- разместить ферзя в J столбце на I строке
    2DUP X BC!
        0 OVER A BC!
            2DUP + B 0 SWAP BC!
                2DUP - C 0 SWAP BC!     CONT ;
: QUEENS
    1 A 8 BLANK 1 B 15 BLANK -7 C 15 BLANK
    1 BEGIN ( перебор столбцов )
            LOOK-OVER-LINES MAY-BE-PLACED? PLACE
                BDROP DUP 8 <
        WHILE
                1+ BACK 1- TRACKING
      REPEAT PRINT ;

Приложение 3 - Нестандартные слова и системная зависимость.

IFNOT     эквивалентно 0= IF
RDROP     : RDROP R> R> DROP >R ;
PRESS     : PRESS SWAP DROP ;
LATEST    ( -> nfa ) NFA последнего определенного слова
CFL       Code Field Length - длина поля кода

Кроме того, предполагается, что адреса на стеке возвратов - 16-разрядные, и
: EXIT1 R> DROP ;
эквивалентно EXIT .

Литература

[1] Агафонов В.Н., Борщев В.Б., Воронков А.А. Логическое программирование в широком смысле (обзор). - В кн.: Логическое программирование, М.:Мир, 1988, стр. 298-366.

[2] Баранов С.Н., Ноздрунов Н.Р. Язык Форт и его реализации. Л.:Машиностроение, 1988.

[3] Броуди Л. Начальный курс программирования на языке Форт. М.:Финансы и статистика, 1990.

[4] Бураго А.Ю., Кириллин В.А., Романовский И.В. Форт - язык для микропроцессоров. Л.:Знание, 1989.

[5] Карлсон, М. Пролог средствами функционального программирования. - В кн.: Язык Пролог в пятом поколении ЭВМ. М.:Мир, 1988, с.159-172.

[6] Лисков, Б., Гатег, Дж. Использование абстракций и спецификаций при разработке программ. М.:Мир, 1989.

[7] Пильщиков В.Н. Язык Плэнер. М.:Наука, 1983

[8] Стерлинг, Л., Шапиро, Э. Искусство программирования на языке Пролог. М.:Мир, 1990.

[9] Тикаяма Т. ESP - предварительная версия базового языка программирования компьютеров пятого поколения. - В кн.: Язык Пролог в пятом поколении ЭВМ. М.:Мир, 1988, с.17-32.

[10] Хоггер, К. Введение в логическое программирование. М.:Мир, 1988.

[11] Charlton, G. FOSM: A FOrth String Matcher. Proc. of the 1991 euroFORML Conf., 1991.

[12] Rodriguez, B. A BNF Parser in Forth. ACM SIGForth Newsletter, vol.2, no.2, December 1990, p.13-18.

[13] Rodriguez, B. Pattern matching in Forth. Proc. of the 1989 FORML Conf., 1989.

[14] Rodriguez, B. J. Rules Evaluation through Program Execution. Proc. of the 1990 Rochester Forth Conf., 1990, p.123-125.

[15] Rosen, E. High Speed, Low Memory Consumption Structures (QUAN and VECT). Proc. of the 1982 FORML Conf., p.191-196.
 
 
 
  < end of document >