Константы и переменные, работа с памятью
Программисту часто бывает удобно работать не с «анонимными» значениями, а с именованными. По аналогии со средствами других языков эти средства языка Форт называются константами и переменными. Впоследствии мы увидим, что они являются не «изначальными», а (наряду с определениями через двоеточие) частными случаями более общего понятия «определяющие слова».
Слово CONSTANT (константа) А --> работает следующим образом. Со стека снимается верхнее значение, а из входного текста выбирается очередное слово и запоминается в словаре как новая команда. Ее действие состоит в следующем: поместить на стек значение А, снятое со стека в момент ее определения. Например, 4 CONSTANT XOP. В дальнейшем при исполнении слова XOP число 4 будет положено на стек.
Слово VARIABLE (переменная) А --> резервирует в словаре два байта, а из входного потока выбирает очередное слово и вносит его в словарь как новую команду, которая кладет на стек адрес зарезервированной двухбайтной области. Можно сказать, что переменная работает, как константа, значением которой является адрес зарезервированной двухбайтной области.
Работа с переменной помимо получения ее адреса состоит в получении ее текущего значения и присваивании нового. Для этого язык Форт имеет следующие слова:
@ A ---> B ! B,A --->
Слово @ (читается «разыменовать») снимает со стека значение и, рассматривая его как адрес области оперативной памяти, кладет на стек двухбайтное значение, находящееся по этому адресу. Обратное действие выполняет слово ! (восклицательный знак, читается «присвоить»), которое снимает со стека два значения и, рассматривая верхнее как адрес области оперативной памяти, засылает по нему второе снятое значение. Эти слова можно использовать не только для переменных, но и для любых адресов оперативной памяти. Следующий протокол работы показывает порядок использования переменной в сочетании с этими словами:
> VARIABLE X 1 X ! OK > X @ . 1 ОК > Х @ NEGATE X ! X @ . -1 ОК
В первой строке определяется переменная X, и ей присваивается начальное значение 1. Затем текущее значение переменной X распечатывается. После этого текущее значение меняется на противоположное по знаку и вновь распечатывается.
Полезным вариантом слова ! является слово +! (плюс-присвоить) N,A --> , которое увеличивает на N значение, находящееся в памяти по адресу A. Несмотря на то, что это слово легко выразить через + и !:
: +! ( N,A ---> ) DUP @ ROT + SWAP ! ;
оно включено в обязательный набор слов.
Слова, определенные через CONSTANT и VARIABLE, — такие же равноправные слова форт-системы, как и определенные через двоеточие. Их также можно использовать в определениях других слов и исключать из словаря словом FORGET.
Для работы со значениями двойной длины имеются слова
2CONSTANT AA ---> 2VARIABLE ---> 2@ A ---> BB 2! BB,A --->
из стандартного расширения двойных чисел. При размещении в памяти такие значения рассматриваются как пары смежных двухбайтных значений одинарной длины: сперва (в меньших адресах) располагается старшая половина, затем (в больших адресах) младшая. Адресом значения двойной длины считается адрес его старшей половины.
Константы и переменные позволяют программисту использовать память в словаре вполне определенным образом. А как быть, если требуется что-то иное? Общий принцип языка Форт состоит в том, чтобы не закрывать от программиста даже самые элементарные единицы, из которых строятся его более сложные слова, а предоставлять их наравне с другими словами. Элементарными словами для работы с памятью в словаре, помимо приведенных выше @ и ! , являются следующие:
HERE ---> A ALLOT A ---> , A --->
Предполагается, что словарь занимает некоторую начальную часть адресного форт-пространства и у него имеется указатель текущей вершины (словарь растет в сторону увеличения адресов). Слово HERE (здесь) возвращает текущее значение указателя (адрес первого свободного байта, следующего за последним занятым байтом словаря).
Слово ALLOT (распределить) резервирует в словаре область памяти, размер которой (в байтах) снимает со стека. Резервирование состоит в том, что текущее значение указателя изменяется на заданную величину, поэтому при положительном значении запроса память отводится вплотную от вершины словаря, а при отрицательном значении запроса происходит освобождение соответствующего участка памяти. Наконец, слово , (запятая) выполняет так называемую «компиляцию» значения, снимаемого со стека: значение переносится на вершину словаря, после чего указатель вершины продвигается на 2 (размер в байтах скомпилированного значения). Некоторые из приведенных слов легко выражаются через другие:
: , ( A --> ) HERE ! 2 ALLOT ; : 2@ ( A --> BB ) DUP 2 + @ SWAP @ ;
Такая избыточность позволяет программисту быстрее находить нужные ему решения и делает программы более удобочитаемыми.
А как создать в словаре поименованную область памяти? Можно завести область и связать ее адрес с именем через описание константы: HERE 10 ALLOT CONSTANT X10. Слово HERE оставляет на стеке адрес текущей вершины словаря, затем при исполнении текста 10 ALLOT от этой вершины резервируется 10 байт, после чего слово CONSTANT связывает адрес зарезервированной области с именем X10. В дальнейшем при исполнении слова X10 этот адрес будет положен на стек.
Другой возможный путь состоит в использовании слова CREATE (создать) --> в таком контексте: CREATE X10 10 ALLOT. Слово CREATE, подобно слову VARIABLE, выбирает из входной строки очередное слово и определяет его как новую команду с таким действием: положить на стек адрес вершины словаря на момент создания этого слова. Поскольку следующие действия в приведенном примере резервируют память, то слово X10 будет класть на стек адрес зарезервированной области. Очевидно, что слово VARIABLE можно выразить через CREATE
: VARIABLE ( ---> ) CREATE 2 ALLOT ;
или иначе (если мы хотим инициализировать нулем значение создаваемой переменной):
: VARIABLE ( ---> ) CREATE 0 , ;
В стандарте определен ряд системных переменных, к которым программист может свободно обращаться, в том числе STATE (состояние) --> А и BASE (основание) --> A. Исполнение каждого из этих слов заключается в том, что на стеке оставляется адрес ячейки, в которой хранится значение данной переменной.
Переменная STATE представляет текущее состояние текстового интерпретатора: нуль для исполнения и не нуль (обычно -1) для компиляции. Поэтому вся реализация слов [ и ] , переключающих интерпретатор из одного состояния в другое, сводится к одному присваиванию:
: [ ( ---> ) 0 STATE ! ; IMMEDIATE : ] ( ---> ) -1 STATE ! ;
Переменная BASE хранит текущее основание системы счисления для ввода и вывода чисел, поэтому реализация слов для установки стандартных систем выглядит так:
: DECIMAL ( ---> ) 10 BASE ! ; : HEX ( ---> ) 16 BASE ! ;
Отсюда следует более изящный способ кратковременной смены системы счисления во время компиляции определения: [ BASE @ HEX ] FF00 [ BASE ! ]. Сначала на стеке запоминается текущее основание, и система счисления переключается на основание 16, в котором и воспринимается следующее число FF00, после чего восстанавливается прежнее основание. А как узнать текущее основание системы счисления? Исполнение текста BASE @ . не поможет, поскольку ответом всегда будет 10 (почему?). Правильный ответ даст исполнение текста BASE @ DECIMAL . , в результате чего значение основания будет напечатано как число в десятичной системе. Еще более правильным было бы использовать текст BASE @ DUP DECIMAL . BASE ! , который после печати основания в десятичной системе восстанавливает его прежнее значение.