This change file is for the TOPS-20 version of MetaFont.

Copyright (C) 1985 by Tomas Rokicki.  All rights are reserved.
Based on the TeX change file by David Fuchs.

Problem areas are marked with `fix me'.  Please report any bugs to
ROKICKI@SU-SCORE.

This file makes an INIMF.  To get a production MF, change the INIT and
TINI macros to @{ and @}, respectively, and re-TANGLE.  You may wish to do
the same for DEBUG and GUBED, and perhaps even STAT and TATS.


@x [0] Tell WEAVE to print only the changes:
  \def\?##1]{\hbox to 1in{\hfil##1.\ }}
  }
@y
  \def\?##1]{\hbox{Changes to \hbox to 1em{\hfil##1}.\ }}
  }
\let\maybe=\iffalse
\def\ttw{{\mc TOPS-20}}
\def\title{\MF\ changes for \ttw}
@z

@x [1] TOPS-20's banner:
@d banner=='This is METAFONT, Version 1.5' {printed when \MF\ starts}
@y

Comments on the \ttw\ implementation (based on David Fuchs' \tex\ change
file) of \MF\ follow:
@^Fuchs, David Raymond@>

Firstly, input files are considered to have pages, which are delimited with
form-feeds.  All error messages give the page number, along with the line
number on that page (if the page number is one, then the page number is
not printed, to avoid confusing people who do not use form-feeds).

Secondly, the area \.{mfbases:} is where \MF\ looks for it's \.{.pool} file.

Thirdly, \MF\ reads your \ttw\ command line that invoked it, and uses
everything after the command name as input.  For instance, you might say
`\.{mf\ foo}' to \ttw, and \MF\ will act the same as if you had
started it and then said `\.{foo}' to the initial \.{**} prompt.  You can
also say more complicated things,
like `\.{mf\ \&myfmt\ \\input bar}'.
Moreover, \MF\ tries to remember your entire incantation,
so that the next time you can simply say `\.{mf}', and \MF\ will pretend
you had repeated the stuff you said last time.  Note that if you give a
new, nonempty command line, it will be paid attention to, and the new
command line will then be remembered for next time.  If you want to get a
\MF\ that ignores any previous command line, say `\.{mf\ \\}' to \ttw,
and \MF\ will totally forget any old command line you gave it.
The command line memory is implemented with logical names
$\hbox{mfmem}x,\,x\in\{0,1,\ldots\}$,
so when you log out, the memory fades.

Fourthly, the interrupt feature is activated by typing control-g.

Fifthly, \MF\ will suggest a spooling command when it is done with
your job.  It will `sti' the line `\.{mfspool:\ foo.gf.1}' if it wrote
said dvi file; thus users can send their output by default to their
favorite device by appropriatly defining \.{mfspool:}.

Sixthly, \MF\ allows you to use logical names where file names
normally go; for instance you can say `\.{mf foo:}' when you've
already done a \.{define foo: bar} or \.{def foo: adisk:<me>x.mf}
or even \.{def foo: bar:} followed by \.{def bar: <you>x}.  Don't
expect \.{mf foo:.x} to work, though.

Lastly, \MF\ tries to be smart about swapping to your favorite editor
when you reply `e' to one of its error messages.  Unfortunatly, there is
no good system interface for doing this sort of thing, so the technique is
both fallible, and special code needs to be added for each editor supported
(currently, emacs, edit, tvedit and zed).  \MF\ looks at the
logical name \.{editor:} and tries to figure out which one you've got so that it
can \.{sti} the appropriate commands.

@d banner=='This is METAFONT, Tops-20 Version 1.5'
@d max_TOPS20_file_name_length = 300 {That ought to be big enough}
@z

@x [1] Switches for debugging and statistics:
@d debug==@{ {change this to `$\\{debug}\equiv\null$' when debugging}
@d gubed==@t@>@} {change this to `$\\{gubed}\equiv\null$' when debugging}
@f debug==begin
@f gubed==end
@#
@d stat==@{ {change this to `$\\{stat}\equiv\null$' when gathering
  usage statistics}
@d tats==@t@>@} {change this to `$\\{tats}\equiv\null$' when gathering
  usage statistics}
@f stat==begin
@f tats==end
@y
@d debug==
@d gubed==
@f debug==begin
@f gubed==end
@#
@d stat==
@d tats==
@f stat==begin
@f tats==end
@z

@x [1] The INIMF switch:
@d init== {change this to `$\\{init}\equiv\.{@@\{}$' in the production version}
@d tini== {change this to `$\\{tini}\equiv\.{@@\}}$' in the production version}
@y
@d init==
@d tini==
@z

@x [1] Compiler directives:
@{@&$C-,A+,D-@} {no range check, catch arithmetic overflow, no debug overhead}
@!debug @{@&$C+,D+@}@+ gubed {but turn everything on when debugging}
@y The space after the B is due to a compiler bug.
@{@&$C-,A+,D-,P:510000@&B@= @>@}
  {no range check, catch arithmetic overflow, no debug overhead,
  move the code up to make room for the global data in production}
@!debug @{@&$C+,D+,X+,P:400000@&B@= @>@}@+ gubed
  {we turn everything on when debugging and we use extended addressing
  (otherwise there's not enough room with all the debug stuff)}
@z

@x [1] Compile-time constants:
@!mem_max=30000; {greatest index in \MF's internal |mem| array;
  must be strictly less than |max_halfword|;
  must be equal to |mem_top| in \.{INIMF}, otherwise |>=mem_top|}
@!max_internal=100; {maximum number of internal quantities}
@!buf_size=500; {maximum number of characters simultaneously present in
  current lines of open files; must not exceed |max_halfword|}
@!error_line=72; {width of context lines on terminal error messages}
@!half_error_line=42; {width of first lines of contexts in terminal
  error messages; should be between 30 and |error_line-15|}
@!max_print_line=79; {width of longest text lines output; should be at least 60}
@!screen_width=768; {number of pixels in each row of screen display}
@!screen_depth=1024; {number of pixels in each column of screen display}
@!stack_size=30; {maximum number of simultaneous input sources}
@!max_strings=2000; {maximum number of strings; must not exceed |max_halfword|}
@!string_vacancies=8000; {the minimum number of characters that should be
  available for the user's identifier names and strings,
  after \MF's own error messages are stored}
@!pool_size=32000; {maximum number of characters in strings, including all
  error messages and help texts, and the names of all identifiers;
  must exceed |string_vacancies| by the total
  length of \MF's own strings, which is currently about 22000}
@!move_size=5000; {space for storing moves in a single octant}
@!max_wiggle=300; {number of autorounded points per cycle}
@!gf_buf_size=800; {size of the output buffer, must be a multiple of 8}
@!file_name_size=40; {file names shouldn't be longer than this}
@!pool_name='MFbases:MF.POOL                         ';
  {string of length |file_name_size|; tells where the string pool appears}
@.MFbases@>
@!path_size=300; {maximum number of knots between breakpoints of a path}
@!bistack_size=785; {size of stack for bisection algorithms;
  should probably be left at this value}
@!header_size=100; {maximum number of \.{TFM} header words, times~4}
@!lig_table_size=300; {maximum number of ligature/kern steps}
@!max_font_dimen=50; {maximum number of \&{fontdimen} parameters}
@y
@!mem_max=58000; {greatest index in \MF's internal |mem| array;
  must be strictly less than |max_halfword|;
  must be equal to |mem_top| in \.{INIMF}, otherwise |>=mem_top|}
@!max_internal=100; {maximum number of internal quantities}
@!buf_size=500; {maximum number of characters simultaneously present in
  current lines of open files; must not exceed |max_halfword|}
@!error_line=72; {width of context lines on terminal error messages}
@!half_error_line=42; {width of first lines of contexts in terminal
  error messages; should be between 30 and |error_line-15|}
@!max_print_line=79; {width of longest text lines output; should be at least 60}
@!screen_width=768; {number of pixels in each row of screen display}
@!screen_depth=1024; {number of pixels in each column of screen display}
@!stack_size=30; {maximum number of simultaneous input sources}
@!max_strings=2000; {maximum number of strings; must not exceed |max_halfword|}
@!string_vacancies=8000; {the minimum number of characters that should be
  available for the user's identifier names and strings,
  after \MF's own error messages are stored}
@!pool_size=32000; {maximum number of characters in strings, including all
  error messages and help texts, and the names of all identifiers;
  must exceed |string_vacancies| by the total
  length of \MF's own strings, which is currently about 22000}
@!move_size=5000; {space for storing moves in a single octant}
@!max_wiggle=300; {number of autorounded points per cycle}
@!gf_buf_size=800; {size of the output buffer, must be a multiple of 8}
@!file_name_size=69; {file names shouldn't be longer than this}
@!pool_name=
 'MFbases:MF.POOL                                                      ';
  {string of length |file_name_size|; tells where the string pool appears}
@.MFbases@>
@!path_size=300; {maximum number of knots between breakpoints of a path}
@!bistack_size=785; {size of stack for bisection algorithms;
  should probably be left at this value}
@!header_size=100; {maximum number of \.{TFM} header words, times~4}
@!lig_table_size=300; {maximum number of ligature/kern steps}
@!max_font_dimen=50; {maximum number of \&{fontdimen} parameters}
@!max_rescan=500; {maximum length of the rescan buffer}
@z

@x [1] TANGLE-time constants:
@d mem_min=0 {smallest index in the |mem| array, must not be less
  than |min_halfword|}
@d mem_top==30000 {largest index in the |mem| array dumped by \.{INIMF};
  must be substantially larger than |mem_min|
  and not greater than |mem_max|}
@d hash_size=2100 {maximum number of symbolic tokens,
  must be less than |max_halfword-3*param_size|}
@d hash_prime=1777 {a prime number equal to about 85\% of |hash_size|}
@d max_in_open=6 {maximum number of input files and error insertions that
  can be going on simultaneously}
@d param_size=150 {maximum number of simultaneous macro parameters}
@^system dependencies@>
@y
@d mem_min=0 {smallest index in the |mem| array, must not be less
  than |min_halfword|}
@d mem_top==58000 {largest index in the |mem| array dumped by \.{INIMF};
  must be substantially larger than |mem_min|
  and not greater than |mem_max|}
@d hash_size=2100 {maximum number of symbolic tokens,
  must be less than |max_halfword-3*param_size|}
@d hash_prime=1777 {a prime number equal to about 85\% of |hash_size|}
@d max_in_open=6 {maximum number of input files and error insertions that
  can be going on simultaneously}
@d param_size=150 {maximum number of simultaneous macro parameters}
@^system dependencies@>
@z

@x [2] System-dependent character set changes:
@^character set dependencies@>
@^system dependencies@>

@<Set init...@>=
for i:=1 to @'37 do xchr[i]:=' ';
@y
@^character set dependencies@>
@^system dependencies@>

The code shown here is intended to be used on \ttw\ systems,
and at other installations where only the printable ASCII set, plus
|carriage_return|, |tab|, and |form_feed| will show up in text files.
All |line_feed| and |null| characters are skipped.

Note that |form_feed|'s are considered to be page delimiters, and this version
of \TeX\ will keep track of which page of input it is on, for use in error
messages, as well as in swapping to various editors.

@d form_feed=@'14 {ASCII code used at end of a page}
@d carriage_return=@'15 {ASCII code used at end of a line}
@d tab=@'11

@<Set initial values...@>=
for i:=1 to @'37 do xchr[i]:=' ';
xchr[form_feed]:=chr(form_feed);
xchr[tab]:=chr(tab);
@z

@x [3] Opening files:
@d reset_OK(#)==erstat(#)=0
@d rewrite_OK(#)==erstat(#)=0

@p function a_open_in(var @!f:alpha_file):boolean;
  {open a text file for input}
begin reset(f,name_of_file,'/O'); a_open_in:=reset_OK(f);
end;
@#
function a_open_out(var @!f:alpha_file):boolean;
  {open a text file for output}
begin rewrite(f,name_of_file,'/O'); a_open_out:=rewrite_OK(f);
end;
@#
function b_open_out(var @!f:byte_file):boolean;
  {open a binary file for output}
begin rewrite(f,name_of_file,'/O'); b_open_out:=rewrite_OK(f);
end;
@#
function w_open_in(var @!f:word_file):boolean;
  {open a word file for input}
begin reset(f,name_of_file,'/O'); w_open_in:=reset_OK(f);
end;
@#
function w_open_out(var @!f:word_file):boolean;
  {open a word file for output}
begin rewrite(f,name_of_file,'/O'); w_open_out:=rewrite_OK(f);
end;
@y
@d reset_OK(#)==(erstat(#)=0) or (erstat(#)=@'0600220) {empty file}
@d rewrite_OK(#)==erstat(#)=0

@p function erstat(var f:file):integer; extern; {in the runtime library}

function a_open_in(var f:alpha_file):boolean;
  {open a text file for input}
begin reset(f,name_of_file,'/E/O');
  {the \.{/E} switch distinguishes |form_feed| from |carriage_return|;
  the \.{/O} switch gives error control to us}
a_open_in:=reset_OK(f);
end;
@#
function a_open_out(var f:alpha_file):boolean;
  {open a text file for output}
begin rewrite(f,name_of_file,'/O'); a_open_out:=rewrite_OK(f);
end;
@#
function b_open_out(var f:byte_file):boolean;
  {open a binary file for output}
begin rewrite(f,name_of_file,'/B:8/O'); b_open_out:=rewrite_OK(f);
end;
@#
function w_open_in(var f:word_file):boolean;
  {open a word file for input}
begin reset(f,name_of_file,'/O'); w_open_in:=reset_OK(f);
end;
@#
function w_open_out(var f:word_file):boolean;
  {open a word file for output}
begin rewrite(f,name_of_file,'/O'); w_open_out:=rewrite_OK(f);
end;
@z

@x [3] New input_ln:
representing the beginning and ending of a line of text.

@<Glob...@>=
@y
representing the beginning and ending of a line of text.

We will read the lines first into an auxiliary buffer, in order to
save the running time of procedure-call overhead. This uses a nice
feature of \ph\ that Knuth chose not to mention in \TeX82.
@^Knuth, Donald Ervin@>

On \ttw\ we want to recognize page marks (indicated by |form_feed|
characters), and keep track of the current page number.  EDIT-type
line number are skipped over automatically.

@d aux_buf_len=80 {length of the auxiliary buffer}

@<Glob...@>=
@!aux_buf:array[0..aux_buf_len-1] of text_char; {where the characters go first}
@^system dependencies@>
@z

@x
@p function input_ln(var @!f:alpha_file;@!bypass_eoln:boolean):boolean;
  {inputs the next line or returns |false|}
var @!last_nonblank:0..buf_size; {|last| with trailing blanks removed}
begin if bypass_eoln then if not eof(f) then get(f);
  {input the first character of the line into |f^|}
last:=first; {cf.\ Matthew 19\thinspace:\thinspace30}
if eof(f) then input_ln:=false
else  begin last_nonblank:=first;
  while not eoln(f) do
    begin if last>=max_buf_stack then
      begin max_buf_stack:=last+1;
      if max_buf_stack=buf_size then
        overflow("buffer size",buf_size);
@:METAFONT capacity exceeded buffer size}{\quad buffer size@>
      end;
    buffer[last]:=xord[f^]; get(f); incr(last);
    if buffer[last-1]<>" " then last_nonblank:=last;
    end;
  last:=last_nonblank; input_ln:=true;
  end;
end;
@y
On \ttw, we do just that, using |aux_buf|.

@p function input_ln(var f:alpha_file;@!bypass_eoln:boolean):boolean;
  {inputs the next line or returns |false|}
label 1,done;
var n: integer;
@!k,@!m: 0..buf_size; {indices into |buffer|}
@!more: boolean; {is there more on the line that didn't get into |aux_buf|?}
begin if bypass_eoln then {input the first character of the line into |f^|}
  begin if not eof(f) then get(f);
  if not eof(f) then if f^=chr(@'12) then get(f); {skip past a |line_feed|}
  end;
last:=first;
if eof(f) then input_ln:=false
else  begin
  read(f,aux_buf:n);
  more:=n=aux_buf_len+1;
  if more then n:=aux_buf_len;
1:  if last+n>max_buf_stack then
    if last+n>=buf_size then
      begin max_buf_stack:=buf_size;
      overflow("buffer size",buf_size);
@:METAFONT capacity exceeded buffer size}{\quad buffer size@>
      end
    else max_buf_stack:=last+n;
  if n>0 then
    begin m:=last;
    last:=m+n;
    for k:=m to last-1 do buffer[k]:=xord[aux_buf[k-m]];
    if more then begin
      read(f,aux_buf:n);
      more:=n=aux_buf_len+1;
      if more then n:=aux_buf_len;
      goto 1;
      end;
    end;
  if (f^<>chr(carriage_return)) and (not eof(f)) then begin
    if f^=chr(form_feed) then begin page:=page+1; line:=0; end;
    aux_buf[0]:=f^; n:=1; more:=true; get(f); goto 1;
    end;
  loop@+  begin if last=first then goto done;
    if buffer[last-1]<>" " then goto done;
    decr(last);
    end;
done:  input_ln:=true;
  end;
end;
@^system dependencies@>
@z

@x [3] Terminal I/O:
is considered an output file the file variable is |term_out|.
@^system dependencies@>

@<Glob...@>=
@!term_in:alpha_file; {the terminal as an input file}
@!term_out:alpha_file; {the terminal as an output file}

@ Here is how to open the terminal files
in \ph. The `\.{/I}' switch suppresses the first |get|.
@^system dependencies@>

@d t_open_in==reset(term_in,'TTY:','/O/I') {open the terminal for text input}
@d t_open_out==rewrite(term_out,'TTY:','/O') {open the terminal for text output}
@y
is considered an output file the file variable is |term_out|.
On \ttw, this point is moot, since we use the built-in |TTY| file.
@^system dependencies@>

@d term_in==TTY {the terminal as an input file}
@d term_out==TTY {the terminal as an output file}

@ Here is how to open the terminal files on \ttw: we don't do anything,
since |TTY| is always open.  Note that |eoln(term_in)| is initially |true|.
(Acutally, some very picky people might want to be able to distinguish
between different |eoln| characters on terminal input, so we have to
re-reset the |term_in| file with a few magic switches)

@^system dependencies@>

@d t_open_in == reset(term_in,'','/I/E') {distinguish |eoln| characters}
@d t_open_out==do_nothing {open the terminal for text output}
@z

@x [3] Special terminal controls:
@d update_terminal == break(term_out) {empty the terminal output buffer}
@d clear_terminal == break_in(term_in,true) {clear the terminal input buffer}
@d wake_up_terminal == do_nothing {cancel the user's cancellation of output}
@y
@d CFIBF=@'100 {Clear File Input BuFfer JSYS}
@d PRIIN=@'100 {PRImary INput JFN JSYS}
@d PRIOUT=@'101 {PRImary OUTput JFN JSYS}
@d RFMOD=@'107 {Return File MODe word JSYS}
@d SFMOD=@'110 {Set File MODe word JSYS}
@#
@d update_terminal == {the terminal output buffer is always emptied}
@d clear_terminal == jsys(CFIBF;PRIIN) {clear the terminal input buffer}
@d wake_up_terminal == begin {cancel the user's cancellation of output}
  jsys(RFMOD;PRIOUT;wake_up_junk,wake_up_junk);
  wake_up_junk:=wake_up_junk-[0]; {turn off TT\%OSP}
  jsys(SFMOD;PRIOUT,wake_up_junk);
  end
@^system dependencies@>

@<Glob...@>=
@!wake_up_junk: set of 0..35;
@z

@x [3] Initializing the terminal:
@ The following program does the required initialization
without retrieving a possible command line.
It should be clear how to modify this routine to deal with command lines,
if the system permits them.
@^system dependencies@>

@p function init_terminal:boolean; {gets the terminal input started}
label exit;
begin t_open_in;
loop@+begin wake_up_terminal; write(term_out,'**'); update_terminal;
@.**@>
  if not input_ln(term_in,true) then {this shouldn't happen}
    begin write_ln(term_out);
    write(term_out,'! End of file on the terminal... why?');
@.End of file on the terminal@>
    init_terminal:=false; return;
    end;
  loc:=first;
  while (loc<last)and(buffer[loc]=" ") do incr(loc);
  if loc<last then
    begin init_terminal:=true;
    return; {return unless the line was all blank}
    end;
  write_ln(term_out,'Please type the name of your input file.');
  end;
exit:end;
@y
@ The following program does the required initialization
and accepts interrupts and also retrieves a possible command line, using
a fair bit of system stuff to get and remember them.
@^system dependencies@>

@p function init_terminal:boolean; {gets the terminal input started}
label exit,done; {|done| is for the command line code}
var @<Command line variables@>@/
@!line_found:boolean; {is there a spceial command line?}
begin t_open_in;
@<Set up Control-G@>;
last:=first; {|buffer| empty}
@<Process possible command line@>;
line_found:=(last>first); {did we put anything into |buffer|?}
loop@+  begin loc:=first;
  while (loc<last)and(buffer[loc]=" ") do incr(loc);
  if loc<last then
    begin init_terminal:=true;
    return; {return unless the line was all blank}
    end;
  if line_found then
    write_ln(term_out,'Please type the name of your input file.');
  wake_up_terminal;
  write(term_out,'**'); update_terminal;
@.**@>
  if not input_ln(term_in,true) then {this shouldn't happen}
    begin write_ln(term_out);
    write(term_out,'! End of file on the terminal... why?');
@.End of file on the terminal@>
    init_terminal:=false; return;
    end;
  line_found:=true;
  end;
exit:end;
@z

@x [6] The `E' option:
line ready to be edited. But such an extension requires some system
wizardry, so the present implementation simply types out what file should be
edited and the relevant line number.
@y
line ready to be edited. The present implementation does this by loading
the line editor with the appropriate call to the editor. We treat `\.T'
the same as `\.E', because other programs invoke the editor when the user
says `\.T'.
@z

@x
"E": if file_ptr>0 then
  begin print_nl("You want to edit file ");
@.You want to edit file x@>
  print(input_stack[file_ptr].name_field);
  print(" at line "); print_int(line);
  interaction:=scroll_mode; jump_out;
  end;
@y
"E","T": if file_ptr>0 then
  begin
  set_pseudo_to_edit_commands;
  jump_out;
  end;
@z

@x [8] Changes for 36-bit machines:
The values defined here are recommended for most 32-bit computers.

@d min_quarterword=0 {smallest allowable value in a |quarterword|}
@d max_quarterword=255 {largest allowable value in a |quarterword|}
@d min_halfword==0 {smallest allowable value in a |halfword|}
@d max_halfword==65535 {largest allowable value in a |halfword|}
@y
The values defined here are recommended for most 36-bit computers.

@d min_quarterword=0 {smallest allowable value in a |quarterword|}
@d max_quarterword=511 {largest allowable value in a |quarterword|}
@d min_halfword==0 {smallest allowable value in a |halfword|}
@d max_halfword==262143 {largest allowable value in a |halfword|}
@z

@x [6] Eliminating addition/subtraction of zero:
@ The operation of subtracting |min_halfword| occurs rather frequently in
\MF, so it is convenient to abbreviate this operation by using the macro
|ho| defined here.  \MF\ will run faster with respect to compilers that
don't optimize the expression `|x-0|', if this macro is simplified in the
obvious way when |min_halfword=0|. Similarly, |qi| and |qo| are used for
input to and output from quarterwords.
@^system dependencies@>

@d ho(#)==#-min_halfword
  {to take a sixteen-bit item from a halfword}
@d qo(#)==#-min_quarterword {to read eight bits from a quarterword}
@d qi(#)==#+min_quarterword {to store eight bits in a quarterword}
@y
@ The operation of subtracting |min_halfword| occurs rather frequently in
\MF, so it is convenient to abbreviate this operation by using the macro
|ho| defined here.  \MF\ will run faster with respect to compilers that
don't optimize the expression `|x-0|', if this macro is simplified in the
obvious way when |min_halfword=0|.  Similarly, |qi| and |qo| are used for
input to and output from quarterwords.  For \ttw, we simplify them for the
compiler.
@^system dependencies@>

@d ho(#)==# {to take a sixteen-bit item from a halfword}
@d qo(#)==# {to read eight bits from a quarterword}
@d qi(#)==# {to store eight bits in a quarterword}
@z

@x [17] Date and time:
Since standard \PASCAL\ cannot provide such information, something special
is needed. The program here simply specifies July 4, 1776, at noon; but
users probably want a better approximation to the truth.

Note that the values are |scaled| integers. Hence \MF\ can no longer
be used after the year 32767.

@p procedure fix_date_and_time;
begin internal[time]:=12*60*unity; {minutes since midnight}
internal[day]:=4*unity; {fourth day of the month}
internal[month]:=7*unity; {seventh month of the year}
internal[year]:=1776*unity; {Anno Domini}
end;
@y
It uses a \ttw\ monitor call that returns various data and time
information in three variables.

@d ODCNV=@'222 {Output Date and time CoNVersion JSYS}

@p procedure fix_date_and_time;
var y,d,t:integer; {raw year/month, day and time}
g:integer; {garbage}
begin jsys(ODCNV;0,-1,0,0;g,y,d,t);
internal[year]:=y div @'1000000 * unity; {year in left half-word}
internal[month]:=((y mod @'1000000)+1) * unity;
    {month in right half-word, zero means January}
internal[day]:=((d div @'1000000)+1) * unity;
  {day in left half-word, zero means the first of the month}
internal[time]:=((t+@'377777*@'1000000) mod @'1000000) div 60 * unity;
  {sign bit was on}
end;
@^system dependencies@>
@z

@x [12] Special form_feed initialization:
char_class[127]:=invalid_class;
@y
char_class[127]:=invalid_class;
char_class[form_feed]:=space_class;
char_class[tab]:=space_class;
@z

;@x  ONLY FOR RUNNING TRAP TEST
begin init_screen:=false;
;@y
begin init_screen:=true;
;@z

@x [31] Page number maintenance:
If more information about the input state is needed, it can be
included in small arrays like those shown here. For example,
the current page or segment number in the input file might be
put into a variable |@!page|, maintained for enclosing levels in
`\ignorespaces|@!page_stack:array[1..max_in_open] of integer|\unskip'
by analogy with |line_stack|.
@y
Similarly, we maintain a global variable |page| and a corresponding
|page_stack|.
@z
@x
@!line_stack : array[1..max_in_open] of integer;
@y
@!line_stack : array[1..max_in_open] of integer;
@!page : integer; {current page number in the current source file}
@!page_stack : array[1..max_in_open] of integer;
@z

@x [22] Printing the page number:
else  begin print_nl("l."); print_int(line);
@y
else  begin if page>1 then
    begin print_nl("p."); print_int(page); print(",l.");
    end
  else print_nl("l.");
  print_int(line);
@z

@x [23] More page number maintenance:
or |limit| or |line|.
@y
or |limit| or |line| or |page|.
@z

@x
line_stack[index]:=line; start:=first;
@y
line_stack[index]:=line; start:=first;
page_stack[index]:=page;
@z

@x
begin first:=start; line:=line_stack[index];
@y
begin first:=start; page:=page_stack[index]; line:=line_stack[index];
@z

@x [29] Logical name translation:
@ The third.
@^system dependencies@>

@p procedure end_name;
begin if str_ptr+3>max_str_ptr then
  begin if str_ptr+3>max_strings then
    overflow("number of strings",max_strings-init_str_ptr);
@:METAFONT capacity exceeded number of strings}{\quad number of strings@>
  max_str_ptr:=str_ptr+3;
  end;
if area_delimiter=0 then cur_area:=""
else  begin cur_area:=str_ptr; incr(str_ptr);
  str_start[str_ptr]:=area_delimiter+1;
  end;
if ext_delimiter=0 then
  begin cur_ext:=""; cur_name:=make_string;
  end
else  begin cur_name:=str_ptr; incr(str_ptr);
  str_start[str_ptr]:=ext_delimiter; cur_ext:=make_string;
  end;
end;
@y
@ The third.  We have to check to see if a logical name has been
referred to, and if so, translate it.
@^system dependencies@>

@d LNMST=@'504 {convert Logical NaMe to STring JSYS}

@p procedure end_name;
label restart,exit;
var s,t:packed array[1..max_TOPS20_file_name_length+1] of char;
@!LNMST_return: integer; {which skip return did the LNMST jsys take?}
@!i:pool_pointer;
begin
restart:
if (str_pool[area_delimiter]=":") and (pool_ptr=area_delimiter+1) then
  begin
  cur_area:=make_string;
  for i:=1 to length(cur_area)-1 do
    s[i]:=xchr[str_pool[str_start[cur_area]+i-1]];
  s[length(cur_area)]:=chr(0); {ASCIZ it}
  jsys(LNMST,-2,LNMST_return;0,s,t); {job-wide}
  if LNMST_return<>2 then
    jsys(LNMST,-2,LNMST_return;1,s,t); {system-wide}
  if LNMST_return<>2 then begin
    cur_ext:=""; cur_name:=""; {silly case}
    return;
    end;
  flush_string(cur_area); {needn't remember logical name in |cur_area|}
  begin_name;
  i:=1;
  while ord(t[i])>0 do
    if more_name(xord[t[i]]) then incr(i)
    else goto restart;
  goto restart; {heavy!}
  end;
if str_ptr+3>max_str_ptr then
  begin if str_ptr+3>max_strings then
    overflow("number of strings",max_strings-init_str_ptr);
@:METAFONT capacity exceeded number of strings}{\quad number of strings@>
  max_str_ptr:=str_ptr+3;
  end;
if area_delimiter=0 then cur_area:=""
else  begin cur_area:=str_ptr; incr(str_ptr);
  str_start[str_ptr]:=area_delimiter+1;
  end;
if ext_delimiter=0 then
  begin cur_ext:=""; cur_name:=make_string;
  end
else  begin cur_name:=str_ptr; incr(str_ptr);
  str_start[str_ptr]:=ext_delimiter; cur_ext:=make_string;
  end;
exit:
end;
@z

@x [29] The real file names:
@ Operating systems often make it possible to determine the exact name (and
possible version number) of a file that has been opened. The following routine,
which simply makes a \MF\ string from the value of |name_of_file|, should
ideally be changed to deduce the full name of file~|f|, which is the file
most recently opened, if it is possible to do this in a \PASCAL\ program.
@^system dependencies@>

This routine might be called after string memory has overflowed, hence
we dare not use `|str_room|'.

@p function make_name_string:str_number;
var @!k:1..file_name_size; {index into |name_of_file|}
begin if (pool_ptr+name_length>pool_size)or(str_ptr=max_strings) then
  make_name_string:="?"
else  begin for k:=1 to name_length do append_char(xord[name_of_file[k]]);
  make_name_string:=make_string;
  end;
end;
function a_make_name_string(var @!f:alpha_file):str_number;
begin a_make_name_string:=make_name_string;
end;
function b_make_name_string(var @!f:byte_file):str_number;
begin b_make_name_string:=make_name_string;
end;
function w_make_name_string(var @!f:word_file):str_number;
begin w_make_name_string:=make_name_string;
end;
@y
@ Operating systems often make it possible to determine the exact name (and
possible version number) of a file that has been opened. The following routine
shows how to deduce the full name of file~|f| using \ttw\ system calls.
@^system dependencies@>

This routine might be called after string memory has overflowed, hence
we dare not use `|str_room|'.

@d JFNS=@'30 {translage JFN to String JSYS}

@p function make_name_string(var f:f@&i@&l@&e):str_number;
var s:packed array[1..max_TOPS20_file_name_length+1] of char;
@!j,k:1..max_TOPS20_file_name_length;
begin jsys(JFNS;s,0:f,@'111110:1);
j:=1; while (ord(s[j+1])<>0) and (j<max_TOPS20_file_name_length) do incr(j);
str_room(j);
for k:=1 to j do append_char(xord[s[k]]);
make_name_string:=make_string;
end;
function a_make_name_string(var @!f:alpha_file):str_number;
begin a_make_name_string:=make_name_string(f);
end;
function b_make_name_string(var @!f:byte_file):str_number;
begin b_make_name_string:=make_name_string(f);
end;
function w_make_name_string(var @!f:word_file):str_number;
begin w_make_name_string:=make_name_string(f);
end;
@z

@x [29] LST instead of LOG:
@p procedure pack_job_name(@!s:str_number); {|s = ".log"|, |".gf"|, or
@y
@p procedure pack_job_name(@!s:str_number); {|s = ".lst"|, |".gf"|, or
@z

@x
pack_job_name(".log");
@y
pack_job_name(".lst");
@z

@x
prompt_file_name("transcript file name",".log");
@y
prompt_file_name("transcript file name",".lst");
@z

@x [29] Start page number at 1:
buffer[limit]:="%"; first:=limit+1; loc:=start; line:=1;
@y
buffer[limit]:="%"; first:=limit+1; loc:=start; line:=1; page:=1;
@z

@x [32] Pack gf_buf
@!gf_buf:array[gf_index] of eight_bits; {buffer for \.{GF} output}
@y
@!gf_buf:packed array [gf_index] of eight_bits; {buffer for \.{GF} output}
@z

@x [32] "TeXspool":
b_close(gf_file);
@y
b_close(gf_file);
if pseudo_typein=0 then
  begin k:=selector; selector:=new_string;
  pool_ptr:=str_start[str_ptr];
  print("MFspool: "); print(output_file_name);
  selector:=k;
  if (pool_ptr<pool_size) and (str_ptr<max_strings) then
     {|overflow| can't occur}
    pseudo_typein:=make_string;
  end;
@z

@x [51] The endgame:

@ Here we do whatever is needed to complete \MF's job gracefully on the
local operating system. The code here might come into play after a fatal
error; it must therefore consist entirely of ``safe'' operations that
cannot produce error messages. For example, it would be a mistake to call
|str_room| or |make_string| at this time, because a call on |overflow|
might lead to an infinite loop.
@^system dependencies@>

This program doesn't bother to close the input files that may still be open.

@<Last-minute...@>=
procedure close_files_and_terminate;
var @!k:integer; {all-purpose index}
@!lh:integer; {the length of the \.{TFM} header, in words}
@!p:pointer; {runs through a list of \.{TFM} dimensions}
@!x:scaled; {a |tfm_width| value being output to the \.{GF} file}
begin
@!stat if internal[tracing_stats]>0 then
  @<Output statistics about this job@>;@;@+tats@/
wake_up_terminal; @<Finish the \.{TFM} and \.{GF} files@>;
if job_name>0 then
  begin wlog_cr;
  a_close(log_file); selector:=selector-2;
  if selector=term_only then
    begin print_nl("Transcript written on ");
@.Transcript written...@>
    print(log_name); print_char(".");
    end;
  end;
end;
@y
@ Here we do whatever is needed to complete \MF's job gracefully on the
local operating system. The code here might come into play after a fatal
error; it must therefore consist entirely of ``safe'' operations that
cannot produce error messages. For example, it would be a mistake to call
|str_room| or |make_string| at this time, because a call on |overflow|
might lead to an infinite loop.
@^system dependencies@>

@d STI=@'114 {Simulate Terminal Input JSYS}
@d SIBE=@'102 {See If Buffer Empty JSYS}

@<Last-minute...@>=
procedure close_files_and_terminate;
var @!j,@!k:integer; {all-purpose indices}
@!lh:integer; {the length of the \.{TFM} header, in words}
@!p:pointer; {runs through a list of \.{TFM} dimensions}
@!x:scaled; {a |tfm_width| value being output to the \.{GF} file}
@!SIBE_return: integer; {did the SIBE skip?}
@!temp_file: alpha_file; {read unbuffered from |term_in|}
@!old_mode,@!new_mode:set of 0..35; {a word by the bits}
begin
@!stat if internal[tracing_stats]>0 then
  @<Output statistics about this job@>;@;@+tats@/
wake_up_terminal; @<Finish the \.{TFM} and \.{GF} files@>;
if job_name>0 then
  begin wlog_cr; a_close(log_file); selector:=selector-2;
  if selector=term_only then
    begin print_nl("Transcript written on ");
@.Transcript written...@>
    print(log_name); print_char(".");
    end;
  end;
{note we don't use |xchr[str_pool[k]]| here because of ESC and CR characters}
if (pseudo_typein<>0) and (interaction>batch_mode) then
  begin
  j:=str_start[pseudo_typein+1]-1; {last character to STI}
  jsys(SIBE,2,SIBE_return;PRIIN);
  if SIBE_return<>1 then goto done;
  reset(temp_file,'tty:','/I/M:1'); {read unbuffered from the terminal}
  jsys(RFMOD;PRIIN;old_mode); {get terminal mode}
  new_mode:=old_mode-[24]; {TT\%ECO bit}
  jsys(SFMOD;PRIIN;new_mode); {turn off echo}
  while SIBE_return=1 do begin
    get(temp_file); {there must be user input}
    incr(j);
    if eoln(temp_file) then str_pool[j]:=13
    else str_pool[j]:=ord(temp_file^);
    jsys(SIBE,2,SIBE_return;PRIIN);
    end;
  done:
  jsys(SFMOD;PRIIN;old_mode); {turn echo back on}
  for k:=str_start[pseudo_typein] to j do jsys(STI;PRIIN,str_pool[k]);
  end;
end;
@z

@x [54] Final system-dependent changes:
This section should be replaced, if necessary, by changes to the program
that are necessary to make \MF\ work at a particular installation.
It is usually best to design your change file so that all changes to
previous sections preserve the section numbering; then everybody's version
will be consistent with the published program. More extensive changes,
which introduce new sections, can be inserted here; then only the index
itself will get a new section number.
@^system dependencies@>
@y
Here are the remaining things needed to make the implementation
complete on \ttw.
@^system dependencies@>

@d ESC=@'33
@d CR=@'15
@d control_R=@'22
@d control_Z=@'32

@<Basic printing...@>=
procedure@?set_pseudo_to_edit_commands; forward;@t\2@>@/

@ The |pseudo_typein| variable is set nonzero if the |error| routine
uses the `\.E' option to exit and edit.

@<Glob...@>=
@!pseudo_typein:str_number;

@ @<Set init...@>=
pseudo_typein:=0; page:=0;

@ This procedure gets called when the user wants to set up to swap to the
editor upon seeing an error.  It figures out which editor the user wants,
and then prints the proper command to start up that editor on the correct
file at the correct position.

@d edit_file==input_stack[file_ptr] {file to edit}

@<Last-minute procedures@>=
procedure set_pseudo_to_edit_commands;
label done;
var @!LNMST_return: integer; {which skip return did the LNMST jsys take?}
@!edit_name: packed array[1..max_TOPS20_file_name_length] of ASCII_code;
@!short_name: str_number; {|edit_name| with directory, etc. removed}
@!from_here, @!to_there, @!i: integer; {help turn |edit_name| into |shorname|}
begin
selector:=new_string; pool_ptr:=str_start[str_ptr];
@<Set |edit_name| to the full file name of user's favorite editor@>;
@<Make |from_here| and |to_there| delimit the short editor name@>;
@<Put the name of the editor into |short_name|@>;
with edit_file do {avoid typeing |edit_file| alot}
if str_vs_str(short_name,"EMACS")=0 then @<Print EMACS startup commands@>
else if str_vs_str(short_name,"TVEDIT")=0 then
     @<Print TVEDIT startup commands@>
else if str_vs_str(short_name,"EDIT")=0 then @<Print EDIT startup commands@>
else if str_vs_str(short_name,"ZED")=0 then @<Print ZED startup commands@>
else @<Print unfound editor message@>;
done:
pseudo_typein:=make_string;
selector:=term_and_log; interaction:=scroll_mode;
end;

@ There is a bug here is the logical name for the editor points to
another logical name which then points to the real editor name.

@<Set |edit_name| to the full file name of user's favorite editor@>=
logical_name:='EDITOR  '; logical_name[7]:=chr(0); {ASCIZ it}
jsys(LNMST,-2,LNMST_return;0,logical_name,edit_name); {job-wide}
if LNMST_return<>2 then
  jsys(LNMST,-2,LNMST_return;1,logical_name,edit_name);
  {system-wide}
if LNMST_return<>2 then begin
  print("; You must DEFINE EDITOR: before METAFONT knows who to swap to.");
  print_char(CR); goto done;
  end;

@ @<Make |from_here| and |to_there|...@>=
from_here:=1;
i:=1;
while edit_name[i]>0 do begin
  if (edit_name[i]=":") or (edit_name[i]=">") then from_here:=i+1;
  incr(i);
  end;
decr(i);
to_there:=i;
while i>from_here do begin
  if edit_name[i]="." then to_there:=i-1;
  decr(i);
  end;

@ @<Put the name of the editor...@>=
str_room(to_there-from_here+1);
for i:=from_here to to_there do append_char(edit_name[i]);
short_name:=make_string;

@ @<Print unfound editor message@>=
begin
print("; Sorry, but METAFONT doesn't know about EDITOR: ");
print(short_name);
print_char(CR);
print("; You want to edit file ");
print(name_field);
print(" on page ");
print_int(page);
print(", line ");
print_int(line);
print_char(".");
print_char(CR);
end

@ @<Print EMACS startup commands@>=
begin
print("EDIT"); print_char(CR);
print_char(control_Z); print("x^r execute minibuffer"); print_char(CR);
print("mmFind file"); print_char(ESC);
jsys(JFNS;edit_name,0:input_file[index_field],@'221100:1);
{Get file name without device and directory if possible, and no version number}
i:=1; while edit_name[i]>0 do begin print_char(edit_name[i]); incr(i); end;
print_char(ESC); print_char(CR);
print_int(curpos(input_file[index_field])-(limit_field-loc_field)-1);
print_char("j"); print_char(control_Z); print_char(control_Z);
end

@ @<Print ZED startup commands@>=
begin
print("EDIT "); print(name_field); print_char(CR);
print("XTECO"); print_char(ESC);
print_int(curpos(input_file[index_field])-(limit_field-loc_field)-1);
print_char("j"); print_char(control_R); print_char(control_Z);
end

@ @<Print TVEDIT startup commands@>=
begin
print("EDIT "); print(name_field); print_char(CR);
print_char(ESC); print_char(ESC); print_int(page); print_char(".");
  print_int(line); print_char("G"); {diabolical!}
if loc_field>start_field then begin
  print_char(ESC); print_char(ESC);
  print_int(loc_field-start_field); print_char(" ");
  end;
end

@ @<Print EDIT startup commands@>=
begin
print("EDIT "); print(name_field); print_char(CR);
{fix me -- there's a bug if EDIT decides to split a very large page}
print("P^+"); print_int(line-1);
if page>1 then begin
  print_char("/"); print_int(page);
  end;
print_char(CR);
end

@ This is the actual Control-G interrupt routine, and two system routines
used to enable it.  We have to put them somewhere, so pardon the little
lie\dots

@<Basic printing...@>=
procedure cntl_G; begin interrupt:=interrupt+1; end; {non-interruptable}
procedure psidefine(chan,level:integer;procedure p); extern; @t\2@>@;
procedure psienable(chan:integer); extern; @t\2@>@;

@ This code sets things up such that each time the user types control-G,
the procedure |cntl_G| gets called, and |interrupt| gets incremented.
The program can change |interrupt| whenever it wants to,
but |interrupt| had better be a global variable.

@d ATI=@'137 {Assign Terminal code to Interrupt channel JSYS}
@d cntl_G_chan=34 {Channel for Control-G interrupts}

@<Set up Control-G@>=
begin
psidefine(cntl_G_chan,1,cntl_G); {call |cntl_G| on interrupt, level 1}
psienable(cntl_G_chan); {turn on those interrupts}
jsys(ATI;7:cntl_G_chan); {assign control-G to channel}
end

@ Here is the code that does all the command line system magic.
@d RSCAN=@'500 {ReSCAN buffer JSYS}
@d CRLNM=@'502 {CReate Logical NaMe JSYS}

@<Command line variables@>=
@!ac1: integer; {AC1 from Rescan}
@!rescan: packed array[1..max_rescan] of char; {rescan buffer}
@!rescan_len: integer; {amount of |rescan| used}
@!definition: packed array [1..79] of char; {ASCIZ, with control-V's}
@!i,@!j: integer; {temporary}

@ The array |logical_name| holds the logical name that we use to implement
\TeX's memory from run to run.  Actually, we may have to use a series of
logical names, since we only get to save 39 characters in each one.

@d next_logical_name==logical_name[6]:=chr(ord(logical_name[6])+1)

@<Glob...@>=
@!logical_name: packed array[1..8] of char; {ASCIZ ``MFMEMx'', x=0,1,etc.}

@ @<Set init...@>=
logical_name:='MFMEM0  '; logical_name[7]:=chr(0); {ASCIZ it}

@ \ttw\ puts the user command line into the so-called rescan buffer.
Actually, we have to use a real hack to see if it's a bogus Execute,
Start, Continue, Debug, etc.\ command, in which case we should pretend
there was no command line, since the command line that was there was
not intended for \TeX.

@<Get the |rescan| buffer and check that it's for us@>=
jsys(RSCAN,1,i;0;ac1); {put the command line into the |TTY| input buffer}
if (i<>2) or (ac1<=0) then goto done; {RSCAN failed, somehow}
if eoln(term_in) then read_ln(term_in); {for some TOPS-20's}
read(term_in,rescan:rescan_len); {read in rescan buffer}
if rescan_len>max_rescan then begin
  write_ln(term_out,'Command line longer than ',max_rescan:0,
    'characters, so I''m ignoring it');
  read_ln(term_in); goto done; end;
@/{The following line is based upon experimentation with \ttw!}
if rescan_len=ac1-2 then goto done; {EX, ST, DEB commands}


@ Now that we have a command line, we have to strip off the TeX command
and see if there is a lone backslash (which means that the user wanted
us to ignore the stuff in the logical name memory.

@<Make |i| point to the actual command text@>=
i:=1; while rescan[i]>' ' do incr(i); {skip the command name, presumably TeX}@/
while (i<=rescan_len) and (rescan[i]=' ') do incr(i); {skip spaces}@/
if (i=rescan_len) and (rescan[i]='\') then
  begin @<Unremember logical names@>;
  goto done; end; {escape to ignore memory}

@ Here's where we actually do the command line stuff.  Various parts of the
code go to the label |done| if they realize that no special command line
processing should happen.

@<Process possible command line@>=
@<Get the |rescan| buffer...@>;
@<Make |i| point to the actual command text@>;
if i<=rescan_len then @<We got command text@>
else @<Use the memorized command text, if any@>;
done:

@ We get here if there was command text for \MF.  It gets put into |buffer|
and into the logical name(s).  We need to intersperse the characters with
Control-V's so that they don't get capitalized and so that non-alphabetic
characters get remembered properly.  `Why doesn't \ttw\ have variables
that can be defined?' you may ask; and well you may.

@<We got command text@>=
begin
j:=1;
while i<=rescan_len do begin
  buffer[last]:=xord[rescan[i]]; incr(last);
  definition[j]:=chr(@'26); {control-V}
  incr(j);
  definition[j]:=rescan[i];
  incr(j);
  incr(i);
  if (j=79) or (i>rescan_len) then begin
    definition[j]:=chr(0);
    jsys(CRLNM,2,j;4,logical_name,definition;ac1);
    if j=1 then write_ln(term_out,'CRLNM returned ',ac1:12:O);
    j:=1;
    next_logical_name;
    end;
  end;
@<Unremember...@>; {In case the old one was longer than this one.}
end

@ We get here if we should put the memorized command line into |buffer|.

@<Use the memorized command text, if any@>=
loop@+begin
  jsys(LNMST,2,j;0,logical_name,definition);
  if j<>2 then goto done;
  j:=1;
  while (definition[j]<>chr(0)) do begin
    buffer[last]:=xord[definition[j]];
    incr(last); incr(j);
    end;
  next_logical_name;
  end

@ This code makes \MF\ wipe out all logical names beginnig with the current
one.

@<Unremember...@>=
repeat
  jsys(CRLNM,2,j;0,logical_name);
  next_logical_name;
until j<>2

@z