MULISP6
.doc
File: IO.LES
CLRSCRN
В данном уроке речь пойдет об организации ввода и вывода в LISP'е.
Все программы (да, и LISP-программы тоже!) общаются с окружающей средой
при помощи средств ввода/вывода и, при отсутствии таковых, самая потрясаю-
щая программа представляла бы для нас лишь черный ящик, не производящий к
тому же никаких действий. Вследствие этого Ваше знание какого-либо языка
или системы программирования нельзя считать полным, пока не рассмотрен сей
действительно заслуживающий внимания раздел.
Как Вы знаете, ввод/вывод во всех языках программирования
представляет собой наиболее плохо выполненную и/или плохо переносимую
часть. Не составил исключения из общего правила и LISP.
Многие реализации LISP'а предоставляют достаточно широкие
возможности для организации ввода/вывода, вследствие чего это разнообразие
привело к несовместимости отдельных функций. Здесь мы рассмотрим стандарт-
ные функции LISP'а, существующие как в COMMON LISP'е, так и в muLISP'е,
если противное не отмечено особо...
CONTINUE
Ввод/вывод осуществляется в LISP'е через потоки. Стандартные
потоки ввода/вывода в COMMON LISP'е задаются глобальными переменными
*STANDARD-INPUT* и *STANDARD-OUTPUT*. Эти переменные отсутствуют в
MULISP'е, зато есть функции (RDS) и (WRS), которые назначают входной и
выходной потоки, соответственно, например,
(RDS 'D:ANIMAL)
открывает файл "D:ANIMAL.LSP" и назначает поток ввода на него.
CONTINUE
После того, как файл был открыт и назначен функцией (RDS) или
(WRS), поток ввода или вывода можно вновь назначить на консоль путем
присвоения переменной RDS или WRS, соответственно, значения NIL. В даль-
нейшем, при присвоении переменной ненулевого значения, поток вновь будет
связан с текущим открытым файлом (чтение/запись будет возобновлена с теку-
щей позиции). (RDS) и (WRS) без аргумента назначают поток ввода (вывода)
на консоль и закрывают файлы открытые для ввода (вывода). Например,
(WRS 'OUTPUT) ; Открывает файл "OUTPUT.LSP" для вывода
; и назначает вывод на него
(WRITE-STRING "Hello!") ; Выводится в OUTPUT.LSP
(SETQ WRS NIL) ; Вывод - вновь на консоль (а файл открыт)
(WRITE-STRING "How are you"); Выводится на консоль
(SETQ WRS 123) ; Вывод опять идет в файл
(WRITE-STRING "The End") ; ... туда же
(WRS) ; Файл закрыт, вывод идет на консоль.
(WRITE-STRING "Congrads!") ; С чем и поздравляем.
CONTINUE
Хотя в большинстве реализаций предоставляются возможности для
работы с двоичными файлами, наиболее часто используются функции
посимвольного ввода/вывода. Эти функции вводят/выводят объекты LISP'а,
используя их печатное представление.
----------------------
В общем случае объекты LISP'а являются достаточно сложными структу-
рами, однако для удобства пользователя LISP предоставляет возможность ра-
боты с ними в виде печатного текста, который и называется печатным предс-
тавлением объекта. Скорее всего, Вы уже не раз видели объекты LISP'а в их
печатном представлении (если, конечно, Вы не начали изучение LISP'а с это-
го абзаца). Такие функции, как PRINT, принимают объект и посылают его пе-
чатное представление в поток вывода.
Аналогично, при вводе, функция ввода (например, READ) берет
символы из потока, интерпретирует их как печатное представление
LISP-объекта и строит этот объект. Таким образом, при чтении печатного
представления получается объект, равный (в смысле EQUAL) исходному
объекту.
CONTINUE
Функции ввода
-------------
READ
(READ) считывает одно целое выражение из стандартного потока ввода
и возвращает эквивалентный связанный список. Правильно составленные
выражения с использованием либо списковых, либо точечных изображений,
либо их комбинаций, являются допустимыми входными данными для функции
READ. Пробельные символы служат только для разграничения считанных
знаков и (с других точек зрения) функцией READ игнорируются.
Ниже символу SYMB присваивается значение выражения, читаемого из
стандартного потока ввода:
(SETQ SYMB (READ))
Таким образом, READ можно рассматривать как функцию, имеющую
побочный эффект -- чтение печатного представления символа.
CONTINUE
Числа при вводе интерпретируются в соответствии со значением
глобальной переменной *READ-BASE* (изначально -- 10).
Например, после выполнения
$ (SETQ *READ-BASE* 2)
основание системы счисления для ввода принимается равным 2.
Можете попробовать что-нибудь сделать в течение этого перерыва,
например, установить основание снова равным 10 (предварительно вспомнив,
как записать 10 в двоичной системе счисления).
BREAK
CLRSCRN
$ (SETQ *READ-BASE* *PRINT-BASE*)
-- это на всякий случай...
Макро-символы при вводе обрабатываются специальным образом. Когда
LISP-READER встречает макро-символ, он выполняет некоторую функцию,
ассоциированную с данным символом. В стандартно определенный набор
входят следующие символы:
<Tab>, <Space>, <NewLine> - разделители;
\ - "Escape" -- игнорируется обычное
синтаксическое значение следующего
символа;
| - начало и конец объекта (эквивалентно '\'
перед каждой буквой определения);
( - начало ввода списка или точечной пары;
) - конец ввода списка или точечной пары;
' - возвращает следующее за ним выражение с
QUOTE;
; - начало комментария (до конца строки);
" - начало или конец строки;
Макро-символы в составе имен можно употреблять, только выделив их
с помощью '\' или '|', которые блокируют макро-обработку.
CONTINUE
Новое определение макро-символа можно осуществить используя функцию
SET-MACRO-CHAR в MULISP'е (SET-MACRO-CHARACTER в COMMON LISP'е).
В MULISP'е аргументы SET-MACRO-CHAR -- определяемый символ и выра-
жение, которое определяет требуемое действие. В качестве результата в MU-
LISP'е возвращается T в случае успеха и NIL в противном случае.
Например, ниже приведено определение для символа ' (quote):
(SET-MACRO-CHAR '\' (QUOTE (LAMBDA ()
(LIST (QUOTE QUOTE) (READ)))) )
В перерыве Вы можете попробовать сделать так, чтобы LISP при
появлении знака '~' в строке возвращал объект, полученный путем инверсии
объекта, представленного в оставшейся части строки (очень полезные
действия!) или определить символ ',' как блокирующий действие ''' (QUOTE)
(чуть полезнее, этого как раз нет в MuLISP'е, хотя это и не совсем то,
что есть в COMMON LISP'е.). Впрочем, мы не сомневаемся, что вдумчивый чи-
татель придумает множество более интересных и полезных примеров использо-
вания макро-символов...
BREAK
CLRSCRN
У нас это получилось после
(SET-MACRO-CHAR '\~ '(lambda () (reverse (read))))
(SET-MACRO-CHAR '\, '(lambda () (eval (read))))
Существуют также функции, получающие текущее определение для мак-
ро-символа:
GET-MACRO-CHARACTER -- COMMON LISP
GET-MACRO-CHAR -- MULISP
Аргумент -- символ, определение которого необходимо получить.
COMMON LISP предоставляет гораздо более широкие возможности, но мы
их здесь не будем рассматривать по причине отсутствия полноценного COMMON
LISP'а.
CONTINUE
READ-CHAR
(READ-CHAR [peek-flag]) считывает очередной элемент из потока
ввода и возвращает объект, P-имя которого состоит из этого знака. Если
peek-flag не равен NIL, элемент не извлекается из входного потока
(особенность MULISP'а)
UNREAD-CHAR
(UNREAD-CHAR) восстанавливает последний элемент, считанный из
входного потока, и возвращает NIL. Т.к. может быть восстановлен только
последний считанный элемент, вызов функции повторно без выполнения
операции считывания не будет иметь результата.
PEEK-CHAR [flag] Function
(PEEK-CHAR) считывает следующий элемент из входного потока, не
извлекая его оттуда, т.е.,
(PEEK-CHAR) <==> (PROG1 (READ-CHAR) (UNREAD-CHAR))
CONTINUE
(PEEK-CHAR T) считывает элементы из входного потока до тех пор,
пока встречается непустой элемент и выполняет для него вышеизложенную опе-
рацию.
(PEEK-CHAR <элемент>), где <элемент> - символ, считывает элементы
из входного потока до тех пор, пока не встретится элемент, равный первому
символу в Р-имени <элемента>. Во всех случаях, PEEK-CHAR восстанавливает
последний считанный из входного потока элемент во входном потоке.
Например,
$ (PEEK-CHAR 'M) COMPUTER
производит именно те действия, которые Вы можете видеть на предыдущей
строке.
CLEAR-INPUT
(CLEAR-INPUT) очищает текущий буфер ввода.
CONTINUE
READ-LINE
(READ-LINE) считывает элементы из стандартного потока ввода до тех
пор, пока не встретится <Return>, и возвращает символ , у которого Р-имя
состоит из всех считанных элементов, кроме <Return>. READ-LINE
возвращает строку такой, как она есть, не отбрасывая пустые места или
комментарии.
LISTEN
Если стандартный поток ввода содержит элементы, доступные для
ввода, (LISTEN) возвращает Т, в противном случае - NIL.
CONTINUE
Функции вывода
--------------
Функции вывода, как отмечалось ранее, выводят печатное
представление символа LISP'а в стандартный поток вывода.
PRIN1
(PRIN1 <обьект>) передает символьное представление <обьекта> в
стандартный поток вывода и возвращает <обьект> в качестве значения. PRIN1
печатает символы, используя их Р-имена. PRIN1 печатает cons'ы, используя
их списковые изображения , где это возможно, и изображения с использовани-
ем точечной нотации, где это необходимо.
Можете попробовать вывести объекты (CONS 'A '(B)) и (CONS 'A 'B) и
посмотреть, что из этого получится...
BREAK
CLRSCRN
На вывод печатных представлений символов оказывают влияние
значения следующих глобальных переменных:
*PRINT-ESCAPE* -- при T выводятся ESCAPE-символы ('\', '|' и т.п.)
NIL не выводятся
*PRINT-BASE* -- основание системы счисления, используемое при выводе
чисел.
*PRINT-POINT* -- количество знаков после запятой при выводе чисел с пла-
вающей точкой.
CONTINUE
В MULISP'е, кроме вышеприведенных, используются следующие
переменные:
*PRINT-DOWNCASE* -- T - преобразовывать при выводе буквы к нижнему
регистру;
NIL - преобразовывать при выводе буквы к
верхнему регистру;
*PRINTER-ECHO* -- T - дублировать вывод на принтер
NIL - не дублировать.
В COMMON LISP'е существует ряд других переменных, предоставляющих
достаточно гибкие возможности для управления выводом, но, в силу
отмеченной ранее причины, мы не будем здесь в них углубляться.
PRINC
PRINC идентична PRIN1, кроме того, что Р-имена, содержащие специ-
альные символы, не ограничиваются разграничительными символами, причем
значение контрольной переменной *PRINT-ESCAPE* не играет роли. Эквивалент-
ное определение PRINC через PRIN1:
(DEFUN PRINC (OBJ *PRINT-ESCAPE*) (SETQ *PRINT-ESCAPE* T)
(PRIN1 OBJ) )
CONTINUE
(PRINT обьект) передает символьное представление <обьекта> в
стандартный поток вывода, используя PRIN1, переходит на следующую строку и
возвращает <обьект> в качестве результата. Эквивалентное определение
PRINT:
(DEFUN PRINT (OBJ) (PRIN1 OBJ) (TERPRI) OBJ )
TERPRI
(TERPRI [<n>]) передает в стандартный поток вывода <n> символов
новой строки. При отсутствии <n> выводится 1 символ. В качестве
результата возвращается NIL.
Функции вывода COMMON LISP'а, отсутствующие, к сожалению, в
MULISP'е, мы не будем здесь рассматривать (наиболее существенным уроном,
по-видимому, следует считать отсутствие форматного вывода (FORMAT...)).
CONTINUE
Приведем здесь также некоторые функции, специфичные для MULISP'а:
WRITE-BYTE
Если <n> - целое число в пределах от 0 до 255 включительно,
(WRITE-BYTE <n>) записывает байт с ASCII кодом <n> в стандартный поток
вывода и возвращает <n>. Примерно эквивалентная функция в COMMON LISP'е
называется WRITE-CHAR и выводит знак, т.е. (WRITE-BYTE <n>) <==>
(WRITE-CHAR (ASCII <n>))
SPACES
(SPACES [<n>]) передает в стандартный поток вывода <n> символов
пробела. При отсутствии <n> выводится 1 символ. В качестве результата
возвращается количество переданных символов.
CONTINUE
WRITE-STRING
WRITE-LINE
Если <символ> - символ,
(WRITE-STRING <символ>) и (WRITE-LINE <символ>)
записывают элементы Р-имени <символа> в поток вывода и возвращает <сим-
вол>. WRITE-LINE записывает элемент новой строки после пересылки Р-имени.
Если <символ> - не символ, то обе функции возвращают NIL. Следует заме-
тить, что функции COMMON LISP'а с такими же названиями выводят подстроку
заданной строки, выделяемую начальной и конечной позициями.
LINELENGTH
Если <n> - положительное целое, (LINELENGTH <n>) определяет длину
строки для вывода ее системным принтером в файле так, что строки
автоматически ограничиваются по длине <n> символами. В качестве
результата возвращается предыдущая длина строки. Если <n> неположительное
целое или не задано, функция выдает текущую длину строки. На вывод строк
на консоль данная функция не оказывает влияния.
CONTINUE
MULISP, естественно, имеет ряд функций, ориентированных на работу
с экраном консоли, которые (что также вполне естественно) специфичны
только для данной реализации и имеют малое отношение к COMMON LISP'у и
другим стандартам. Мы думаем, что Вы сможете рассмотреть их самостоятель-
но...
CONTINUE
CLRSCRN
$(RDS)