Изменения:
09.10.2001
Добавлен раздел "Конструкция DO ... WHILE ... LOOP ... ELSE ... UNLOOP THEN".
15.10.2000 для Т32-Форта версии 1.02
DO ... WHILE ... LOOP ... ELSE ... UNLOOP THEN
CASE ... ESAC
CASE ... ESACCASE ... ESACCASE ... ESACWHILE
CASE ... ESACCASE ... ESACDONE: еще один способ организации цикла с несколькими выходами
START ... EMERGEПри этом даже если какая-то структура управления требует приобретения человеком специфических знаний или навыков, это может (при определенных условиях) считаться приемлемым. Однако обязательным условием применения структуры управления в приложении является выигрыш в затрачиваемых на работу с кодом усилиях.
Кроме этого, выбор тех или иных структур управленния — дело вкуса. (Когда мы чувствуем, что что-то должно быть так, а не иначе, и не можем объяснить почему, мы объясняем это вкусом. Возможно, что психолог тут смог бы говорить об объективных закономерностях человеческого мышления. )
IF ... THEN{
IF ... ELSE ... THEN
BEGIN ... WHILE ... REPEAT
BEGIN ... UNTIL
BEGIN ... AGAIN
DO ... LOOP
?DO ... LOOP
DO ... +LOOP
?DO ... +LOOP
CASE ... OF ... ENDOF } ... ENDCASE
(Фигурными скобками '{' и '}' выделена часть, которая может повторяться 0 или более раз.) Эти структуры управления описаны в учебниках и здесь не рассматриваются.
Например,
\ IF ( C: -- orig ) \ THEN ( C: orig -- ) : 0MAX DUP 0< ( C: ) IF ( C: orig ) DROP 0 ( C: orig ) THEN ( C: ) ;Слово
IF порождает команду перехода неизвестно куда,
а слово THEN указывает, куда именно нужно осуществлять переход.
Значение orig нужно для того, чтобы слово THEN знало,
какую команду нужно поправить.
Переходы назад порождаются также в два приема:
\ BEGIN ( C: -- dest ) \ UNTIL ( C: dest -- ) \ convert digits until the quotient is zero : #S ( ud1 -- ud2 ) ( C: ) BEGIN ( C: dest ) # ( C: dest ) 2DUP D0= ( C: dest ) UNTIL ( C: ) ;Слово
BEGIN вырабатывает значение dest с информацией,
по какому адресу нужно вернуться, а слово UNTIL порождает
условный переход на этот адрес.
AHEAD ( C: -- orig ) порождает безусловный переход вперед.
Адрес назначения указывается словом THEN, так что можно считать,
что вся конструкция имеет вид
AHEAD ... THEN
Такое использование слова AHEAD на практике встречается
крайне редко, хотя и является допустимым.
Слово AHEAD предназначено для использования внутри других
компилирующих слов, в том числе компилирующих слов, определяемых пользователем.
Слова CS-PICK, CS-ROLL, CS-DROP и
CS-SWAP предназначены для манипулирования со значениями
orig и dest. В отличие от слов, реализующих структуры управления
(например, IF), эти слова не имеют признака IMMEDIATE.
На приведенных ниже стековых диаграммах символ od обозначает значение orig или dest.
CS-PICK
( C: od[u] ... od[0] u -- od[u] ... od[0] od[u] )
Скопировать u-ое значение orig или dest
(нумерация начинается от вершины стека с нуля).
CS-ROLL
( C: od[u] od[u-1] ... od[0] u -- od[u-1] ... od[0] od[u] )
Переместить u-ое значение orig или dest из глубины стека на вершину
(нумерация начинается от вершины стека с нуля).
CS-DROP ( C: od -- ) Убрать со стека значение
orig или dest.
CS-SWAP ( C: od[1] od[2] -- od[2] od[1] )
Поменять местами на стеке два значения orig или dest.
Из этих слов CS-PICK и CS-ROLL являются стандартными,
а CS-DROP предложено для включения в стандарт 2001(?) года.
Слово же CS-SWAP можно определить через CS-ROLL как:
: CS-SWAP 1 CS-ROLL ;
CS-SWAP:
: ELSE ( orig1 -- orig2 ) [COMPILE] AHEAD CS-SWAP [COMPILE] THEN ; IMMEDIATE(Слова
[COMPILE] будут объяснены позднее).
В конструкции IF ... ELSE ... THEN
слово IF порождает команду условного перехода вперед (не указывая
адреса назначения) и оставляет на стеке значение orig1.
Слово ELSE порождает команду безусловного перехода вперед
(на стеке остается orig2), меняет местами этот orig2 с
оставленным словом IF значением orig1,
и вызывает слово THEN, завершая начатое словом IF
построение команды условного перехода.
На стеке остается orig2, дожидающийся своего слова THEN.
Еще раз:
( C: ) IF ( C: orig1 ) ... ( C: orig1 ) ELSE ( C: orig2 ) ... ( C: orig2 ) THEN ( C: )или, с учетом определения слова
ELSE,
( C: ) IF ( C: orig1 ) ... ( C: orig1 ) AHEAD ( C: orig1 orig2 ) [ CS-SWAP ] ( C: orig2 orig1 ) THEN ( C: orig2 ) ... ( C: orig2 ) THEN ( C: )Теперь о словах
[COMPILE].
Если мы напишем
: foo AHEAD CS-SWAP THEN ; IMMEDIATEто слова
AHEAD и THEN выполнятся во время компиляции,
и получится слово, которое ничего не делает. При обращении к этому слову
выполнится перожденный словом AHEAD переход на THEN,
после чего будет выполнен выход из процедуры. Чтобы слова
AHEAD и THEN скомпилировались, перед ними
пишется [COMPILE].
Еще один пример.
: WHILE [COMPILE] IF CS-SWAP ; IMMEDIATE : REPEAT [COMPILE] AGAIN [COMPILE] THEN ; IMMEDIATEВот как это работает:
BEGIN ( C: dest ) ... ( C: dest ) WHILE ( C: orig dest ) ... ( C: orig dest ) REPEAT ( C: )или, более подробно,
BEGIN ( C: dest ) ... ( C: dest ) IF ( C: dest orig ) [ CS-SWAP ] ( C: orig dest ) ... ( C: orig dest ) AGAIN ( C: orig ) THEN ( C: )Нетрудно видеть, что
WHILE (вызывая слово IF)
начинает построение условного перехода вперед, а REPEAT
(вызывая слово THEN) завершает построение команды перехода.
IF ( C: -- orig ) Начать построение условного перехода впередAHEAD ( C: -- orig ) Начать построение безусловного перехода впередTHEN ( C: orig -- ) Закончить построение перехода впередELSE ( C: orig1 -- orig2 ) Выполняет слова AHEAD CS-SWAP THENBEGIN ( C: -- dest ) Начать построение перехода назадUNTIL ( C: dest -- ) Закончить построение условного перехода назадAGAIN ( C: dest -- ) Закончить построение безусловного перехода назадWHILE ( C: od -- orig od ) Выполняет слова IF CS-SWAPREPEAT ( C: dest orig -- ) Выполняет слова AGAIN THEN
Замечание. Фраза "выполняет слова" не означает, что приведенный далее текст
можно помещать в фортовское определение. Если Вы хотите скомпилировать
вызовы этих слов в текущее определение, то перед словами с признаком IMMEDIATE
надо ставить [COMPILE], например, [COMPILE] IF[ CS-SWAP ]
REPEAT является сокращением для
AGAIN THENWHILE,
завершающийся фразой AGAIN THEN THENWHILE, завершающийся фразой
AGAIN THEN THEN THENОтвет: стандарт языка Форт позволяет это. Приведем несколько примеров.
Пример 1.
BEGIN ( C: dest ) ... ( C: dest ) WHILE ( C: orig1 dest ) ... ( C: orig1 dest ) WHILE ( C: orig1 orig2 dest ) ... ( C: orig1 orig2 dest ) AGAIN ( C: orig1 orig2 ) THEN ( C: orig1 ) THEN ( C: )Пример 2. То же самое, но с использованием
REPEAT.
BEGIN ( C: dest ) ... ( C: dest ) WHILE ( C: orig1 dest ) ... ( C: orig1 dest ) WHILE ( C: orig1 orig2 dest ) ... ( C: orig1 orig2 dest ) REPEAT ( C: orig1 ) THEN ( C: )Пример 3. Выход из цикла возможен как через
WHILE,
так и через UNTIL.
BEGIN ( C: dest ) ... ( C: dest ) WHILE ( C: orig dest ) ... ( C: orig dest ) UNTIL ( C: orig ) THEN ( C: )Пример 4. В зависимости от того, невыполнение какого условия привело к прекращению цикла, нужно выполнить те или иные действия.
BEGIN ( C: dest ) ... ( C: dest ) WHILE ( C: orig1 dest ) ... ( C: orig1 dest ) UNTIL ( C: orig1 ) \ если вышли через until ... ( C: orig1 ) ELSE ( C: orig2 ) \ если вышли через while ... ( C: orig2 ) THEN ( C: ) ... \ общая частьПример 5. В зависимости от того, невыполнение какого условия привело к прекращению цикла, нужно выполнить те или иные действия.
BEGIN ( C: dest ) ... ( C: dest ) WHILE ( C: orig1 dest ) ... ( C: orig1 dest ) WHILE ( C: orig1 orig2 dest ) ... ( C: orig1 orig2 dest ) AGAIN ( C: orig1 orig2 ) THEN ( C: orig1 ) \ если вышли через второй while ... ( C: orig1 ) ELSE ( C: orig3 ) \ если вышли через первый while ... ( C: orig3 ) THEN ( C: ) ... \ общая частьРазумеется,
AGAIN THEN можно заменить на REPEAT.
Вот что говорится в стандарте ANS Forth (American National Standard for Information Systams — Programming Languages — Forth, American National Standards Institute, 1994):
"[Известные читателю] основные структуры управления могут быть дополнены,
как показано на примерах ... (см. выше — М.Г.), конструкциями
BEGIN ... UNTILBEGIN ... WHILE ... REPEATWHILE.
Однако, для каждого добавочного WHILE в конце структуры
необходим THEN.
THEN и WHILE образуют единую систему,
THEN показывает, с какого места следует продолжать
исполнение когда WHILE передает управление.
Использование более чем одного добавочного WHILE
возможно, но случается редко.
Обратите внимание, что если пользователь находит такое использование
THEN нежелательным, можно определить слово-синоним
с более привлекательным именем."
"Дополнительные действия могут быть выполнены между словом,
завершающим цикл (REPEAT или UNTIL)
и словом THEN, соответствующим
добавочному WHILE. Более того, если для случаев
раннего завершения и нормального завершения требуются дополнительные,
взаимоисключающие действия, эти действия могут быть отделены
друг от друга обычным фортовским ELSE.
Все действия при завершении цикла указываются после конца цикла."
Обратите внимание, что когда WHILE ставится в соответствие
ELSE или THEN, REPEAT создает
некоторую неправильность, больше всего заметную при сравнении со случаем
BEGIN ... UNTILELSE или THEN будет на единицу меньше числа
слов WHILE из-за того, что REPEAT
[один раз выполняет THEN, разрешая одно
WHILE (здесь в стандарте небольшая путаница — М.Г.)].
Как и в вышеупомянутом случае, если
пользователь находит такое несоответствие в подсчете
нежелательным, REPEAT можно заменить в исходном тексте на
его собственное определение (то есть, на AGAIN THEN
DO ... WHILE ... LOOP ... ELSE ... UNLOOP THEN
WHILE внутри
цикла DO ... LOOP, т.к. размер значения do-sys может
отличаться от размера orig и dest. В Форте T32, однако,
размеры do-sys, orig и dest совпадают.
Marcel Hendrix в сообщении <9pq1s2$46g$1@news.IAEhv.nl> привел следующий пример:
CREATE tbl 1 , 2 , 4 , 33 , 77 , 7 , 0 , 457 , 99 , 100 ,
: .OK? ( -- )
10 0 DO I CELLS tbl + @ 456 <
WHILE I .
LOOP CR ." All values OK"
ELSE CR ." Bad value in tbl: " I . UNLOOP
THEN
CR ." Finished checking the table" ;
.OK?
0 1 2 3 4 5 6
Bad value in tbl: 7
Finished checking the table ok[Dec]
Обратите внимание на то, что если в массиве оказывается "плохой" элемент
(больший 456), WHILE передает управление на код за
ELSE, т.е. за пределы цикла, однако параметры цикла со стека
возвратов еще не убраны. Мы можем получить значение счетчика цикла с помощью
слова I, однако убирать параметры цикла нам приходится самим —
мы применяем слово UNLOOP. Если бы мы забыли слово UNLOOP,
это скорее всего закончилось бы ошибкой "access violation".
Разумеется, ничто не препятствует использовать слово DONE
или несколько частей WHILE (включая
использование CASE ... ESAC для разрешения
нескольких WHILE или даже применение для этого
CASE ... ELESAC ... THEN).
Не увлекайтесь, однако наворотами. Подобные структуры управления нужны очень редко.
CASE ... ESAC
ESAC работал по-разному, а то, что описано здесь,
является некоторой переработкой. По крайней мере, идеи использования
слова esac в качестве имени фортовского слова, а также
применения ESAC для сокращенного вычисления булевских выражений
и для разрешения нескольких WHILE я узнал от него
через телеконференцию comp.lang.forth.
(По некоторым сведениям, это Wil Baden разработал для ANSI-стандарта
спецификацию взаимодействия компилирующих слов,
таких как WHILE и THEN).
Слово CASE оставляет на стеке компилятора пометку.
После слова CASE на стеке компилятора может остаться
любое количество значений orig (описателей перехода вперед).
Слово ESAC завершит построение всех переходов, описатели
которых лежат выше отметки, выполнив для каждого из этих значений слово
THEN.
В конструкции
CASE { ... OF ... ENDOF } ... ESAC
ESAC отличается от ENDCASE только тем, что
ENDCASE компилирует DROP:
CASE { ... OF ... ENDOF } ... ENDCASE
эквивалентно
CASE { ... OF ... ENDOF } ... DROP ESAC
Если использование конструкции CASE ... ESACESAC можно
определить как эквивалент 0 ENDCASEESAC эквивалентно
0 ENDCASECASE ... ENDCASEOF ... ENDOF
Слово esac (по-русски читается "езак") является словом case, написанным наоборот. Впервые этот словесный урод появился в Пересмотренном сообщении об алгоритмическом языке Алгол-68 (Ван-Вийнгаарден и др.).
: CASE ?COMP $CA5E 0 ; IMMEDIATE
: OF ?COMP COMPILE (OF) >MARK ; IMMEDIATE
: ENDOF ?COMP [COMPILE] ELSE ; IMMEDIATE
: ESAC ?COMP
BEGIN ?DUP WHILE [COMPILE] THEN REPEAT
$CA5E ?PAIRS
; IMMEDIATE
: ENDCASE ?COMP COMPILE DROP [COMPILE] ESAC
; IMMEDIATE
: ELESAC [COMPILE] AHEAD 2>R [COMPILE] ESAC 2R>
; IMMEDIATE
Слово ELESAC описано ниже.
О подходах к реализации см. также в разделе "Почему не стоит запоминать глубину стека компилятора".
CASE ... ESAC
CASE ( n )
значение1 ( n n1 ) OF ( ) действия-A1 ENDOF
условие1 ( n f1 ) IF ( n ) действия-B1 ENDOF
...
значениеN ( n nN ) OF ( ) действия-AN ENDOF
условиеM ( n fM ) IF ( n ) действия-BM ENDOF
( n ) действия-в-противном-случае
ESAC
в конструкции можно использовать IF и OF вперемешку,
слово OF эквивалентно фразе OVER = IF DROP,
слова ENDOF и ELSE являются синонимами,
значения должны иметь стековый эффект ( -- n ) или ( n1 -- n1 n2 ),
условия должны иметь стековый эффект ( n -- n flag ),
действия-в-противном-случае должны снять со стека значение n.
CASE ... ESAC: CAND POSTPONE DUP POSTPONE IF POSTPONE DROP ; IMMEDIATE : COR POSTPONE DUP POSTPONE IFNOT POSTPONE DROP ; IMMEDIATEСлово cand ("канд") означает conditional and, а cor ("кор") — conditional or. Или, если угодно, сокращенные and и or. Или and и or для использования внутри case. Конструкция выглядит так:
CASE условие1 CAND условие2 CAND ... условиеN ESAC
или
CASE условие1 COR условие2 COR ... условиеN ESAC
Каждое условие и вся конструкция в целом имеют стековый эффект ( ... -- ... f ).
Каждое следующее условие вычисляется только в том случае, если
результат еще не ясен (т.е. исполнение продолжается только если
CAND получает через стек истину, а COR — ложь).
Условия вычисляются слева направо, приоритет операций
CAND и COR один и тот же.
У Wil'а Baden'а слова CAND и COR назывались
ANDIF и ORIF. Эти имена представляются мне
неудачными, я никогда не могу вспомнить, что именно делают ANDIF
и ORIF, а вот смысл CAND и COR
вспоминается легко — это как && и || в языке Си.
Недостатком имен ANDIF и ORIF
является то, что они, имея внутри себя if, не поглощают,
а модифицируют логическое значение.
Baden'а это, впрочем, не смущает — он запросто может написать и
ANDIF ... EXIT THEN
CASE ... ESACWHILE
CASE BEGIN ... WHILE ... WHILE ... WHILE ... AGAIN ESACНемного непонятно, да? Давайте распишем:
CASE ( C: case-label ) BEGIN ( C: case-label dest ) ... WHILE ( C: case-label orig1 dest ) ... WHILE ( C: case-label orig1 orig2 dest ) ... WHILE ( C: case-label orig1 orig2 orig3 dest ) ... AGAIN ( C: case-label orig1 orig2 orig3 ) ESAC ( C: )Действительно,
ESAC разрешает все недостроенные переходы вперед.
ESAC — это как несколько THEN.
Точнее, как THEN для всех orig выше отметки.
Конструкция предложена W.Baden'ом в одном из сообщений из comp.lang.forth.
CASE ... ESACCASE условие1 IF условие2 IF ... условиеN IF действия ESAC
Чтобы выполнились действия необходимо, чтобы все условия оставили на стеке истину.
CASE ... ESACELESAC.
Конструкция выглядит так:
CASE условие1 IF условие2 IF ... условиеN IF действия-если-да ELESAC действия-если-нет THEN
действия-если-да выполняются тогда, когда выполнены все условия;
действия-если-нет выполняются тогда, когда хотя бы одно условие не выполнено.
Слово ELESAC определено как
: ELESAC ( C: case-label orig1 ... origN -- orig-new ) [COMPILE] AHEAD 2>R [COMPILE] ESAC 2R> ; IMMEDIATEЗдесь используется знание того, что роль стека компилятора играет стек данных, и того, что размер значения orig на стеке — две ячейки. Обратите внимание на сходство с
ELSE:
: ELSE ( orig1 -- orig2 ) [COMPILE] AHEAD CS-SWAP [COMPILE] THEN ; IMMEDIATE : ELSE ( orig1 -- orig2 ) [COMPILE] AHEAD 2>R [COMPILE] THEN 2R> ; IMMEDIATEС учетом сделанных выше предположений, эти два определения слова
ELSE эквивалентны.
Нетрудно видеть, что ELESAC отличается от ELSE тем,
что в нем вызывается ESAC там, где в ELSE
вызывается THEN.
Этимология слова ELESAC. В языке Алгол-68 имеется слово
esac, это записанное наоборот слово case.
Слово elif образовано как else+if.
По аналогии с и в память об Алголе-68, elesac=else+esac.
На самом деле в таком словообразовании ничего страшного нет:
в Советском Союзе
Преимуществом конструкции CASE ... IF ... IF ... ELESAC ... THENCASE ... CAND ... ESAC IF ... ELSE ... THEN
\ Программа стандарта ANS Forth с зависимостями от окружения: \ - компилирующие слова оставляют orig и dest на стеке данных; \ - размер значений orig и dest - два элемента стека. CREATE temp 2 CELLS ALLOT : >: temp 2! ; IMMEDIATE : <: temp 2@ ; IMMEDIATEПример использования:
: vvv
DUP 2 AND
IF
DUP 1 AND
IF
DUP 4 AND
IF
." yes"
ELSE
>:
THEN
THEN
." no"
<:
THEN
.
;
То же самое в записи "структуры управления справа"
(такая запись всегда более компактна и читается
: vvv
DUP 2 AND IF
DUP 1 AND IF
DUP 4 AND IF
." yes" ELSE >: THEN THEN
." no" <: THEN
.
;
Понять такой пример можно только если отойти от понятия условного
оператора и вспомнить, что IF компилирует условный
переход вперед, THEN указывает адрес назначения такого перехода,
связь между ними осуществляется через значения orig,
а ELSE — это AHEAD [ CS-SWAP ] THEN
Если мы вспомним все это, становится понятным, что каждый IF
в случае неудачи проверки передает управления на соответствующий
THEN (в последнем примере IF располагается над
соответствующим ему THEN).
Фраза ELSE :>IF в предыдущей строке;
значение orig прячется в переменной temp.
Фраза <: THENtemp и разрешает соответствующую команду
перехода вперед.
Мы определим слова
[>Z] ( C: ... orig|dest -- ... ) Спрятать orig|dest.
Слово с признаком immediate.
[Z>] ( C: ... -- ... orig|dest ) Достать ранее спрятанное
словом [>Z] значение orig|dest.
Слово с признаком immediate.
Почему такое имя.
Z — последняя буква в алфавите, так что
[>Z] означает "запрятать подальше", а [Z>],
соответственно, "вытащить оттуда".
Реализация предполагает, что:
CASE ... ENDCASE[>Z] и [Z>] она не будет.
T32 не использует глубину стека для управления разрешением ссылок в
ESAC или других структурах управления.
В общем же случае для хранения значений orig|dest надо заводить
отдельный стек.
\ file: Z.FTH
\ ANS Forth code snippet requiring ROLL and ?DO from CORE EXT
: -ROLL DUP 0 ?DO DUP >R ROLL R> LOOP DROP ;
\ ANS Forth code snippet
\ with the environmental dependencies that:
\ - control-flow stack is the same as data stack
\ - orig's and dest's are two-cell values
\ - control-flow stack elements are not bound to stack depth
\ - control structures do not remember stack depth
: [>Z] ( C: ... orig|dest -- orig|dest ... )
DEPTH 1- -ROLL
DEPTH 1- -ROLL
; IMMEDIATE
: [Z>] ( C: orig|dest ... -- ... orig|dest )
DEPTH 1- ROLL
DEPTH 1- ROLL
; IMMEDIATE
\ ANS Forth code requiring [>Z] and [Z>] defined above
: vvv
DUP 1 AND IF
DUP 2 AND IF
DUP 4 AND IF
." OK " AHEAD [>Z]
THEN THEN THEN
." NO "
[Z>] THEN
U.
;
\ 1 vvv
\ NO 1 ok
\ 2 vvv
\ NO 2 ok
\ 4 vvv
\ NO 4 ok
\ 3 vvv
\ NO 3 ok
\ 7 vvv
\ OK 7 ok
\ 6 vvv
\ NO 6 ok
С помощью этих слов слово ELESAC можно определить как
: ELESAC [COMPILE] AHEAD [COMPILE] [>Z] [COMPILE] ESAC [COMPILE] [Z>] ; IMMEDIATE
CASE ... ENDCASEOF ... ENDOFENDCASE)
разрешает произвольное число ссылок вперед (или назад).
Разумеется, такая конструкция должна каким-то образом отличать
"свои" элементы стека управления от "чужих".
Стандарт ANS Forth этого вопроса вообще не затрагивает, оставляя все вопросы реализации на усмотрение разработчиков.
Наиболее популярны два способа разметки стека — с помощью вспомогательных значений и путем запоминания глубины стека компилятора.
В стандарте ANS Forth имеется слово CS-ROLL.
Это слово позволяет поднять значение orig|dest из глубин стека
компилятора на вершину.
Если структура управления, такая как CASE ... ENDCASECS-ROLL непредсказуемы. Точнее, до некоторой степени предсказуемы,
поскольку ясно,
что взамен "импортированного" из-под отмеченной глубины значения под отметкой
окажется одно из принадлежащих конструкции значений orig|dest.
Это приведет к появлению неочевидной ошибки.
В случае же использования в качестве отметки положенного на стек компилятора
значения, отметка "опускается" вместе с остальными значениями.
Другое дело, что если размер отметки отличается от размера значения
orig|dest (например, отметка — целое число, а orig — адрес
и число), пользоваться словом CS-ROLL нельзя.
С другой стороны, если мы знаем, что в качестве стека компилятора используется
стек данных и знаем размеры значений, мы можем пользоваться
словом ROLL (стандарт рекомендует в таком случае
задокументировать сделанные предположения об архитектуре системы).
Другим примером конструкции, нарушающей предположение о неизменности
расположения значений на стеке компилятора, являются слова
[>Z] и [Z>].
DONE: еще один способ организации цикла с несколькими выходами
: DONE POSTPONE ELSE CS-SWAP ; IMMEDIATEВозможны следующие варианты использования:
CASE BEGIN ... ... IF .... DONE ... IF .... DONE ... IF .... DONE ... AGAIN ESACПосле того, как выполняется код между словами
IF
и DONE, выполняется выход из цикла.
Для разрешения ссылок вперед используется
CASE ... ESACBEGIN ... ... IF .... DONE ... IF .... DONE ... IF .... DONE ... AGAIN THEN THEN THENТо же самое, но для разрешения ссылок вперед используются
THEN.
BEGIN ... IF .... DONE ... REPEATПоследний пример эквивалентен конструкции
BEGIN ... 0= WHILE ... REPEAT ....и отличие состоит только в подчеркивании того, что действия, обозначенные четырьмя точками, логически относятся к циклу.
START ... EMERGESTART ... EMERGE
START { ... DIVE ... } ... EMERGE
Слова START и EMERGE обозначают границы блока,
DIVE обозначает рекурсивный вызов блока. Слово
DIVE# ( C: ... "n" -- ... )DIVE# 1DIVE).
\ : (CALL) R@ REF+ >R< REF@ >R ; \ : (START) R@ REF@ >R< REF+ >R ; : START ?COMP COMPILE (START) >MARK <MARK 201 ; IMMEDIATE : EMERGE ?COMP 201 ?PAIRS CS-DROP COMPILE EXIT >RESOLVE ; IMMEDIATE \ ищем #-ый набор (forward backward 201 ), \ при этом addr после (START) : (DIVE#) ( an .. a1 # --> an a1 ) DEPTH 5 > IF DEPTH 5 - 1+ 1 DO ( an .. a1 # ) CASE I PICK 201 = IF I 1+ PICK backward = IF I 3 + PICK forward = IF I 2 + PICK I 4 + 1+ PICK REF+ = IF I 4 + PICK REF- TOKEN@ ['] (START) = IF 1- DUP 0= IF \ # -- счетчик DROP ( одно значение сброшено!) COMPILE (CALL) I 1+ PICK ( это значение номер i+2 ) I 1+ PICK ( это значение номер i+1 ) <RESOLVE UNLOOP EXIT ESAC LOOP THEN 1 ABORT" more than nesting level" ; : DIVE ?COMP 1 (DIVE#) ; IMMEDIATE : DIVE# ( "n" -- ) ?COMP S$GETWORD S$NUMBER? -1 <> ABORT" single number expected" D>S (DIVE#) ; IMMEDIATE \ DIVE# n calls the n-th enclosing block. \ DIVE# 1 is equivalent to DIVE .
fib. ( i -- ): fib. START DUP 3 < IF DROP 1 EXIT THEN DUP 1- DIVE SWAP 2 - DIVE + EMERGE . ; : tst 11 1 DO I fib. SPACE LOOP ; DECIMAL tst 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 ok
: ttt START R@ @ R> CELL+ >R EMERGE [ 6 , ] . ; ok ttt 6 okВ этом примере текст
START R@ @ R> CELL+ >R EMERGELIT.
: tttt LIT [ 6 , ] . ; ok tttt 6 ok : newLIT R@ @ R> CELL+ >R ; ok : vvv newLIT [ 8 , ] . ; ok vvv 8 ok
Приведем только несколько ссылок:
A BNF PARSER IN FORTH by Bradford J. Rodriguez, Ph.D.
НОВЫЕ СТРУКТУРЫ УПРАВЛЕНИЯ В ЯЗЫКЕ FORTH дипломная работа Гасаненко М.Л., к.ф.-м.н., и другие статьи про бэктрекинг.