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:x.mf} or even \.{def foo: bar:} followed by \.{def bar: 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@> @= 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 @= 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. @= @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} @= @!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@> @= @!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@> @= @!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 @p function init_terminal:boolean; {gets the terminal input started} label exit,done; {|done| is for the command line code} var @@/ @!line_found:boolean; {is there a spceial command line?} begin t_open_in; @; last:=first; {|buffer| empty} @; line_found:=(last>first); {did we put anything into |buffer|?} loop@+ begin loc:=first; while (loc 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 This program doesn't bother to close the input files that may still be open. @= 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 @;@;@+tats@/ wake_up_terminal; @; 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} @= 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 @;@;@+tats@/ wake_up_terminal; @; 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 @= 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. @= @!pseudo_typein:str_number; @ @= 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} @= 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]; @; @; @; with edit_file do {avoid typeing |edit_file| alot} if str_vs_str(short_name,"EMACS")=0 then @ else if str_vs_str(short_name,"TVEDIT")=0 then @ else if str_vs_str(short_name,"EDIT")=0 then @ else if str_vs_str(short_name,"ZED")=0 then @ else @; 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. @= 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; @ @= 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; @ @= str_room(to_there-from_here+1); for i:=from_here to to_there do append_char(edit_name[i]); short_name:=make_string; @ @= 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 @ @= 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 @ @= 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 @ @= 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 @ @= 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 @= 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} @= 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} @= @!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) @= @!logical_name: packed array[1..8] of char; {ASCIZ ``MFMEMx'', x=0,1,etc.} @ @= 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. @= 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. @= 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 @; 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. @= @; @; if i<=rescan_len then @ else @; 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. @= 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; @; {In case the old one was longer than this one.} end @ We get here if we should put the memorized command line into |buffer|. @= 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. @= repeat jsys(CRLNM,2,j;0,logical_name); next_logical_name; until j<>2 @z