Язык Форт и его реализации

         

Литеры и строки, форматный вывод чисел


В современном программировании важное место занимает обработка текстовых данных. С каждой литерой, которую можно ввести с внешнего устройства или вывести на него, связывается некоторое число — код этой литеры, так что в памяти ЭВМ литеры представлены своими кодами. Стандарт языка Форт предусматривает использование таблицы кодов ASCII, в которой задействованы все числа в диапазоне от 0 до 127. Каждый код занимает один 8-разрядный байт, в котором для представления литеры используются младшие 7 разрядов.

Такая привязка к одной конкретной кодировке не является существенным препятствием к использованию других, если сохраняется условие, что код литеры занимает один байт. В применяемых в нашей стране форт-системах помимо ASCII применяются коды КОИ-7, КОИ-8, ДКОИ и другие.

Для доступа к однобайтным значениям, расположенным в памяти, используются слова C@ A-->B и C! B,A -->, которые аналогичны словам @ и !. Префикс С (от слова CHARACTER — литера) говорит о том, что эти слова работают с литерными кодами (однобайтными значениями).

Слово C@ возвращает содержимое байта по адресу A, дополняя его до 16-разрядного значения нулевыми разрядами. Обратное действие выполняет слово C!, которое засылает младшие 8 разрядов значения B в память по адресу A.

Для политерного обмена с терминалом стандарт предусматривает такие слова:

KEY ---> A EMIT A ---> CR ---> TYPE A,N ---> EXPECT A,N --->

Слово KEY (клавиша) возвращает в младших разрядах значения A — код очередной литеры, введенной с терминала. В отличие от слова C@ старшие разряды зависят от реализации и могут быть ненулевыми. Обратное действие выполняет слово EMIT (испустить), которое снимает значение со стека и, рассматривая его младшие разряды как код литеры, выводит эту литеру на терминал. Специальное слово CR (сокращение от CARRIAGE RETURN — возврат каретки) вынолняет перевод строки при выводе на терминал.

Расположенные в памяти побайтно коды литер образуют строку, которую можно напечатать на терминале словом TYPE (напечатать).
Это слово снимает со стека число — количество литер (оно должно быть неотрицательно) и адрес начала строки (ее первого байта):

: TYPE ( A,N ---> ) ?DUP IF 0 DO DUP I + C@ EMIT LOOP THEN DROP ;

Если число литер равно нулю, то ничего не печатается.

Обратное действие — ввод строки литер — выполняет слово ЕХРЕСТ (ожидать), которое снимает со стека длину и адрес области памяти для размещения вводимых литер. Коды литер, последовательно вводимых с терминала, помещаются в указанную область до тех пор, пока не будет введено заданное число литер или не будет введена управляющая литера «возврат каретки» (код этой литеры в память не заносится). Фактическое число введенных литер сообщается в стандартной переменной SPAN (размер), эти литеры к тому же отображаются на терминале.

Ввиду его особой важности для кодирования пробела выделена специальная константа BL (от BLANK — пробел), которую для кода ASCII можно задать так: 32 CONSTANT BL. При исполнении слова BL на стеке остается код пробела. Чтобы вывести пробел на терминал, имеются следующие стандартные слова:

: SPACE ( ---> ) BL EMIT ; : SPACES ( N ---> ) ?DUP IF 0 DO SPACE LOOP THEN ;

Слово SPACE (пробел) выводит на терминал один пробел, а слово SPACES (пробелы) — несколько, снимая их количество со стека (это значение, как и длина строки в слове TYPE, должно быть неотрицательным).



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

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


Строка со счетчиком представляется полем байт, причем в первом байте записана длина строки. Стандарт не определяет форму представления счетчика длины, оставляя решение этого вопроса на усмотрение разработчиков конкретной реализации. Для перехода от строки со счетчиком к строке с явно заданной длиной имеется слово COUNT (счетчик) A --> B,N, которое преобразует адрес A строки со счетчиком в адрес B ее первой литеры (обычно это A+1) и значение счетчика. Строки со счетчиком используются при вводе слов из входной строки. Стандартное слово WORD (слово) C--> A снимает со стека код литеры-ограничителя и выделяет из входной строки подстроку, ограниченную этой литерой (начальные вхождения литеры-ограничителя пропускаются). Из выделенной подстроки формируется строка со счетчиком, адрес которой возвращается в качестве результата. Слова-команды языка Форт вводятся исполнением текста BL WORD, а текстовая строка в слове ." — исполнением текста QUOTE WORD, где слово QUOTE — константа, обозначающая код кавычки. Литеры введенной строки обычно располагаются вслед за вершиной словаря, т.е. в незащищенном месте, и поэтому их нужно как-то защитить, если предполагается их дальнейшее использование. Некоторые реализации предусматривают слово " (кавычка), которое используется внутри определения через двоеточие и во время его исполнения кладет на стек адрес следующей строки как строки со счетчиком. Это позволяет работать с явно заданными текстами.

Чтобы программист мог задавать коды литер, не связывая себя конкретной кодировкой, во многие реализации введено слово С", которое кладет на стек код первой литеры следующего слова и может использоваться как внутри определения через двоеточие, так и вне его:

: C" ( ---> C ) BL WORD COUNT DROP C@ [COMPILE] LITERAL ; IMMEDIATE

Исполнение текста BL WORD COUNT DROP C@ оставляет на стеке код первой литеры следующего слова. Далее этот код нужно либо скомпилировать как число, либо оставить на стеке в зависимости от текущего состояния текстового интерпретатора.


Для этого используется уже известное нам слово LITERAL. Однако включить его непосредственно в текст нельзя, так как это слово имеет признак немедленного исполнения и будет исполняться во время компиляции данного определения. Чтобы скомпилировать слово с признаком немедленного исполнения, используется слово [COMPILE] (от COMPILE — компилировать), которое само имеет такой признак. Оно принудительным образом компилирует следующее за ним слово независимо от наличия у него признака немедленного исполнения. Таким образом, ввод строки, ограниченной кавычкой, с помощью слова C" можно задать так: C" " WORD. Такой текст более нагляден, чем тот, в котором используется конкретный код или обозначающая его константа.

Важной областью применения строковых значений являются форматные преобразования, позволяющие переводить число из машинного двоичного представления в строку литер. Эти преобразования выполняются над числами двойной длины, результирующая строка размещается во временном буфере PAD (прокладка), который заполняется с конца. Такое название буфера связано с тем, что он располагается в незащищенной части адресного пространства между словарем и стеком. Слово PAD кладет на стек адрес конца этого буфера и обычно определяется так:

: PAD ( ---> А ) HERE 100 + ;

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

Собственно форматное преобразование начинается словом <# которое устанавливает служебную переменную HLD на конец буфера PAD:

: <# ( ---> ) PAD HLD ! ;

Занесение очередной литеры в буфер PAD выполняет слово HOLD (сохранить):

: HOLD ( С ---> ) -1 HLD +! HLD @ C! ;

Преобразование числа выполняет слово # DD1 --> DD2, которое работает со значениями двойной длины. Параметр делится на текущее значение переменной BASE (основание системы счисления) и заменяется на стеке получившимся частным, а остаток переводится в литеру соответствующую ему как цифра в данной системе счисления, и через слово HOLD эта литера добавляется в буфер PAD.


Полный перевод числа выполняет слово #S:

: #S ( DD ---> 0,0 ) BEGIN # 2DUP D0= UNTIL ;

которое выделяет цифры числа последовательным делением, пока не получится нуль. Наконец, слово #> завершает форматное преобразование, возвращая адрес и длину получившейся текстовой строки:

: #> ( DD ---> A,N ) 2DROP HLD @ PAD OVER - ;

Для вывода знака «минус» имеется слово SIGN (знак):

: SIGN ( A ---> ) 0< IF C" - HOLD THEN ;

которое добавляет в буфер PAD знак «минус», если параметр на вершине стека (число одинарной точности) отрицателен.

С помощью перечисленных средств легко определить стандартные слова D. и . для печати чисел в свободном (минимальном) формате:

: D. ( DD ---> ) 2DUP DABS ( DD,DDABS ) <# #S ( DD,0,0 ) ROT ( D-МЛ,0,0,D-СТ ) SIGN ( D-МЛ,0,0 ) #> ( D-ML,A,N ) TYPE SPACE DROP ; : . ( N ---> ) S>D D. ;

Слово D. сначала переводит абсолютное значение исходного числа в строку литер, потом добавляет к ней возможный знак «минус», анализируя для этого старшую половину первоначального значения, и затем печатает получившуюся строку, выводя после нее еще один пробел. Слово . дополняет свой параметр до значения двойной длины распространением знакового разряда и обращается к слову D. для печати получившегося числа. Аналогичным образом реализуются стандартные слова D.R и .R , которые печатают число в поле заданного размера вплотную к его правому краю (отсюда в их мнемонике присутствует буква R от RIGHT — правый), добавляя при необходимости начальные пробелы:

: D.R ( DD:ЧИСЛО,F:РАЗМЕР ПОЛЯ ---> ) OVER 2SWAP DABS ( D-СТ,F,DD,DABS ) <# #S ROT SIGN #> ( F,A,N ) ROT OVER - ( A,N,F-A ) DUP 0> IF SPACES ELSE DROP THEN TYPE ; : .R (N:ЧИСЛО,F:РАЗМЕР ПОЛЯ ---> ) SWAP S>D D.R ;

В заключение рассмотрим программу шестнадцатиричиой распечатки областей памяти словом DUMP (дамп), которое получает на стеке адрес области и ее длину:

: DUMP ( A:АДРЕС,N:ДЛИНА ---> ) OVER BASE @ HEX 2SWAP ( A,B,A,N ) + ROT -2 AND ( B,A+N,A ) DO I <# C" * HOLD ( B,AI ) 0 15 DO DUP I + ( B,AI,AI+J ) C@ DECODE HOLD -1 +LOOP ( B,AI ) C" * HOLD ( B,AI ) 0 14 DO BL HOLD DUP ( B,AI,AI+J ) @ 0 # # 2DROP -2 +LOOP ( B,AI ) BL HOLD BL HOLD 0 # # # # #> ( B,AT,NT ) CR TYPE 16 +LOOP BASE ! ;



Внешний цикл с шагом 16 формирует и печатает текстовую строку. Первый внутренний цикл с шагом -1 засылает в буфер PAD литерные значения, соответствующие распечатываемым байтам. Здесь слово DECODE C --> C/C1 заменяет код C на некоторый код C1 (например, код литеры «точка»), если он не является кодом литеры, которую можно напечатать на данном терминале. Второй внутренний цикл с шагом -2 засылает в буфер четыре шестнадцатиричные цифры — представления двухбайтных значений, разделяя их одним пробелом. Далее в буфере PAD (т.е. в начале строки) формируется адрес тоже в виде четырехзначного числа. Перед началом работы устанавливается шестнадцатиричная система счисления, а в конце восстанавливается первоначальная. Следующий протокол работы показывает результат исполнения слова DUMP в конкретном случае:

> 1000 40 DUMP 03E8 03C0 03EC 45E0 A220 0003 4520 A390 07FA *...Ж..s..,..TM.3* 03F8 04C5 D4C9 E300 03E4 0402 9180 A331 4780 *.EMIT..U..JЦT..B* 0408 A414 45E0 A230 0004 4520 A37A 9180 A330 *U...S.....T:JЦT.*

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


Содержание раздела