\chapter{Input and Output}
\section{Introduction}
One category of input and output in LISP is "symbolic" I/O.
This allows a user to print or read possibly complex LISP
objects with one or a few calls on standard functions. PSL also
has powerful general-purpose I/O.
Input from multiple sources and output to multiple destinations
can be done in PSL all at the same time. PSL
provides I/O functions with explicit specification of sources
and destinations for I/O. On the other hand for convenience it
is often desirable to let the source or destination be implicit.
PSL provides the full set of I/O operations through functions
with an implicit source or destination.
The functions with and without an explicit channel designator
argument are described together in this chapter. In each case
calling the function with the implicit source or destination is
the same as calling the version with explicit channel argument
and supplying the value of the variable in* or out* as the
channel.
The current input or output channel can be changed by setting
or rebinding the variables {\bf in*} or {\bf out*}.
\index{*in}\index{*out} Historically, the
functions rds and wrs have been used for this and they are also
available along with their special features.
\subsection{Organization of this Chapter}
We first discuss the syntax used for symbolic input and
output. The syntax described applies to PSL programs,
interactive typein, format of data in data files, and to output
by PSL programs except when special formatting is used.
Functions for printing and reading follow. All (textual)
input and output functions are discussed. Next is open, for
setting up input and output with files, plus related functions.
A great deal of user input/output programming can be done using
just a subset of the functions described in these first
sections.
PSL includes functions that load program modules and execute
command files. They are essential to building of software
systems even if the system itself does no I/O. Functions of
this type are described next.
The section on I/O channels discusses some features available
for switching the current output from channel to channel, and
documents some fluid variables used in directing some of the
system's input and output.
Functions in the next section actually operate on objects such
as lists and strings! Since I/O functions scan input and format
output, and since it is possible to read from or print to a
string, I/O functions can be useful for building strings and for
scanning them. Some built-in functions are described.
The last two sections describe mechanisms that make possible
some sophisticated uses of the PSL I/O system. One describes
the mechanism in PSL that permits writing to a string or taking
input from the text buffer of a text editor. The other
discusses the tables used by the PSL scanner, which is
modifiable.
\section{Printed Representation of LISP Objects}
Most of this section is devoted to the representation of
tokens. In addition to tokens there are composite objects with
printed representations: lists and vectors. We briefly discuss
their printed formats first.
\begin{verbatim}
"(" expression expression . . . ")"
"(" expression expression . . . "." expression)
"[" expression expression . . . "]"
\end{verbatim}
Of these the first two are for lists. Where possible, the
first notation is preferred and the printing routines use it
except when the second form is needed. The second form is
required when the cdr of a pair is neither nil nor another pair.
The third notation is for vectors. For example:
\begin{verbatim}
(A . (B . C)) % An s-expression
(A B . C) % Same value, list notation
(A B C) % An ordinary list
[A B C] % A vector
\end{verbatim}
The following standards for representing tokens are used:
\begin{itemize}
\item An id normally consists of uppercase letters and digits.
The first character can be a digit when the first non-digit
character is a plus sign, minus sign, or a letter other
than "E" or "B". We exclude lowercase letters because the
PSL reader will normally convert lowercase letters to
uppercase. Although the user may use lowercase letters,
the interpreter only sees uppercase. This conversion is
controlled by the value of the switch *raise, a value of
nil will suppress this conversion. In addition to letters
and numbers, the following characters are considered
alphabetic for the purpose of notating symbols.
\begin{verbatim}
+ - $ & * / : ; | < = > ? ^ _ ~ @
\end{verbatim}
There is an escape convention for notating an id whose name
contains special characters. In addition to lowercase
letters the following characters are considered special.
\begin{verbatim}
! " % ' ( ) . [ ] ` , # |
JULIE % three ways to notate the same symbol
julie
JuLie
+$
1+
+1 % this is a number, not an id
x^2+y^2 % a single symbol
\end{verbatim}
\item Strings begin with a double quote (") and include all
characters up to a closing double quote. A double quote
can be included in a string by doubling it. An empty
string, consisting of only the enclosing quote marks,
is allowed. The characters of a string are not affected
by the value of the *raise. Examples:
\begin{verbatim}
"This is a string"
"This is a ""string"""
""
\end{verbatim}
\item Integers begin with a digit, optionally preceded by a + or
- sign, and consist only of digits. The global input radix
is 10; there is no way to change this. However, numbers of
different radices may be read by the following convention.
A decimal number from 2 to 36 followed by a sharp sign (\#),
causes the digits (and possibly letters) that follow to be
read in the radix of the number preceding the \#. [Footnote:
Octal numbers can also be written as a string of digits
followed by the letter "B".] Thus 63 may be entered as
8\#77, or 255 as 16\#ff or 16\#FF. The output radix can be
changed, by setting outputbase*. If outputbase* is not 10,
the printed integer appears with appropriate radix.
Leading zeros are suppressed and a minus sign precedes the
digits if the integer is negative. Examples:
\begin{verbatim}
100
+5234
-8#44 (equal to -36)
\end{verbatim}
\item Floats have a period and/or a letter "e" or "E" in them.
Any of the following are read as floats. The value appears
in the format [-]n.nn...nnE[-]mm if the magnitude of the
number is too large or small to display in
[-]nnnn.nnnn format. The crossover point is
determined by the implementation. In BNF, floats are
recognized by the grammar:
\begin{verbatim}
::= .|
.|
.
::= |
::= |
e|
e-|
e+|
E|
E-|
E+
::= |
+|
-
\end{verbatim}
That is:
\begin{verbatim}
[+|-][nnn][.]nnn{e|E}[+|-]nnn
nnn.
.nnn
nnn.nnn
\end{verbatim}
Examples:
\begin{verbatim}
1e6
.2
2.
2.0
-1.25E-9
\end{verbatim}
\item Code-pointers cannot be read directly, but can be printed
and constructed. Currently printed as
\begin{verbatim}
#.
\end{verbatim}
\item Anything else is printed as \#$<$Unknown:nnnn$>$, where nnnn is
the hexadecimal value found in the argument register. Such
items are not legal LISP entities and may cause garbage
collector errors if they are found in the heap. They
cannot be read in.
\end{itemize}
\section{Functions for Printing}
\subsection{Basic Printing}
\de{prin1}{(prin1 ITM:any): ITM:any}{expr}
{}
\de{channelprin1}{(channelprin1 CHAN:io-channel ITM:any): ITM:any}{expr}
{ Channelprin1 is the basic printing function. For
well-formed, non-circular structures, the result can be
parsed by the function read.
}
\de{prin2}{(prin2 ITM:any): ITM:any}{expr}
{}
\de{channelprin2}{(channelprin2 CHAN:io-channel ITM:any): ITM:any}{expr}
{ Channelprin2 is similar to channelprin1, except that strings
are printed without the surrounding double quotes, and
delimiters within ids are not preceded by the escape
character.
}
The following example illustrates the difference between prin1
and prin2.
\begin{verbatim}
ARGUMENT PRIN1 PRIN2
A!%WORD A!%WORD A%WORD
"STRING" "STRING" STRING
\end{verbatim}
\de{print}{(print U:any): U:any}{expr}
{}
\de{channelprint}{(channelprint CHAN:io-channel U:any): U:any}{expr}
{ Display U using channelprin1 and then terminate the line
using channelterpri.
}
\subsection{Whitespace Printing Functions}
\de{terpri}{(terpri): nil}{expr}
{}
\de{channelterpri}{(channelterpri CHAN:io-channel): nil}{expr}
{ Write an end of line character. The number of characters
output on the current line (that which is referenced by
channelposn), is defined to be zero and the number of lines
output on the current page (that which is referenced by
channelposn), is incremented.
}
\de{spaces}{(spaces N:integer): nil}{expr}
{}
\de{channelspaces}{(channelspaces CHAN:io-channel N:integer): nil}{expr}
{ N spaces are written.
}
\de{tab}{(tab N:integer): nil}{expr}
{}
\de{channeltab}{(channeltab CHAN:io-channel N:integer): nil}{expr}
{ Move to column N }
{\tt(channelspaces ch (- N (channelposn ch)))}
If the position on the current output line is past column N
then channelterpri is called before moving to column N.
\subsection{Formatted Printing}
\de{printf}{(printf FORMAT:string [ARGS:any]): nil}{expr}
{}
\de{channelprintf}{(channelprintf CHAN:io-channel FORMAT:string [ARGS:any]):nil}{expr}
{ Channelprintf is a simple routine for formatted printing.
The FORMAT is a string. The characters of this string are
printed unless a % is encountered. The character following
a % is interpreted as a formatting directive and is used to
interpret and print the other arguments to channelprintf.
The following format characters are currently supported.
}
\begin{tabular}{lp{11.0cm}}
\%b & The next argument is assumed to be an integer, it is passed to spaces.\\
\%c & The next argument should be a single character,
it is printed by a call on writechar.\\
\%d & Print the next argument as a decimal integer.\\
\%e & The next argument is evaluated by a call on eval.\\
\%f & Print an end-of-line character if not
at the beginning of the output line
(does not use a matching argument).\\
\%l & Print the next argument using print2l,
this is the same as \%w except that lists
are printed without the top level
pair of parenthesis.
The empty list is printed as a blank.\\
\%n & Print end-of-line character
(does not use a matching argument).\\
\%o & Print the next argument as an
octal integer.\\
\%p & Print the next argument using prin1.\\
\%p & Print the next argument using prin1.\\
\%r & Print the next argument using errprin, the
result is the same as \%p except that a
surrounding pair of quotes are also printed.\\
\%s & The next argument is assumed to be a string,
the surrounding double quotes are not printed.\\
\%t & The next argument is assumed to be an integer,
it is passed to tab.\\
\%w & Print the next argument using prin2.\\
\%x & Print the next argument as a hexadecimal
integer.\\
\end{tabular}
If the character following \% is not either one of the above
or another \%, it causes an error. Thus, to include a \% in
the format to be printed, use \%.
There is no checking for correspondence between the number
of arguments that FORMAT expects and the number given. If
the number given is less than the number in the FORMAT
string, garbage will be inserted for the missing arguments.
If the number given is greater than the number in the FORMAT
string, then the extra ones are ignored.
\de{prettyprint}{(prettyprint U:form): U:form}{expr}
{ Prettyprints U.
}
\subsection{The Fundamental Printing Function}
\de{writechar}{(writechar CH:character): character}{expr}
{}
\de{channelwritechar}{(channelwritechar CHANNEL:io-channel\\
CH:character): character}{expr}
{
Write one character to the device specified by CHANNEL. All
output is defined in terms of this function. The number of
characters output on the current line (that which is
referenced by channelposn), and the number of lines output
on the current page (that which is referenced by
channellposn), are updated. Each channel specifies an
output function, it is this function that is applied to
CHANNEL and CH to actually write the character.
}
\subsection{Additional Printing Functions}
\de{prin2l}{(prin2l L:any): {L:any, nil}}{expr}
{ Prin2, except that a list is printed without the top level
parentheses. If L is a pair then the return value is nil,
otherwise the return value will be L. }
\de{prin2t}{(prin2t X:any): any}{expr}
{}
\de{channelprin2t}{(channelprin2t CHAN:io-channel X:any): any}{expr}
{ Output X using channelprin2 and terminate line with
channelterpri.
}
\de{princ}{(princ ITM:any): ITM:any}{expr}
{}
\de{channelprinc}{(channelprinc CHAN:io-channel ITM:any): ITM:any}{expr}
{ Same function as channelprin2.
}
\de{errprin}{(errprin U:any): None Returned}{expr}
{ Prin1 with special quotes to highlight U.
}
\de{errorprintf}{(errorprintf FORMAT:string [ARGS:any]): nil}{expr}
{ Errorprintf is similar to printf, except that errout* is
used in place of the currently selected output channel.
Channelterpri is called before and after printing if the
line position is greater than zero. }
\de{eject}{(eject): nil}{expr}
{}
\de{channeleject}{(channeleject CHAN:io-channel): nil}{expr}
{ Skip to top of next output page.
}
\subsection{Printing Status and Mode}
For information on directing various kinds of output see the
section on channels.
\variable{outputbase*}{[Initially: 10]} {global}
{ This fluid can be set to control the radix in which integers
are printed out. If the radix is not 10, the radix is given
before a sharp sign. }
\begin{verbatim}
1 lisp> 16
16
2 lisp> (setq outputbase* 8)
8#10
3 lisp> 16
8#20
\end{verbatim}
\de{posn}{(posn): integer}{expr}
{}
\de{channelposn}{(channelposn CHAN:io-channel): integer}{expr}
{ Returns number of characters output on the current line of
this channel.
}
\de{lposn}{(lposn): integer}{expr}
{}
\de{channellposn}{(channellposn CHAN:io-channel): integer}{expr}
{ Returns number of lines output on this page of this channel. }
\de{linelength}{(linelength LEN:\{integer, nil\}): integer}{expr}
{}
\de{channellinelength}{(channellinelength CHAN:io-channel
LEN: \{integer, nil\}): integer}{expr}
{ For each channel there is a restriction on the length of
output lines. If LEN is nil then the value returned will be
the current maximum length. If LEN is an integer greater
than zero then it will become the new maximum. However, if
the argument is zero there will be no restrictions on the
size of the output lines. The following example illustrates
how this length is used. PSL uses a similar function to
apply output functions like prin1.}
\begin{verbatim}
(de check-line-length (length ch fn token)
(when (and (> (+ (channelposn ch) length)
(channellinelength ch nil))
(> (channellinelength ch nil) 0))
(channelwritechar ch (char eol)))
(idapply fn (list ch token)))
\end{verbatim}
The fluid variables PrinLevel and PrinLength allow the user to
control how deep the printer will print and how many elements at
a given level the printer will print. This is useful for
objects which are very large or deep. These variables affect
the functions prin1, prin2, princ, print, and printf (and the
corresponding Channel functions).
\variable{prinlevel}{[Initially: nil]}{global}
{ Controls how many levels deep a nested data object will print.
If PrinLevel is nil, then no control is exercised. Otherwise the
value should be an integer. An object to be printed is at
level 0.
}
\variable{prinlength}{[Initially: nil]}{global}
{
Controls how many elements at a given level are printed. A
value of nil indicates that there be no limit to the number
of components printed. Otherwise the value of PrinLength
should be an integer.
}
\section{Functions for Reading}
\subsection{Reading S-Expressions}
\de{read}{(read): any}{expr}
\de{channelread}{(channelread CHAN:io-channel): any}{expr}
{
Reads and returns the next S-expression from input channel
CHAN. Valid input forms are: vector-notation, dot-notation,
list-notation, numbers, strings, and identifiers.
Identifiers are interned (see the intern function in Chapter
4), unless the fluid variable *compressing is non-nil.
Channelread returns the value of the global variable \$eof\$
when the end of the currently selected input channel is
reached.
}
\begin{verbatim}
(de channelread (ch)
(let ((currentscantable* lispscantable*)
(currentreadmacroindicator* 'lispreadmacro))
(channelreadtokenwithhooks ch)))
\end{verbatim}
Channelread uses the function channelreadtokenwithhooks.
Tokens are scanned within the context of the scan table
bound to lispscantable*. The scan table is used to
recognize special character types, for example, delimiters
like (, ), and space. The read macro mechanism is used to
parse s-expressions (see section 12.4.5 for more information
on read macros). PSL uses a number of read macros which are
described below.\\
\begin{tabular}{lp{12.0cm}}
( & S-expressions are gathered into a list until
the matching right parenthesis is read. Both
list and dot notation are recognized. A pair
or list is returned.\\
) & If a right parenthesis is read and a matching
left parenthesis had not been previously read
then it will be ignored.\\
$[$ & S-expressions are gathered into a vector until
a matching right bracket is read. A vector is
returned.\\
' & The next s-expression read is quoted, the
result is of the form (QUOTE S-EXPRESSION).\\
(char eof) & If an end of file character is read while a
list or vector is being constructed then the
following error will occur.
***** Unexpected EOF while reading on channel.\\
`N' & Otherwise the value of $eof$ is returned.\\
\end{tabular}\\
The following read macros are defined in the {\tt useful} module.
\begin{tabular}{lp{11.0cm}}
\#\' & this is like the quote mark ' but it is used
for function instead of quote. For example
\#'name reads as (FUNCTION NAME).\\
\#/ & this returns the numeric form of the following
character read without raising it. For
example \#/a is 97 while \#/A is 65.\\
\#$\backslash$ & This is a read macro for char (see Chapter 6
for more information). Note that the argument
will be raised if *raise is non-nil. For
example, \#$\backslash$a = \#$\backslash$A = 65.\\
\#. & This causes the following expression to be
evaluated at read time. For example, `(1 2
\#.(plus 1 2) 4) reads as (1 2 3 4).\\
\#+ & The next two expressions are read. If the
first is a system name which is the current
system then the second expression is returned.
Otherwise another expression is read and it
becomes the value returned.\\
\#- & The next two expressions are read. If the
first is a system name which is not the
current system then the second expression is
returned. Otherwise another expression is
read and it becomes the value returned.\\
\end{tabular}
\subsection{Reading Single Characters}
\de{readchar}{(readchar): character}{expr}
{}
\de{channelreadchar}{(channelreadchar CHANNEL:io-channel):
character}{expr} {
Reads one character (an integer) from CHANNEL. All input is
defined in terms of this function. If CHANNEL is not open
or is open for writing only, an error is generated. If
there is a non-zero value in the backup buffer associated
with CHANNEL, the buffer is emptied (set to zero) and the
value returned. Otherwise, the reading function associated
with CHANNEL is called with CHANNEL as argument, and the
value it returns is returned by channelreadchar.}
\begin{verbatim}
***** Channel not open
\end{verbatim}
\begin{verbatim}
***** Channel open for write only
\end{verbatim}
\de{readch}{(readch): id}{expr}
{}
\de{channelreadch}{(channelreadch CHAN:io-channel): id}{expr}
{ Like channelreadchar, but returns the id for the character
rather than its ASCII code.
}
\de{unreadchar}{(unreadchar CH:character): Undefined}{expr}
{}
\de{channelunreadchar}{(channelunreadchar CHAN:io-channel
CH:character): Undefined}{expr}
{
The input backup function. Ch is deposited in the backup
buffer associated with Chan. This function should be only
called after channelreadchar is called, and before any
intervening input operations, since it is used by the token
scanner. The unread buffer only holds one character, so it
is generally useless to unread more than one character.
}
\subsection{Reading Tokens}
The functions described here pertain to the token scanner and
reader. Globals and switches used by these functions are
defined at the end of this section.
\de{channelreadtoken}{(channelreadtoken CHANNEL:io-channel):
\{id, number, string\}}{expr}
{
This is the basic PSL token scanner. The value returned is
an item corresponding to the next token from the input
stream. An id will be interned unless the switch
{\bf *compressing}\index{*compressing} is non-nil.
If *compressing is t then the
print name of the id is passed to newid, otherwise it is
passed to intern.
}
The global variable toktype* \index{toktype*} is set as
follows.
\begin{tabular}{ll}
0 & if the token is an ordinary id,\\
1 & if the token is a string,\\
2 & if the token is a number, or\\
3 & if the token is an unescaped delimiter such as\\
& "(", but not "!(" In this last case, the value\\
& returned is the id whose print name is the\\
& same as the delimiter.\\
\end{tabular}
The precise behavior of this function depends on two fluid
variables:
Currentscantable* is bound to a vector known as a scan
table. Described below.
Currentreadmacroindicator* is bound to an id known as a read
macro indicator. Described below.
\de{ratom}{(ratom): \{id, number, string\}}{expr}
{ Reads a token from the current input channel. }
\de{channelreadtokenwithhooks}{(channelreadtokenwithhooks CHANNEL:io-channel):\\
any}{expr}
{ This function reads a token and performs any action
specified if the token is marked as a read macro under the
current indicator. Read uses this function internally.
Uses the variable currentreadmacroindicator*
{\index currentreadmacroindicator*} to determine
the current indicator.
}
\subsection{Reading Entire Lines}
\de{readline}{(readline): string}{expr}
{ Same as (channelreadline in*)}
\de{channelreadline}{(channelreadline CHANNEL:io-channel): string}{expr}
{ A string is returned which contains each character from the
current position of the scanner to the next end-of-line or
end-of-file character.
}
\subsection{Read Macros}
At the top level of PSL, an expression is read, it is
evaluated, and then the result is printed. Normally a macro is
expanded during evaluation. The read macro is a different type
of macro, it is expanded during the reading phase. When the
reader encounters a read macro character, the the macro is
executed and the result is inserted in place of the read macro
character.
A read macro must be a function of two arguments, the first
should represent a IO channel, the second a character. A
character which represents a read macro must be set to one of
two types in the scan table. It may be either a delimiter or a
diphthong. Diphthong corresponds to double character read
macros, delimiter to single character read macros. In addition,
the id which corresponds to the character must have a reference
to the name of the function on its property list. For a
diphthong the indicator must be lispdiphthong, for a delimiter
it must be lispreadmacro.
The quote macro may be defined as follows. Note that we
cannot use 'form in place of (quote form) until we have defined
the read macro.
\begin{verbatim}
(de doquote (channel ch)
(list (quote quote) (channelread channel)))
\end{verbatim}
\begin{verbatim}
(put (quote !') (quote lispreadmacro)(function doquote))
\end{verbatim}
\begin{verbatim}
(putv lispscantable* (char !') delimiter)
\end{verbatim}
This says that when a single quote is read, PSL should replace
it with a list consisting of quote and the next expression in
the input (obtained by an explicit call to channelread). Since
defining a character as a read macro makes it difficult to use
the character in a normal way, read macros should not be letters
or digits.
\subsection{Terminal Interaction}
\de{yesp}{(yesp MESSAGE:string): boolean}{expr}
{ If the user responds y or yes, yesp returns a non-nil value.
A response of n or no results in a value of nil. It is
possible to enter a break loop by responding with b. After
quitting the break loop, one must still respond y, yes, n,
or no.
}
\subsection{Input Status and Mode}
\variable{promptstring*}{ [Initially: "x lisp$>$"]}{global}
{
Displayed as a prompt when any input is taken from TTY.
Prompts should therefore not be directly printed. Instead
the value should be bound to promptstring*.
}
\variable{*eolinstringok}{[Initially: nil]}{switch}
{
If *eolinstringok is non-nil, the warning message
{\tt *** String continued over end-of-line }\\
is suppressed.
}
\variable{*raise}{ [Initially: t]}{switch}
{
If *raise is non-nil, all characters input for ids
through PSL input functions are raised to upper case. If
*raise is nil, characters are input as is. A string is
unaffected by *raise.
}
\variable{*compressing}{[Initially: nil] }{switch}
{
If *compressing is non-nil, channelreadtoken and other
functions that call it do not intern ids.
}
\variable{currentscantable*}{[Initially: NIL]}{global}
{ This variable is set to lispscantable* by the function read }
\variable{currentreadmacroindicator*}{ [Initially: NIL ]}{global}
{
The function read binds this variable to the value
LISPREADMACRO. Its value determines the property list
indicator used in looking up read macros. The user may
define a set of read macros using some new indicator and
rebind this variable. Ordinary read macros may be added by
putting properties on ids under the LISPREADMACRO indicator.
}
\section{File System Interface: Open and Close}
\de{open}{(open {\small FILENAME:string ACCESSTYPE:id):
CHANNEL:io-channel}}{expr}
{ If AccessType is eq to input or output, an attempt is made
to access the system-dependent FILENAME for reading or
writing. If the attempt is unsuccessful, an error is
generated; otherwise a free channel is returned and
initialized to the default conditions for ordinary file
input or output.
}
If none of these conditions hold, a file is not available,
or there are no free channels, an error is generated.
\begin{verbatim}
***** Unknown access type
***** Improperly set-up special IO open call
***** File not found
***** No free channels
\end{verbatim}
If AccessType is eq to SPECIAL, no file is opened. Instead
the channel is initialized as a generalized input and/or
output stream. See below.
\de{filep}{(filep NAME:string): boolean}{expr}
{ This function will return t if file NAME can be opened, and
nil if not, e.g. if it does not exist.
}
\de{close}{(close CHANNEL:io-channel): io-channel}{expr}
{ The closing function associated with CHANNEL is called, with
CHANNEL as its argument. If it is illegal to close CHANNEL,
if CHANNEL is not open, or if CHANNEL is associated with a
file and the file cannot be closed by the operating system,
this function generates an error. Otherwise, CHANNEL is
marked as free and is returned.
}
Here is a simple example of input from a particular file with
output sent to the current output channel. This function reads
forms from the file MYFILE.DAT and prints out all those whose
car is eq to its parameter. Using unwind-protect, we are
assured that the channel (and the file), will be closed in all
cases, including errors.
\begin{verbatim}
(de filter-my-file (x)
(let ((chan (open "MYFILE.DAT" 'input))
form)
(unwind-protect
(while (neq (setq form (channelread chan))
$eof$)
(if (and (pairp form) (eq (car form) x))
(print form)))
(close chan))))
\end{verbatim}
\section{Loading Modules}
Two convenient procedures are available for loading modules.
Various facilities described in this manual are actually in
loadable modules and their documentation notes that they must be
loaded. Loadable modules typically exist as FASL files .B
files see the section on the compiler for information on
producing FASL files.
\de{load}{(load [FILE:\{string, id\}]): nil}{macro}
{ For each argument FILE, an attempt is made to locate a
corresponding file. If a file is found then it will be
loaded by a call on an appropriate function. A full file
name is constructed by using the directory specifications in
loaddirectories* and the extensions in loadextensions*. The
strings from each list are used in a left to right order,
for a given string from loaddirectories* each extension from
loadextensions* is used. More information about
loaddirectories* and loadextensions* can be found below.
}
While the file is being loaded *usermode will be set to nil.
If a file cannot be found the call on load will be aborted.
\begin{verbatim}
***** `FILE' load module not found
\end{verbatim}
If either *verboseload or *printloadnames is non-nil then
\index{*verboseload}\index{*printloadnames}
\begin{verbatim}
*** loading FULL-NAME
\end{verbatim}
is printed just prior to loading the file. Once a file has
been loaded the message
\begin{verbatim}
*** FILE loaded
\end{verbatim}
will be printed if the *verboseload is non-nil. In
addition, the name FILE is added to the list referenced by
options*. If an attempt is made to load a file which has
been partially loaded then
\begin{verbatim}
*** Warning: Load of FILE previously requested, but incomplete.
\end{verbatim}
will be printed. If FILE is found to be in options* then
the attempt to load FILE will be ignored.
\begin{verbatim}
*** FILE already loaded
\end{verbatim}
Note that memq is used to determine if FILE is in options*.
Therefore when you use string arguments for loading files,
although identical names for ids refer to the same object,
identical names for strings refer to different objects.
\de{reload}{(reload [FILE:\{string,id\}]): nil}{macro}
{ Reload is very similar to load. The difference between the
two is that for each name FILE, the first occurance of FILE
in options* will be removed before attempting to load the
file.
}
\de{imports}{(imports FILES:list): nil}{expr}
{ This function is also used to load modules. If imports is
invoked as another module is being loaded, then the modules
specified by FILES will not be loaded until the loading of
the current module is complete. Otherwise imports is
identical to load.
}
\variable{loaddirectories* }{[Initially: list]}{global}
{ References a list of strings to append to the front of
file names passed as arguments to load, reload, and imports. }
\variable{loadextensions*}{[Initially: association-list]}{global}
{
References the a-list {\tt ((".b" . faslin) (".lap" . lapin)) }
The car of each pair is a string which represents an
extension to append to the end of the file names passed as
arguments to load, reload, and imports. The cdr is a
function appropriate for loading a file of a particular
extension. For example, a file whose extension is B is
loaded with the function faslin. }
\variable{options*}{[Initially: nil]}{global}
{
Once a file corresponding to an argument FILE to either
load, reload, or imports has been loaded, FILE will be added
to the list referenced by options*. An attempt to load a
file by applying load or imports will be aborted if FILE is
found in options*. Reload removes the first occurance of
FILE from options* before passing its argument to load.
}
\variable{*verboseload}{[Initially: nil]}{switch}
{
If non-nil, a message is displayed when a request is made to
load a file which has already been loaded, when a file is
about to be loaded, and when the loading of a file is
complete. Since *redefmsg is set to the value of
*verboseload, a non-nil value will also cause a message to
be printed whenever a function is redefined during a load.
}
\variable{*printloadnames}{[Initially: nil]}{switch}
{
If non-nil, a message is printed when a file is about to be
loaded.
}
\section{Reading Files into PSL}
The following procedures are used to read complete files into
PSL, by first calling open, and then processing each top level
form in the file. The effect is similar to what would happen if
the file were typed into PSL. File names are represented by
strings. File names may be given using full system dependent
file name conventions.
\variable{*echo}{[Initially: nil]}{switch}
{
The switch echo is used to control the echoing of input.
When (on echo) is placed in an input file, the contents of
the file are echoed on the standard output device. Dskin
does not change the value of *echo, so one may say (on echo)
before calling dskin, and the input will be echoed.
}
\de{dskin}{(dskin NAME:string): {nil, abort}}{expr}
{ The contents of the file are processed as if they were typed
in. If the processing of the file is aborted the return
value will be the identifier abort and the following error
message will be printed.
}
{\tt ***** DSKIN of `NAME' aborted after N form(s) }\\
A count of each top level form is kept as they are
processed, which is the value N in the error message. Once
in* has been bound to the channel which represents the open
file, each form is processed by a function similar to the
one below.
\begin{verbatim}
(de dskin-step ()
(let ((form (funcall toploopread*)))
(cond ((eq form $eof$) 'eof)
((not *defn)
(funcall toploopprint* (funcall toploopeval* form))
(dfprint* (funcall dfprint* form))
(t (prettyprint form)))))
\end{verbatim}
Note that the functions used for reading, evaluating and
printing are the same as those used in toploop (see Chapter
15 for more information). If dfprint* has a value (and the
switch *defn is non-nil), then it will be applied to the
expression instead of applying the functions bound to
toploopeval* and toploopprint*. If dfprint* does not have a
value (and the switch *defn is nil), then the expression
will be simply be printed by a call on prettyprint.
Dskin is flagged ignore. This means that when a file is
compiled, a top level application of dskin will be evaluated
but not compiled. For more information about the flag
ignore see Chapter 19.
\de{lapin}{(lapin NAME:string): \{nil, abort\}}{expr}
{ Almost identical to dskin. The difference is that the
function bound to toploopprint* is not applied. In general,
this means that the results of evaluation are not printed.
}
Note that lapin is not flagged ignore as is dskin. This
means that a top level application of lapin is compiled but
not evaluated. For more information on how the flags ignore
and eval effect compilation see Chapter 19.
\de{faslin}{(faslin FILENAME:string): nil}{expr}
{ This is an efficient binary read loop, which fetches blocks
of code, constants and compactly stored ids. It uses a
bit-table to relocate code and to identify special
LISP-oriented constructs. FILENAME must be a complete file
name.
}
\section{About I/O Channels}
\de{rds}{(rds {CHANNEL:io-channel, nil}): io-channel}{expr}
{ Rds sets in* to the value of its argument, and returns the
previous value of in*. In addition, if specialrdsaction* is
non-nil, it should be a function of 2 arguments, which is
called with the old channel as its first argument and the
new channel as its second argument. (rds nil) does the same
as (rds stdin*).
}
\de{wrs}{(wrs {CHANNEL:io-channel, nil}): io-channel}{expr}
{ Wrs sets out* to the value of its argument and returns the
previous value of out*. In addition, if specialwrsaction*
is non-nil, it should be a function of 2 arguments, which is
called with the old channel as its first argument and the
new channel as its second argument. (wrs nil) does the same
as (wrs stdout*).
}
Global variables containing information about channels are
listed below.
\variable{in*}{[Initially: 0]}{global}
{
Contains the currently selected input channel. May be set
or rebound by the user. This is changed by the function
rds.
}
\variable{out*}{[Initially: 1]}{global}
{
Contains the currently selected output channel. May be set
or rebound by the user. This is changed by the function
wrs.
}
\variable{stdin*}{ [Initially: 0]}{global}
{
The standard input channel (but not in the Unix sense of
standard input). Channel 0 is ordinarily the terminal and
this variable is not intended to be set or rebound.
}
\variable{stdout*}{[Initially: 1]}{global}
{
The standard output channel. Like channel 0, channel 1 is
ordinarily always the terminal, and this variable is not
intended to be set or rebound.
}
\variable{breakin*}{[Initially: nil]}{global}
{
The channel from which the break loop gets its input. It
has been set to default to stdin*, but may have to be
changed on some systems with buffered-IO.
}
\variable{breakout*}{[Initially: nil]}{global}
{
The channel to which the break loop sends its output. It
has been set to default to stdout*, but may have to be
changed on some systems with buffered-IO.
}
\variable{errout*}{[Initially: 1]}{global}
{
The channel used by the errorprintf.
}
\variable{specialrdsaction*}{[Initially: nil]}{global}{}
\variable{specialwrsaction*}{[Initially: nil]}{global}{}
\section{I/O to and from Lists and Strings}
\de{bldmsg}{(bldmsg FORMAT:string, [ARGS:any]): string}{expr}
{ Printf to string. This can be used as a very convenient way
of obtaining the printed representation of an object for
further analysis. In many cases it is also a very
convenient way of constructing a needed string. An error
will occur on overflow. Overflow occurs when the length of
the result exceeds the value of maxtokensize.
}
\begin{verbatim}
***** Buffer overflow while constructing error message: FORMAT
\end{verbatim}
\de{flatsize}{(flatsize U:any): integer}{expr}
{ Character length of prin1 S-expression. }
\de{flatsize2}{(flatsize2 U:any): integer}{expr}
{ Prin2 version of flatsize. }
\de{explode}{(explode U:any): id-list}{expr}
{ Explode takes the constituent characters of an S-expression
and forms a list of single character ids. It is implemented
via the function channelprin1, with a list rather than a
file or terminal as destination. Returned is a list of
interned characters representing the characters required to
print the value of U.
}
\begin{verbatim}
1 lisp> (explode 'foo)
(f O O)
2 lisp> (explode '(a . b))
(!( a ! !. ! b !))
\end{verbatim}
\de{explode2}{(explode2 U:{atom}-{vector}): id-list}{expr}
{ Prin2 version of explode. }
\de{compress}{(compress U:id-list): {atom}-{vector}}{expr}
{ U is a list of single character identifiers which is built
into a PSL entity and returned. Recognized are numbers,
strings, and identifiers with the escape character
prefixing special characters. The formats of these items
appear in the "Primitive Data Types" Section, Section 2.1.2.
Identifiers are not interned on the id-hash-table. Function
pointers may not be compressed. If an entity cannot be
parsed out of U or characters are left over after parsing an
error occurs: }
\begin{verbatim}
***** Poorly formed atom in COMPRESS
\end{verbatim}
\de{implode}{(implode U:id-list): atom}{expr}
{ Compress with ids interned.
}
\section{Generalized Input/Output Streams}
All input and output functions are implemented in terms of
operations on "channels". A channel is just a small integer
\footnote{ The range of channel numbers is from 0 to
MaxChannels, where MaxChannels is a system-dependent constant,
currently 31, defined in the module {\bf io-decls}} which has 3
functions and some other information associated with it. The
three functions are:
\begin{enumerate}
\item A reading function, which is called with the channel as
its argument and returns the integer ASCII value of the
next character of the input stream. If the channel is for
writing only, this function is writeonlychannel. If the
channel has not been opened, this function is
channelnotopen. The reading function is responsible for
echoing characters if the flag *echo is non-nil. It should
use the function writechar to echo the character. It may
not be appropriate for a read function to echo characters.
For example, the "disk" reading function does echoing,
while the reader used to implement the compress function
does not.
The read function must also be concerned with the
handling of ends of "files" (actually, ends of
channels) and ends of lines. It should return the
ASCII code for an end of file character (system
dependent) when reaching the end of a channel. It
should return the ASCII code for a line feed
character to indicate an end of line (or "newline").
This may require that the ASCII code for carriage
return be ignored when read, not returned.
\item A writing function, which is called with the channel as its
first argument and the integer ASCII value of the
character to write as its second argument. If
the channel is for reading only, this function is
readonlychannel. If the channel has not been
opened, this function is channelnotopen.
\item A closing function, which is called with the channel as its
argument and performs any action necessary for the
graceful termination of input and/or output operations
to that channel. If the channel is not open, this
function is channelnotopen.
\end{enumerate}
The other information associated with a channel includes the
current position in the output line (used by posn), the maximum
line length allowed (used by linelength and the printing
functions), the single character input backup buffer (used by
the token scanner), and other system-dependent information.
Ordinarily, the user need not be aware of the existence of
this mechanism. However, because of its generality, it is
possible to implement operations other than just reading from
and writing to files using it. In particular, the LISP
functions explode and compress are performed by writing to a
list and reading from a list, respectively (on channels 3 and 4
respectively).
\subsection{Using the "Special" Form of Open}
If Open is called with AccessType eq to SPECIAL and the global
variables specialreadfunction*, specialwritefunction*, and
specialclosefunction* are bound to ids, then a free channel is
returned and its associated functions are set to the values of
these variables. Other non system-dependent status is set to
default conditions, which can later be overridden. The
functions readonlychannel and writeonlychannel are available as
error handlers. The parameter Filename is used only if an error
occurs.
The following globals are used by the functions in this
section.
\variable{specialclosefunction* }{[Initially: nil]}{global}{}
\variable{specialreadfunction*}{[Initially: nil]}{global}{}
\variable{specialwritefunction*}{[Initially: nil]}{global}{}
\section{Scan Table Internals}
The scan table controls the behaviour of the reader. It can
be modified to extend the syntax of PSL or to aid the writing of
other parsers. This table contains information about the syntax
of each character. It is possible to have several tables
describing different syntaxes and to switch from one to another
by binding the variable lispscantable*.
The table is a vector of 129 entries, indexed by 0 through
128. The first 128 entries correspond to ASCII character code.
Each entry contains an integer between 0 and 21. The meaning of
these numbers is given below.
\begin{tabular}{lp{13.0cm}}
0 \ldots 9 & DIGIT: indicates the character is a digit and gives the
corresponding numeric value.\\
10 & LETTER: indicates the character is alphabetic.\\
11 & DELIMITER: indicates the character is a delimiter, the first
character of a diphthong should not be classified as
a delimiter.\\
12 & COMMENT: indicates the character begins a comment, terminated
by an end of line.\\
13 & DIPHTHONG: indicates the character is a delimiter which may be
the
starting character of a diphthong. A diphthong is a
two character sequence read as one token.\\
14 & IDESCAPE: indicates that the character is an escape character, this
character is used within id names to reference a character
which is not a digit or alphabetic.\\
15 & STRINGQUOTE: indicates that the character is used to delimit strings.\\
16 & PACKAGE: indicates that the character is used to introduce explicit
package names.\\
17 & IGNORE: indicates that the character is to be ignored.\\
18 & MINUS: indicates that the character represents a minus sign.\\
19 & PLUS: indicates that the character represents a plus sign.\\
20 & DECIMAL: indicates that the character represents a decimal point.\\
21 & IDSURROUND: indicates that the character is to act for identifiers
as a string quote for strings.\\
%\end{TABELLE***}
\end{tabular}
\vspace{0.5cm}
It may be tedious to insert an idescape character (!), before
every delimiter character in the name of an id. By setting the
type of a character (for example |), to idsurround this can be
avoided. Every character between the vertical bars is taken as
part of the ids name, as if ! were written before each. Note
that !! corresponds to ! and !| to |.
% establish | as the character to surround the name of ids
\begin{verbatim}
(PUTV LISPSCANTABLE* (CHAR '|) IDSURROUND)
|"| % the same as !"
|dave| % the name of the id is dave not DAVE
|!|!!| % the name is |!, the same effect is gotten
% by writing !|!!
\end{verbatim}
\variable{lispscantable*}{[Initially: as described below]}{global}
{
Lispscantable* associates a type with each character. It is
a vector of 129 entries. The type for a character C is
stored at index (CHAR C). The table below lists each
character under its type. The 128'th entry is the diphthong
indicator, LISPDIPHTHONG.
}
\begin{verbatim}
DIGIT 0 through 9
LETTER a through z, A through Z, #, $, &, *, /,
@, ;, :, <, >, =, ^, _, {, }, |, ~, ?,
^A through ^H, ^K, ^N through ^Y, ^\,^],
^^, ^_, rubout
DELIMITER (, ), ', `, ^Z, [, ]
COMMENT %
DIPHTHONG ,
IDESCAPE !
STRINGQUOTE "
PACKAGE \
IGNORE null, tab, line-feed, ^L,
carriage-return and space
MINUS -
PLUS +
DECIMAL .
\end{verbatim}
\section{Scan Table Utility Functions}
The following functions are provided to manage scan tables, in
the {\bf read-utils} module.
\de{printscantable}{(printscantable TABLE:vector): nil}{expr}
{ Prints the entire scantable, gives the 0 ... 127 entries
with the name of the character class. Also prints the
indicator used for diphthongs.
}
\de{copyscantable}{(copyscantable OLDTABLE:\{vector, nil\}): vector}{expr}
{ Copies the existing scantable (or currentscantable* if given
nil). The 128'th entry (the diphthong indicator), of the
copy is set to the result of a call on gensym.
}
\de{putdiphthong}{(putdiphthong TABLE:vector D1:id ID2:id DIP:id): nil}{expr}
{ Installs DIP as the name of the diphthong Id1 followed by
ID2 in the given scan table.
}
\section{Binary I/O Functions}
Binary input and output are necessary to provide an efficient access to
large files e.g. for loading or generating binary files (e.g. faslin
or faslout) or for
communication with other
processors e.g. graphics or database processors.
Another important application is the connection between two or more
PSL processors on incompatible platforms.
In the following descriptions HANDLE means something system specific
which in many cases should better not be interpreted by the LISP
evaluator. One can very well think of HANDLE as a FILE* object
belonging to the world of C's I/O library.
The results of a binary read function is usually not
meant for LISP evaluation, either.
\de{binaryopenread}{(binaryopenread FILENAME:string): HANDLE}{expr}
{opens the file FILENAME for reading. If the file cannot be opened,
an error message {\tt Couldn't open binary file for input} will be shown,
an error condition is raised.}
\de{binaryopenwrite}{(binaryopenwrite FILENAME:string): HANDLE}{expr}
{opens the file FILENAME for writing. If the file cannot be opened,
an error message {\tt Couldn't open binary file for output} will be shown,
an error condition is raised.}
\de{binaryopenappend}{(binaryopenappend FILENAME:string): HANDLE}{expr}
{opens the file FILENAME in append mode. If the file cannot be opened,
an error message {\tt Couldn't open binary file for append} will be shown,
an error condition is raised.}
\de{binaryopenupdate}{(binaryopenupdate FILENAME:string): HANDLE}{expr}
{opens the file FILENAME for update. If the file cannot be opened,
an error message {\tt Couldn't open binary file for update} will be shown,
an error condition is raised.}
\de{binaryclose}{(binaryclose Filepointer:handle):NIL}{expr}
{Closes the file referenced by filepointer}
\de{binaryread}{(binaryread CHANNEL:handle): any}{expr}
{reads one LISP item from file channel and returns it as value. There is
no way to distinguish error conditions from normal return values.}
\de{binaryreadblock}{(binaryreadblock CHANNEL:handle ADDR:int SIZE:int): int}
{expr}
{reads SIZE LISP items from the file into an array pointed to by ADDR.
The number of actually read items is returned,
indicating error or end-of-file if the returned
value is less than the number of items requested.}
\de{binarywrite}{(binarywrite CHANNEL:handle WORD:any): NIL}{expr}
{writes one LISP item in WORD to file channel.}
\de{binarywriteblock}{(binarywriteblock Filepointer:handle ADDR:int SIZE:int):
NIL}
{expr}
{writes SIZE LISP items from the an array pointed to by ADDR to the file
referenced by Filepointer.
The number of actually written items is returned,
indicating error if the returned value is less than the number of
items requested.}
\de{binarypositionfile}{(binarypositionfile CHANNEL:handle, SPECIAL:int): any}{expr}
{this corresponds to C's fseek operation. The special value (the address)
as well as the
return value is system dependent, please investigate.}