There is 1 other file named sail.pub in the archive. Click here to see a list.
.require "pubmac" source!file;
.<<xgpcol - not at Rutgers>>
This section describes changes and additions to Sail since the August 1976
Double precision floating-point arithmetic is available.
Use the <type!qualifier> LONG in declarations. For example,
LONG REAL X, Y, Z;
LONG REAL ARRAY XA[0:N];
Currently LONG has meaning only when it appears as part of LONG REAL.
(At some future time LONG INTEGERs may also exist.)
The runtime routines LREALIN and LREALSCAN operate the same as REALIN and
REALSCAN, except for returning LONG REAL values. The routine CVEL takes a
LONG REAL value and returns a string representation like that of CVE,
except that "@@" is used to signify LONG when delimiting the exponent.
Any of "@", "@@", "E", or "D" are acceptable exponent delimiters to
LREALIN and LREALSCAN.
Variables which are declared LONG REAL are represented in KI10 hardware
format double precision, take two consecutive words of storage, and
provide 62 bits of precision (approximately 18 decimal digits) to
represent the fraction part of a floating-point number. Regular REAL
variables occupy a single word and have 27 bits (8 decimal digits) of
precision. The exponent range of both REAL and LONG REAL variables
is from -128 to 127, where 2^127 is approximately 10^38.
LONG REAL is a dominant type in arithmetic operations +-*/%^ MAX MIN and
arithmetic relationals <>= LEQ GEQ NEQ. If one operand is LONG REAL then both
operands will be converted to LONG REAL (if necessary) before performing
the operation. An exponentiation involving a LONG REAL raised to a positive
integer constant is an exception to this rule. The type coercion path is
linear: STRING -> INTEGER -> REAL -> LONG REAL. Conversion from REAL to
LONG REAL is performed by assigning the (only) word of the REAL to the
most significant word of the LONG REAL and setting the second (least
significant) word of the LONG REAL to zero. Conversion from LONG REAL to
REAL is by UUO which rounds.
Arithmetic and assignment operations are compiled into DFAD, DFSB, DFMP,
DFDV, DMOVE, DMOVEM instructions. The Sail operations ASH, LSH, ROT,
LAND, LOR, EQV, XOR are performed on both words (ASHC, LSHC, ROTC, 2 ANDs,
2 IORs, etc.). LOCATION of a LONG REAL variable gives an address E such
that DMOVE AC,E fetches the appropriate words of memory. When passed by
value as an actual parameter to a procedure, both words are placed on the
P stack: PUSH P,X ; PUSH P,X+1. LONG REAL fields in record classes are
handled much like STRING fields, except that the address in the record
field points to the first word of a 2-word block (rather than to the
second word as in the case with STRINGs).
LONG REAL ARRAYs are stored as contiguous blocks of 2-word values.
ARRTRAN done on two LONG REAL arrays is a transparent operation, but for
ARRYIN, ARRYOUT, or ARRBLT the actual word count is specified; think about
whether you should multiply by 2! At runtime the array descriptor for a
LONG ARRAY has bit 12 (40,,0 bit) set in MULT(n), the multiplier for the
last dimension (which would otherwise be =1). Similarly, a LONG ARRAY is
allocated by setting bit 12 (40,,0) bit in the parameter which specifies
the number of dimensions to ARMAK.
Runtime support for LEAP items with LONG REAL datums does not yet
exist, although the compiler does generate suitable code.
Runtime support for double precision exponentiation is also limited
for the moment. Any exponentiation X^K where K is a positive
integer constant is compiled inline using the binary ("Russian peasant")
method, regardless of the type of X. Other exponentiations involving
LONG REALs are merely translated into procedure calls on
LONG REAL PROCEDURE DPOW (INTEGER EXPONENT; LONG REAL BASE);
LONG REAL PROCEDURE DLOGS (LONG REAL EXPONENT, BASE);
depending on the type of the exponent. The Sail runtime system
does not yet contain such procedures, so you will have to roll your own.
.ss|Declarations and Scope|
Sail declarations must occur
before use. For example, in the following program the argument to PRINT
is interpreted as the K on line 2, even though by the ALGOL60 notion of
scope it should be interpreted as the K on line 5.
INTEGER K; COMMENT this is line 2;
PROCEDURE BAR; BEGIN PRINT(K) END;
INTEGER K; COMMENT this is line 5;
The compiler now recognizes "**" for "^", ":=" for "_",
"<=" for "LEQ", and ">=" for "GEQ".
REQUIRE OVERLAP!OK; will suppress the message which occurs at initialization
when two programs have declared items.
REQUIRE VERIFY!DATUMS; causes the compiler to generate three additional instructions
for each DATUM reference, to make sure (dynamically, at run time) that the type
of the item in the DATUM construct is the same as the compiler expected.
This is similar to (the unimplimented effect of) declaring all itemvars CHECKED.
It is planned that VERIFY!DATUMS will soon be a bit in the /A switch
and that the corresponding REQUIRE will disappear.
REQUIRE PROCESSES; insures that MAINPR, the main process, is initialized.
You need not specify this REQUIRE if you use APPLY or SPROUT, but if the
only use of processes is via INTSET then you must REQUIRE PROCESSES;.
In an explicitly numbered CASE statement the word ELSE can appear where
a bracketed case number is normally used. The statement following the ELSE is a
catch-all for any case number not mentioned, including anything which
would otherwise generate a CASE index error. For example,
CASE K OF BEGIN  J_3; ELSE J_4;  J_5 END
is another way of accomplishing
IF K=3 THEN J_3
ELSE IF K=5 THEN J_5
A CASE statement containing an ELSE case does not generate a call to the CSERR
runtime routine, and in addition the jump table usually contains only
maxcase - mincase +1 words (rather than maxcase +1).
To define two record classes, both of which contain RECORD!POINTER fields
refering to the other class, say
FORWARD RECORD!CLASS BAR (RECORD!POINTER (ANY!CLASS) Q2);
RECORD!CLASS FOO (RECORD!POINTER (BAR) Q1);
RECORD!CLASS BAR (RECORD!POINTER (FOO) Q2);
In general, declare one class to be FORWARD and list its RECORD!POINTER
fields as pointers to ANY!CLASS. This breaks the circularity and allows
maximum compile-time type checking.
.sec|Support for Tops-20|
The "Tenex" version of SAIL has been modified slightly to support
Tops-20. In general SAIL should work the same on Tops-20 as it does
on Tenex. In particular, both the compiler and compiled SAIL programs
can be moved between vanilla Tenex and Tops-20 sites and do the
equivalent things. This is possible because all tests for Tops-20
or Tenex-specific code are made at run-time. Code for both will
be present in all Tenex versions.
The one exception to this happy picture is with runtimes that are
named after JSYS's and are documented as simply doing that JSYS.
SAIL makes no attempt to insulate you from subtle differences in
the meaning of bits, or the fact that some of the JSYS's do not
exist in one of the systems. The most infamous of these problems
is the removal of the STDIR JSYS in Tops-20. That means that
SAIL programs using STDIR will blow up on Tops-20. (However
similar results may be obtained using the RCDIR and RCUSR routines,
which support the corresponding JSYS's in Tops-20.)
The following differences in behavior follow from the desire to
make SAIL on Tops-20 look like a "real" Tops-20 system:
The SAIL compiler's command scanner has been totally rewritten for
Tops-20. The old scanner will still be used for Tenex. The most
important feature of the new scanner is that it links properly to
the EXEC's commands COMPILE, EXECUTE, LOAD, and DEBUG. Note that
in order to make DEBUG work, /BAIL:17 is forced for all compilations
done with the EXEC commands. The DEBUG command then causes the
program to be loaded with BAIL and a breakpoint inserted at the start
of the program.
If you run SAIL explicitly, you will find that the
top-level parser follows the conventions of recent Tops-20 command
interpreters. It will expect a list of source file names, just as
the Tenex one did, but switches are on the same line, rather than
appearing as subcommands. The switch names have been lengthened
for readability, but we trust no one will have trouble matching
the new names with the manual. Arguments are now put after the
switch name and a colon, as usual. Instead of ^R and ^L to select
the output file names, the switches /BINARY, /LIST, and /CREF can
optionally take file names as arguments. To suppress creation of
an output file, specify /NOBINARY, /NOLIST, or /NOCREF. The
default is /BINARY/NOLIST, as with Tenex. Switches that took
octal numbers to specify sets of features now allow keyword
arguments. If you do not find the new form self-explanatory with
a bit of usage of ?, you can still use the old octal numbers.
On Tenex, certain files are expected to be on <SAIL> or <SUBSYS>.
The most important examples are <SAIL>T-6-SAISG8.SAV and
<SUBSYS>LOADER.SAV. On Tops-20, such files are looked for
on logical devices SAI: and SYS: respectively. It is assumed that
any site using SAIL will define SAI: as a system-wide logical
device, probably referring to PS:<SAIL>. By using SAI: instead
of <SAIL>, we allow users to redefine SAI:, for testing private
versions of the sharable segment, etc. It also means that it is
not necessary to have a separate <SAIL> directory on each mountable
structure, as would be needed if <SAIL> were used. Also, files
having the extension .SAV on Tenex are assumed to have .EXE on
Tops-20. E.g. the sharable segment is gotten from SAI:T-6-SAISG8.EXE,
and the loader from SYS:LINK.EXE.
On Tops-20, the default line-editors for TTY input use the RDTTY
JSYS. RDTTY will be used whether the user specifies the "Tenex-style"
or "Tops-10 style" line editor, since RDTTY is sort of a merging of
features of both. The RDTTY JSYS implements the standard Tops-20
editing characters, for which we refer you to DEC's Tops-20
In some cases, user programs may wish to determine which operating
system they are running on. At startup time, the SAIL runtimes
execute a special GETTAB UUO that is supposed to return a code
indicating the operating system. This GETTAB is implemented on
all known operating systems that use PDP-10 hardware, except for
the Stanford A.I. Lab monitor, and possible certain out-of-date
Tenex systems. It is even implemented on ITS! The value returned
by this UUO is put in a variable $OS, which may be declared
as an EXTERNAL INTEGER by your program so you can look at it.
The codes are returned in bits 18-23:
1 - Tops-10
2 - ITS
3 - Tenex
4 - Tops-20
5 - Stanford A.I. Lab monitor
The other bits in this word are reserved for indicating subversions
of these various operating systems.
For the Tenex SAIL system only, there is another EXTERNAL INTEGER
variable, $OSTYP. This is set to 0 for Tenex and 2 for anything
else. This is the variable actually used by the runtimes to
determine whether to execute Tenex or Tops-20 code.
.ss|New runtimes for Tops-20|
In an effort to support programming on Tops-20, the following
runtimes have been added. Note that they will result in errors
if executed on Tenex systems, since they call JSYS's that do not
exist on Tenex. No attempt is made to prevent these errors, on
the theory that some Tenex system might decide to implement one
of them. So we recommend that these be used only in programs
that test $OS or $OSTYP and provide special code for both
Tenex and Tops-20.
SUCCESS __ ACCES("DIRECTORY","PASSWORD",FLAGS,JOBNO)
This runtime calls the ACCES JSYS. It gives the specified job
access to the requested directory. Only the first argument
is required. Defaults are
Jobno\the current job.
Note that because of the implementation of defaults in SAIL, it
is not possible to specify a jobno of 0. However on Tops-20
job 0 is part of the operating system, so this does not seem to
be a serious limitation. (A SAIL program running as part of
job 0 would of course be able to specify the current job.)
'400#000#000#000\Equivalent to CONNECT monitor command
'200#000#000#000\Equivalent to ACCESS monitor command
'100#000#000#000\Equivalent to END-ACCESS monitor command
JOBNO is the number of the job, or -1 for current job.
(Requires privileges to set for other jobs).
On success, returns true; on failure, false, with TENEX
error code in !SKIP!.
DIRNUM __ RCDIR(@"DIRECTORY",@DIRNO,@FLAGS)
This procedure calls the RCDIR JSYS.
A 36-bit directory number is returned as the value of the call and
also put in the reference parameter DIRNO (useful for stepping
If recognition is not specifically disabled, the reference string
DIRECTORY is updated.
Input FLAGS are:
'10#000#000\Allow partial recognition
#'4#000#000\Step to next directory in group (Previous
directory number is in DIRNO, and the "wildcard" bit must be set)
#'2#000#000\Allow wildcards in "DIRECTORY"
#'1#000#000\Match given string exactly (suppresses recognition)
The flags returned by the monitor are put in the reference
On failure, zero is returned with !SKIP! set to:
1\string does not match
2\string is ambiguous
3\no more in this group (while stepping)
On error, zero is returned with !SKIP! set to the TENEX
error code. (Note the subtle difference here: an error involves
an illegal format for a directory name, or some other invalid
argument. Such an error generates a Tenex error code of the
usual sort. A directory name which is valid, but just does
not happen to exist on this system does not generate a Tenex
error code, but is simply called a failure.)
USRNUM __ RCUSR(@"USER",@USRNO,@FLAGS)
This translates a user name string to its corresponding
user number in the identical manner as RCDIR does for
directory strings. See RCDIR for details. Note that a
valid directory name always has < > around it, while a
user name does not.
SUCCESS __ RSCAN(FUNCTION,STRING)
This procedure calls the RSCAN JSYS.
This allows the program to replace the string
in the rescan buffer, set input to come from that
buffer, or determine how many characters are still to be read
from that buffer.
Both arguments are optional. If omitted, the values used are
The values of FUNCTION mean:
0\Set input to come from the rescan buffer.
1\Return number of characters still to be read
from that buffer.
The value returned by the call is the number of characters
in the buffer for FUNCTION 0, and number of characters
left for FUNCTION 1.
If STRING is non-null, FUNCTION is ignored, the string
is placed in the rescan buffer, and the value returned by
the call will be true on success.
On failure or error, zero is returned and !SKIP! is set to
the TENEX error code.
errmessage _ ERSTRING(ERRNO, FORKNO)
This is similar to ERSTR. It will get the error message
corresponding to the given error number on the given fork.
However instead of printing it on the terminal, as ERSTR does,
it returns it as a string. !SKIP! will be set if the error
number is illegal. In that case no guarantee is made about
what is returned. ERRNO and FORKNO are both optional. The
default values are the most recent error (-1) for ERRNO, and
the current fork for FORKNO.
.ss|Changes to existing procedures to support Tops-20|
CLOSF now takes an optional extra argument, CLOSF(chan,bits).
This argument is an integer. Its left half will be put in the
left half of AC1 in the CLOSF jsys. The most useful option
is bit '4#000#000#000. This specifies that the file is to be
aborted, rather than closed normally. If the file is on disk,
it will not appear (unless it has been closed and reopened).
If it is on tape, the 2 EOF's will not be written.
Note that the SAIL runtimes have a strange habit of closing and
reopenning files, to try to get read/write mode. If you want to
be able to abort creation of a file, you will have to open it
"RW". If you do, then CLOSF with bit '4000000000 set will cause
it not to show up on disk at all. If you had specified only "W",
SAIL would have created the file, closed it, and reopened it, in
order to get it in "RW" mode, which the runtimes prefer to use.
Then it would not be possible to abort creation of the file by
doing an abort close, since it has already been closed normally.
Note that CLOSF always sets bit '400#000#000#000 (do not release
the jfn). To release the jfn, you can do a RLJFN after the CLOSF.
If the extra argument is omitted, no bits other than '400#000#000#000
DELF now takes an extra optional argument, DELF(chan,bits). This
argument specifies bits to go in the left half of AC1 in the
DELF jsys. The most useful bit is '200#000#000#000. Setting this
bit will cause the file to be expunged as well as deleted. Note
that DELF always sets bit '400#000#000#000 (do not release
the jfn). To release the jfn, you can do a RLJFN after the DELF.
If the extra argument is omitted, no bits other than '400#000#000#000
IDTIM now takes an extra optional argument, IDTIM(datime,bits).
This argument specifies certain details of the format expected
by the jsys. The default value if this argument is omitted is 0.
Openfile uses a somewhat obscure algorithm to determine the setting
of bit '400#000#000#000 in the GTJFN jsys. This is the bit that
determines whether an existing version will be used if there is
one, or whether the next higher version number will be used.
If you specify "O" or "N", this bit is never used, since it is
meaningless in that case. Otherwise, the next higher version is
always used when "W" is set, and the highest existing version
when "W" is not set. (The one exception is that "W" with "A" will
still use an existing version, for obvious reasons.) This causes
problems for people who wish to open an existing file for "RW"
access, since "RW" will cause the bit to be set that moves one to
a new version. "ORW" will cause you to update an existing version,
but it will then give an error if there is no existing version.
In many cases you want to use the existing version if there is one,
else create a new file. This corresponds to setting no special
bits in the GTJFN at all.
To allow this useful case, we have added an option "U" (Update)
to OPENFILE. This is like "RW", except that it does not set the
bit causing the version number to be advanced. So it will use an
existing version if there is one, else create a new file.
RUNPRG will now supply a default extension if you leave it out
in the string. This is to allow people to write programs that
will work on both Tenex and Tops-20 without changing the file
name, since the normal extensions for executable files are
different on these two systems. The default extension supplied
will be .SAV for Tenex and .EXE for Tops-20.
.sec|New Tops-10 Features|
Current versions of SAIL now support standard DEC Tops-10 systems
more fully than in the past. Tops-10 is no longer treated as
a poor relation of Stanford WAITS. Descriptions of the features
specific to Tops-10 follow:
Any routine that accepts a file spec will allow SFD's as part of the
directory. Note that CVFIL has a problem in this regard, as it tries
to return the PPN in a reference integer parameter. If the file spec
includes SFD's, there is no obvious place to put them. I have taken
the quick and dirty route of returning a pointer to the path block in
place of a PPN. This strategy is reasonable because the monitor will
accept such a pointer in any context where it wants a PPN. Because
old programs may be expecting a real PPN, I do set !SKIP! in this
case. However I set it to +1 for SFD's, and -1 for a real error. Thus
new programs can be written to accept SFD's.
The project and programmer numbers can be omitted, as with PIP.
Thus [,,FOO] is the SFD FOO in your area. [,] is your area. But
 is 0, i.e. your default path.
.ss|Using SAIL from batch jobs|
SAIL knows whether your job is a batch job or not. (It looks at a
GETTAB bit set up by the batch job controller.) This is desirable
because of SAIL's error handling. It normally goes into an interactive
error handler that asks the user what to do. This is obviously
undesirable in a batch job, because the error handler is liable to read
characters intended as input or commands. Thus the interactive error
handler is not called for batch jobs. In the compiler, SAIL assumes
a line feed response to the error handler. I.e. the compilation
continues. This allows you to see the maximum number of errors. In a
user program, SAIL aborts the program on the first error.
.ss|USETI and USETO|
There has been a problem previously with using USETI and USETO in
buffered I/O modes. The problem is that the corresponding monitor
calls do not clear your I/O buffers. Thus you can do a USETI, but you
won't see any data from the new block until you exhaust the data in
you current buffer ring. (Of course there is no easy way to know how
much data you would have to skip over.) Similarly, after a USETO,
nothing will go out to the block you specified until the current block
Under the new version of SAIL, USETI and USETO both clear
the buffer ring. USETI clears the input ring. USETI and USETO both
force out anything in the output ring that hasn't gone out yet, if
any. Thus the next SAIL I/O call will use the block you specified.
Do USETI before input and USETO before output.
Note that at Stanford the monitor clears the buffers as described
above. Thus this change is mostly simulating what would happen at
Stanford. However there output is only forced by a USETO. Thus
I am not sure how one would switch from output to input at Stanford.
Here one would simply do USETI to the desired block, but at Stanford
that will not work.
USETI -1 is a special case. It gets
you to the end of the file under TOPS-10. (At Stanford another method
must be used for this.) If the normal buffer clearing were done,
USETI -1 would set your EOF variable. Since it is normally used as
a prelude to output, you do not want EOF set, probably. Thus USETI -1,
as a special case, does not clear the buffer ring.
Mag tape positioning commands (MTAPE) have a problem with buffers
that is exactly the same as the problem solved by the USETI and USETO
patches. Nothing has been done about this, however. Should you be
in the middle of reading a tape and do a rewind, the next read would
get the next buffer in the ring, rather than the first thing on the
tape. This can apparently be solved by closing and reopening the
device, however, so it did not seem to be worth troubling about.
(That would not work for USETI and USETO because closing the disk
also loses the file one is working with.)
.ss|Changes to buffering|
It was discovered that SAIL gave a different interpretation to the
byte count in the routines intended for ASCII files (INPUT, OUT,
NUMIN, etc.) and those intended for binary files (WORDIN, ARRYIN,
INOUT, etc.). This meant that one could not mix those routines.
E.g. one could not use both INPUT and WORDIN for the same file.
Trying to do so resulted in losing characters from the file. This
problem has been fixed, so it is now OK to use WORDIN with ASCII
files. It will return the next character from the file, as an
integer. Be warned that under TOPS-20 WORDIN will always return
the next 36 bits, even for ASCII files, so use of WORDIN to get
a character will make your program incompatible with TOPS-20 SAIL.
(There is another function under TOPS-20 to return the next
character, however, so the incompatibility is not serious.)
It should now be possible to do non-blocking I/O. The only
problem before was that you couldn't tell when an I/O operation had been
prematurely terminated due to no input being available. The
runtimes would think they had set EOF, but input failures in
non-blocking I/O don't set any error bits. Thus the runtimes
carefully set EOF to zero! I have fixed the
runtimes to guarantee that EOF will be non-zero when an I/O
function takes the error return. If all of the error bits are
zero (bits 760000), I set bit 010000, which previously the runtimes
always cleared. Thus bit 010000 will always be set for a non-blocking
failure. Probably this will be the only case when it is set, though
physical end of tape may also do it. Note that it is perfectly
possible for EOF to be set in this way and for you to still have
valid data. All it means is that the operation was terminated
prematurely (before your count was exhausted) because an input
failure occurred. You should save how much you got and try to
get the rest when the next input available interrupt comes along.
.ss|TMPIN and TMPOUT|
TMPIN now deletes nulls and line numbers from the tempcore file being
read. TMPOUT has been fixed so it no longer randomly sets bit 35 in
the words outputted.
The default value of /A is /0A. This gives you the linkage
appropriate for F40 for FORTRAN PROCEDURES, and does not use KI or
KL op codes. This is probably wrong for most places, but is retained
for compatibility with the manual. An individual user can reset this
by specifying /nA in a REQUIRE COMPILER!SWITCHES statement.
A site can change the default by patching the compiler. To do
this without recompiling, just look at XINI4+7. You will find
MOVEI TEMP,0; MOVEM TEMP,ASWITCH. You can change the MOVEI TEMP,0
to use whatever value you want as default. To change this in
the source, see the file SAIL.
Users should consider setting /26A. In addition to F10 linkage,
this causes KI-10 instructions to be used for converting between
integer and real numbers. However it causes rounding to be used,
while the default is trucation (FLOOR).
We are supplying a .COR file for COMPIL, to improve the handling
of SAIL by COMPILE-class commands. The following assumes that
these changes have been made.
You may use the switch /BAIL:nn It will pass /nnB to the compiler in
the appropriate place. It will also cause COMPIL to look for a .SM1
file rather than a .REL file. Thus your program will be recompiled if
it had not originally been compiled with BAIL. If you use /BAIL alone,
i.e. without an argument, /BAIL:17 will be assumed.
The DEBUG command now uses BAIL as its debugger by default. You need
not have any declarations or calls for BAIL in your program. My
magic linkage will automatically put a call to BAIL in your program
before the first executable instruction. When you get in this BAIL
breakpoint you can then set any other breakpoints you want. That
breakpoint that I set up is immediately removed, by the way. DEBUG
forces /BAIL:17, which in turn forces recompilation if your program
had not originally been compiled with /BAIL. To debug with DDT,
specify DEBUG/DDT. Note that the linkage to BAIL used by DEBUG causes
the descriptors to be loaded for all SAIL runtimes. Thus it
simulates the 20 bit in the /B switch. However, these descriptors
are loaded only when the DEBUG command is used, whereas /20b would
have them in your core image even during normal executions. Thus our
implementation saves some core.
Using /Y will cause your program to be loaded with the SAIL sharable
high segment (SAISG8). Note that /Y must appear before or after the
first program name in the command. Any later and it will not work.
/Y may be used with DEBUG as well as LOAD and EXECUTE. (It has no
effect for COMPILE, of course.)
.require "lies" source!file;