1
0
mirror of https://github.com/PDP-10/its.git synced 2026-03-06 03:19:18 +00:00

Added SRCCOM program and documentation.

SRCCOM is a source/binary comparison program.
SRCCOM also support source merges.
This commit is contained in:
Eric Swenson
2016-11-26 13:29:31 -08:00
committed by Lars Brinkhoff
parent f97c1e6ae8
commit 7ed0d1d8ce
5 changed files with 5114 additions and 0 deletions

View File

@@ -91,6 +91,7 @@ from scratch.
- CHTN, CFTP, Chaosnet TELNET and FTP support
- FIND, search for files.
- TTLOC, Advertises physical location of logged in users
- SRCCOM, Compares/merges source files, compares binary files
6. A brand new host table is built from the host table source and
installed into SYSBIN; HOSTS3 > using H3MAKE.

View File

@@ -424,6 +424,9 @@ respond "*" ":link sys2;ts chtn,sysbin;supdup bin\r"
respond "*" ":midas sys;ts ttloc_sysen1;ttloc\r"
expect ":KILL"
respond "*" ":midas sys;ts srccom_sysen2;srccom\r"
expect ":KILL"
respond "*" ":link kshack;good ram,.;ram ram\r"
respond "*" ":link kshack;ddt bin,.;@ ddt\r"
respond "*" $emulator_escape

519
doc/info/srccom.9 Normal file
View File

@@ -0,0 +1,519 @@
SRCCOM command strings look like <outfile>_<in1>,<in2>
Just <in1>,<in2> outputs to the terminal. <in2> defaults
to <in1> except that the 2nd filename defaults to ">".
SRCCOM switches are:
/digit number of consecutive matching lines it takes to tell
SRCCOM that the changes have stopped.
/@ Indirect. Take switches and file name from a previous
SRCCOM output file. Must go with output file or 1st input.
/A Archive. Concatenate the differences to the front of the
output file instead of overwriting it.
/B don't ignore Blank lines.
/C ignore Comments.
/D Disown self and keep running, logging out when finished.
/E follow each run of changed lines with the next (unchanged)
line, if any.
/F output a copy of file 2, with changed lines Flagged.
/I automatic merge mode.
/K ignore differences in Kase.
/L try to detect Labels (MIDAS style).
/M Manual Merge Mode.
/S ignore differences in Spacing.
/W say Which file (1 or 2) on each line of changes
/X eXecute commands from a file.
/Y try to detect labels, in a general way.
/! Force comparison even if the two filespecs specify the
same file.
/# Do binary compare of files.
/$ Do binary compare of executable file address space. This should
be the first thing (preceding filenames).
For further details, or for an introductory description,
do :INFO SRCCOM

File: SRCCOM Node: TOP Up: (DIR) Next: Generalities
SRCCOM is a program for comparing or merging similar ascii text files;
for example, two versions of the same program.
You can see the whole documentation file in INFO now by typing
a series of "N"'s.
* Menu:
* Gen: Generalities Overview of using SRCCOM
* Com: Commands Details of SRCCOM command lines
* Switch: Switches Commands contain switches which say what
sort of comparison and output is wanted
* Smart: Smart Telling SRCCOM to ignore certain kinds of changes
* Format: Format Requesting optional info in the output file
* Merge: Merge Merging two input files (manual or automatic)
* Binary: Binary Comparing binary file
* Archive: Archive Keeping an archival record of changes of one
file, from version to version.
* Xfile: Com Files Taking commands from a file
* Output: Output What everything in a SRCCOM output file means.

File: SRCCOM, Node: GENERALITIES, Up: Top, Previous: Top, Next: Commands
What you can do with SRCCOM:
Comparing produces a file listing the places where the two input files
differ, and what text each file has. There are two methods of merging:
one finds each place where the input files differ, prints out each
file's version, and asks the user to choose between them for the
merge file. The other produces automatically a file in which each
point of difference has the two alternate versions both present and
identified by which file they came from. Searching for *** will find
all of them. It is also made easy to compare two files and append
the differences to the front of a CMPARC file - this is good for
keeping a record of all changes in a program from version to version.
If SRCCOM is given a command via DDT, as in :SRCCOM <command> <cr>,
SRCCOM will execute the one command and then commit suicide,
if there was no error. If SRCCOM is run without a command, as
in SRCCOM^K, it will read commands from the terminal, prompting for
each one with a "*", until a command ending in "^C" is typed, or
SRCCOM is otherwise exited. A command ending in ^C is executed
normally, but when it is finished SRCCOM will commit suicide.
Whenever SRCCOM reads input from the terminal, <rubout> cancels one
character, and ^U cancels a whole line.
The easiest thing to do with SRCCOM is to compare two files and type the
differences on the terminal. Just give the command
:SRCCOM <file1>,<file2> <cr>
to do this.
At the bottom of the page on the terminal, SRCCOM will print
"--MORE--" (unless :TCTYP NOMORE or ^_M was used to turn off
this feature). At that point, the user has these alternatives:
space -- causes SRCCOM to continue typing.
^C -- causes SRCCOM to kill itself.
rubout -- causes the rest of the current set of differences
not to be printed.
other -- is like rubout, but remains in the input buffer
acting as normal input. It will eventually be
read by SRCCOM, or some other program.

File: SRCCOM, Node: COMMANDS, Up: Top, Previous: Generalities, Next: Switches
Details of Command Strings:
A command string for SRCCOM should have the format
<out>_<in1>,<in2>
or
<out>_<in>
where <out> is the output file spec,
and <in1> and <in2> are the input file specs.
Switches, preceded by slashes, may also be present in between names.
The short form is an abbreviation causing <in2> to be completely
defaulted.
The filename defaults are as follows:
the SNAME default is sticky, starting out as the user's MSNAME.
The FN1 default is also sticky, but at least one FN1 must be
specified in the command line, unless no device needs one,
and the output FN1 will default to the real FN1 of the first input
file, or that of the second in the case that the first is on a
non-directory device.
The device default starts as DSK: for the input files, and is sticky.
For the output file, it is DSK:.
The input fn2's default individually to ">".
The output fn2 defaults to "COMPAR" normally, but in merge
mode the default is ">", and when /A is specified the default
is "CMPARC". In /F mode, the default is "FLAGGD".
if the output device is to be TTY:, that can be specified
by omitting the output spec and the backarrow ("_"), thus:
<in1>,<in2> or <in>
Command String Examples:
_SYSENG;TECO < compares SYSENG;TECO < with SYSENG;TECO >,
and outputs to <MSNAME>;TECO COMPAR.
FOO;UGH CMP_SYSENG;TECO 500,^X501
compares SYSENG;TECO 500 with SYSENG;TECO 501,
and outputs to FOO;UGH CMP.
FOO;_BLETCH,MUMBLE compares FOO;BLETCH > with FOO;MUMBLE >,
and outputs to FOO;MUMBLE COMPAR.

File: SRCCOM, Node: SWITCHES, Up: Top, Previous: Commands, Next: Smart
Switches:
Switches are used to select a mode of operation (merging verses
comparison, for example), and to enable bells and whistles. They
are all explained in more detail in the following sections.
Each switch in this list is the name of a footnote that leads to
the node containg the description of the switch.
The switches are:
/digit number of consecutive matching lines it takes to tell
SRCCOM that the changes have stopped. *Note /digit: Smart
/@ Indirect. Take switches and file name from a previous
SRCCOM output file. *Note /@: Archive
/A Archive. Concatenate the differences to the front of the
output file instead of overwriting it.
*Note /A: Archive
/B don't ignore Blank lines. *Note /B: Smart
/C ignore Comments. *Note /C: Smart
/D Disown self and keep running, logging out when finished.
/E follow each run of changed lines with the next (unchanged)
line, if any. *Note /E: Format
/F output a copy of file 2, with changed lines Flagged.
*Note /F: Format
/I automatic merge mode. *Note /I: Merge
/K ignore differences in Kase. *Note /K: Smart
/L try to detect Labels (MIDAS style). *Note /L: Format
/M Manual Merge Mode. *Note /M: Merge
/S ignore differences in Spacing. *Note /S: Smart
/W say Which file on each line of changes *Note /W: Format
/X eXecute commands from a file. *Note /X: Com File
/Y try to detect labels, in a general way *Note /Y: Format
/# Binary compare (word by word) *Note /#: Binary
/$ Binary compare of executable files *Note /$: Binary
/! Force comparison even if the two filespecs specify the
same file. If you ask to compare FOO < and FOO > when there
is only one version of FOO, SRCCOM will normally see this
instantly and just say "NO DIFFERENCES ENCOUNTERED".
This switch would force a comparison. I am not sure
if it is ever necessary; I put it in to make sure nobody
would be screwed by the feature.

File: SRCCOM, Node: SMART, Up: Top, Previous: Switches, Next: FORMAT
More Intelligent Comparison -- /C:
/C causes differences in the text of comments to be ignored, but
only when they are isolated. In other words, if a whole run of
differences, preceded and followed by matching lines, consists of
nothing but changes in comments, it is ignored, but changes in
comments are not ignored when other, more serious, changes are nearby.
Unless /B was specified, lines containing nothing but comments will
be treated as blank lines and will be ignored if inserted or deleted.
Less Stringent Comparison -- /K, /S:
/K causes SRCCOM to ignore differences in case, so that "a" and
"A" will be considered identical. /S causes SRCCOM to ignore changes
in spacing (and tabs, as well), so that MOVE A and MOVE A will
be considered identical. Unlike comment changes, spacing and case
changes are ignored even if there is a more serious change nearby.
However, if the space- or case-changed lines are actually printed
because of more serious changes, the space or case changes will be
visible in the printout. The intent is to allow comparison of files
which have been thoroughly reformatted or converted from all upper
case to all lower case.
If /S has been specified, and /B has not been (blank lines are still
ignored), then a line containing nothing but spaces is considered
blank and its insertion or deletion is ignored. If /S and /C are
both specified, lines containing only spaces and comments are
ignored.
Less Intelligent Comparison -- /B:
/B causes SRCCOM not to ignore blank lines. More precisely,
SRCCOM normally considers all sequences of ^J's, ^L's and ^M's
equivalent. /B disables that. /B is automatically implied
by /I or /M, to prevent blank lines from being lost in the merging
process. /B is also useful if the difference file is to be read
by some other program that will process the input files with it.
"In Phase" Criterion -- /digit:
Normally, SRCCOM does not consider that a run of changes has
ended just because it finds a pair of lines that match. There
have to be three lines in a row from both files, matching. You
can change the number of lines in a row that have to match by
specifying /1 ... /9, /3 thus being the default. The time when
it may help to increase the number of consecutive matching lines
required is when SRCCOM is confused by changes occurring near
places where parts of one of the input files are repeated
identically, and SRCCOM is triggering on the wrong one.

File: SRCCOM, Node: FORMAT, Up: Top, Previous: Smart, Next: Merge
Formatting Options -- /L, /Y, /W, /E.
/W causes each line of differences to begin with "1)" or "2)", saying
which of the two input files the line came from. That information is
redundant, but may be a useful reminder inside a long run of differences
when the header line that gives the file name is off the screen.
/L or /Y tells SRCCOM to try to find the labels in the files, and for
each run of differences to give the most recent preceding label in
each of the files. This may make it easier to find the place in the
files where the change occurred. /L looks for assembler labels,
ending with a colon. /Y treats any unindented line as a label,
unless it starts with a ";". It is good for LISP files and is
likely to be good for other sorts.
/E tells SRCCOM that after each set of differing lines is printed
the first following line (which DOES match a line from the other
file) should be printed. This provides slightly more context for
the human reader. Old versions of SRCCOM had this mode only.
Flagging Mode -- /F:
/F says "flag all the lines in file 2 that don't match file 1".
Instead of a list of the differences between the two input files,
you get a copy of file 2, with marks to indicate which lines were
changed from file 1. The lines of file 2 are indented with tabs,
and at the front of each changed or inserted line there is a "|".
Places where lines form file 1 were deleted in file 2 are indicated
by four uparrows ("^") at the beginning of the next line of output.
/F automatically turns on /B, and prints nothing on the tty.
The default FN2 of the output file is "FLAGGD".

File: SRCCOM, Node: MERGE, Up: Top, Previous: Format, Next: Binary
Merge Mode -- /M and /I:
/M specifies Manual merge mode. In this mode, all the differences
are typed on the TTY, rather than output to the specified
output file; all the identical portions of the input files go
in the output file, as well as such versions of the different
portions as the user specifies. Thus the output file comes
to contain a merge of the two input files.
When differences are encountered in merge mode, they will be typed
out. The user will then be asked for input. If "1<cr>" is typed in,
the first input file's version will go in the merge.
"2<cr>" selects the second file's version. "12<cr>" or "21<cr>"
says that both versions should go in the output file
in the spec'd order.
"I<cr>" says that both versions should go in the merge file,
surrounded by easily identified headers and trailers, as follow:
*** MERGE LOSSAGE ***
*** FILE DSK:SYSENG;FOO 1 HAS:
<text from file 1>
*** FILE DSK:SYSENG;FOO 69 HAS:
<text from file 2>
*** END OF MERGE LOSSAGE ***
The other character that may go in the
input is "T", which will cause SRCCOM to copy from the TTY
to the output file up to an altmode. "T" may be accompanied by
"1", "2" or "I", and may appear more than once. For example,
"T1T2T" could be used to simulate "I", if the appropriate strings
are typed in afterward for the three "T"'s.
Also, if anyone cares, "C###" sets the number of columns printed out,
and "L###" sets the number of lines of differences printed out
(### should be replaced by 3 digits) - again, when merge mode is
asking for tty input.
Automatic Merge Mode -- /I:
/I selects automatic merge mode. This differs from manual merge mode
only in that instead of asking the user what to do, SRCCOM assumes
"I" as an answer, and doesn't bother to print out the differences.
See above under manual merge mode for what "I" does as an answer.
The result is that all the points of difference may be found by
searching through the merge file for "*** MERGE LOSSAGE ***";
The user may then edit the file to select the desired combination
of the two versions.

File: SRCCOM, Node: BINARY, Up: Top, Previous: Merge, Next: Archive
Binary Compares -- /# and /$:
It is possible to use SRCCOM for doing a "binary compare" of two files,
by using either the /# or /$ switch. These are almost the same,
except that /$ requires the files to be executable program files;
it will load each into a process and compare the process address spaces.
Thus, differences due to symbol tables, word blocking, file formats,
and other misc info are eliminated. /# on the other hand treats each
file as a bunch of 36-bit words and compares those one by one.
Binary compares do not search for a match when a difference is seen.
Thus, the word at location 100 of file 1 is always compared with
the word at location 100 of file 2, never with any other.
Notes: If used, /$ must be the FIRST thing in the command line, preceding
any file specs.
These switches interact very poorly with most others. In
particular, /I and /M will likely blow up SRCCOM.
/$ is unlikely to work for files saved with PDUMP or SSAVE
which have "holes" in their page maps.
These are considered bugs which eventually may be fixed.

File: SRCCOM, Node: ARCHIVE, Up: Top, Previous: Binary, Next: Com Files
Archiving -- /A:
/A selects archiving. In this mode, the output file is assumed to
be an archive of all changes to a single program from version to
version, and another page is added to the front of it describing
the new set of changes. It is ok for the file not to exist - it
will be created. Archiving makes a difference only when the file
already exists, in appending instead of overwriting.
When archiving, the default output FN2 is "CMPARC".
To avoid certain rare screws, the /A should always be mentioned with
the output file or the first input file - never with the second input
file.
Indirection -- /@:
The first input file, <in1>, may be specified "indirectly". That
means that the name of the output file from a previous comparison
is specified, that file is read to discover the names of the files
used to generate it, and what was then the second input file is used
as the first input file now. From the second line of the previous
comparison's output come the switches used for that comparison; they
are automatically turned on again (only the switches ABCELSW are hacked).
An indirect specification is indicated by the switch "/@".
Thus, after FOO 1 and FOO 2 are compared into FOO COMP2, specifying
"FOO COMP2/@" in a subsequent command will cause FOO 2 to be the
first input file. Not only the filenames but also the device and
sname are obtained from the indirect file, and they become the
defaults for the second input file. In the previous example,
if the second input file weren't specified, FOO > would be used.
All the names of the indirect file (the previous output file,
indirected through) default to the names of the output file. Thus,
if the command
FOOCMP 1_FOO 1
compares FOO 1 with FOO 30, and FOO 31 is generated, the command
FOOCMP >_/@
will compare FOO 30 with FOO 31 to give FOOCMP 2.
This mode works well with archiving; if FOO CMPARC is an archive
whose last comparison is FOO 29 against FOO 30,
FOO_/@/A
will compare FOO 30 and FOO >, appending the differences to the front
of FOO CMPARC. When using /@ and /A, if the second input file is
explicitly specified the /A must come no later in the command string
than the first input file; otherwise the defaulting of the indirect
file's FN2 will not work properly (this doesn't apply if either the
output file or the indirect file has its FN2 explicitly specified).

File: SRCCOM, Node: COM FILES, Up: Top, Previous: Archive, Next: Output
Command Files -- /X:
If a program consists of several files, it is convenient to be able
to compare all of them against old versions automatically. This
requires that SRCCOM be able to find out what all the files' names
are. That is done by putting a command to compare each of the files
in the desired way in a single command file. SRCCOM may then be
given a command to take successive commands from that command file,
which may be the same file as the main file of the program! That is
because the command file contains not bare SRCCOM commands, but
SRCCOM commands surrounded and identified by prefixes and suffixes,
and all text in the file not so identified (the program, for example)
is ignored.
The prefix that identifies a command is ";;SRCCOM COMMAND ;;".
The suffix that marks the end of it is ";;" (or <cr>).
Only the first page of the command file will be scanned.
If the prefix by itself doesn't suffice to
make the SRCCOM commands into comments for all other purposes, then
surely suitable characters in front of it will do so.
For example, if the MIDAS program FOO has files FOO, and BAR, the
following text could be put in the FOO file to make it into a SRCCOM
command file, telling SRCCOM to archive-compare both files:
;;SRCCOM COMMAND ;;FOO_/@/A
;;SRCCOM COMMAND ;;BAR_/@/A
The way to tell SRCCOM to read from a command file is to use the /X
switch, and specify the command file alone as an input file. For
example, just "FOO/X" would cause the commands in FOO to be executed.
Each command read from a command file is typed on the terminal. If
an error (such as an open failure) occurs, the reading of the command
file will terminate. It is an error to try to nest command files.
Filename defaulting works slightly differently while a command file
is in use. The command that specifies the command file also sets
up default devices and snames for the commands read from the file.
The output device and sname specifed with the command file (they may
be defaulted, of course, but will default to DSK: rather than TTY:)
become the defaults for output files while the command file is being
read. Those files will never default to TTY: either, even if no
arrow appears in the command. The device and sname that the command
file came from are the defaults for the first input file while the
command file is being read. The second input file still defaults to
the first, and the handling of FN1's and FN2's is unchanged. Also,
indirect files still default to the output files, and still override
the defaults for the input files.

File: SRCCOM, Node: OUTPUT, Up: Top, Previous: Com Files
SRCCOM's Output Format for Difference Files:
Each difference file begins with a blank line, followed
by some number of comments describing the files and switches
that controlled SRCCOM's operation - currently two. Then
comes another blank line. Programs should ignore all up to
the second blank line. An example is
;SOURCE COMPARE OF DSK:SYSENG;SRCCOM 40 AND DSK:SYSENG;SRCCOM 42
;OPTIONS ARE /3 /L
Each set of differences looks like this:
**** FILE DSK:SYSENG;SRCCOM 40, 2-41 (2009)
<text from file 1>
**** FILE DSK:SYSENG;SRCCOM 42, 2-47 (2315)
<text from file 2>
***************
In "2-41 (2009)", the "2" is the page number, the "41" is the
line number on the page, and the "2009" is the character position
in the file (0-origin, suitable for a TECO J command)
of the start of the first line of text.
When /L is set, header lines look like this, after the first label:
**** FILE DSK:SYSENG;SRCCOM 40, 2-41 (2009) AFTER FOOBAR:
where FOOBAR is the most recent label before the first following line.

Local Modes:
Mode:Text
End:

3804
src/sysen2/srccom.129 Normal file

File diff suppressed because it is too large Load Diff

787
src/syseng/rubout.3 Normal file
View File

@@ -0,0 +1,787 @@
.BEGIN RUBOUT ;Rubout processor routine for -*-MIDAS-*- programs.
.AUXIL ;Don't mention all my symbols in crefs of programs that use me.
;PRINT VERSION NUMBER
.TYO6 .IFNM1
.TYO 40
.TYO6 .IFNM2
PRINTX/ INCLUDED IN THIS ASSEMBLY.
/
;The main entry points of the RUBOUT package are INIT and READ.
;Each time you want to start reading one object, you should call INIT.
;After that, you call READ one or more times until you have enough
;input to act on. When you wish to start reading a "new" object -- and not
;allow the old one to be rubbed out any longer -- call INIT again.
;There are also subsidiary entry points called character service routines.
;These handle the display updating for one input character. The
;rubout-processing significance of all characters is determined by
;a routine which you supply, called DISPATCH, which examines the
;character and goes to the appropriate service routines.
;We supply a default definition of DISPATCH for you to use as
;all or part of your dispatch routine, and also as a guide to what
;dispatch routines should do.
;Calling Conventions:
;All subroutines herein are called by PUSHJ P,. They normally do NOT skip-return.
;Character service routines skip, with a return code in A,
;to tell READ to return with that code.
;Arguments are passed in ACs A,B,C and D. D must be C+1.
;Subroutines may alter A-D as documented with each routine.
;These switches may be set by the caller to request optional features.
;They may be defined local to the block RUBOUT.
IFNDEF $$MULTI,$$MULTI==0 ;Be efficient for CRs, BSs and TABs in the text.
;$$MULTI==1 makes the code larger but makes
;rubbing out these characters do less display.
IFNDEF $$FCI,$$FCI==1 ;Handle full char set input
;(distinguish Alpha from C-B).
;$$FCI==1 works for ASCII input streams too.
IFNDEF $$PROMPT,$$PROMPT==0 ;1 => call PROMPT to type a prompt string when needed.
IFNDEF $$HELP,$$HELP==0 ;1 => call HELP when the user types Top-H.
IFNDEF $$CTLBRK,$$CTLBRK==0 ;1 => the default dispatch routine
;makes all random control characters break.
IFNDEF $$CTLECH,$$CTLECH==1 ;1 => control chars that perform editing functions
;also echo, and must be erased. Doesn't include Rubout.
IFNDEF $$BRKINS,$$BRKINS==1 ;1 => break characters insert themselves first,
;in the default dispatch routine.
IFNDEF $$FFCLR,$$FFCLR==1 ;1 => ^L echoes as clear screen.
;0 => it echoes as uparrow-L, and we re-use the same lines.
;These exits must be defined by the user. They may be defined local
;to the block RUBOUT:
;INCHR reads a character, returning it in A.
;OUTCHR outputs the character in A. --MORE-- should, for
; most applications, be inhibited
; by having %TJMOR set in the channel used for output,
; if it is a terminal. If you want to do --MORE--,
; you should make sure that when the --MORE-- is proceeded
; its line is re-used so that the screen is as if
; the --MORE-- had never been.
;DISPLAY outputs the character in A in display mode (%TJDIS).
; It can be any stream that knows the meaning of ^P.
; If outputting to a terminal, set %TJMOR.
;PROMPT types a prompt-string. Called at appropriate times.
; Needed only if $$PROMPT==1. Not called when READ is
; entered; if you want that, do it yourself.
;HELP types a help-string when the user typed Top-H.
; Needed only if $$HELP==1. On displays,
; HELP may wish to wait for input to be available
; and then jump to REDISP without reading it.
;DISPATCH takes a character in A, and jumps off to the service
; routine for it, which may be one of those provided
; by RUBOUT (such as RUBOUT, INSERT, BREAK, etc.).
; A sample dispatch routine is called RB$DSP.
; You can use it, if it is right, by doing
; RUBOUT"DISPATCH==RUBOUT"RB$DSP
; after the .INSRT SYSENG;RUBOUT, or your dispatch
; can jump off to it, after handling a few cases specially.
;Service routines provided in this file include
;INSERT, BREAK, BRKCR, BRKINS, INSCR, QUOTE, QUIT, CANCEL, RUBOUT,
;IGNORE, REDISP, CLRLIN, CTLU.
;Each call to the rubout processor must give the address of an argument
;block, called the rubout processor block, in B.
;This block enables you to carry on several transactions with
;RUBOUT at the same time (or one inside another). If you wish,
;you can make your DISPATCH, OUTCHR, etc. entries jump through
;extra words at the end of the rubout processor block (words which
;normally are not used), and thus have different routines for
;each transaction.
;These are the words in the rubout processor argument block
;that are used by this package:
RB.==777777 ;Bit typeout mode prefix.
RB.POS==0 ;Vpos,,Hpos of 1st character in buffer.
RB.WID==1 ;Width of line on terminal.
RB.TYP==2 ;TTY characteristics.
;positive => printing terminal.
RB%FIL==2 ; bit 3.2 => not even that, just reading from a file.
;0 => glass teletype (character erase only, no move up).
;negative => full display.
RB%CID==1 ; bit 3.1 => can insert/delete as well.
RB%SAI==2 ; BIT 3.2 => SAIL mode on, ctl chars echo as one pos.
RB.BEG==3 ;B.P. to ILDB start of buffer.
RB.END==4 ;B.P. to ILDB 1st character after end buffer.
;Actually, you should leave space for two characters
;to be stored past RB.END.
RB.PTR==5 ;B.P. for storing into buffer.
RB.PRS==6 ;B.P. to last character parsed at next level up.
RB.STAT==7 ;Word of flags indicating temporary state of editing
RB.LEN==10 ;Number of words in the rubout block
;The caller should set up RB.BEG and RB.END, then call INIT which will
;initialize RB.TYP, RB.WID, and RB.POS and RB.PTR.
;The use of RB.PRS:
;Complicated break-conditions do not need to be expressed in the DISPATCH routine.
;If you use $$BRKINS==1 (Insert break characters in the buffer), then
;if the higher level parser decides it needs more characters it can just call
;READ again to get more. It could then re-parse the whole input string.
;To save time, it can put in RB.PRS the pointer to the last character that
;it actually parsed. On return from READ, if RB.PRS is unchanged, all
;characters up to that point were not changed (ie, not rubbed out)
;and the parser can continue from where it left off.
;Otherwise, RB.PRS will be zero, to indicate that the parser must
;rescan the entire string as returned by READ.
;If you like, you can keep the parser's fetch-pointer in RB.PRS,
;calling READ when it reaches RB.PTR, and noticing when it is zeroed.
DEFINE SYSCAL NAME,ARGS
.CALL [SETZ ? SIXBIT/NAME/ ? ARGS ((SETZ))]
TERMIN
;Get a character's graphic type. Takes character in A, returns type in A.
;Type codes are:
;0 - 1-POSITION CHARACTER.
;1 - ORDINARY CTL CHAR - USUALLY 2-POSITION, BUT 1-POSITION IN SAIL MODE.
;2 - BACKSPACE.
;3 - CR
;4 - LF
;5 - TAB.
;6 - SPECIAL CTL CHARACTER - 2-POSITION EVEN IN SAIL MODE.
;7 - ^G
CHRTYP:
IFN $$FCI,[
PUSH P,C ;If storing 9-bit bytes in the buffer,
LDB C,[300600,,RB.PTR(B)]
CAIN C,7
JRST CHRTY1
CAIGE A,177 ;assume that anything
JRST [ SETZ A, ;below 177 is either an ASCII graphic or a SAIL graphic.
JRST POPCJ]
CHRTY1: PUSHJ P,CHRASC ;Other things were echoed by converting them to ASCII.
POP P,C
];IFN $$FCI
CAIN A,177
JRST [ MOVEI A,6 ;Assume rubout is a 2-position character,
POPJ P,] ;even though we shouldn't be echoing them.
PUSH P,C
PUSH P,D
MOVE C,A
IDIVI C,6
CAIL A,40
TDZA A,A
LDB A,RRCHBP(D)
JRST POPDCJ
;TABLES USED BY CHRTYP THE ENTRY FOR EACH
;CHARACTER IS AN INDEX INTO RRFORT OR RRBACT.
RRCHBP: REPEAT 6,<360600-<6*.RPCNT>_12.>,,RRCHTB(C)
RRCHTB: .BYTE 6
6 ;^@
1 ;^A
1 ;^B
1 ;^C
1 ;^D
1 ;^E
1 ;^F
7 ;^G
2 ;^H
5 ;^I
4 ;^J
1 ;^K
1 ;^L
3 ;^M
1 ;^N
1 ;^O
1 ;^P
1 ;^Q
1 ;^R
1 ;^S
1 ;^T
1 ;^U
1 ;^V
1 ;^W
1 ;^X
1 ;^Y
1 ;^Z
0 ;ALTMODE, 1 POSITION.
1 ;^^
1 ;[ ;^]
1 ;^\
1 ;^_
.BYTE
;INIT empties the buffer and also tells RUBOUT about
;the characteristics of the output stream used
;for echoing of output during rubout processing.
;If it is a terminal, the capabilities of it are also remembered for use
;in chosing display strategies. If it is not a terminal, or if -1 is
;passed as the channel number, we assume that no echoing of input or rubbed
;out characters is desired.
;INIT takes the output channel in A and the rubout processing block address in B.
;It sets RB.TYP, RB.WID and RB.POS. It also initializes the buffer to be empty.
INIT: PUSHJ P,INITB
;Learn about a new output source, without marking the buffer empty.
INITO: PUSH P,C
PUSH P,D
SYSCAL CNSGET,[A ? MOVEM D ? MOVEM RB.WID(B) ;Read tty width into RB.WID.
REPEAT 3,[ ? MOVEM D]] ;Read TTYOPT of TTY into D.
JRST INIT1 ;Jump if not really a TTY.
SETZ C, ;First assume it's a glass TTY.
TLNN D,%TOMVU
TLNE D,%TOOVR ;If it isn't one, try a printing TTY.
MOVSI C,200000 ;RB.TYP should be positive.
TLNE D,%TOERS
MOVSI C,600000 ;For erasable display, it should be negative.
TLNE D,%TOCID
TLO C,RB%CID ;If we can insert/delete chars, remember that.
MOVEM C,RB.TYP(B)
SYSCAL RCPOS,[A ? MOVEM RB.POS(B)]
.LOSE %LSFIL
POPDCJ: POP P,D
POPCJ: POP P,C
CPOPJ: POPJ P,
INIT1: MOVSI C,200000+RB%FIL
MOVEM C,RB.TYP(B)
JRST POPDCJ
;Set up to do some rubout processing. Takes the Rubout processing block addr in B
;and marks the buffer empty.
INITB: PUSH P,C
MOVE C,RB.BEG(B)
MOVEM C,RB.PTR(B)
JRST POPCJ
IFN $$FCI,[
;Normalize a 9-bit character so we can do comparisons on it.
CHRNRM: ;ANDCMI A,%TXSFT+%TXSFL
TRNE A,%TXCTL+%TXMTA ;Convert lower case controls/metas to upper case,
TRNN A,100
JRST CHRNR1
TRC A,177
TRCN A,177 ;but don't turn Control-Rubout into Control-_.
TRZ A,40
CHRNR1: TRNE A,%TXTOP+%TXCTL+140 ;ASCII control chars must be turned into %TXCTL+char.
POPJ P,
CAIE A,33 ;except for altmode.
ADDI A,%TXCTL+100
POPJ P,
;Convert a 9-bit character in A to ASCII, if necessary for insertion in the buffer
;(that is, if the buffer is 7-bit).
CHRASI: PUSH P,C
LDB C,[300600,,RB.PTR(B)]
CAIE C,7
JRST POPCJ
POP P,C
;Convert a 9-bit character in A to ASCII.
CHRASC: ANDI A,%TXCTL+%TXASC
TRZN A,%TXCTL
POPJ P,
CAIN A,177
POPJ P,
CAIE A,40
CAIL A,140
SUBI A,40
ANDCMI A,100
POPJ P,
];IFN $$FCI
SUBTTL Main rubout processing routine
;The main function of the rubout package is READ.
;It takes the pointer to a rubout processor argument block in B.
;It reads characters and processes them until either a break character
;is read, the buffer becomes full, or an attempt is made to rub out
;past the beginning of the buffer.
;On return, A contains -1 if the buffer is full; -2, in case of
;over-rubout; otherwise, it contains the break character that caused
;the exit. No other accumulator is clobbered.
READ: PUSH P,C
PUSH P,D
RBLOOP: PUSHJ P,INCHR ;Read next character into A.
SKIPLE RB.TYP(B) ;After a CR coming form a file, flush the LF.
CAIE A,^M
JRST RBLOO2
PUSHJ P,INCHR
MOVEI A,^M
RBLOO2:
IFN $$HELP,[
CAIN A,%TXTOP+"H ;If user supplied a HELP routine, call it for Top-H.
JRST [ PUSHJ P,HELP ;Must check for Top-H before the CHRNRM, which makes it H.
JRST RBLOOP]
];IFN $$HELP
IFN $$FCI,PUSHJ P,CHRNRM
PUSHJ P,DISPATCH ;Let user decide what to do with the character.
JRST RBLOO1
JRST POPDCJ ;His routine skips => it wants to exit.
;It should have put the return code in A.
RBLOO1: MOVE C,RB.PTR(B) ;User didn't ask to return,
CAME C,RB.END(B) ;but return anyway if the buffer has become full.
JRST RBLOOP
MOVNI A,1
JRST POPDCJ
IFE $$FCI,[
;Here is the default RB.DSP character dispatch routine which we provide.
RB$DSP: CAIN A,177
JRST RUBOUT
CAIN A,^U
JRST CTLU
CAIN A,^D
JRST CANCEL
CAIN A,^G
JRST QUIT
CAIN A,^M
IFN $$BRKINS, JRST BRKCR
IFE $$BRKINS, JRST BREAK
CAIE A,^_
CAIN A,^C
IFN $$BRKINS, JRST BRKINS
IFE $$BRKINS, JRST BREAK
CAIN A,^Q
JRST QUOTE
CAIN A,^L
JRST REDISP
IFN $$CTLBRK,[
CAIGE A,40
IFN $$BRKINS, JRST BRKINS
IFE $$BRKINS, JRST BREAK
]
JRST INSECH
];IFE $$FCI
IFN $$FCI,[
;Default dispatch for 9-bit characters.
RB$DSP: CAIN A,177
JRST RUBOUT
CAIN A,%TXCTL+"U
JRST CTLU
CAIN A,%TXCTL+"D
JRST CANCEL
CAIN A,%TXCTL+"G
JRST QUIT
CAIN A,%TXCTL+"M
IFN $$BRKINS, JRST BRKCR
IFE $$BRKINS, JRST BREAK
CAIE A,%TXCTL+"_
CAIN A,%TXCTL+"C
IFN $$BRKINS, JRST BRKINS
IFE $$BRKINS, JRST BREAK
CAIN A,%TXCTL+"Q
JRST QUOTE
CAIN A,%TXCTL+"L
JRST REDISP
IFN $$CTLBRK,[
TRNE A,%TXCTL+%TXMTA
IFN $$BRKINS, JRST BRKINS
IFE $$BRKINS, JRST BREAK
]
JRST INSECH
];IFN $$FCI
SUBTTL Various action routines for input characters.
;Dispatch here for a character that quotes the next one (eg, ^Q).
;The quoting character goes in the buffer so that it can quote
;the next character at the next level of parsing.
QUOTE:
IFE $$CTLECH,PUSHJ P,OUTECH
IFN $$FCI,PUSHJ P,CHRASI
IDPB A,RB.PTR(B) ;Store the ^Q.
PUSHJ P,INCHR ;Read the quoted character.
;Insert a character, echoing it if it was a non-echoed control-character.
INSECH: CAIE A,177
IFN $$CTLECH,CAIA
.ELSE [
IFN $$FCI, TRNE A,%TXCTL
.ELSE CAIGE A,40
]
PUSHJ P,OUTECH
JRST INSERT
;Insert char followed by a LF. For CR, when it isn't a break.
INSCR: MOVEI A,^M
IDPB A,RB.PTR(B)
MOVEI A,^J
MOVE C,RB.END(B) ;Note that the buffer may become full
CAMN C,RB.PTR(B) ;after just the CR.
JRST [ MOVEI B,1
AOS (P)
POPJ P,]
;Dispatch here for an ordinary character, that should just be inserted,
;and has already been echoed.
INSERT:
IFN $$FCI,PUSHJ P,CHRASI ;If char is 9-bit and buffer is 7-bit, convert.
IDPB A,RB.PTR(B)
POPJ P,
;Insert char and a LF, then break.
BRKCR: PUSH P,A
MOVEI A,^M
IDPB A,RB.PTR(B)
MOVEI A,^J
IDPB A,RB.PTR(B)
POP P,A
JRST BREAK
;Dispatch here for a break character. We store it and a null,
;then signal READ to return the break character.
;If the character is a control char which didn't echo, we echo it.
BRKINS: PUSH P,A
PUSHJ P,INSECH ;Insert, maybe echoing or converting 9-bit to 7-bit.
POP P,A
;Dispatch here for a break character that should not be stored
;in the buffer.
BREAK: SETZ C, ;Store a 0 to make it ASCIZ,
MOVE D,RB.PTR(B) ;but don't make RB.PTR point after it.
IDPB C,D
AOS (P)
POPJ P,
;Here to "quit". Acts like too many rubouts, flushing any amount of input.
QUIT: MOVE C,RB.BEG(B) ;Mark the buffer empty.
MOVEM C,RB.PTR(B)
SETZM RB.PRS(B) ;Tell user his old parsing work is no use.
MOVNI A,2 ;Make READ return -2.
AOS (P)
POPJ P,
;Empty the entire buffer. The same as exactly enough rubouts
;but types out differently.
CANCEL: SETZM RB.PRS(B) ;Tell user his old parsing is no longer any use.
SKIPGE D,RB.TYP(B) ;On a display, clear the display window first,
JRST CANCE1 ;while we can still tell how long it is.
MOVE C,RB.BEG(B) ;Empty the buffer.
MOVEM C,RB.PTR(B)
TLNE D,RB%FIL
POPJ P, ;If reading from file, don't type anything.
MOVEI A,40 ;Printing terminal, type "XXX?" and CRLF.
PUSHJ P,OUTCHR
MOVEI A,"X
PUSHJ P,OUTCHR
PUSHJ P,OUTCHR
PUSHJ P,OUTCHR
MOVEI A,"?
PUSHJ P,OUTCHR
JRST WCLEAR ;CRLF, and prompt if appropriate.
CANCE1: PUSHJ P,WCLEAR ;Clear out the display window.
MOVE C,RB.BEG(B) ;Empty the buffer.
MOVEM C,RB.PTR(B)
POPJ P,
CTLU:
IFN $$CTLECH,PUSHJ P,IGNORE ; If the ^U echoed, must erase it too.
;Delete one line backwards. Deletes at least one character.
CLRLIN: PUSHJ P,RUBOUT ; DELETE AT LEAST ONE CHAR, STOPPING ONLY AT LINE BEG.
CAIA
JRST POPJ1 ; Beginning of buffer => propagate the skip.
MOVE C,RB.PTR(B)
CAMN C,RB.BEG(B) ; Else stop rubbing if now at start of line.
POPJ P,
LDB A,C
CAIE A,^J
JRST CLRLIN
POPJ P,
;Dispatch here to ignore a character. On a display, erase it if it echoed.
IGNORE:
IFE $$CTLECH,[
IFE $$FCI,CAIGE A,40
IFN $$FCI,TRNE A,%TXCTL
POPJ P,
]
SKIPL RB.TYP(B)
POPJ P,
JRST RUBOU1
SUBTTL Various display operations
;Here to retype the entire contents of the buffer.
REDISP:
IFN $$FFCLR,IFN $$PROMPT, PUSHJ P,PROMPT ;If ^L clears the screen, just re-prompt.
IFE $$FFCLR,PUSHJ P,WCLEAR ;Else, erase all the lines that are in use
;and put cursor after the previous prompt.
MOVE C,RB.BEG(B)
REDIS1: CAMN C,RB.PTR(B)
POPJ P,
ILDB A,C
PUSHJ P,OUTECH
JRST REDIS1
;Clear the display window. On printing terminal, just CRLF and prompt.
WCLEAR: SKIPL RB.TYP(B)
JRST [ PUSHJ P,CRLF
IFN $$PROMPT, PUSHJ P,PROMPT
POPJ P,]
;Here for clearing buffer on a display.
IFN $$MULTI,[
PUSHJ P,RESYNCH ;Compute vpos,,hpos of end of buffer in D.
PUSH P,D
PUSHJ P,CRSHOME ;Move cursor to beginning of rubout processor area.
MOVEI A,"L
PUSHJ P,DISP2 ;Clear rest of that line.
HLRZS (P)
POP P,D ;Then CRLF down thru the lines that were occupied.
HLRZ C,RB.POS(B)
CAMN C,D
POPJ P,
WCLEA1: CAMN C,D
JRST CRSHOM ;Then move cursor to beginning of area again.
MOVEI A,"D
PUSHJ P,DISP2 ;Move down one line
MOVEI A,"L
PUSHJ P,DISP2 ;and clear the one we get to.
AOJA C,WCLEA1
];IFN $$MULTI
.ELSE [
PUSHJ P,CRSHOME ;Move cursor to beginning of rubout processor area.
MOVEI A,"L
JRST DISP2 ;Clear rest of that line.
];IFN $$MULTI
;On a display only, move the cursor to the beginning of the window area.
CRSHOM: MOVEI A,"V
PUSHJ P,DISP2
HLRZ A,RB.POS(B)
ADDI A,10
PUSHJ P,DISPLAY
MOVEI A,"H
PUSHJ P,DISP2
HRRZ A,RB.POS(B)
ADDI A,10
PUSHJ P,DISPLAY
POPJ P,
;Output a ^P followed by the character in A, using DISPLAY supplied by user.
DISP2:
PUSH P,A
MOVEI A,^P
PUSHJ P,DISPLAY
POP P,A
PUSHJ P,DISPLAY
POPJ P,
;Output the character in A as it should be echoed, preserving A.
OUTECH: PUSH P,A
PUSHJ P,OUTEC1
POP P,A
POPJ P,
OUTEC1: CAIGE A,177
JRST OUTPUT
IFN $$FCI,PUSHJ P,CHRASC
CAIN A,177
JRST OUTRUB
CAIN A,^M
JRST CRLF
JRST OUTPUT
OUTRUB: MOVEI A,"^
PUSHJ P,OUTPUT
MOVEI A,"?
JRST OUTPUT
;Type a CRLF via the output 1 character instruction.
CRLF: MOVEI A,^M
PUSHJ P,OUTCHR
MOVEI A,^J
PUSHJ P,OUTCHR
POPJ P,
SUBTTL The operation of rubbing out one character
;This is the character action routine for Rubout, which we assume
;was not echoed.
RUBOUT: PUSHJ P,DBPPTR ;Back up RB.PTR one byte.
JRST QUIT ;It was at beginning => return -2 ("overrrubout") from READ.
MOVE A,C
ILDB A,A ;What character are we rubbing out?
LDB D,C
CAIN A,^J ;If rubbing out a ^J which follows a ^M,
CAIE D,^M
JRST RUBOU3
CAMN C,RB.BEG(B)
JRST RUBOU3
SKIPG RB.TYP(B) ;Rub out the ^M too.
JRST RUBOU4 ;On display, just call rubout again after this call is done.
PUSHJ P,DBPPTR ;On printing TTY, type a CRLF and flush both characters.
JRST CRLF
RUBOU4: PUSH P,[RUBOUT]
RUBOU3: SKIPLE RB.TYP(B) ;Printing terminal?
OUTPUT: JRST OUTCHR ;on printing terminal, just echo the character.
;Here to rub the character in A out on a display screen.
RUBOU1: PUSHJ P,CHRTYP
XCT RUBTAB(A)
RUBOU2: MOVEI A,"X
JRST DISP2
RUBTAB: JFCL ;Normal character; fall through to RUBOU2 and send ^PX.
JRST RUBCTL ;Control char may be one position or two.
JRST RUBBS ;Backspace rubs out by moving forward.
JRST RUBCR ;CR rubs out requiring redisplay.
JRST RUBLF ;LF is simple, but maybe also rub preceding CR
JRST RUBHT ;Tab is hard to rub out.
PUSHJ P,RUBOU2 ;Rubout and ^@ take two positions always.
JRST OUTPUT ;^G takes no positions; echo it to rub it out.
RUBCTL: MOVSI D,RB%SAI ;Control chars are one pos or two according to sail mode.
TDNN D,RB.TYP(B)
PUSHJ P,RUBOU2
JRST RUBOU2
RUBBS: RUBCR: RUBHT:
IFN $$MULTI,[
PUSHJ P,RESYNCH ;Compute current vpos,,hpos in D.
MOVEI A,"H
PUSHJ P,DISP2 ;Move cursor to that position.
MOVEI A,10(D)
PUSHJ P,DISPLAY
POPJ P,
]
.ELSE JRST REDISP
RUBLF: MOVEI A,"U ;Rubbing out LF: move up one line.
JRST DISP2
;Decrement RB.PTR by one byte, updating RB.PRS as necessary.
;Skips unless RB.PTR was pointing at the beginning of the buffer,
;in which case it was not changed. RB.PTR is left in C, either way.
DBPPTR: MOVE C,RB.PTR(B)
CAMN C,RB.BEG(B)
POPJ P,
CAMN C,RB.PRS(B)
SETZM RB.PRS(B)
PUSHJ P,DBPC
MOVEM C,RB.PTR(B)
POPJ1: AOS (P)
POPJ P,
;Decrement the byte pointer in C. If $$FCI, we handle 7-bit and 9-bit byte pointers.
;Otherwise, we handle only 7-bit byte pointers.
DBPC:
IFN $$FCI,[
LDB D,[300600,,C]
CAIE D,7
JRST DBP9
]
ADD C,[070000,,]
SKIPGE C
SUB C,[430000,,1] ;Decrement byte pointer.
POPJ P,
IFN $$FCI,[
DBP9: ADD C,[110000,,]
SKIPGE C
SUB C,[440000,,1]
POPJ P,
]
IFN $$MULTI,[
;Compute current Vpos,,hpos at RB.PTR and return it in D,
;by assuming that RB.POS is valid for beginning of buffer
;and scanning through the buffer, seeing what the characters do to the cursor.
;Clobbers A and C.
RESYNCH:
HRRZI C,1(P) ;C points to 3-word block on stack.
MOVE D,RB.BEG(B) ;(C) holds b.p. to scan with.
PUSH P,D
HRRZ D,RB.POS(B) ;1(C) holds HPOS.
PUSH P,D
HLRZ D,RB.POS(B) ;2(C) holds VPOS.
PUSH P,D
RBCRS1: MOVE D,(C)
CAMN D,RB.PTR(B) ;Scan till reach end of occupied region.
JRST RBCRS2
PUSHJ P,RBFORW
JRST RBCRS1
RBCRS2: HRRZ D,1(C) ;D gets the new Vpos,,Hpos.
HRL D,2(C) ;Flush the block of temporary variables.
SUB P,[3,,3]
POPJ P,
;Cursor position calculating routines.
;Move pointer in (C) one character forward, adjusting VPOS in 2(C)
;and HPOS in 1(C) for the character moved over.
RBFORW: ILDB A,(C)
PUSHJ P,CHRTYP ;Get the character's "type code".
XCT RRFORT(A) ;DISPATCH ON TYPE OF CHAR.
RRFOR1: AOS A,1(C)
RRFOR3: CAMGE A,RB.WID(B) ;HAVE WE MOVED PAST RIGHT MARGIN?
POPJ P,
CAMN A,RB.WID(B) ;CHECK FOR JUST REACHING THE RIGHT MARGIN.
JRST RRCNTN
RRCNT1: SUB A,RB.WID(B)
MOVEM A,1(C)
RRFORV: AOS 2(C)
POPJ P,
RRCNTN: MOVE D,(C)
ILDB D,D
CAIN D,^M
POPJ P,
JRST RRCNT1
RRFORT: AOSA A,1(C) ;ORDINARY CHAR, MOVE FWD 1 POS.
JRST RRFORC ;NON-FORMATTING CONTROLS.
JRST RRFORH ;MOVE FWD OVER ^H - CHECK ^HPRINT FLAG.
JRST RRFWCR ;^M, SPECIAL.
JRST RRFORL ;^J, DOWN 1 LINE.
JRST RRFOTB ;^I
JRST RRFOR2 ;2-POS CTL CHRS NOT AFFECTED BY FS SAIL (Rubout and ^@)
POPJ P, ;^G takes up no space.
RRFOTB: MOVE D,1(C)
MOVEI A,10(D)
ANDCMI A,7 ;A HAS NEXT TAB STOP'S POSITION.
CAMLE A,RB.WID(B) ;BUT IF THAT'S OFF THE SCREEN, TAB STOP IS RIGHT MARGIN,
CAMN D,RB.WID(B) ;UNLESS WE'RE ALREADY AT THE MARGIN, IN WHICH CASE
CAIA ;WE CAN TAB 8 SPACES INTO NEXT LINE VIA CONTINUATION.
MOVE A,RB.WID(B)
MOVEM A,1(C)
JRST RRFOR3
RRFORL: AOS 2(C)
POPJ P,
RRFWCR: SETZM 1(C)
POPJ P,
RRFORH: SKIPN 1(C)
JRST RRFOR2
SOS 1(C)
POPJ P,
;NON-FORMATTING CONTROLS, CHECK FS SAIL FLAG.
RRFORC: MOVE D,RB.TYP(B)
TLNN D,RB%SAI ;SAIL mode => it is a one-position character.
RRFOR2: AOS 1(C) ;Else treat as 2-pos ctl char.
JRST RRFOR1
] ;IFN $$MULTI
.END RUBOUT