В предыдущей части мы начали разговор о стеке - основной структуре данных языка Forth.
Чтобы понять, каким образом Forth использует стек, рассмотрим простой пример. Вот строка на Forth’e:
2 30 * 12 / .
И вот как она выполняется:
Шаг 1. Forth кладёт число 2 на вершину стека.
Шаг 2. Forth кладёт число 30 на вершину стека над 2.
Шаг 3. Оператор * (умножить) перемножает два верхних числа на стеке (2 и 30), удаляет их и кладёт результат 60 на вершину стека.
Шаг 4. Forth кладёт число 12 на вершину стека над 60.
Шаг 5. Оператор / (разделить) делит два верхних числа на стеке (60 на 12), удаляет их и кладёт результат 5 на вершину стека.
Шаг 6. Оператор . (dot) удаляет верхнее число со стека и показывает его пользователю. Стек пуст. См. рисунок:
Пришло время подробно разобрать короткий, но достаточно замысловатый пример. В этом примере будет вычисляться последовательность чисел Фибоначчи. Эта знаменитая в математике последовательность начинается с двух единиц и продолжается таким образом, что каждое следующее число является суммой двух предыдущих: 1, 1, 2, 3, 5, 8, 13, 21, 34,…и так далее до бесконечности. Таким образом, каждое следующее число Фибоначчи может быть вычислено с помощью простой формулы: fn+1 = fn + fn-1.
Программа:
: Fib ( n -- , display all Fibonacci numbers smaller than n )
1 ( initial number )
SWAP ( set up upper limit )
1 ( set up loop index )
DO DUP U. ( print current number )
I ( the next Fibonacci number )
SWAP ( prepare the next to come )
+LOOP ( add current to index ...)
( ... and repeat until sum>n )
U. ( print the last Fib )
Шаг 1. Вызывается функция Fib. Вызов должен выглядеть как 1000 Fib, что означает: "положить на стек число 1000, которое будет служить верхней границей искомых чисел Фибоначчи и вызвать функцию (слово) Fib.
Шаг 2. Forth кладёт на вершину стека число 1 - первое число Фибоначчи.
Шаг 3. SWAP меняет местами два верхних элемента стека. Для чего это нужно, станет ясно через один шаг.
Шаг 4. Forth кладёт на вершину стека число 1.
Шаг 5. DO - начинается выполнение цикла. Эта инструкция удаляет со стека два его верхних элемента: самый верхний (в нашем случае 1) представляет собой начальный индекс цикла, за ним следует верхняя граница цикла (1000).
Шаг 6. DUP создает копию верхнего элемента стека и кладёт её на вершину. Это действие необходимо для последующей печати на экран верхнего элемента стека. Так как оператоор печати удаляет со стека свой операнд, мы должны предварительно его размножить.
Шаг 7. U. распечатывает верхний элемент стека как целое число без знака и удаляет его.
Шаг 8. Оператор I кладёт на стек текущее значение индекса цикла. В этот момент оно равно 1 (см. шаг 5).
Шаг 9. SWAP меняет местами два верхних элемента стека. В данный момент такое действие не имеет большого смысла, но впоследствии оно будет служить для извлечения следующего по порядку числа Фибоначчи. (Вспомните - два первых числа Фибоначчи - это 1 и 1).
Шаг 10. Оператор +LOOP выполняется так: к текущему значению переменной цикла (в нашем случае это 1) прибавляется значение верхнего элемента стека (тоже 1) и полученный результат сравнивается с верхней границей цикла (в нашем случае 1000). Если этот результат меньше верхней границы, цикл повторяется.
Шаг 11. Итак, мы опять вернулись к выполнению оператора DUP, который, как уже было сказано, служит только для того, чтобы распечатать текущее число Фибоначчи, не сняв его со стека. Верхнее число стека размножается, становясь аргументом следующего оператора.
Шаг 12. U. распечатывает верхний элемент стека.
Шаг 13. Вспомните шаг 10. Внутренняя переменная цикла была увеличена и стала равна 2. Её значение мы и кладём на стек опять с помощью оператора I - следующее число Фибоначчи готово!
Шаг 14. SWAP менят местами два верхних элемента стека - предыдущее число Фибоначчи отправилось наверх для сложения с текущим, чтобы получить следующее. (Вспомните: fn+1 = fn + fn-1).
Шаг 15. И снова оператор +LOOP увеличивает текущую переменную цикла (2, то есть fn) на число с вершины стека (1, то есть fn-1), чтобы получить новую переменную цикла (3, то есть следующее число Фибоначчи - fn+1).
Шаг 16, Шаг 17. Текущее число распечатывается.
Шаг 18 - Шаг 22. Цикл повторяется, распечатывается новое число Фибоначчи.
И так далее...
Ну и наконец, после того, как читатель познакомился с синтаксисом Forth'а, пришло время показать каким образом Forth управляет телескопами и другими приборами - по крайней мере приблизительно. Для этого мы используем отрывок из воображаемой программы управления стиральной машиной[1] - чем не телескоп!
(Washing Machine Application)
...
: DRAIN VALVE ON 3 MINUTES VALVE OFF ;
: AGITATE MOTOR ON 10 MINUTES MOTOR OFF ;
: SPIN CLUTCH ON MOTOR ON 5 MINUTES MOTOR OFF CLUTCH OFF ;
: FILL FAUCETS ON TILL-FULL FAUCETS OFF ;
: WASH FILL DETERGENT ADD AGITATE DRAIN ;
: RINSE FILL AGITATE DRAIN ;
: WASHER WASH SPIN RINSE SPIN ;
Начинать чтение нужно с конца. Программа запускается с помощью слова WASHER (стиральная машина). Как видите, это слово раскрывается в следующую последовательность слов:
WASH SPIN RINSE SPIN,
что определяет основную последовательность действий, производимую стиральной машиной:
СТИРАТЬ КРУТИТЬ ПОЛОСКАТЬ КРУТИТЬ.
В свою очередь WASH обозначает такие действия:
FILL DETERGENT ADD AGITATE DRAIN,
то есть
ЗАПОЛНИТЬ ПОРОШОК[2] ДОБАВИТЬ ВЗБОЛТАТЬ ВЫСУШИТЬ,
а SPIN раскрывается в следующую последовательность:
CLUTCH ON MOTOR ON 5 MINUTES MOTOR OFF CLUTCH OFF,
что значит
ВКЛЮЧИТЬ СЦЕПЛЕНИЕ ВКЛЮЧИТЬ МОТОР 5 МИНУТ ВЫКЛЮЧИТЬ МОТОР ВЫКЛЮЧИТЬ СЦЕПЛЕНИЕ,
где ON и OFF - это специальные слова, управляющие внешними устройствами;
и так далее. Теперь уже просто расшифровать все действия, производимые стиральной машиной. Как видите, это совсем не сложно на Forth'е!
[1] Программа взята из Forth Programming Handbook, входящей в пакет SwiftForth – продукта компании Forth Inc., производящей Forth-software.
[2]ПОРОШОК в данном случае - константа, а не слово.