% This is etex_vms.ch, a WEB change file produced by webmerge.tex % to be applied to [-.knuth]tex.web % combining the changes (one after the other) from % 1. [-]etex.ch % 2. tex_vms.ch % 3. etex_vms.ech @x l.1 % This program is copyright (C) 1982 by D. E. Knuth; all rights are reserved. % Copying of this file is authorized only if (1) you are D. E. Knuth, or if % (2) you make absolutely no changes to your copy. (The WEB system provides % for alterations via an auxiliary file; the master file should stay intact.) % See Appendix H of the WEB manual for hints on how to install this program. % And see Appendix A of the TRIP manual for details about how to validate it. % TeX is a trademark of the American Mathematical Society. % METAFONT is a trademark of Addison-Wesley Publishing Company. @y % e-TeX is copyright (C) 1994,96 by the NTS team; all rights are reserved. % Copying of this file is authorized only if (1) you are a member of the % NTS team, or if (2) you make absolutely no changes to your copy. % (Programs such as PATCHWEB, TIE, or WEBMERGE allow the application of % several change files to tex.web; the master files tex.web and etex.ch % should stay intact.) % See etex_gen.tex for hints on how to install this program. % And see etripman.tex for details about how to validate it. % e-TeX and NTS are trademarks of the NTS group. % TeX is a trademark of the American Mathematical Society. % METAFONT is a trademark of Addison-Wesley Publishing Company. % This program is directly derived from Donald E. Knuth's TeX; % the change history which follows and the reward offered for finders of % bugs refer specifically to TeX; they should not be taken as referring % to e-TeX, although the change history is relevant in that it % demonstrates the evolutionary path followed. This program is not TeX; % that name is reserved strictly for the program which is the creation % and sole responsibility of Professor Knuth. @z @x l.50 % Although considerable effort has been expended to make the TeX program % correct and reliable, no warranty is implied; the author disclaims any % obligation or liability for damages, including but not limited to % special, indirect, or consequential damages arising out of or in % connection with the use or performance of this software. This work has % been a ``labor of love'' and the author hopes that users enjoy it. @y % A preliminary version of TeX--XeT was released in April 1992. % TeX--XeT version 1.0 was released in June 1992, % version 1.1 prevented arith overflow in glue computation (Oct 1992). % A preliminary e-TeX version 0.95 was operational in March 1994. % Version 1.0beta was released in May 1995. % Version 1.01beta fixed bugs in just_copy and every_eof (December 1995). % Version 1.02beta allowed 256 mark classes (March 1996). % Version 1.1 changed \group{type,level} -> \currentgroup{type,level}, % first public release (October 1996). % Although considerable effort has been expended to make the e-TeX program % correct and reliable, no warranty is implied; the authors disclaim any % obligation or liability for damages, including but not limited to % special, indirect, or consequential damages arising out of or in % connection with the use or performance of this software. This work has % been a ``labor of love'' and the authors hope that users enjoy it. @z @x l.61 \let\mc=\ninerm % medium caps for names like SAIL @y \let\mc=\ninerm % medium caps for names like SAIL \def\eTeX{$\varepsilon$-\TeX} \font\revrm=xbmc10 % for right-to-left text % to generate xbmc10 (i.e., reflected cmbx10) use a file % xbmc10.mf containing: %+++++++++++++++++++++++++++++++++++++++++++++++++ % if unknown cmbase: input cmbase fi % extra_endchar := extra_endchar & % "currentpicture:=currentpicture " & % "reflectedabout((.5[l,r],0),(.5[l,r],1));"; % input cmbx10 %+++++++++++++++++++++++++++++++++++++++++++++++++ \ifx\beginL\undefined % this is TeX \def\XeT{X\kern-.125em\lower.5ex\hbox{E}\kern-.1667emT} \def\TeXeT{\TeX-\hbox{\revrm \XeT}} % for TeX-XeT \def\TeXXeT{\TeX-\hbox{\revrm -\XeT}} % for TeX--XeT \else % this is TeX--XeT (or TeX-XeT) \ifx\TeXXeTstate\undefined \else % this is e-TeX \TeXXeTstate=1 \fi \def\TeXeT{\TeX-{\revrm\beginR\TeX\endR}} % for TeX-XeT \def\TeXXeT{\TeX-{\revrm\beginR\TeX-\endR}} % for TeX--XeT \fi @z @x l.64 \def\pct!{{\char`\%}} % percent sign in ordinary text @y \def\pct!{{\char`\%}} % percent sign in ordinary text \def\grp{\.{\char'173...\char'175}} @z @x l.80 \def\title{\TeX82} @y \def\title{\eTeX} % system dependent redefinitions of \title should come later % and should use: % \toks0=\expandafter{\title} % \edef\title{...\the\toks0...} \let\maybe=\iffalse % print only changed modules @z @x l.86 \pageno=3 @y \pageno=3 \let\maybe=\iffalse \toks0=\expandafter{\title} \maybe \edef\title{\the\toks0\ 3.14159 [PD VMS 3.6]} \else \edef\title{\the\toks0\ 3.14159 [PD VMS 3.6] changes} \fi \def\LaTeX{{\rm L\kern-.3em\raise.33ex\hbox{\sc A}\kern-.15em\TeX}} @z @x l.91 This is \TeX, a document compiler intended to produce typesetting of high @y This is \eTeX, a program derived from and extending the capabilities of \TeX, a document compiler intended to produce typesetting of high @z @x l.179 If this program is changed, the resulting system should not be called @y This program contains code for various features extending \TeX, therefore this program is called `\eTeX' and not @z @x l.185 November 1984]. @y November 1984]. A similar test suite called the ``\.{e-TRIP} test'' is available for helping to determine whether a particular implementation deserves to be known as `\eTeX'. @z @x l.187 @d banner=='This is TeX, Version 3.14159' {printed when \TeX\ starts} @y This change file is the result of a long odyssey of change files beginning with the original change files created in 1984 by David Fuchs; many people have made significant contributions since then, the most notable of whom have been Brian Hamilton Kelly, Niel Kempson, and Adrian Clark @d banner=='This is TeX, Version 3.14159 [PD VMS 3.6]' @# @d eTeX_version_string=='3.14159-1.1' {current \eTeX\ version} @d eTeX_version=1 { \.{\\eTeXversion} } @d eTeX_revision==".1" { \.{\\eTeXrevision} } @# @d eTeX_banner=='This is e-TeX, Version ', eTeX_version_string, '[PD VMS 3.6f]' {printed when \eTeX\ starts} @# @d TEX==ETEX {change program name into |ETEX|} @# @d TeXXeT_code=0 {the \TeXXeT\ feature is optional} @# @d eTeX_states=1 {number of \eTeX\ state variables in |eqtb|} @z @x l.249 procedure initialize; {this procedure gets things started properly} @y @t\4@>@@/ procedure initialize; {this procedure gets things started properly} @z @x l.292 @d debug==@{ {change this to `$\\{debug}\equiv\null$' when debugging} @d gubed==@t@>@} {change this to `$\\{gubed}\equiv\null$' when debugging} @y @d debug==@{ @d gubed==@t\2@>@} @z @x l.297 @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} @y @d stat== @d tats==@t\2@> @z @x l.309 Parts of the program that are needed in (1) but not in (2) are delimited by the codewords `$|init|\ldots|tini|$'. @d init== {change this to `$\\{init}\equiv\.{@@\{}$' in the production version} @d tini== {change this to `$\\{tini}\equiv\.{@@\}}$' in the production version} @y Parts of the program that are needed in (1) but not in (2) are delimited by the codewords `$|init|\ldots|tini|$'. @d init==if init_flag then begin @d tini==end; @z @x l.321 @ If the first character of a \PASCAL\ comment is a dollar sign, \ph\ treats the comment as a list of ``compiler directives'' that will affect the translation of this program into machine language. The directives shown below specify full checking and inclusion of the \PASCAL\ debugger when \TeX\ is being debugged, but they cause range checking and other redundant code to be eliminated when the production system is being generated. Arithmetic overflow will be detected in all cases. @^system dependencies@> @^Overflow in arithmetic@> @= @{@&$C-,A+,D-@} {no range check, catch arithmetic overflow, no debug overhead} @!debug @{@&$C+,D+@}@+ gubed {but turn everything on when debugging} @y @ When the \PASCAL\ program generated as a result of `tangling' the \.{WEB} with the change file is compiled under VMS, command line qualifiers should be included to specify full checking and inclusion of debugger symbol records whilst \TeX\ is being debugged, but eliminate range checking and other redundant code when the production system is being generated. Arithmetic overflow should be detected in all cases. @^system dependencies@> @^Overflow in arithmetic@> Under VMS, we arrange to `inherit' the descriptions of standard system services and named constants from the precompiled \PASCAL\ environment held in |'SYS$LIBRARY:STARLET.PEN'|---we do \&{not} specify whether or not any specific level of run-time checks shall be included, because any such attribute applied within the source code cannot be overridden by a command line qualifier when \TeX\ is compiled. This library does not include \&{all} the library routines that are used by \TeX82 under VMS, so other routines are declared as required using \PASCAL's syntax for |extern| routines. @f extern==forward @= @/@=[inherit('sys$library:starlet')]@>@\ {allows us to use system symbols and routines} @z @x l.364 @d othercases == others: {default for cases not listed explicitly} @y Fortunately for us, VAX-\PASCAL\ \&{does} support this default mechanism. @d othercases == otherwise {default for cases not listed explicitly} @z @x l.373 @^system dependencies@> @= @!mem_max=30000; {greatest index in \TeX's internal |mem| array; must be strictly less than |max_halfword|; must be equal to |mem_top| in \.{INITEX}, otherwise |>=mem_top|} @!mem_min=0; {smallest index in \TeX's internal |mem| array; must be |min_halfword| or more; must be equal to |mem_bot| in \.{INITEX}, otherwise |<=mem_bot|} @!buf_size=500; {maximum number of characters simultaneously present in current lines of open files and in control sequences between \.{\\csname} and \.{\\endcsname}; 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} @!stack_size=200; {maximum number of simultaneous input sources} @!max_in_open=6; {maximum number of input files and error insertions that can be going on simultaneously} @!font_max=75; {maximum internal font number; must not exceed |max_quarterword| and must be at most |font_base+256|} @!font_mem_size=20000; {number of words of |font_info| for all fonts} @!param_size=60; {maximum number of simultaneous macro parameters} @!nest_size=40; {maximum number of semantic levels simultaneously active} @!max_strings=3000; {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 control sequences and font names, after \TeX'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 fonts and control sequences; must exceed |string_vacancies| by the total length of \TeX's own strings, which is currently about 23000} @!save_size=600; {space for saving values outside of current group; must be at most |max_halfword|} @!trie_size=8000; {space for hyphenation patterns; should be larger for \.{INITEX} than it is in production versions of \TeX} @!trie_op_size=500; {space for ``opcodes'' in the hyphenation patterns} @!dvi_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='TeXformats:TEX.POOL '; {string of length |file_name_size|; tells where the string pool appears} @y Since a number of arrays of |file_name_size| are used in this program to receive the full file specification of files when they are opened, it is necessary to extend this constant to 255, which is the maximum possible size that VAX/RMS can @.RMS@> @^Record Management Services@> return. It is not necessary, however, to pad out |pool_name| to this size (which would in any case prove ``difficult'' in WEB, because VAX-\PASCAL\ automatically pads short strings with spaces when assigned into longer variables. Furthermore, because the area where the pool file resides is specified at run time, we need to make |pool_name| into a variable which will be set shortly after we determine the value of the name of the format area. Here, we define the constant |pool_f_name| instead, which holds the name of the pool file without any area specification. @^system dependencies@> @= @!mem_max=327144; {greatest index in \TeX's internal |mem| array; must be strictly less than |max_halfword|; must be equal to |mem_top| in \.{INITEX}, otherwise |>=mem_top|} @!mem_min=0; {smallest index in \TeX's internal |mem| array; must be |min_halfword| or more; must be equal to |mem_bot| in \.{INITEX}, otherwise |<=mem_bot|} @!buf_size=2048; {maximum number of characters simultaneously present in current lines of open files and in control sequences between \.{\\csname} and \.{\\endcsname}; must not exceed |max_halfword|} @!size_input_line=133; {maximum size of an input line} @!error_line=79; {width of context lines on terminal error messages} @!half_error_line=50; {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} @!stack_size=200; {maximum number of simultaneous input sources} @!max_in_open=12; {maximum number of input files and error insertions that can be going on simultaneously} @!font_max=255; {maximum internal font number; must not exceed |max_quarterword| and must be at most |font_base+256|} @!font_mem_size=81920; {number of words of |font_info| for all fonts} @!param_size=60; {maximum number of simultaneous macro parameters} @!nest_size=40; {maximum number of semantic levels simultaneously active} @!max_strings=15400; {maximum number of strings; must not exceed |max_halfword|} @!string_vacancies=160000; {the minimum number of characters that should be available for the user's control sequences and font names, after \TeX's own error messages are stored} @!pool_size=186000; {maximum number of characters in strings, including all error messages and help texts, and the names of all fonts and control sequences; must exceed |string_vacancies| by the total length of \TeX's own strings, which is currently about 23800 for VMS \TeX\ and 24550 for VMS \eTeX} @!save_size=2000; {space for saving values outside of current group; must be at most |max_halfword|} @!trie_size=45000; {space for hyphenation patterns; should be larger for \.{INITEX} than it is in production versions of \TeX} @# @!trie_op_size=1000; {space for ``opcodes'' in the hyphenation patterns} @!dvi_buf_size=1024; {size of the output buffer; must be a multiple of 8} @!VAX_block_length=512; {must be half |dvi_buf_size| on VMS} @!file_name_size=255; {file names shouldn't be longer than this} @# @!pool_f_name='ETEX.POOL'; {string \&{not} of length |file_name_size|; tells the name of the string pool} @z @x l.427 @d mem_bot=0 {smallest index in the |mem| array dumped by \.{INITEX}; must not be less than |mem_min|} @d mem_top==30000 {largest index in the |mem| array dumped by \.{INITEX}; must be substantially larger than |mem_bot| and not greater than |mem_max|} @d font_base=0 {smallest internal font number; must not be less than |min_quarterword|} @d hash_size=2100 {maximum number of control sequences; it should be at most about |(mem_max-mem_min)/10|} @d hash_prime=1777 {a prime number equal to about 85\pct! of |hash_size|} @d hyph_size=307 {another prime; the number of \.{\\hyphenation} exceptions} @y @d mem_bot=0 {smallest index in the |mem| array dumped by \.{INITEX}; must not be less than |mem_min|} @d mem_top==327144 {largest index in the |mem| array dumped by \.{INITEX}; must be substantially larger than |mem_bot| and not greater than |mem_max|} @d font_base=0 {smallest internal font number; must not be less than |min_quarterword|} @d hash_size=10000 {maximum number of control sequences; it should be at most about |(mem_max-mem_min)/10|} @d hash_prime=8501 {a prime number equal to about 85\pct! of |hash_size|} @d hyph_size=503 {another prime; the number of \.{\\hyphenation} exceptions} @z @x l.719 @^character set dependencies@> @^system dependencies@> @= for i:=0 to @'37 do xchr[i]:=' '; for i:=@'177 to @'377 do xchr[i]:=' '; @y @^character set dependencies@> @^system dependencies@> The code shown here is intended to be used on VMS 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. We also permit characters taken from columns 10--15 of the extended ISO character set; macro packages can then utilize these character codes for multilingual support. @d form_feed=@'14 {ASCII code used at end of a page} @d tab=@'11 @= xchr[0]:=' '; for i:=1 to @'37 do xchr[i]:=chr(i); xchr[form_feed]:=chr(form_feed); xchr[tab]:=chr(tab); for i:=@'177 to @'237 do xchr[i]:=' '; for i:=@'240 to @'377 do xchr[i]:=chr(i); @z @x l.766 The program actually makes use also of a third kind of file, called a |word_file|, when dumping and reloading base information for its own initialization. We shall define a word file later; but it will be possible for us to specify simple operations on word files before they are defined. @y The program actually makes use also of a third kind of file, called a |word_file|, when dumping and reloading base information for its own initialization. We shall define a word file later; but it will be possible for us to specify simple operations on word files before they are defined. Since the \.{WEB} already uses the name |text| for its own purposes, we have to define a macro to permit access to this VAX-\PASCAL\ file type identifier. @d VAX_text==@= text @> @z @x l.773 @!alpha_file=packed file of text_char; {files that contain textual data} @!byte_file=packed file of eight_bits; {files that contain binary data} @y @!alpha_file=VAX_text; {files that contain textual data} @!byte_block=packed array[0..VAX_block_length-1] of eight_bits; @!byte_file=packed file of byte_block; {files that contain binary data} @z @x l.784 implement \TeX\ can open a file whose external name is specified by |name_of_file|. @^system dependencies@> @= @!name_of_file:packed array[1..file_name_size] of char;@;@/ {on some systems this may be a \&{record} variable} @!name_length:0..file_name_size;@/{this many characters are actually relevant in |name_of_file| (the rest are blank)} @y implement \TeX\ can open a file whose external name is specified by |name_of_file|. Any VAX-\PASCAL\ defaults may be supplied in |default_name|; this is used to expand partial file specifications given on such qualifiers as \.{/LOG}, in combination with other parts taken from the file specification of the \.{.TeX} file. @^system dependencies@> @= @!name_of_file, @!default_name:packed array[1..file_name_size] of char;@;@/ {on some systems this may be a \&{record} variable} @!name_length, @!deflt_length : file_size;@/ {this many characters are actually relevant in |name_of_file| (the rest are blank)} @z @x l.794 @ The \ph\ compiler with which the present version of \TeX\ was prepared has extended the rules of \PASCAL\ in a very convenient way. To open file~|f|, we can write $$\vbox{\halign{#\hfil\qquad&#\hfil\cr |reset(f,@t\\{name}@>,'/O')|&for input;\cr |rewrite(f,@t\\{name}@>,'/O')|&for output.\cr}}$$ The `\\{name}' parameter, which is of type `{\bf packed array $[\langle\\{any}\rangle]$ of \\{char}}', stands for the name of the external file that is being opened for input or output. Blank spaces that might appear in \\{name} are ignored. The `\.{/O}' parameter tells the operating system not to issue its own error messages if something goes wrong. If a file of the specified name cannot be found, or if such a file cannot be opened for some other reason (e.g., someone may already be trying to write the same file), we will have |@!erstat(f)<>0| after an unsuccessful |reset| or |rewrite|. This allows \TeX\ to undertake appropriate corrective action. @:PASCAL H}{\ph@> @^system dependencies@> \TeX's file-opening procedures return |false| if no file identified by |name_of_file| could be opened. @d reset_OK(#)==erstat(#)=0 @d rewrite_OK(#)==erstat(#)=0 @p function a_open_in(var f:alpha_file):boolean; @y @ Under VAX-\PASCAL, we can open files with names that are not known at compile time through use of the VAX-specific procedure |open|, which takes too many varied parameters to describe here: for example, the third parameter controls whether a new file shall be generated, or can enforce that an existing file cannot possibly be altered. @^system dependencies@> However, one in particular deserves special mention: the |user_action| parameter when included causes execution of a user-supplied routine which can manipulate the data structures used by RMS (Record Management Services) and thus @.RMS@> @^Record Management Services@> permit finer control over the actions undertaken during the opening or creation of files. All file manipulation procedures in VAX-\PASCAL\ (including |open|/|close|, |read|/|write|, etc.)\ can take an optional parameter which specifies whether or no the program shall continue execution after an error. Since we code to detect such errors, we nearly always make use of this facility. \TeX's file-opening procedures return |false| if no file identified by |name_of_file| could be opened: note that VAX-\PASCAL's |status| function returns zero if the previous file operation was successfully completed, |-1| if |eof| would be |true|, and a positive integer if any error was detected. When a |new| file is opened, we specify that it shall be deleted when the program exits; this ensures that output files are correctly discarded if \TeX\ is interrupted in its work. Later, when the files are closed, we can arrange to keep the files instead. VAX-\PASCAL's |open| procedure also allows us to specify a `default' file specification, which is used to supply defaults for those parts of the specification of the file being created that have not otherwise been provided by the user. Whenever a |word_file| is opened, the variable |fmt_count| is reset to zero to ensure that the first byte of the VAX block is that first accessed. @d VAX_user_action==@=user_action@> @# @d VAX_new==@= new @> @d VAX_readonly==@= readonly @> @# @d VAX_default==@= default @> @# @d VAX_disposition_delete==@=disposition:=delete@> @d VAX_ignore_error==@=error:=continue@> @p function a_open_in(var f:alpha_file):boolean; @z @x l.822 begin reset(f,name_of_file,'/O'); a_open_in:=reset_OK(f); @y begin open(f,name_of_file,VAX_readonly,VAX_user_action:=user_reset, VAX_ignore_error); if status(f)>0 then a_open_in:=false else begin reset(f,VAX_ignore_error); a_open_in:=status(f)<=0; end; @z @x l.827 begin rewrite(f,name_of_file,'/O'); a_open_out:=rewrite_OK(f); @y begin open(f,name_of_file,VAX_new,16383,VAX_disposition_delete, VAX_default:=default_name, VAX_user_action:=user_rewrite,VAX_ignore_error); if status(f)>0 then a_open_out:=false else begin linelimit(f,maxint); rewrite(f,VAX_ignore_error); a_open_out:=status(f)<=0; end; @z @x l.832 begin reset(f,name_of_file,'/O'); b_open_in:=reset_OK(f); @y begin open(f,name_of_file,VAX_readonly,VAX_user_action:=user_reset, VAX_ignore_error); if status(f)>0 then b_open_in:=false else begin reset(f,VAX_ignore_error); b_open_in:=status(f)<=0; end; @z @x l.837 begin rewrite(f,name_of_file,'/O'); b_open_out:=rewrite_OK(f); @y begin open(f,name_of_file,VAX_new,VAX_disposition_delete, VAX_default:=default_name, VAX_user_action:=user_rewrite,VAX_ignore_error); if status(f)>0 then b_open_out:=false else begin rewrite(f,VAX_ignore_error); b_open_out:=status(f)<=0; end; @z @x l.842 begin reset(f,name_of_file,'/O'); w_open_in:=reset_OK(f); @y begin open(f,name_of_file,VAX_readonly,VAX_user_action:=user_reset, VAX_ignore_error); if status(f)>0 then w_open_in:=false else begin reset(f,VAX_ignore_error); w_open_in:=status(f)<=0; end; fmt_count:=0; @z @x l.847 begin rewrite(f,name_of_file,'/O'); w_open_out:=rewrite_OK(f); @y begin open(f,name_of_file,VAX_new,VAX_disposition_delete, VAX_user_action:=user_rewrite,VAX_ignore_error); if status(f)>0 then w_open_out:=false else begin rewrite(f,VAX_ignore_error); w_open_out:=status(f)<=0; end; fmt_count:=0; @z @x l.850 @ Files can be closed with the \ph\ routine `|close(f)|', which @^system dependencies@> should be used when all input or output with respect to |f| has been completed. This makes |f| available to be opened again, if desired; and if |f| was used for output, the |close| operation makes the corresponding external file appear on the user's area, ready to be read. These procedures should not generate error messages if a file is being closed before it has been successfully opened. @p procedure a_close(var f:alpha_file); {close a text file} begin close(f); @y @ Files can be closed with the VAX-\PASCAL\ routine |close(f,disposition,error)|, which @^system dependencies@> should be used when all input or output with respect to |f| has been completed. This makes |f| available to be opened again, if desired; and if |f| was used for output, the |close| operation can make the corresponding external file appear in the user's directory, ready to be read: this depends upon the value of the |disposition| parameter, which can (\\{inter alia}) control whether the file is kept or discarded. If this parameter is not specified, then disposition of the file is determined by the corresponding parameter of the |open| routine. It is through this mechanism that we are able to ensure that all output files are discarded if the operation of \TeX\ is aborted by the user, and yet are kept if the program terminates correctly. These procedures should not generate error messages if a file is being closed before it has been successfully opened; the |error| parameter is used here to ensure that any such errors do not cause run-time failures. @d VAX_disposition_save==@=disposition:=save@> @p procedure a_close(var f:alpha_file); {close a text file} begin close(f,VAX_disposition_save,VAX_ignore_error); @z @x l.865 begin close(f); @y begin close(f,VAX_disposition_save,VAX_ignore_error); @z @x l.869 begin close(f); @y begin close(f,VAX_disposition_save,VAX_ignore_error); @z @x l.885 representing the beginning and ending of a line of text. @= @y representing the beginning and ending of a line of text. On VMS, we will read the lines first into an auxiliary buffer, in order to save the running time of procedure-call overhead. We have to be very careful to handle lines longer than the arbitrarily chosen length of the |aux_buf|. This buffer is declared using a VAX-\PASCAL\ extension for variable length strings, namely the |varying| array type. Such arrays actually appear as if they were a record declared with two fields, thus: |varying [max_size] of = record length: 0..max_size; body: packed array [1..max_size] of|. @d VAX_length(#)==#.@=length@> @d VAX_body(#)==#.@=body@> @f varying==array @= @!aux_buf:varying [size_input_line] of char; {where the characters go first} @z @x l.928 of characters at once, if such routines are available. The following code uses standard \PASCAL\ to illustrate what needs to be done, but finer tuning is often possible at well-developed \PASCAL\ sites. @^inner loop@> @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 @; 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 of characters at once, if such routines are available. The following code uses VAX-\PASCAL\ extensions, such as |varying| strings to perform input of larger amounts of characters with a single input instruction. @^inner loop@> Under VAX-\PASCAL, it is not necessary to take special action to |bypass_eoln|, since the terminator character will be included in those read into the |aux_buf|. @p function input_ln(var f:alpha_file;@!bypass_eoln:boolean):boolean; {inputs the next line or returns |false|} label found; var @!len:integer; {length of line input} @!k:0..buf_size; {index into |buffer|} begin last:=first; {cf.\ Matthew 19\thinspace:\thinspace30} if status(f)<>0 then input_ln:=false else begin while not eoln(f) do begin read(f,aux_buf,VAX_ignore_error); len:=VAX_length(aux_buf); if last+len>=max_buf_stack then begin if last+len; end; for k:=last to last+len-1 do buffer[k]:=xord[aux_buf[k-last+1]]; last:=last+len; end; found: if last>first then if buffer[last-1]=" " then begin decr(last); goto found; end; input_ln:=true; read_ln(f,VAX_ignore_error); end; end; @z @x l.964 @ 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 @ Here is how to open the terminal files under VMS. @^system dependencies@> The standard input and output proces-permanent files \.{SYS\$INPUT} and \.{SYS\$OUTPUT} @.SYS{\$}INPUT@> @.SYS{\$}OUTPUT@> are opened, and the addresses of the associated |FAB| and |RAB| noted so that special actions (such as flushing the input buffer) can be coded. Output occurs without any implicit carriage-control: this permits the output buffer to be flushed to the terminal without terminating the line of output; it is necessary to output the carriage-return, line-feed character pair explicitly when the line is to be terminated. @d VAX_sys_input==@= 'SYS$INPUT' @> @d VAX_sys_output==@= 'SYS$OUTPUT' @> @d VAX_PAS_FAB==@= PAS$FAB@> @d VAX_PAS_RAB==@= PAS$RAB@> @d VAX_carriage_control==@= carriage_control @> @d VAX_none==@= none @> @# @d t_open_in==begin open(term_in,VAX_sys_input); reset(term_in); in_FAB:=VAX_PAS_FAB(term_in); in_RAB:=VAX_PAS_RAB(term_in); end {open the terminal for text input} @d t_open_out==begin open(term_out,VAX_sys_output,VAX_carriage_control:=VAX_none); linelimit(term_out,maxint); rewrite(term_out); out_FAB:=VAX_PAS_FAB(term_out); out_RAB:=VAX_PAS_RAB(term_out); end {open the terminal for text output} @z @x l.982 these operations can be specified in \ph: @^system dependencies@> @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 these operations can be specified on VMS in VAX-\PASCAL\ or DEC-\PASCAL, through manipulation of the data structures maintained in the |RAB| (Record Access Block) @^Record Access Block@> by RMS. @^RMS@> @^Record Management Services@> Since |wake_up_terminal| is only called just before output of an error message, there's no significant overhead in its being a procedure, and this saves 8k bytes of \PASCAL\ source compared with having it as a \.{WEB} definition. @^system dependencies@> To prevent spurious empty writes to the terminal in |batch_mode|, we apply a condition to |update_terminal|. @d VAX_RAB_purge_typeahead== @=RAB$V_PTA@> @d VAX_RAB_cancel_ctrlO== @=RAB$V_CCO@> @# @d update_terminal == if odd(selector) then write_ln(term_out) {empty the terminal output buffer} @d clear_terminal == in_RAB^.VAX_RAB_purge_typeahead:=true {clear the terminal input buffer} @.PTA@> @d crlf == chr(13),chr(10) @# @= procedure wake_up_terminal; begin out_RAB^.VAX_RAB_cancel_ctrlO:=true; write_ln(term_out); out_RAB^.VAX_RAB_cancel_ctrlO:=false; end; {cancel the user's cancellation of output} @.CCO@> @z @x l.1055 @ 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 @^system dependencies@> Since any command line passed to \TeX\ from DCL via \.{LIB\$GET\_VALUE} will have been ``up-cased'', we might consider converting everything to lower-case, so that all (usually lowercase) \TeX\ commands therein can be recognized; of course, any such commands which are named with upper-case letters would be ``ruined''. But, since we are using the full DCL command line interpreter interface, commands consisting of more than one word have to be enclosed in `\.{\char'042}' quotation marks, anyway. The correct case is thus preserved; and file names (which are by far the most common single word arguments) are case insensitive on VMS. Therefore, lower case conversion has not been implemented. @d VAX_cli_present==@= cli$present@> @d VAX_cli_get_value==@= cli$get_value@> @p function init_terminal:boolean; {gets the terminal input started} label exit; var command_line: packed array[1..256] of char; @!len: sixteen_bits; @!i: integer; begin t_open_in; if cmd_line_present then begin VAX_cli_get_value('COMMAND_LINE',command_line,len); i:=1; while (i<=len) and (command_line[i]=' ') do incr(i); if i<=len then begin loc:=first; last:=first; while i<=len do begin buffer[last]:=xord[command_line[i]]; incr(last); incr(i); end; init_terminal:=true; return; end; end; loop@+begin wake_up_terminal; write(term_out,'**'); update_terminal; @.**@> if not input_ln(term_in,true) then {this shouldn't happen} begin write(term_out,crlf); write_ln(term_out,'! End of file on the terminal... why?',crlf); @.End of file on the terminal@> init_terminal:=false; return; end; loc:=first; while (loc; exit:end; tini @y or give an error message and return |false|@>; exit:end; @z @x l.1279 Unprintable characters of codes 128--255 are, similarly, rendered \.{\^\^80}--\.{\^\^ff}. @y Unprintable characters of codes 128--159 and 255 are, similarly, rendered \.{\^\^80}--\.{\^\^9f} and \.{\^\^ff}. We will print characters in the range 160--254 if the qualifier \.{/EIGHT\_BIT} is used. @./EIGHT_BIT@> @z @x l.1296 (k<" ")or(k>"~") @y (k<" ")or((k>"~")and (k<160))or(k=255)or((k>=160)and not eight_qual) @z @x l.1298 @ When the \.{WEB} system program called \.{TANGLE} processes the \.{TEX.WEB} description that you are now reading, it outputs the \PASCAL\ program \.{TEX.PAS} and also a string pool file called \.{TEX.POOL}. The \.{INITEX} @.WEB@>@.INITEX@> @y @ When the \.{WEB} system program called \.{TANGLE} processes the \.{TEX.WEB} description that you are now reading, together with the merged change file for your system's implementation of \eTeX, it should output the \PASCAL\ program \.{ETEX.PAS} and also a string pool file called \.{ETEX.POOL}. The \.{EINITEX} @.WEB@>@.EINITEX@> @z @x l.1307 @!init @!pool_file:alpha_file; {the string-pool file output by \.{TANGLE}} tini @y @!pool_file:alpha_file; {the string-pool file output by \.{TANGLE}} @z @x l.1310 @ @d bad_pool(#)==begin wake_up_terminal; write_ln(term_out,#); @y @ As noted before, it is not necessary for the string |pool_name| to have the same length as the |array name_of_file|, because VAX-\PASCAL\ automatically pads such shorter strings with spaces when an assignment is made into a longer string variable. @d bad_pool(#)==begin wake_up_terminal; write_ln(term_out,#,crlf); @z @x l.1322 else bad_pool('! I can''t read TEX.POOL.') @.I can't read TEX.POOL@> @y else bad_pool('! I can''t read ETEX.POOL.') @.I can't read ETEX.POOL@> @z @x l.1326 begin if eof(pool_file) then bad_pool('! TEX.POOL has no check sum.'); @.TEX.POOL has no check sum@> @y begin if eof(pool_file) then bad_pool('! ETEX.POOL has no check sum.'); @.ETEX.POOL has no check sum@> @z @x l.1332 bad_pool('! TEX.POOL line doesn''t begin with two digits.'); @.TEX.POOL line doesn't...@> @y bad_pool('! ETEX.POOL line doesn''t begin with two digits.'); @.ETEX.POOL line doesn't...@> @z @x l.1346 @ The \.{WEB} operation \.{@@\$} denotes the value that should be at the end of this \.{TEX.POOL} file; any other value means that the wrong pool @y @ The \.{WEB} operation \.{@@\$} denotes the value that should be at the end of this \.{ETEX.POOL} file; any other value means that the wrong pool @z @x l.1354 bad_pool('! TEX.POOL check sum doesn''t have nine digits.'); @.TEX.POOL check sum...@> @y bad_pool('! ETEX.POOL check sum doesn''t have nine digits.'); @.ETEX.POOL check sum...@> @z @x l.1360 done: if a<>@$ then bad_pool('! TEX.POOL doesn''t match; TANGLE me again.'); @.TEX.POOL doesn't match@> @y done: if a<>@$ then bad_pool('! ETEX.POOL doesn''t match; TANGLE me again.'); @.ETEX.POOL doesn't match@> @z @x l.1387 \hang |new_string|, appends the output to the current string in the string pool. @y \hang |new_string|, appends the output to the current string in the string pool. \hang |edcmd_write|, prints the characters into the |edcmd_file| only. @z @x l.1393 \noindent The symbolic names `|term_and_log|', etc., have been assigned numeric codes that satisfy the convenient relations |no_print+1=term_only|, |no_print+2=log_only|, |term_only+2=log_only+1=term_and_log|. @y \noindent The symbolic names `|term_and_log|', etc., have been assigned numeric codes that satisfy the convenient relations |no_print+1=term_only|, |no_print+2=log_only|, |term_only+2=log_only+1=term_and_log|. The interface to various ``callable editors'', described toward the end of this program, necessitates the use of a separate output file (in addition to \TeX's 16 auxiliary output streams), |edcmd_file|, which is declared here, together with the associated value |edcmd_write| of the global |selector| variable. @z @x l.1411 @d max_selector=21 {highest selector setting} @= @!log_file : alpha_file; {transcript of \TeX\ session} @y @d edcmd_write=22 {printing is deflected to the |edcmd_file|} @d max_selector=22 {highest selector setting} @= @!log_file : alpha_file; {transcript of \TeX\ session} @!edcmd_file : alpha_file; {command file used when invoking some editors} @z @x l.1433 by changing |wterm|, |wterm_ln|, and |wterm_cr| in this section. @^system dependencies@> @d wterm(#)==write(term_out,#) @d wterm_ln(#)==write_ln(term_out,#) @d wterm_cr==write_ln(term_out) @d wlog(#)==write(log_file,#) @d wlog_ln(#)==write_ln(log_file,#) @d wlog_cr==write_ln(log_file) @y by changing |wterm|, |wterm_ln|, and |wterm_cr| in this section. @^system dependencies@> We also introduce here analogous macros for writing to the |diag_file|, which is used to generate diagnostic messages for use in conjunction with DEC's Language-sensitive editor (LSEdit). @^Language-sensitive editor@> @^LSE@> Yet another set of macros is concerned with writing to |temp_file|, which is a purely internal file, used to concatenate the various elements of \TeX's error messages for use in diagnostic and other files. @d wterm(#)==write(term_out,#) @d wterm_ln(#)==write_ln(term_out,#,crlf) @d wterm_cr==write_ln(term_out,crlf) @d wlog(#)==if log_qual then write(log_file,#) @d wlog_ln(#)==if log_qual then write_ln(log_file,#) @d wlog_cr==if log_qual then write_ln(log_file) @d wdiag(#)==if diag_qual then write(diag_file,#) @d wdiag_ln(#)==if diag_qual then write_ln(diag_file,#) @d wdiag_cr==if diag_qual then write_ln(diag_file) @d wtemp(#)==write(temp_file,#) @d wtemp_ln(#)==write_ln(temp_file,#) @d wtemp_cr==write_ln(temp_file) @= procedure diag_char(@!s:ASCII_code); var ch : char; begin ch := xchr[s]; wdiag(ch); if ch='"' then wdiag(ch) end; @# procedure temp_char(@!s:ASCII_code); var ch : char; begin ch := xchr[s]; wtemp(ch); if ch='"' then wtemp(ch) end; @# procedure diag_print( s : integer); var j : pool_pointer; begin j:=str_start[s]; while j < str_start[s+1] do begin diag_char(so(str_pool[j])); incr(j) end; end; @z @x l.1455 no_print,pseudo,new_string: do_nothing; @y no_print,pseudo,new_string: do_nothing; edcmd_write: write_ln(edcmd_file); @z @x l.1460 @ The |print_char| procedure sends one character to the desired destination, using the |xchr| array to map it into an external character compatible with |input_ln|. All printing comes through |print_ln| or |print_char|. @= procedure print_char(@!s:ASCII_code); {prints a single character} label exit; begin if @ then if selector= procedure print_char(@!s:ASCII_code); {prints a single character} label exit; begin if @ then if selector; case selector of term_and_log: begin wterm(xchr[s]); wlog(xchr[s]); incr(term_offset); incr(file_offset); if term_offset=max_print_line then begin wterm_cr; term_offset:=0; end; if file_offset=max_print_line then begin wlog_cr; file_offset:=0; end; end; log_only: begin wlog(xchr[s]); incr(file_offset); if file_offset=max_print_line then print_ln; end; term_only: begin wterm(xchr[s]); incr(term_offset); if term_offset=max_print_line then print_ln; end; no_print: do_nothing; pseudo: if tally} @y in_RAB^.VAX_RAB_purge_typeahead:=false; {turn off purging of typeahead} @.PTA@> term_offset:=0; {the user's line ended with \<\rm return>} @z @x l.1726 @ The global variable |interaction| has four settings, representing increasing amounts of user interaction: @y @ The global variable |interaction| has four settings, representing increasing amounts of user interaction: This version of \TeX\ can generate a diagnostics file for use with the \.{REVIEW} mode of DEC's Language-sensitive editor (LSEdit). @^Language-sensitive editor@> So whenever \TeX\ starts to generate an error message, we arrange for the text which is sent to the terminal and/or the transcript file to be copied also into our |temp_file|. @z @x l.1733 @d print_err(#)==begin if interaction=error_stop_mode then wake_up_terminal; print_nl("! "); print(#); end @= @!interaction:batch_mode..error_stop_mode; {current level of interaction} @y @d print_err(#)==begin if interaction=error_stop_mode then wake_up_terminal; print_nl("! "); copy_err:=save_it; rewrite(temp_file); print(#); {Other |print|s will add to |temp_file|} end @= @!interaction:batch_mode..error_stop_mode; {current level of interaction} @z @x l.1857 @ Here now is the general |error| routine. @y @ Here now is the general |error| routine, which completes output of the error message. @z @x l.1865 begin if history; @y begin if history; show_context; if interaction=error_stop_mode then @; @z @x l.1886 @ It is desirable to provide an `\.E' option here that gives the user an easy way to return from \TeX\ to the system editor, with the offending line ready to be edited. But such an extension requires some system wizardry, so the present implementation simply types out the name of the file that should be edited and the relevant line number. @^system dependencies@> @y @ It is desirable to provide an `\.E' option here that gives the user an easy way to return from \TeX\ to the system editor, with the offending line ready to be edited. This version of \TeX\ invokes callable versions of various DEC editors, depending upon the value of the \.{/EDITOR} switch (normally set to \.{TEX\_EDIT}), @./EDITOR@> @.TEX_EDIT@> including \.{EDT}, \.{TPU}, DEC's Language-sensitive editor (LSEdit), and even @^EDT@> @^TPU@> @^Language-sensitive editor@> @^LSE@> \.{TECO}. @^TECO@> Other editors may be run in a sub-process by setting \.{/EDITOR} to any DCL command, including activating a command procedure. In addition, if the \.{/CONTINUE} qualifier is present on the command line, @./CONTINUE@> \TeX\ will continue processing after returning from the editor. @^system dependencies@> @z @x l.1903 "E": if base_ptr>0 then begin print_nl("You want to edit file "); @.You want to edit file x@> slow_print(input_stack[base_ptr].name_field); print(" at line "); print_int(line); interaction:=scroll_mode; jump_out; end; @y "E": if base_ptr>0 then begin if edit_file(input_stack[base_ptr],line) then begin if continue_qual then begin show_context; goto continue; end else begin interaction:=scroll_mode; jump_out; end end else begin print_nl("You want to edit file "); @.You want to edit file x@> slow_print(input_stack[base_ptr].name_field); print(" at line "); print_int(line); interaction:=scroll_mode; jump_out; end end; @z @x l.1935 @= begin error_count:=0; interaction:=batch_mode+c-"Q"; print("OK, entering "); case c of "Q":begin print_esc("batchmode"); decr(selector); end; "R":print_esc("nonstopmode"); "S":print_esc("scrollmode"); end; {there are no other cases} print("..."); print_ln; update_terminal; return; end @y @= begin error_count:=0; interaction:=batch_mode+c-"Q"; print("OK, entering "); case c of "Q":print_esc("batchmode"); "R":print_esc("nonstopmode"); "S":print_esc("scrollmode"); end; {there are no other cases} print("..."); print_ln; update_terminal; if c = "Q" then decr (selector); return; end @z @x l.2085 @ Users occasionally want to interrupt \TeX\ while it's running. If the \PASCAL\ runtime system allows this, one can implement a routine that sets the global variable |interrupt| to some nonzero value when such an interrupt is signalled. Otherwise there is probably at least a way to make |interrupt| nonzero using the \PASCAL\ debugger. @^system dependencies@> @^debugging@> @y @ Users occasionally want to interrupt \TeX\ while it's running. By using a VAX system service, we can declare an Asynchronous System Trap (AST) handler which will be called when the user types \.{Ctrl-C}. The AST handler then sets the global variable |interrupt| to some nonzero value when such an interrupt is signalled. @^system dependencies@> Since this variable may be changed at any time, we must prevent the compiler from applying optimizations to the code related to this variable (for example, it would not do for it to be held in a machine register), so we give it the VAX-\PASCAL\ `attribute' \.{volatile}, which is defined at this point. We also define a couple of other attributes that may be applied to affect the placement of variables under VAX-\PASCAL. Assuming that it's possible to assign an I/O channel to device \.{SYS\$COMMAND}, @.SYS{\$}COMMAND@> which should be the case provided the program is being run interactively, then the Control-C handler is declared by a call of the \.{\$QIOW} system service. @.{\$}QIOW@> Some parameters for this system service have to passed by the `immediate' parameter-passing mechanism; we take this opportunity to define all the means used in \TeX\ to override VAX-\PASCAL's default parameter-passing mechanisms. @d VAX_volatile==@= volatile @> @d VAX_unsafe==@= unsafe @> @d VAX_aligned==@= aligned @> @d VAX_static==@= static @> @# @d VAX_immed==@= %immed @> @d VAX_stdescr==@= %stdescr @> @d VAX_ref==@= %ref @> @# @d VAX_io_setmode==@= io$_setmode @> @d VAX_iom_ctrlcast==@= io$m_ctrlcast @> @# @d VAX_qiow==@= $qiow@> @d VAX_assign==@= $assign@> @# @z @x l.2093 @d check_interrupt==begin if interrupt<>0 then pause_for_instructions; end @= @!interrupt:integer; {should \TeX\ pause for instructions?} @y @d check_interrupt==begin if interrupt<>0 then pause_for_instructions; end @d enable_control_C== VAX_qiow(,tt_chan,VAX_io_setmode+VAX_iom_ctrlcast,,,, VAX_immed ctrlc_rout,,VAX_immed 3,,,); @= @!interrupt: [VAX_volatile] integer; {should \TeX\ pause for instruction?} @z @x l.2101 interrupt:=0; OK_to_interrupt:=true; @y interrupt:=0; OK_to_interrupt:=true; if VAX_assign('SYS$COMMAND',tt_chan,,)=VAX_ss_normal then enable_control_C; @z @x l.2345 @d set_glue_ratio_zero(#) == #:=0.0 {store the representation of zero ratio} @d set_glue_ratio_one(#) == #:=1.0 {store the representation of unit ratio} @d float(#) == # {convert from |glue_ratio| to type |real|} @d unfloat(#) == # {convert from |real| to type |glue_ratio|} @d float_constant(#) == #.0 {convert |integer| constant to |real|} @= @!glue_ratio=real; {one-word representation of a glue expansion factor} @y On VMS, we use some hackery to cause floating point numbers stored in |mem| to be |single|, but other |real| variables and expressions are done as |double| length reals. @d set_glue_ratio_zero(#) == #:=0.0 {store the representation of zero ratio} @d set_glue_ratio_one(#) == #:=1.0 {store the representation of unit ratio} @d real == double {use double precision reals for computation} @d float(#) == dble(#) {convert from |glue_ratio| to type |real|} {FIX ME} @d unfloat(#) == sngl(1.0@&D0 * #) {convert from |real| to type |glue_ratio|} @d float_constant(#) == #.0@&D0 {convert |integer| constant to |real|} @= @!glue_ratio=r@&e@&a@&l; {one-word representation of a glue expansion factor} @z @x l.2405 @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 @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==327144+1 {largest allowable value in a |halfword|} @z @x l.2438 @d qi(#)==#+min_quarterword {to put an |eight_bits| item into a quarterword} @d qo(#)==#-min_quarterword {to take an |eight_bits| item out of a quarterword} @d hi(#)==#+min_halfword {to put a sixteen-bit item into a halfword} @d ho(#)==#-min_halfword {to take a sixteen-bit item from a halfword} @y @d qi(#)==# @d qo(#)==# @d hi(#)==# @d ho(#)==# @z @x l.2469 @!memory_word = record@;@/ case four_choices of 1: (@!int:integer); 2: (@!gr:glue_ratio); 3: (@!hh:two_halves); 4: (@!qqqq:four_quarters); end; @y @!memory_word = packed record@;@/ case four_choices of 1: (@!int:integer); 2: (@!gr:glue_ratio); 3: (@!hh:two_halves); 4: (@!qqqq:four_quarters); end; @z @x l.2476 @!word_file = file of memory_word; @y @!word_block = packed array[0..VAX_block_length-1] of memory_word; @!word_file = packed file of word_block; @z @x l.2778 @p @!init procedure sort_avail; {sorts the available variable-size nodes by location} @y @p procedure sort_avail; {sorts the available variable-size nodes by location} @z @x l.2792 tini @y @z @x l.3069 the amount of surrounding space inserted by \.{\\mathsurround}. @y the amount of surrounding space inserted by \.{\\mathsurround}. In addition a |math_node| with |subtype>after| and |width=0| will be (ab)used to record a regular |math_node| reinserted after being discarded at a line break or one of the text direction primitives ( \.{\\beginL}, \.{\\endL}, \.{\\beginR}, and \.{\\endR} ). @z @x l.3073 @d after=1 {|subtype| for math node that winds up a formula} @y @d after=1 {|subtype| for math node that winds up a formula} @# @d M_code=2 @d begin_M_code=M_code+before {|subtype| for \.{\\beginM} node} @d end_M_code=M_code+after {|subtype| for \.{\\endM} node} @d L_code=4 @d begin_L_code=L_code+begin_M_code {|subtype| for \.{\\beginL} node} @d end_L_code=L_code+end_M_code {|subtype| for \.{\\endL} node} @d R_code=L_code+L_code @d begin_R_code=R_code+begin_M_code {|subtype| for \.{\\beginR} node} @d end_R_code=R_code+end_M_code {|subtype| for \.{\\endR} node} @# @d end_LR(#)==odd(subtype(#)) @d end_LR_type(#)==(L_code*(subtype(#) div L_code)+end_M_code) @d begin_LR_type(#)==(#-after+before) @z @x l.3546 math_node: print_char("$"); @y math_node: if subtype(p)>=L_code then print("[]") else print_char("$"); @z @x l.3713 begin print(", shifted "); print_scaled(shift_amount(p)); end; @y begin print(", shifted "); print_scaled(shift_amount(p)); end; if eTeX_ex then @; @z @x l.3735 arbitrary random value. The following code assumes that a properly formed nonzero |real| number has absolute value $2^{20}$ or more when it is regarded as an integer; this precaution was adequate to prevent floating point underflow on the author's computer. @y arbitrary random value. The following WEB macro simulates VAX/DEC-\PASCAL's predeclared routine |undefined|, which returns |true| if its argument is not a properly constituted |real| number. (The real function cannot be used, because it is currently unsupported on DEC-\PASCAL\ for AXP.) @d VAX_undefined(#)== (#::VAX_F_float.Sign and (#::VAX_F_float.Expo = 0)) @z @x l.3747 if abs(mem[p+glue_offset].int)<@'4000000 then print("?.?") @y if VAX_undefined(glue_set(p)) then print("?.?") @z @x l.3811 begin print_esc("math"); @y if subtype(p)>after then begin if end_LR(p) then print_esc("end") else print_esc("begin"); if subtype(p)>R_code then print_char("R") else if subtype(p)>L_code then print_char("L") else print_char("M"); end else begin print_esc("math"); @z @x l.3844 begin print_esc("mark"); print_mark(mark_ptr(p)); @y begin print_esc("mark"); if subtype(p)>min_quarterword then begin print_char("s"); print_int(qo(subtype(p))); end; print_mark(mark_ptr(p)); @z @x l.4091 @d valign=33 {vertical table alignment ( \.{\\valign} )} @y @d valign=33 {vertical table alignment ( \.{\\valign} )} {or text direction directives ( \.{\\beginL}, etc.~)} @z @x l.4107 @d left_right=49 {variable delimiter ( \.{\\left}, \.{\\right} )} @y @d left_right=49 {variable delimiter ( \.{\\left}, \.{\\right} )} {or \.{\\middle}} @z @x l.4151 \.{\\insertpenalties} )} @y \.{\\insertpenalties} )} {or \.{\\interactionmode}} @z @x l.4163 @d prefix=93 {qualify a definition ( \.{\\global}, \.{\\long}, \.{\\outer} )} @y @d prefix=93 {qualify a definition ( \.{\\global}, \.{\\long}, \.{\\outer} )} {or \.{\\protected}} @z @x l.4166 @d read_to_cs=96 {read into a control sequence ( \.{\\read} )} @y @d read_to_cs=96 {read into a control sequence ( \.{\\read} )} {or \.{\\readline}} @z @x l.4181 @d input=max_command+4 {input a source file ( \.{\\input}, \.{\\endinput} )} @y @d input=max_command+4 {input a source file ( \.{\\input}, \.{\\endinput} )} {or \.{\\scantokens}} @z @x l.4186 @d the=max_command+9 {expand an internal quantity ( \.{\\the} )} @y @d the=max_command+9 {expand an internal quantity ( \.{\\the} )} {or \.{\\unexpanded}, \.{\\detokenize}} @z @x l.4289 user's output routine. @y user's output routine. A seventh quantity, |eTeX_aux|, is used by the extended features \eTeX. In vertical modes it is known as |LR_save| and holds the LR stack when a paragraph is interrupted by a displayed formula. In display math mode it is known as |LR_box| and holds a pointer to a prototype box for the display. In math mode it is known as |delim_ptr| and points to the most recent |left_noad| or |middle_noad| of a |math_left_group|. @z @x l.4304 @!head_field,@!tail_field: pointer; @y @!head_field,@!tail_field: pointer; @!eTeX_aux_field: pointer; @z @x l.4311 @d tail==cur_list.tail_field {final node on current list} @y @d tail==cur_list.tail_field {final node on current list} @d eTeX_aux==cur_list.eTeX_aux_field {auxiliary data for \eTeX} @d LR_save==eTeX_aux {LR stack when a paragraph is interrupted} @d LR_box==eTeX_aux {prototype box for display} @d delim_ptr==eTeX_aux {most recent left or right noad of a math left group} @z @x l.4342 mode:=vmode; head:=contrib_head; tail:=contrib_head; @y mode:=vmode; head:=contrib_head; tail:=contrib_head; eTeX_aux:=null; @z @x l.4358 incr(nest_ptr); head:=get_avail; tail:=head; prev_graf:=0; mode_line:=line; @y incr(nest_ptr); head:=get_avail; tail:=head; prev_graf:=0; mode_line:=line; eTeX_aux:=null; @z @x l.4720 @d err_help_loc=local_base+9 {points to token list for \.{\\errhelp}} @d toks_base=local_base+10 {table of 256 token list registers} @y @d every_eof_loc=local_base+9 {points to token list for \.{\\everyeof}} @d err_help_loc=local_base+10 {points to token list for \.{\\errhelp}} @d toks_base=local_base+11 {table of 256 token list registers} @z @x l.4787 othercases print_esc("errhelp") @y @/@@/ othercases print_esc("errhelp") @z @x l.4820 cat_code("\"):=escape; cat_code("%"):=comment; @y cat_code("\"):=escape; cat_code("%"):=comment; cat_code(form_feed):=car_ret; @z @x l.4955 @d count_base=int_base+int_pars {256 user \.{\\count} registers} @y @d tracing_assigns_code=int_pars {show assignments} @d tracing_groups_code=int_pars+1 {show save/restore groups} @d tracing_ifs_code=int_pars+2 {show conditionals} @d tracing_scan_tokens_code=int_pars+3 {show pseudo file open and close} @d pre_display_direction_code=int_pars+4 {text direction preceding a display} @d eTeX_state_code=int_pars+5 {\eTeX\ state variables} @d count_base=int_base+eTeX_state_code+eTeX_states {256 user \.{\\count} registers} @z @x l.5016 @d error_context_lines==int_par(error_context_lines_code) @y @d error_context_lines==int_par(error_context_lines_code) @d tracing_assigns==int_par(tracing_assigns_code) @d tracing_groups==int_par(tracing_groups_code) @d tracing_ifs==int_par(tracing_ifs_code) @d tracing_scan_tokens==int_par(tracing_scan_tokens_code) @d pre_display_direction==int_par(pre_display_direction_code) @z @x l.5081 othercases print("[unknown integer parameter!]") @y @/@@/ othercases print("[unknown integer parameter!]") @z @x l.5222 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. @p procedure fix_date_and_time; begin time:=12*60; {minutes since midnight} day:=4; {fourth day of the month} month:=7; {seventh month of the year} year:=1776; {Anno Domini} @y The requisite information is obtained through a call of the \.{\$NUMTIM} system @.{\$}NUMTIM@> service. @d VAX_numtim==@= $numtim@> @p procedure fix_date_and_time; var t:array[1..7] of signed_halfword; {raw year, month, day and time} begin VAX_numtim(t); year:=t[1]; month:=t[2]; day:=t[3]; time:=t[4]*60+t[5]; {minutes since midnight} @z @x l.5610 @p @!init procedure primitive(@!s:str_number;@!c:quarterword;@!o:halfword); @y @p procedure primitive(@!s:str_number;@!c:quarterword;@!o:halfword); @z @x l.5612 @!j:small_number; {index into |buffer|} @y @!j:0..buf_size; {index into |buffer|} @z @x l.5616 {we will move |s| into the (empty) |buffer|} for j:=0 to l-1 do buffer[j]:=so(str_pool[k+j]); cur_val:=id_lookup(0,l); {|no_new_control_sequence| is |false|} @y {we will move |s| into the (possibly non-empty) |buffer|} if first+l>buf_size+1 then overflow("buffer size",buf_size); @:TeX capacity exceeded buffer size}{\quad buffer size@> for j:=0 to l-1 do buffer[first+j]:=so(str_pool[k+j]); cur_val:=id_lookup(first,l); {|no_new_control_sequence| is |false|} @z @x l.5621 eq_level(cur_val):=level_one; eq_type(cur_val):=c; equiv(cur_val):=o; end; tini @y eq_level(cur_val):=level_one; eq_type(cur_val):=c; equiv(cur_val):=o; end; @z @x l.5741 expand_after: print_esc("expandafter"); @y expand_after: if chr_code=0 then print_esc("expandafter") @; @z @x l.5747 mark: print_esc("mark"); @y mark: begin print_esc("mark"); if chr_code>0 then print_char("s"); end; @z @x l.5758 read_to_cs: print_esc("read"); @y read_to_cs: if chr_code=0 then print_esc("read") @; @z @x l.5763 the: print_esc("the"); @y the: if chr_code=0 then print_esc("the") @; @z @x l.5766 valign: print_esc("valign"); @y valign: if chr_code=0 then print_esc("valign")@/ @; @z @x l.5820 the entries for that group. @y the entries for that group. Furthermore, in extended \eTeX\ mode, |save_stack[p-1]| contains the source line number at which the current level of grouping was entered. @z @x l.5830 @d level_boundary=3 {|save_type| corresponding to beginning of group} @y @d level_boundary=3 {|save_type| corresponding to beginning of group} @p@t\4@>@ @z @x l.5916 begin check_full_save_stack; @y begin check_full_save_stack; if eTeX_ex then begin saved(0):=line; incr(save_ptr); end; @z @x l.5923 cur_boundary:=save_ptr; incr(cur_level); incr(save_ptr); cur_group:=c; @y cur_boundary:=save_ptr; cur_group:=c; @!stat if tracing_groups>0 then group_trace(false);@+tats@;@/ incr(cur_level); incr(save_ptr); @z @x l.5967 begin if eq_level(p)=cur_level then eq_destroy(eqtb[p]) else if cur_level>level_one then eq_save(p,eq_level(p)); eq_level(p):=cur_level; eq_type(p):=t; equiv(p):=e; @y begin @!stat if tracing_assigns>0 then restore_trace(p,"changing"); tats@;@/ begin if eq_level(p)=cur_level then eq_destroy(eqtb[p]) else if cur_level>level_one then eq_save(p,eq_level(p)); eq_level(p):=cur_level; eq_type(p):=t; equiv(p):=e; end; @!stat if tracing_assigns>0 then restore_trace(p,"into"); tats@;@/ @z @x l.5980 eqtb[p].int:=w; @y @!stat if tracing_assigns>0 then restore_trace(p,"changing"); tats@;@/ eqtb[p].int:=w; @!stat if tracing_assigns>0 then restore_trace(p,"into"); tats@;@/ @z @x l.5990 begin eq_destroy(eqtb[p]); eq_level(p):=level_one; eq_type(p):=t; equiv(p):=e; @y begin @!stat if tracing_assigns>0 then restore_trace(p,"globally changing"); tats@;@/ begin eq_destroy(eqtb[p]); eq_level(p):=level_one; eq_type(p):=t; equiv(p):=e; end; @!stat if tracing_assigns>0 then restore_trace(p,"into"); tats@;@/ @z @x l.5995 begin eqtb[p].int:=w; xeq_level[p]:=level_one; @y begin @!stat if tracing_assigns>0 then restore_trace(p,"globally changing"); tats@;@/ begin eqtb[p].int:=w; xeq_level[p]:=level_one; end; @!stat if tracing_assigns>0 then restore_trace(p,"into"); tats@;@/ @z @x l.6012 @p@t\4@>@@;@/ @y @p @z @x l.6041 done: cur_group:=save_level(save_ptr); cur_boundary:=save_index(save_ptr) @y done: @!stat if tracing_groups>0 then group_trace(true);@+tats@;@/ cur_group:=save_level(save_ptr); cur_boundary:=save_index(save_ptr); if eTeX_ex then decr(save_ptr) @z @x l.6067 @ @= @y @ @= @z @x l.6155 @d end_match_token=@'7000 {$2^8\cdot|end_match|$} @y @d end_match_token=@'7000 {$2^8\cdot|end_match|$} @d protected_token=@'7001 {$2^8\cdot|end_match|+1$} @z @x l.6280 end_match: print("->"); @y end_match: if c=0 then print("->"); @z @x l.6301 else if cur_cmd=top_bot_mark then begin print_char(":"); print_ln; @y else if (cur_cmd=top_bot_mark)and(cur_chr0 then if cur_cmd>=if_test then if cur_cmd<=fi_or_else then begin print(": "); if cur_cmd=fi_or_else then begin print_cmd_chr(if_test,cur_if); print_char(" "); n:=0; l:=if_line; end else begin n:=1; l:=line; end; p:=cond_ptr; while p<>null do begin incr(n); p:=link(p); end; print_int(n); if l<>0 then begin print(" on line "); print_int(l); end; end; print_char("}"); @z @x l.6475 the terminal, under control of the procedure |read_toks|.) @y the terminal, under control of the procedure |read_toks|.) Finally |18<=name<=19| indicates that we are reading a pseudo file created by the \.{\\scantokens} command. @z @x l.6538 @!line_stack : array[1..max_in_open] of integer; @y @!line_stack : array[1..max_in_open] of integer; @!eof_seen : array[1..max_in_open] of boolean; {has eof been seen?} @z @x l.6692 @d mark_text=14 {|token_type| code for \.{\\topmark}, etc.} @d write_text=15 {|token_type| code for \.{\\write}} @y @d every_eof_text=14 {|token_type| code for \.{\\everyeof}} @d mark_text=15 {|token_type| code for \.{\\topmark}, etc.} @d write_text=16 {|token_type| code for \.{\\write}} @z @x l.6743 @@/ begin base_ptr:=input_ptr; input_stack[base_ptr]:=cur_input; {store current state} @y @@/ begin base_ptr:=input_ptr; input_stack[base_ptr]:=cur_input; {store current state} @; @z @x l.6749 if (name>17) or (base_ptr=0) then bottom_line:=true; @y if (name>19) or (base_ptr=0) then bottom_line:=true; @z @x l.6758 done: cur_input:=input_stack[input_ptr]; {restore original state} end; @y done: cur_input:=input_stack[input_ptr]; {restore original state} @; end; @z @x l.6785 @= if name<=17 then if terminal_input then if base_ptr=0 then print_nl("<*>") else print_nl(" ") else begin print_nl(" print_char(">"); end @y @= if name<=17 then begin @; if terminal_input then if base_ptr=0 then print_nl("<*>") else print_nl(" ") else begin print_nl(" print_char(">"); end; @; end @z @x l.6794 else begin print_nl("l."); print_int(line); end; @y else if index<>in_open then {input from a pseudo file} begin print_nl("l."); print_int(line_stack[index+1]); end else begin print_nl("l."); print_int(line); @; end; @z @x l.6798 @ @= case token_type of parameter: print_nl(" "); @y @ @= @; case token_type of parameter: print_nl(" "); @z @x l.6814 every_cr_text: print_nl(" "); @y every_cr_text: print_nl(" "); every_eof_text: print_nl(" "); @z @x l.6815 mark_text: print_nl(" "); write_text: print_nl(" "); othercases print_nl("?") {this should never happen} endcases @y mark_text: print_nl(" "); write_text: print_nl(" "); othercases print_nl("?") {this should never happen} endcases; @ @z @x l.6874 @= if trick_count=1000000 then set_trick_count; {|set_trick_count| must be performed} if tallyerror_line then print("...") @y @= if trick_count=1000000 then set_trick_count; {|set_trick_count| must be performed} if tally; if m+n<=error_line then p:=first_count+m else p:=first_count+(error_line-n-3); for q:=first_count to p-1 do print_char(trick_buf[q mod error_line]); if m+n>error_line then print("..."); @ @z @x l.6896 @= begin_pseudoprint; if buffer[limit]=end_line_char then j:=limit else j:=limit+1; {determine the effective end of the line} if j>0 then for i:=start to j-1 do begin if i=loc then set_trick_count; print(buffer[i]); end @y @= begin_pseudoprint; if buffer[limit]=end_line_char then j:=limit else j:=limit+1; {determine the effective end of the line} if j>0 then for i:=start to j-1 do begin if i=loc then set_trick_count; print(buffer[i]); end; if name<=17 then @ @z @x l.6905 @ @= begin_pseudoprint; if token_type= begin_pseudoprint; if token_type; @z @x l.7037 incr(in_open); push_input; index:=in_open; @y incr(in_open); push_input; index:=in_open; eof_seen[index]:=false; @z @x l.7047 if name>17 then a_close(cur_file); {forget it} @y if (name=18)or(name=19) then pseudo_close else if name>17 then a_close(cur_file); {forget it} @z @x l.7538 if not force_eof then @y if not force_eof then if name<=19 then begin if pseudo_input then {not end of file} firm_up_the_line {this sets |limit|} else if (every_eof<>null)and not eof_seen[index] then begin limit:=first-1; eof_seen[index]:=true; {fake one empty line} begin_token_list(every_eof,every_eof_text); goto restart; end else force_eof:=true; end else @z @x l.7541 else force_eof:=true; @y else if (every_eof<>null)and not eof_seen[index] then begin limit:=first-1; eof_seen[index]:=true; {fake one empty line} begin_token_list(every_eof,every_eof_text); goto restart; end else force_eof:=true; @z @x l.7544 begin print_char(")"); decr(open_parens); update_terminal; {show user that file has been read} @y begin if name>=19 then begin print_char(")"); decr(open_parens); update_terminal; {show user that file has been read} end; @z @x l.7630 @t\4@>@@;@/ @y @t\4@>@@;@/ @t\4@>@@;@/ @z @x l.7637 procedure expand; @y procedure expand; label reswitch; @z @x l.7648 if cur_cmd @y reswitch: if cur_cmd @z @x l.7659 expand_after:@; @y expand_after:if cur_chr=0 then @ else @; @z @x l.7779 input: if chr_code=0 then print_esc("input")@+else print_esc("endinput"); @y input: if chr_code=0 then print_esc("input") @/@@/ else print_esc("endinput"); @z @x l.7782 if cur_chr>0 then force_eof:=true @y if cur_chr=1 then force_eof:=true @/@@/ @z @x l.7835 array by symbolic names |top_mark|, etc. The value of |top_mark| is either @y array by symbolic names |top_mark|, etc.\ (depending on a local or global variable |0<=mark_class<=255|). The value of |top_mark| is either @z @x l.7838 @d top_mark_code=0 {the mark in effect at the previous page break} @y @d marks_code==5 {add this for \.{\\topmarks} etc.} @d cur_mark==cur_marks[mark_class] @# @d top_mark_code=0 {the mark in effect at the previous page break} @z @x l.7850 @!cur_mark:array[top_mark_code..split_bot_mark_code] of pointer; @y @!mark_class:eight_bits; {a mark class} @!cur_marks:array[0..255,top_mark_code..split_bot_mark_code] of pointer; @z @x l.7854 top_mark:=null; first_mark:=null; bot_mark:=null; split_first_mark:=null; split_bot_mark:=null; @y for mark_class:=0 to 255 do begin top_mark:=null; first_mark:=null; bot_mark:=null; split_first_mark:=null; split_bot_mark:=null; end; @z @x l.7870 top_bot_mark: case chr_code of @y top_bot_mark: begin case (chr_code mod marks_code) of @z @x l.7876 endcases; @y endcases; if chr_code>=marks_code then print_char("s"); end; @z @x l.7882 begin if cur_mark[cur_chr]<>null then begin_token_list(cur_mark[cur_chr],mark_text); @y begin if cur_chr>=marks_code then begin t:=cur_chr mod marks_code; scan_eight_bit_int; cur_chr:=t; mark_class:=cur_val; end else mark_class:=0; begin if cur_mark[cur_chr]<>null then begin_token_list(cur_mark[cur_chr],mark_text); end; @z @x l.7945 if info(r)<>end_match_token then @y if info(r)=protected_token then r:=link(r); if info(r)<>end_match_token then @z @x l.8390 |glue_val|, |input_line_no_code|, or |badness_code|. @d input_line_no_code=glue_val+1 {code for \.{\\inputlineno}} @d badness_code=glue_val+2 {code for \.{\\badness}} @y |glue_val|, |last_node_type_code|, |input_line_no_code|, |badness_code|, |eTeX_version_code|, |group_level_code|, or |group_type_code|. @d last_node_type_code=glue_val+1 {code for \.{\\lastnodetype}} @d input_line_no_code=glue_val+2 {code for \.{\\inputlineno}} @d badness_code=glue_val+3 {code for \.{\\badness}} @z @x l.8425 @+else print_esc("insertpenalties"); @y @/@@/ @+else print_esc("insertpenalties"); @z @x l.8434 othercases print_esc("badness") @y @/@@/ othercases print_esc("badness") @z @x l.8457 begin if m=0 then cur_val:=dead_cycles@+else cur_val:=insert_penalties; @y begin if m=0 then cur_val:=dead_cycles @/@@/ else cur_val:=insert_penalties; @z @x l.8493 implemented. The reference count for \.{\\lastskip} will be updated later. @y implemented. The reference count for \.{\\lastskip} will be updated later. A final \.{\\endM} node is temporarily removed. @z @x l.8499 if cur_chr>glue_val then begin if cur_chr=input_line_no_code then cur_val:=line @y if cur_chr>last_node_type_code then begin if cur_chr=input_line_no_code then cur_val:=line @/@@/ @z @x l.8505 cur_val_level:=cur_chr; @y if cur_chr=last_node_type_code then begin cur_val:=int_val; if (tail=head)or(mode=0) then cur_val:=-1; end else cur_val_level:=cur_chr; @z @x l.8507 case cur_chr of @y begin if (type(tail)=math_node)and(subtype(tail)=end_M_code) then remove_end_M; case cur_chr of @z @x l.8513 end; @y end; last_node_type_code: if (type(tail)<>math_node)or(subtype(tail)<>end_M_code) then if type(tail)<=unset_node then cur_val:=type(tail)+1 else cur_val:=unset_node+2; @z @x l.8514 end {there are no other cases} @y end; {there are no other cases} if LR_temp<>null then insert_end_M; end @z @x l.8519 glue_val: if last_glue<>max_halfword then cur_val:=last_glue; @y glue_val: if last_glue<>max_halfword then cur_val:=last_glue; last_node_type_code: cur_val:=last_node_type; @z @x l.9129 @p function str_toks(@!b:pool_pointer):pointer; @y @p @t\4@>@@;@/ function str_toks(@!b:pool_pointer):pointer; @z @x l.9154 @p function the_toks:pointer; @y @p function the_toks:pointer; label exit; @z @x l.9158 begin get_x_token; scan_something_internal(tok_val,false); @y @!c:small_number; {value of |cur_chr|} begin @;@/ get_x_token; scan_something_internal(tok_val,false); @z @x l.9172 end; @y exit:end; @z @x l.9223 othercases print_esc("jobname") @y @/@@/ othercases print_esc("jobname") @z @x l.9232 @!c:number_code..job_name_code; {desired type of conversion} @y @!c:small_number; {desired type of conversion} @z @x l.9249 end {there are no other cases} @y @/@@/ end {there are no other cases} @z @x l.9265 end {there are no other cases} @y @/@@/ end {there are no other cases} @z @x l.9374 if cur_cmd<=max_command then goto done2; @y if cur_cmd>=call then if info(link(cur_chr))=protected_token then begin cur_cmd:=relax; cur_chr:=no_expand_flag; end; if cur_cmd<=max_command then goto done2; @z @x l.9422 @p procedure read_toks(@!n:integer;@!r:pointer); @y @p procedure read_toks(@!n:integer;@!r:pointer;@!j:halfword); @z @x l.9448 loop@+ begin get_token; @y @;@/ loop@+ begin get_token; @z @x l.9489 print_err("File ended within "); print_esc("read"); @y print_err("File ended within "); print_cmd_chr(read_to_cs,j); @.File ended within \\readline@> @z @x l.9500 @d if_char_code=0 { `\.{\\if}' } @y @d unless_code=32 {amount added for `\.{\\unless}' prefix} @# @d if_char_code=0 { `\.{\\if}' } @z @x l.9555 if_test: case chr_code of @y if_test: begin if chr_code>=unless_code then print_esc("unless"); case chr_code mod unless_code of @z @x l.9572 othercases print_esc("if") endcases; @y @/@@/ othercases print_esc("if") endcases; end; @z @x l.9646 done: scanner_status:=save_scanner_status; @y done: scanner_status:=save_scanner_status; if tracing_ifs>0 then show_cur_cmd_chr; @z @x l.9699 begin @;@+save_cond_ptr:=cond_ptr;this_if:=cur_chr;@/ @; @y @!is_unless:boolean; {was this if preceded by `\.{\\unless}' ?} begin if tracing_ifs>0 then if tracing_commands<=1 then show_cur_cmd_chr; @;@+save_cond_ptr:=cond_ptr; is_unless:=(cur_chr>=unless_code); this_if:=cur_chr mod unless_code;@/ @; if is_unless then b:=not b; @z @x l.9742 if_false_code: b:=false; @y if_false_code: b:=false; @/@@/ @z @x l.9871 if cur_chr>if_limit then @y begin if tracing_ifs>0 then if tracing_commands<=1 then show_cur_cmd_chr; if cur_chr>if_limit then @z @x l.9882 end @y end; end @z @x l.9952 following structure: If the name contains `\.>' or `\.:', the file area consists of all characters up to and including the final such character; otherwise the file area is null. If the remaining file name contains `\..', the file extension consists of all such characters from the first remaining `\..' to the end, otherwise the file extension is null. @^system dependencies@> We can scan such file names easily by using two global variables that keep track of the occurrences of area and extension delimiters: @y following structure: If the name contains `\.>', `\.]' or `\.:', the directory consists of all characters up to and including the final such character; otherwise the directory is null. If the remaining file name contains `\..', the file extension consists of all such characters from the first remaining `\..' to the end, otherwise the file extension is null. @^system dependencies@> We can scan such file names easily by using two global variables that keep track of the occurrences of area and extension delimiters: We also take this opportunity to add some more global definitions for an upcoming section. @z @x l.9963 @!area_delimiter:pool_pointer; {the most recent `\.>' or `\.:', if any} @y @!area_delimiter:pool_pointer; {the most recent `\.>', `\.]' or `\.:', if any} @!TEX_area, @!TEX_font_area:str_number; @!inp_name, @!fonts_name : packed array[1..file_name_size] of char; @!inp_len, @!fonts_len : file_size; @!i: integer; @z @x l.9973 @d TEX_area=="TeXinputs:" @.TeXinputs@> @d TEX_font_area=="TeXfonts:" @.TeXfonts@> @y For VMS, we handle setting the names for these two logicals by defining them in the \.{CLD} file. We'll deal with getting that information later. The code that follows here will be executed after we've read the command line and \.{CLD} file and assumes that the first |inp_len| characters of |inp_name| are the value to use for |TEX_area| and the first |fonts_len| characters of |fonts_name| are the value to use for |TEX_font_area|. This code must be inserted just after \TeX\ has initialized all of its strings but before its first attempt at reading a file. I apologize for adding |i| to the globals above, by the way. There's only so much searching for a global in \.{TEX.WEB} one can take. @^frustration@> @= VAX_cli_get_value('TEXINPUTS',inp_name,inp_len); str_room(inp_len); for i:=1 to inp_len do append_char(xord[inp_name[i]]); TEX_area:=make_string; @# VAX_cli_get_value('TEXFONTS',fonts_name,fonts_len); str_room(fonts_len); for i:=1 to fonts_len do append_char(xord[fonts_name[i]]); TEX_font_area:=make_string @z @x l.9985 @ And here's the second. The string pool might change as the file name is being scanned, since a new \.{\\csname} might be entered; therefore we keep |area_delimiter| and |ext_delimiter| relative to the beginning of the current string, instead of assigning an absolute address like |pool_ptr| to them. @^system dependencies@> @p function more_name(@!c:ASCII_code):boolean; begin if c=" " then more_name:=false else begin str_room(1); append_char(c); {contribute |c| to the current string} if (c=">")or(c=":") then begin area_delimiter:=cur_length; ext_delimiter:=0; end else if (c=".")and(ext_delimiter=0) then ext_delimiter:=cur_length; more_name:=true; end; end; @y @ And here's the second. The string pool might change as the file name is being scanned, since a new \.{\\csname} might be entered; therefore we keep |area_delimiter| and |ext_delimiter| relative to the beginning of the current string, instead of assigning an absolute address like |pool_ptr| to them. @^system dependencies@> On VMS, we support wildcards in filenames. But, since the single char wildcard `\.\%' is interpreted as comment delimiter in the \TeX\ input stream, the question mark `\.?' should be used for that purpose, instead. Here, we have to convert those `\.?' characters back to `\.\%'. @p function more_name(@!c:ASCII_code):boolean; begin if c=" " then more_name:=false else begin str_room(1); {check for enough string space to add one char} if (c="?") then c:="%"; {convert `\.?' ``single char wildcard'' into `\.\%'} append_char(c); {contribute |c| to the current string} if (c=">")or(c="]")or(c=":") then begin area_delimiter:=cur_length; ext_delimiter:=0; end else if (c=".")and(ext_delimiter=0) then ext_delimiter:=cur_length; more_name:=true; end; end; @z @x l.10002 @ The third. @^system dependencies@> @p procedure end_name; begin if str_ptr+3>max_strings then overflow("number of strings",max_strings-init_str_ptr); @:TeX capacity exceeded number of strings}{\quad number of strings@> if area_delimiter=0 then cur_area:="" else begin cur_area:=str_ptr; str_start[str_ptr+1]:=str_start[str_ptr]+area_delimiter; incr(str_ptr); end; if ext_delimiter=0 then begin cur_ext:=""; cur_name:=make_string; end else begin cur_name:=str_ptr; str_start[str_ptr+1]:=str_start[str_ptr]+ext_delimiter-area_delimiter-1; incr(str_ptr); 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@> @p procedure end_name; label restart,exit; var @!t:packed array[1..file_name_size] of char; @!i:pool_pointer; @!len:signed_halfword; begin restart: if (str_pool[str_start[str_ptr]+area_delimiter]=si(":")) and @| (pool_ptr=str_start[str_ptr]+area_delimiter+1) then begin cur_area:=make_string; len:=length(cur_area)-1; {don't include the colon} for i:=1 to len do t[i]:=xchr[so(str_pool[str_start[cur_area]+i-1])]; if not translate(t,len) then begin cur_ext:=""; cur_name:=""; {silly case} return; end; flush_string; {needn't remember logical name in |cur_area|} begin_name; for i:=1 to len do if not more_name(xord[t[i]]) then goto restart; goto restart; {heavy!} end; if str_ptr+3>max_strings then overflow("number of strings",max_strings-init_str_ptr); @:TeX capacity exceeded number of strings}{\quad number of strings@> if area_delimiter=0 then cur_area:="" else begin cur_area:=str_ptr; str_start[str_ptr+1]:=str_start[str_ptr]+area_delimiter; incr(str_ptr); end; if ext_delimiter=0 then begin cur_ext:=""; cur_name:=make_string; end else begin cur_name:=str_ptr; str_start[str_ptr+1]:=str_start[str_ptr]+ext_delimiter-area_delimiter-1; incr(str_ptr); cur_ext:=make_string; end; exit: end; @z @x l.10060 @d format_default_length=20 {length of the |TEX_format_default| string} @d format_area_length=11 {length of its area part} @d format_ext_length=4 {length of its `\.{.fmt}' part} @d format_extension=".fmt" {the extension, as a \.{WEB} constant} @= @!TEX_format_default:packed array[1..format_default_length] of char; @y We want to be able to load the name of the area where formats live from an argument on the command line (actually, this will work like the \.{/TEXINPUTS} and \.{/TEXFONTS} qualifiers mentioned above, except that here, the qualifier will be read by both \TeX\ and \.{INITEX}. Also, things are a little simpler since we don't deal with the string pool ourselves. On the other hand some \.{WEB} constants will be changed to variables since we cannot know at compile time how long the argument to \.{/TEXFORMATS} will be. @./TEXFORMATS@> We also will take this opportunity to set the value for |pool_name| since we need the value given by \.{/TEXFORMATS} to construct it. @d format_name_length=9 {length of |'plain.fmt'|} @d format_ext_length=4 {length of its `\.{.fmt}' part} @d format_extension=".fmt" {the extension, as a \.{WEB} constant} @d pool_name_length==@= length@>(pool_f_name) @= @!TEX_f_name_default:packed array[1..format_name_length] of char; {abbreviated name for conflict considerations} @!TEX_format_default:packed array[1..file_name_size] of char; @!pool_name:packed array[1..file_name_size] of char; @!format_area_length:file_size; {length of the area part} @!format_default_length:integer; {length of the whole mess upon construction} @z @x l.10069 TEX_format_default:='TeXformats:plain.fmt'; @.TeXformats@> @y TEX_f_name_default:='plain.fmt'; VAX_cli_get_value('TEXFORMATS',TEX_format_default,format_area_length); pool_name:=TEX_format_default; for i:=1 to pool_name_length do pool_name[i+format_area_length]:=pool_f_name[i]; for i:=1 to format_name_length do TEX_format_default[i+format_area_length]:=TEX_f_name_default[i]; format_default_length:=format_area_length+format_name_length; @z @x l.10074 @ @= if format_default_length>file_name_size then bad:=31; @y @ There used to be a consistency check here, but since the value it checked wouldn't be set until {\it after\/} the consistency checking, we've deleted it. Besides, our code will automatically guarantee consistancy simply by the way |TEX_format_default| is defined. @z @x l.10140 @ 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 \TeX\ 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@> @y @ The VMS operating system is able to determine the exact name (and version number) of a file that has been opened through use of the |user_action| parameter of the |open| routine. The following routine makes a \TeX\ string from the value of |last_name[1..last_length]|, which is the full specification of the most recently opened file. @^system dependencies@> @z @x l.10150 @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)or (cur_length>0) 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; @y @p function make_name_string:str_number; var k:1..file_name_size; {index into |name_of_file|} begin if (pool_ptr+last_length>pool_size)or(str_ptr=max_strings)or (cur_length>0) then make_name_string:="?" else begin for k:=1 to last_length do append_char(xord[last_name[k]]); make_name_string:=make_string; end; end; @z @x l.10211 @ Here is a routine that manufactures the output file names, assuming that |job_name<>0|. It ignores and changes the current settings of |cur_area| and |cur_ext|. @y @ Here is a routine that manufactures the output file names, assuming that |job_name<>0|. It ignores and changes the current settings of |cur_area| and |cur_ext|. Similarly, |pack_default_name| extracts a (possibly partial) file specification from the appropriate command line qualifier, if used, and creates a string which may be used to provide defaults for part of a file specification when opening certain auxiliary files. The routine |clear_default_name| is also provided to ensure that no defaults are applied on successive calls of |open|. @z @x l.10217 @p procedure pack_job_name(@!s:str_number); {|s = ".log"|, |".dvi"|, or |format_extension|} begin cur_area:=""; cur_ext:=s; cur_name:=job_name; pack_cur_name; end; @y @p procedure pack_job_name(@!s:str_number); {|s = ".lis"|, |".dvi"|, |".dia"|, |format_extension|} begin cur_area:=""; cur_ext:=s; cur_name:=job_name; pack_cur_name; end;@# function pack_default_name(qual : boolean; df_name : packed array [l1..u1:integer] of char; df_len : file_size) : boolean; var k : integer; begin for k:=1 to file_name_size do default_name[k] := name_of_file[k]; deflt_length:=name_length; if qual then begin name_of_file := df_name; name_length := df_len; if name_length < file_name_size then for k:=name_length+1 to file_name_size do name_of_file[k]:=' '; end; pack_default_name := qual; {Result is whether file wanted} end;@# procedure clear_default_name; var k : integer; begin for k:=1 to file_name_size do default_name[k]:=' '; end; @z @x l.10223 @ If some trouble arises when \TeX\ tries to open a file, the following routine calls upon the user to supply another file name. Parameter~|s| is used in the error message to identify the type of file; parameter~|e| is the default extension if none is given. Upon exit from the routine, variables |cur_name|, |cur_area|, |cur_ext|, and |name_of_file| are ready for another attempt at file opening. @y @ If some trouble arises when \TeX\ tries to open a file, the following routine calls upon the user to supply another file name. Parameter~|s| is used in the error message to identify the type of file; parameter~|e| is the default extension if none is given. Upon exit from the routine, variables |cur_name|, |cur_area|, |cur_ext|, and |name_of_file| are ready for another attempt at file opening. Because this procedure invokes the |print_err| macro, but does not terminate the ``error'' (by invoking |error|), we have to take special measures to prevent everything from here onwards being written to the |temp_file| used for diagnostics. It does this by resetting the |temp_file|. @z @x l.10245 clear_terminal; prompt_input(": "); @; @y clear_terminal; prompt_input(": "); @; @; @z @x l.10263 @d ensure_dvi_open==if output_file_name=0 then begin if job_name=0 then open_log_file; pack_job_name(".dvi"); while not b_open_out(dvi_file) do prompt_file_name("file name for output",".dvi"); output_file_name:=b_make_name_string(dvi_file); end @y @d ensure_dvi_open==if output_file_name=0 then begin if job_name=0 then open_log_file; pack_job_name(".dvi"); if pack_default_name(dvi_qual,dvif_name,dvif_len) then begin while not b_open_out(dvi_file) do prompt_file_name("file name for output",".dvi"); output_file_name:=b_make_name_string(dvi_file); end else output_file_name:="."; clear_default_name; end @z @x l.10278 @ The |open_log_file| routine is used to open the transcript file and to help it catch up to what has previously been printed on the terminal. @p procedure open_log_file; @y @ The |open_log_file| routine is used to open the transcript file and to help it catch up to what has previously been printed on the terminal. @p @ @# procedure open_log_file; @z @x l.10287 if job_name=0 then job_name:="texput"; @.texput@> pack_job_name(".log"); while not a_open_out(log_file) do @; log_name:=a_make_name_string(log_file); @y if job_name=0 then job_name:="texput"; @.texput@> pack_job_name(".lis"); if pack_default_name(log_qual,logf_name,logf_len) then begin while not a_open_out(log_file) do @; log_name:=a_make_name_string(log_file); end else log_name:="."; clear_default_name; open_diag_file; @z @x l.10320 prompt_file_name("transcript file name",".log"); @y prompt_file_name("transcript file name",".lis"); @z @x l.10324 begin wlog(banner); @y begin wlog(eTeX_banner); @z @x l.10331 end @y wlog_cr; wlog('entering '); if eTeX_ex then wlog('extended') else wlog('compatibility'); wlog(' mode'); end @z @x l.10333 @ Let's turn now to the procedure that is used to initiate file reading when an `\.{\\input}' command is being processed. @y @ Let's turn now to the procedure that is used to initiate file reading when an `\.{\\input}' command is being processed. As originally used by \TeX82 under VMS, this procedure discarded the current file name (as returned to it by the operating system) after it had been printed. However, with this version of \TeX, with its capability of writing diagnostic files for use by LSEdit's review mode, we need to be able to report the full file specifications of any files that may be involved in |show_context|; therefore, we do not call |flush_string| here. @z @x l.10336 @p procedure start_input; {\TeX\ will \.{\\input} something} label done; begin scan_file_name; {set |cur_name| to desired file name} @y Here, |x_open_in|, a modified version of |a_open_in|, is inserted. This function is located here (and not next to |a_open_in|) to eliminate the neccessity of a forward declaration for the |pack_file_name| function. The new function |x_open_in| is intended to replace the calls to |a_open_in| in the procedures |start_input| and |open_or_close_in|. It extends the file opening functionality of |a_open_in| by a second try to open the file using the |TEX_area| path specification, when no explicit path was specified. The code needed for this purpose has been moved out of the |start_input| procedure. This change unifies the file search behaviour of the \.{\\input} and \.{\\openin} commands, a useful modification of \TeX\ recommended by the new edition of the \LaTeX\ macro package. The original function |a_open_in| is still used to read the \TeX\ pool file. When |start_input| decides to initiate the transcript file (this can only happen when |start_input| is called the first time!), the |cur_name| string get updated with the contents of |last_basename|. This string is the base name part of the file opened the previous |x_open_in| call. This modification reveals the correct job name when the primary input file specification contained wildcard characters in the base name part. @p function x_open_in(var f:alpha_file):boolean; begin open(f,name_of_file,VAX_readonly,VAX_user_action:=user_reset, VAX_ignore_error); if status(f)>0 then begin if cur_area="" then begin pack_file_name(cur_name,TEX_area,cur_ext); open(f,name_of_file,VAX_readonly,VAX_user_action:=user_reset, VAX_ignore_error); end; end; if status(f)>0 then x_open_in:=false else begin reset(f,VAX_ignore_error); x_open_in:=status(f)<=0; end; end; @# procedure start_input; {\TeX\ will \.{\\input} something} label done; var k:integer; begin scan_file_name; {set |cur_name| to desired file name} @z @x l.10340 pack_cur_name; loop@+ begin begin_file_reading; {set up |cur_file| and new level of input} if a_open_in(cur_file) then goto done; if cur_area="" then begin pack_file_name(cur_name,TEX_area,cur_ext); if a_open_in(cur_file) then goto done; end; end_file_reading; {remove the level that didn't work} prompt_file_name("input file name",".tex"); @y pack_cur_name; loop@+ begin begin_file_reading; {set up |cur_file| and new level of input} if x_open_in(cur_file) then goto done; end_file_reading; {remove the level that didn't work} prompt_file_name("input file name",".tex"); @z @x l.10350 done: name:=a_make_name_string(cur_file); if job_name=0 then begin job_name:=cur_name; open_log_file; end; {|open_log_file| doesn't |show_context|, so |limit| and |loc| needn't be set to meaningful values yet} @y done: name:=a_make_name_string(cur_file); if job_name=0 then begin if last_basenam_len = length(cur_name) then begin for k:=1 to last_basenam_len do str_pool[str_start[cur_name]+k-1]:=si(xord[last_basename[k]]); end else if (pool_ptr+last_basenam_len<=pool_size)and(str_ptr<>max_strings)and (cur_length<=0) then begin for k:=1 to last_basenam_len do append_char(xord[last_basename[k]]); cur_name:=make_string; end; job_name:=cur_name; open_log_file; end; {|open_log_file| doesn't |show_context|, so |limit| and |loc| needn't be set to meaningful values yet} @z @x l.10359 if name=str_ptr-1 then {we can conserve string pool space now} begin flush_string; name:=cur_name; end; @y @z @x l.10945 if not b_open_in(tfm_file) then abort; @y if not b_open_in(tfm_file) then abort; tfm_count:=0; @z @x l.10956 @d fget==get(tfm_file) @d fbyte==tfm_file^ @y @d fget==begin incr(tfm_count); if tfm_count=VAX_block_length then begin get(tfm_file,VAX_ignore_error); tfm_count:=0; end end @d fbyte==tfm_file^[tfm_count] @z @x l.11161 if eof(tfm_file) then abort; @y if status(tfm_file)<>0 then abort; @z @x l.11855 @ Some systems may find it more efficient to make |dvi_buf| a |packed| array, since output of four bytes at once may be facilitated. @^system dependencies@> @= @!dvi_buf:array[dvi_index] of eight_bits; {buffer for \.{DVI} output} @y @ Some systems may find it more efficient to make |dvi_buf| a |packed| array, since output of four bytes at once may be facilitated. On VMS, we get even more complicated than that, for efficiency. @d dvi_buf==d_buffer.b {buffer for \.{DVI} output} @= @!d_buffer: [VAX_volatile,VAX_aligned(9)] packed record case boolean of false: (@!b:packed array[dvi_index] of eight_bits); true: (@!l:byte_block; @!r:byte_block; @!j:eight_bits); end; @z @x l.11875 @ The actual output of |dvi_buf[a..b]| to |dvi_file| is performed by calling |write_dvi(a,b)|. For best results, this procedure should be optimized to run as fast as possible on each particular system, since it is part of \TeX's inner loop. It is safe to assume that |a| and |b+1| will both be multiples of 4 when |write_dvi(a,b)| is called; therefore it is possible on many machines to use efficient methods to pack four bytes per word and to output an array of words with one system call. @^system dependencies@> @^inner loop@> @^defecation@> @p procedure write_dvi(@!a,@!b:dvi_index); var k:dvi_index; begin for k:=a to b do write(dvi_file,dvi_buf[k]); end; @y @ The actual output of |dvi_buf[a..b]| to |dvi_file| is performed by calling |write| on the other variant of the |dvi_buf| record. Thus, we have to be sure that things line up properly, by padding out with ``signature'' bytes. @^system dependencies@> @^inner loop@> @^defecation@> @= if dvi_buf_size<>2*VAX_block_length then bad:=223; @z @x l.11900 begin write_dvi(0,half_buf-1); dvi_limit:=half_buf; @y begin if dvi_qual then write(dvi_file,d_buffer.l); dvi_limit:=half_buf; @z @x l.11903 else begin write_dvi(half_buf,dvi_buf_size-1); dvi_limit:=dvi_buf_size; @y else begin if dvi_qual then write(dvi_file,d_buffer.r); dvi_limit:=dvi_buf_size; @z @x l.11912 if dvi_limit=half_buf then write_dvi(half_buf,dvi_buf_size-1); if dvi_ptr>0 then write_dvi(0,dvi_ptr-1) @y if (dvi_limit=half_buf) and dvi_qual then write(dvi_file,d_buffer.r); for k:=dvi_ptr to dvi_buf_size do dvi_buf[k]:=223; if (dvi_ptr>0) and dvi_qual then write(dvi_file,d_buffer.l); if (dvi_ptr>half_buf) and dvi_qual then write(dvi_file,d_buffer.r); @z @x l.12238 this is essentially the depth of |push| commands in the \.{DVI} output. @y this is essentially the depth of |push| commands in the \.{DVI} output. For mixed direction text (\TeXXeT) the current text direction is called |cur_dir|. As the box being shipped out will never be used again and soon be recycled, we can simply reverse any R-text (i.e., right-to-left) segments of hlist nodes as well as complete hlist nodes embedded in such segments. Moreover this can be done iteratively rather than recursively. There are, however, two complications related to leaders that require some additional bookkeeping: (1)~One and the same hlist node might be used more than once (but never inside both L- and R-text); and (2)~leader boxes inside hlists must be aligned with respect to the left edge of the original hlist. A math node is changed into a kern node whenever the text direction remains the same, it is replaced by an |edge_node| if the text direction changes; the subtype of an an |hlist_node| inside R-text is changed to |reversed| once its hlist has been reversed. @!@^data structure assumptions@> @z @x l.12240 @d synch_h==if cur_h<>dvi_h then @y @d reversed=min_quarterword+1 {subtype for an |hlist_node| whose hlist has been reversed} @d dlist=min_quarterword+2 {subtype for an |hlist_node| from display math mode} @d left_to_right=0 @d right_to_left=1 @d reflected==1-cur_dir {the opposite of |cur_dir|} @# @d synch_h==if cur_h<>dvi_h then @z @x l.12300 @!g_order: glue_ord; {applicable order of infinity for glue} @y @z @x l.12308 @!edge:scaled; {left edge of sub-box, or right edge of leader space} @y @!edge:scaled; {right edge of sub-box or leader space} @!prev_p:pointer; {one step behind |p|} @z @x l.12309 @!glue_temp:real; {glue value before rounding} begin this_box:=temp_ptr; g_order:=glue_order(this_box); @y begin this_box:=temp_ptr; @z @x l.12315 save_loc:=dvi_offset+dvi_ptr; base_line:=cur_v; left_edge:=cur_h; @y save_loc:=dvi_offset+dvi_ptr; base_line:=cur_v; prev_p:=this_box+list_offset; if eTeX_ex then begin @; if subtype(this_box)=dlist then if cur_dir=right_to_left then begin cur_dir:=left_to_right; cur_h:=cur_h-width(this_box); end else subtype(this_box):=min_quarterword; if (cur_dir=right_to_left)and(subtype(this_box)<>reversed) then @; end; left_edge:=cur_h; @z @x l.12318 prune_movements(save_loc); @y if eTeX_ex then begin @; if subtype(this_box)=dlist then cur_dir:=right_to_left; end; prune_movements(save_loc); @z @x l.12337 p:=link(p); @y prev_p:=p; p:=link(p); @z @x l.12362 kern_node,math_node:cur_h:=cur_h+width(p); @y kern_node:cur_h:=cur_h+width(p); math_node:begin if eTeX_ex then @; cur_h:=cur_h+width(p); end; @z @x l.12364 othercases do_nothing @y @/@@; othercases do_nothing @z @x l.12369 next_p:p:=link(p); @y next_p:prev_p:=p; p:=link(p); @z @x l.12376 temp_ptr:=p; edge:=cur_h; @y temp_ptr:=p; edge:=cur_h+width(p); if cur_dir=right_to_left then cur_h:=edge; @z @x l.12379 cur_h:=edge+width(p); cur_v:=base_line; @y cur_h:=edge; cur_v:=base_line; @z @x l.12401 begin if g_sign=stretching then begin if stretch_order(g)=g_order then begin vet_glue(float(glue_set(this_box))*stretch(g)); @^real multiplication@> rule_wd:=rule_wd+round(glue_temp); end; end else if shrink_order(g)=g_order then begin vet_glue(float(glue_set(this_box))*shrink(g)); rule_wd:=rule_wd-round(glue_temp); end; end; @y add_glue(rule_wd); @z @x l.12428 edge:=cur_h+rule_wd; lx:=0; @y if cur_dir=right_to_left then cur_h:=cur_h-10; edge:=cur_h+rule_wd; lx:=0; @z @x l.12434 cur_h:=edge-10; goto next_p; @y if cur_dir=right_to_left then cur_h:=edge else cur_h:=edge-10; goto next_p; @z @x l.12473 synch_h; save_h:=dvi_h; temp_ptr:=leader_box; @y synch_h; save_h:=dvi_h; temp_ptr:=leader_box; if cur_dir=right_to_left then cur_h:=cur_h+leader_wd; @z @x l.12489 @!g_order: glue_ord; {applicable order of infinity for glue} @y @z @x l.12498 @!glue_temp:real; {glue value before rounding} begin this_box:=temp_ptr; g_order:=glue_order(this_box); @y begin this_box:=temp_ptr; @z @x l.12544 cur_h:=left_edge+shift_amount(p); {shift the box right} @y if cur_dir=right_to_left then cur_h:=left_edge-shift_amount(p) else cur_h:=left_edge+shift_amount(p); {shift the box right} @z @x l.12556 begin synch_h; synch_v; dvi_out(put_rule); dvi_four(rule_ht); dvi_four(rule_wd); @y begin if cur_dir=right_to_left then cur_h:=cur_h-rule_wd; synch_h; synch_v; dvi_out(put_rule); dvi_four(rule_ht); dvi_four(rule_wd); cur_h:=left_edge; @z @x l.12564 begin if g_sign=stretching then begin if stretch_order(g)=g_order then begin vet_glue(float(glue_set(this_box))*stretch(g)); @^real multiplication@> rule_ht:=rule_ht+round(glue_temp); end; end else if shrink_order(g)=g_order then begin vet_glue(float(glue_set(this_box))*shrink(g)); rule_ht:=rule_ht-round(glue_temp); end; end; @y add_glue(rule_ht); @z @x l.12619 begin cur_h:=left_edge+shift_amount(leader_box); synch_h; save_h:=dvi_h;@/ @y begin if cur_dir=right_to_left then cur_h:=left_edge-shift_amount(leader_box) else cur_h:=left_edge+shift_amount(leader_box); synch_h; save_h:=dvi_h;@/ @z @x l.12656 @; @y @; if eTeX_ex then @; @z @x l.12747 print_nl("Output written on "); slow_print(output_file_name); @.Output written on x@> print(" ("); print_int(total_pages); print(" page"); if total_pages<>1 then print_char("s"); print(", "); print_int(dvi_offset+dvi_ptr); print(" bytes)."); b_close(dvi_file); @y if dvi_qual then begin b_close(dvi_file); print_nl("Output written on "); slow_print(output_file_name); @.Output written on x@> end else print_nl("NO output file --- would have had"); print(" ("); print_int(total_pages); print(" page"); if total_pages<>1 then print_char("s"); print(", "); print_int(dvi_offset+dvi_ptr); print(" bytes)."); @z @x l.12876 h:=0; @; @y h:=0; @; if TeXXeT_en then @; @z @x l.12886 exit: hpack:=r; @y exit: if TeXXeT_en then @; hpack:=r; @z @x l.12910 kern_node,math_node: x:=x+width(p); @y kern_node: x:=x+width(p); math_node: begin x:=x+width(p); if TeXXeT_en then @; end; @z @x l.13480 \TeX's \.{\\left} and \.{\\right}. The |nucleus| of such noads is @y \TeX's \.{\\left} and \.{\\right} as well as \eTeX's \.{\\middle}. The |nucleus| of such noads is @z @x l.13497 @d delimiter==nucleus {|delimiter| field in left and right noads} @y @d delimiter==nucleus {|delimiter| field in left and right noads} @d middle_noad==1 {|subtype| of right noad representing \.{\\middle}} @z @x l.13670 right_noad: begin print_esc("right"); print_delimiter(nucleus(p)); end; end; if subtype(p)<>normal then if subtype(p)=limits then print_esc("limits") else print_esc("nolimits"); if type(p)normal then if subtype(p)=limits then print_esc("limits") else print_esc("nolimits"); print_subsidiary_data(nucleus(p),"."); end; @z @x l.14268 done_with_noad: r:=q; r_type:=type(r); @y done_with_noad: r:=q; r_type:=type(r); if r_type=right_noad then begin r_type:=left_noad; cur_style:=style; @; end; @z @x l.14929 r_type:=t; @y if type(q)=right_noad then t:=open_noad; r_type:=t; @z @x l.14970 begin if style; @z @x l.15834 begin type(q):=hlist_node; width(q):=width(p); @y begin type(q):=hlist_node; width(q):=width(p); if nest[nest_ptr-1].mode_field=mmode then subtype(q):=dlist; {for |ship_out|} @z @x l.15852 n:=span_count(r); t:=width(s); w:=t; u:=hold_head; @y n:=span_count(r); t:=width(s); w:=t; u:=hold_head; subtype(r):=min_quarterword; {for |ship_out|} @z @x l.17030 math_node: begin auto_breaking:=(subtype(cur_p)=after); kern_break; @y math_node: begin if subtype(cur_p); @z @x l.17276 @; @y if TeXXeT_en then @; @; if TeXXeT_en then @; @z @x l.17299 else if (type(q)=math_node)or(type(q)=kern_node) then width(q):=0; @y else if type(q)=kern_node then width(q):=0 else if type(q)=math_node then begin width(q):=0; if TeXXeT_en then @; end; @z @x l.18320 @!init @@; tini @y @@; @z @x l.18346 @!init@! trie_op_hash:array[-trie_op_size..trie_op_size] of 0..trie_op_size; @y @! trie_op_hash:array[-trie_op_size..trie_op_size] of 0..trie_op_size; @z @x l.18355 tini @y @z @x l.18438 @!init @!trie_c:packed array[trie_pointer] of packed_ASCII_code; @y @!trie_c:packed array[trie_pointer] of packed_ASCII_code; @z @x l.18447 @t\hskip10pt@>@!trie_hash:packed array[trie_pointer] of trie_pointer; {used to identify equivalent subtries} tini @y @t\hskip10pt@>@!trie_hash:packed array[trie_pointer] of trie_pointer; {used to identify equivalent subtries} @z @x l.18524 @!init@!trie_taken:packed array[1..trie_size] of boolean; {does a family start here?} @y @!trie_taken:packed array[1..trie_size] of boolean; {does a family start here?} @z @x l.18529 @t\hskip10pt@>@!trie_not_ready:boolean; {is the trie still in linked form?} tini @y @t\hskip10pt@>@!trie_not_ready:boolean; {is the trie still in linked form?} @z @x l.18998 begin v:=box(n); @y @!mark_class:eight_bits; begin v:=box(n); for mark_class:=0 to 255 do @z @x l.19032 else loop@+begin if type(p)=mark_node then @y else loop@+begin if type(p)=mark_node then begin mark_class:=qo(subtype(p)); @z @x l.19042 end; @y end; end; @z @x l.19167 The variables |last_penalty| and |last_kern| are similar. And @y The variables |last_penalty|, |last_kern|, and |last_node_type| are similar. And @z @x l.19180 @!last_kern:scaled; {used to implement \.{\\lastkern}} @y @!last_kern:scaled; {used to implement \.{\\lastkern}} @!last_node_type:integer; {used to implement \.{\\lastnodetype}} @z @x l.19317 last_glue:=max_halfword; last_penalty:=0; last_kern:=0; @y last_glue:=max_halfword; last_penalty:=0; last_kern:=0; last_node_type:=-1; @z @x l.19384 last_penalty:=0; last_kern:=0; @y last_penalty:=0; last_kern:=0; last_node_type:=type(p)+1; @z @x l.19660 begin @; @y @!mark_class:eight_bits; begin @; for mark_class:=0 to 255 do @z @x l.19669 if (top_mark<>null)and(first_mark=null) then @y for mark_class:=0 to 255 do if (top_mark<>null)and(first_mark=null) then @z @x l.19725 begin if first_mark=null then @y begin mark_class:=qo(subtype(p)); if first_mark=null then @z @x l.19973 @p @t\4@>@@; @y @p @t\4@>@ @t\4@>@@; @z @x l.20901 since |head| is a one-word node. @y since |head| is a one-word node. A final \.{\\endM} node is temporarily removed. @z @x l.20914 if (type(tail)=hlist_node)or(type(tail)=vlist_node) then @; @y begin if (type(tail)=math_node)and(subtype(tail)=end_M_code) then remove_end_M; if (type(tail)=hlist_node)or(type(tail)=vlist_node) then @; if LR_temp<>null then insert_end_M; end; @z @x l.21119 else line_break(widow_penalty); @y else line_break(widow_penalty); if LR_save<>null then begin flush_list(LR_save); LR_save:=null; end; @z @x l.21175 begin p:=scan_toks(false,true); p:=get_node(small_node_size); type(p):=mark_node; subtype(p):=0; {the |subtype| is not used} @y @!c:eight_bits; {the mark class} begin if cur_chr=0 then c:=0 else begin scan_eight_bit_int; c:=cur_val; end; p:=scan_toks(false,true); p:=get_node(small_node_size); type(p):=mark_node; subtype(p):=qi(c); @z @x l.21203 will be deleted, if present. @y will be deleted, if present. A final \.{\\endM} node is temporarily removed. @z @x l.21213 else begin if not is_char_node(tail) then if type(tail)=cur_chr then @y else begin if not is_char_node(tail) then begin if (type(tail)=math_node)and(subtype(tail)=end_M_code) then remove_end_M; if type(tail)=cur_chr then @z @x l.21224 end; @y if LR_temp<>null then insert_end_M; end; end; @z @x l.21562 vmode+halign,hmode+valign:init_align; @y vmode+halign:init_align; hmode+valign:@@; init_align; @z @x l.21629 procedure init_math; label reswitch,found,not_found,done; var w:scaled; {new or partial |pre_display_size|} @y @t\4@>@@; procedure init_math; label reswitch,found,not_found,done; var w:scaled; {new or partial |pre_display_size|} @!j:pointer; {prototype box for display} @!x:integer; {new |pre_display_direction|} @z @x l.21687 begin if head=tail then {`\.{\\noindent\$\$}' or `\.{\$\${ }\$\$}'} begin pop_nest; w:=-max_dimen; end @y begin j:=null; w:=-max_dimen; if head=tail then {`\.{\\noindent\$\$}' or `\.{\$\${ }\$\$}'} @ @z @x l.21700 eq_word_define(dimen_base+pre_display_size_code,w); @y eq_word_define(dimen_base+pre_display_size_code,w); LR_box:=j; if eTeX_ex then eq_word_define(int_base+pre_display_direction_code,x); @z @x l.21708 v:=shift_amount(just_box)+2*quad(cur_font); w:=-max_dimen; p:=list_ptr(just_box); @y @; @z @x l.21723 done: @y done: @ @z @x l.21734 kern_node,math_node: d:=width(p); @y kern_node: d:=width(p); @t\4@>@@; @z @x l.22243 if type(q)<>left_noad then confusion("right"); @:this can't happen right}{\quad right@> info(numerator(incompleat_noad)):=link(q); link(q):=incompleat_noad; link(incompleat_noad):=p; @y if (type(q)<>left_noad)or(delim_ptr=null) then confusion("right"); @:this can't happen right}{\quad right@> info(numerator(incompleat_noad)):=link(delim_ptr); link(delim_ptr):=incompleat_noad; link(incompleat_noad):=p; @z @x l.22288 else print_esc("right"); @y @/@@/ else print_esc("right"); @z @x l.22297 begin t:=cur_chr; if (t=right_noad)and(cur_group<>math_left_group) then @y @!q:pointer; {resulting mlist} begin t:=cur_chr; if (t<>left_noad)and(cur_group<>math_left_group) then @z @x l.22302 if t=left_noad then begin push_math(math_left_group); link(head):=p; tail:=p; end else begin p:=fin_mlist(p); unsave; {end of |math_left_group|} @y if t=middle_noad then begin type(p):=right_noad; subtype(p):=middle_noad; end; if t=left_noad then q:=p else begin q:=fin_mlist(p); unsave; {end of |math_left_group|} end; if t<>right_noad then begin push_math(math_left_group); link(head):=q; tail:=p; delim_ptr:=p; end else begin @z @x l.22308 info(nucleus(tail)):=p; @y info(nucleus(tail)):=q; @z @x l.22316 print_err("Extra "); print_esc("right"); @.Extra \\right.@> help1("I'm ignoring a \right that had no matching \left."); @y print_err("Extra "); if t=middle_noad then begin print_esc("middle"); @.Extra \\middle.@> help1("I'm ignoring a \middle that had no matching \left."); end else begin print_esc("right"); @.Extra \\right.@> help1("I'm ignoring a \right that had no matching \left."); end; @z @x l.22331 procedure after_math; @y @t\4@>@@; procedure after_math; @z @x l.22338 begin danger:=false; @y begin danger:=false; @; @z @x l.22345 mlist_to_hlist; a:=hpack(link(temp_head),natural); @y mlist_to_hlist; a:=hpack(link(temp_head),natural); subtype(a):=dlist; @z @x l.22348 danger:=false; @y danger:=false; @; @z @x l.22435 w:=width(b); z:=display_width; s:=display_indent; @y w:=width(b); z:=display_width; s:=display_indent; if pre_display_direction<0 then s:=-s-z; @z @x l.22450 resume_after_display @y @; resume_after_display @z @x l.22492 d:=half(z-w); @y subtype(b):=dlist; d:=half(z-w); @z @x l.22513 begin shift_amount(a):=s; append_to_vlist(a); @y begin app_display(j,a,0); @z @x l.22528 shift_amount(b):=s+d; append_to_vlist(b) @y app_display(j,b,d) @z @x l.22533 shift_amount(a):=s+z-width(a); append_to_vlist(a); @y app_display(j,a,z-width(a)); @z @x l.22552 pop_nest; @y flush_node_list(LR_box); pop_nest; @z @x l.22577 control sequence can be defined to be `\.{\\long}' or `\.{\\outer}', and it might or might not be expanded. The prefixes `\.{\\global}', `\.{\\long}', @y control sequence can be defined to be `\.{\\long}', `\.{\\protected}', or `\.{\\outer}', and it might or might not be expanded. The prefixes `\.{\\global}', `\.{\\long}', `\.{\\protected}', @z @x l.22603 else print_esc("global"); @y @/@@/ else print_esc("global"); @z @x l.22665 @; @y @; if tracing_commands>2 then if eTeX_ex then show_cur_cmd_chr; @z @x l.22686 if (cur_cmd<>def)and(a mod 4<>0) then @y if a>=8 then begin j:=protected_token; a:=a-8; end else j:=0; if (cur_cmd<>def)and((a mod 4<>0)or(j<>0)) then @z @x l.22749 q:=scan_toks(true,e); define(p,call+(a mod 4),def_ref); @y q:=scan_toks(true,e); if j<>0 then begin q:=get_avail; info(q):=j; link(q):=link(def_ref); link(def_ref):=q; end; define(p,call+(a mod 4),def_ref); @z @x l.22853 read_to_cs: begin scan_int; n:=cur_val; @y read_to_cs: begin j:=cur_chr; scan_int; n:=cur_val; @z @x l.22861 p:=cur_cs; read_toks(n,p); define(p,call,cur_val); @y p:=cur_cs; read_toks(n,p,j); define(p,call,cur_val); @z @x l.23179 var c:0..1; {0 for \.{\\deadcycles}, 1 for \.{\\insertpenalties}} begin c:=cur_chr; scan_optional_equals; scan_int; if c=0 then dead_cycles:=cur_val @y var c:small_number; {0 for \.{\\deadcycles}, 1 for \.{\\insertpenalties}, etc.} begin c:=cur_chr; scan_optional_equals; scan_int; if c=0 then dead_cycles:=cur_val @/@@/ @z @x l.23439 if c<>0 then begin scan_optional_equals; scan_file_name; if cur_ext="" then cur_ext:=".tex"; pack_cur_name; if a_open_in(read_file[n]) then read_open[n]:=just_open; end; end; @y if c<>0 then begin scan_optional_equals; scan_file_name; if cur_ext="" then cur_ext:=".tex"; pack_cur_name; if x_open_in(read_file[n]) then read_open[n]:=just_open; end; end; @z @x l.23581 show_lists:print_esc("showlists"); @y show_lists:print_esc("showlists"); @@;@/ @z @x l.23586 procedure show_whatever; label common_ending; var p:pointer; {tail of a token list to show} begin case cur_chr of @y procedure show_whatever; label common_ending; var p:pointer; {tail of a token list to show} begin @; case cur_chr of @z @x l.23594 othercases @@;@/ othercases @0 then print_char(" "); print("macro"); @z @x l.23685 @!init procedure store_fmt_file; @y procedure store_fmt_file; @z @x l.23700 @; @; end; tini @y @; @; end; @z @x l.23751 @d dump_wd(#)==begin fmt_file^:=#; put(fmt_file);@+end @d dump_int(#)==begin fmt_file^.int:=#; put(fmt_file);@+end @d dump_hh(#)==begin fmt_file^.hh:=#; put(fmt_file);@+end @d dump_qqqq(#)==begin fmt_file^.qqqq:=#; put(fmt_file);@+end @y @d fmt_put==begin incr(fmt_count); if fmt_count=VAX_block_length then begin put(fmt_file,VAX_ignore_error); fmt_count:=0; end end @d fmt_word==fmt_file^[fmt_count] @d dump_wd(#)==begin fmt_word:=#; fmt_put;@+end @d dump_int(#)==begin fmt_word.int:=#; fmt_put;@+end @d dump_hh(#)==begin fmt_word.hh:=#; fmt_put;@+end @d dump_qqqq(#)==begin fmt_word.qqqq:=#; fmt_put;@+end @z @x l.23763 @d undump_wd(#)==begin get(fmt_file); #:=fmt_file^;@+end @d undump_int(#)==begin get(fmt_file); #:=fmt_file^.int;@+end @d undump_hh(#)==begin get(fmt_file); #:=fmt_file^.hh;@+end @d undump_qqqq(#)==begin get(fmt_file); #:=fmt_file^.qqqq;@+end @y @d fmt_get==begin incr(fmt_count); if fmt_count=VAX_block_length then begin get(fmt_file,VAX_ignore_error); fmt_count:=0; end end @d undump_wd(#)==begin fmt_get; #:=fmt_word;@+end @d undump_int(#)==begin fmt_get; #:=fmt_word.int;@+end @d undump_hh(#)==begin fmt_get; #:=fmt_word.hh;@+end @d undump_qqqq(#)==begin fmt_get; #:=fmt_word.qqqq;@+end @z @x l.23779 dump_int(@$);@/ @y dump_int(@$);@/ @@/ @z @x l.23793 x:=fmt_file^.int; @y x:=fmt_word.int; @z @x l.23794 if x<>@$ then goto bad_fmt; {check that strings are the same} @y if x<>@$ then goto bad_fmt; {check that strings are the same} @/@@/ @z @x l.24140 w_close(fmt_file) @y while fmt_count>0 do dump_int(0); {flush out the buffer} w_close(fmt_file) @z @x l.24195 @!ready_already:integer; {a sacrifice of purity for economy} @y @!init_flag: boolean; @z @x l.24197 @ Now this is really it: \TeX\ starts and ends here. The initial test involving |ready_already| should be deleted if the \PASCAL\ runtime system is smart enough to detect such a ``mistake.'' @^system dependencies@> @y @ Now this is really it: \TeX\ starts and ends here. The initial test involving |ready_already| should be deleted if the \PASCAL\ runtime system is smart enough to detect such a ``mistake'' and in fact it has been deleted. We also take this opportunity to find out if we're supposed to be \TeX\ or \.{INITEX}. (As an interesting note, Knuth simply uses \.{INITEX} for everything now. The performance difference is too minimal on his machine to make it worth maintaining two copies of \TeX.) Since all files are opened with a |disposition:=delete| clause, they will be deleted automatically if the program does not complete properly. However, if a fatal error occurs, the |jumpout| procedure also causes termination of the program without closing the files, therefore we ensure that at least the |log_file| gets closed so that the fatal error can be examined! @z @x l.24206 if ready_already=314159 then goto start_of_TEX; @y if not odd(init_cli(eTeX_CLD_table,'ETEX','TEXFORMATS')) then begin wterm_ln('Ouch---access to DCL command line interface has failed!'); goto final_end; end; init_flag:=odd(VAX_cli_present('INIT')); @z @x l.24215 @!init if not get_strings_started then goto final_end; @y eight_qual:=odd(VAX_cli_present('EIGHT_BIT')); @./EIGHT_BIT@> @!init if not get_strings_started then goto final_end; @z @x l.24219 ready_already:=314159; start_of_TEX: @; @y start_of_TEX: @; @; @z @x l.24225 end_of_TEX: close_files_and_terminate; final_end: ready_already:=0; end. @y end_of_TEX: close_files_and_terminate; final_end: if log_opened then begin wlog_cr; a_close(log_file); selector:=selector-2; if selector=term_only then begin if log_qual then begin print_nl("Transcript written on "); @.Transcript written...@> slow_print(log_name); print_char("."); end else print_nl("NO transcript file."); end; end; @; end. @z @x l.24249 if log_opened then begin wlog_cr; a_close(log_file); selector:=selector-2; if selector=term_only then begin print_nl("Transcript written on "); @.Transcript written...@> slow_print(log_name); print_char("."); end; end; @y if diag_qual then begin wdiag_cr; wdiag_ln('end module'); a_close(diag_file); print_nl("Diagnostics written on "); @.Diagnostics written...@> slow_print(diag_name); print_char("."); end; @z @x l.24301 var c:small_number; {0 for \.{\\end}, 1 for \.{\\dump}} @y var c:small_number; {0 for \.{\\end}, 1 for \.{\\dump}} @!mark_class:eight_bits; {a mark class} @z @x l.24313 print_int(cur_level-level_one); print_char(")"); @y print_int(cur_level-level_one); print_char(")"); if eTeX_ex then show_save_groups; @z @x l.24336 if cur_mark[c]<>null then delete_token_ref(cur_mark[c]); @y for mark_class:=0 to 255 do if cur_mark[c]<>null then delete_token_ref(cur_mark[c]); @z @x l.24344 @!init procedure init_prim; {initialize all the primitives} @y procedure init_prim; {initialize all the primitives} @z @x l.24345 begin no_new_control_sequence:=false; @y begin no_new_control_sequence:=false; first:=0; @z @x l.24347 no_new_control_sequence:=true; end; tini @y no_new_control_sequence:=true; {Read my lips---there will be no new control sequences.} end; @z @x l.24360 if (format_ident=0)or(buffer[loc]="&") then @y @@;@/ if (format_ident=0)or(buffer[loc]="&") then @z @x l.24368 end; @y end; wterm('entering '); if eTeX_ex then wterm('extended') else wterm('compatibility'); wterm_ln(' mode'); @z @x l.24373 @; @y @; @; @z @x l.24573 @t\4@>@@; procedure do_extension; var i,@!j,@!k:integer; {all-purpose integers} @!p,@!q,@!r:pointer; {all-purpose pointers} @y @t\4@>@@; procedure do_extension; var k:integer; {all-purpose integers} @!p:pointer; {all-purpose pointers} @z @x l.24903 @* \[54] System-dependent changes. @y @* \[53a] The extended features of \eTeX. The program has two modes of operation: (1)~In \TeX\ compatibility mode it fully deserves the name \TeX\ and there are neither extended features nor additional primitive commands. There are, however, a few modifications that would be legitimate in any implementation of \TeX\ such as, e.g., preventing arithmetic overflow in the glue to \.{DVI} unit conversion during |ship_out|. (2)~In extended mode there are additional primitive commands and the extended features of \eTeX\ are available. The distinction between these two modes of operation initially takes place when a `virgin' \.{INITEX} starts without reading a format file. Later on the values of all \eTeX\ state variables are inherited when \.{VIRTEX} (or \.{INITEX}) reads a format file. The code below is designed to work for cases where `$|init|\ldots|tini|$' is a run-time switch. @= @!init if (buffer[loc]="*")and(format_ident=" (INITEX)") then begin no_new_control_sequence:=false; @@; incr(loc); eTeX_mode:=1; {enter extended mode} end; tini@;@/ if not no_new_control_sequence then {just entered extended mode ?} no_new_control_sequence:=true@+else @ The \eTeX\ features available in extended mode are grouped into two categories: (1)~Some of them are permanently enabled and have no semantic effect as long as none of the additional primitives are executed. (2)~The remaining \eTeX\ features are optional and can be individually enabled and disabled. For each optional feature there is an \eTeX\ state variable named \.{\\...state}; the feature is enabled, resp.\ disabled by assigning a positive, resp.\ non-positive value to that integer. @d eTeX_state_base=int_base+eTeX_state_code @d eTeX_state(#)==eqtb[eTeX_state_base+#].int {an \eTeX\ state variable} @# @d eTeX_version_code=glue_val+4 {code for \.{\\eTeXversion}} @d eTeX_revision_code=6 {command code for \.{\\eTeXrevision}} @= primitive("lastnodetype",last_item,last_node_type_code); @!@:last_node_type_}{\.{\\lastnodetype} primitive@> primitive("eTeXversion",last_item,eTeX_version_code); @!@:eTeX_version_}{\.{\\eTeXversion} primitive@> primitive("eTeXrevision",convert,eTeX_revision_code);@/ @!@:eTeX_revision_}{\.{\\eTeXrevision} primitive@> @ @= last_node_type_code: print_esc("lastnodetype"); eTeX_version_code: print_esc("eTeXversion"); @ @= else if cur_chr=eTeX_version_code then cur_val:=eTeX_version @ @= eTeX_revision_code: print_esc("eTeXrevision"); @ @= eTeX_revision_code: do_nothing; @ @= eTeX_revision_code: print(eTeX_revision); @ @d eTeX_ex==(eTeX_mode=1) {is this extended mode?} @= @!eTeX_mode: 0..1; {identifies compatibility and extended mode} @ @= eTeX_mode:=0; {initially we are in compatibility mode} @ @= dump_int(eTeX_mode); for j:=0 to eTeX_states-1 do eTeX_state(j):=0; {disable all enhancements} @ @= undump(0)(1)(eTeX_mode); @ The |eTeX_enabled| function simply returns its first argument as result. This argument is |true| if an optional \eTeX\ feature is currently enabled; otherwise, if the argument is |false|, the function gives an error message. @= function eTeX_enabled(@!b:boolean;@!j:quarterword;@!k:halfword):boolean; begin if not b then begin print_err("Improper "); print_cmd_chr(j,k); help1("Sorry, this optional e-TeX feature has been disabled."); error; end; eTeX_enabled:=b; end; @ @= procedure@?scan_eight_bit_int; forward;@t\2@> @ First we implement the additional \eTeX\ parameters in the table of equivalents. @= primitive("everyeof",assign_toks,every_eof_loc); @!@:every_eof_}{\.{\\everyeof} primitive@> primitive("tracingassigns",assign_int,int_base+tracing_assigns_code);@/ @!@:tracing_assigns_}{\.{\\tracingassigns} primitive@> primitive("tracinggroups",assign_int,int_base+tracing_groups_code);@/ @!@:tracing_groups_}{\.{\\tracinggroups} primitive@> primitive("tracingifs",assign_int,int_base+tracing_ifs_code);@/ @!@:tracing_ifs_}{\.{\\tracingifs} primitive@> primitive("tracingscantokens",assign_int,int_base+tracing_scan_tokens_code);@/ @!@:tracing_scan_tokens_}{\.{\\tracingscantokens} primitive@> primitive("predisplaydirection", assign_int,int_base+pre_display_direction_code);@/ @!@:pre_display_direction_}{\.{\\predisplaydirection} primitive@> @ @d every_eof==equiv(every_eof_loc) @= every_eof_loc: print_esc("everyeof"); @ @= tracing_assigns_code:print_esc("tracingassigns"); tracing_groups_code:print_esc("tracinggroups"); tracing_ifs_code:print_esc("tracingifs"); tracing_scan_tokens_code:print_esc("tracingscantokens"); pre_display_direction_code:print_esc("predisplaydirection"); @ The |print_group| procedure prints the current level of grouping and the name corresponding to |cur_group|. @= procedure print_group(@!e:boolean); label exit; begin print_int(cur_level); print(": "); case cur_group of bottom_level: begin print("bottom level"); return; end; simple_group,semi_simple_group: begin if cur_group=semi_simple_group then print("semi "); print("simple"); end; hbox_group,adjusted_hbox_group: begin if cur_group=adjusted_hbox_group then print("adjusted "); print("hbox"); end; vbox_group: print("vbox"); vtop_group: print("vtop"); align_group,no_align_group: begin if cur_group=no_align_group then print("no "); print("align"); end; output_group: print("output"); disc_group: print("disc"); insert_group: print("insert"); vcenter_group: print("vcenter"); math_group,math_choice_group,math_shift_group,math_left_group: begin print("math"); if cur_group=math_choice_group then print(" choice") else if cur_group=math_shift_group then print(" shift") else if cur_group=math_left_group then print(" left"); end; end; {there are no other cases} print(" group"); if saved(-1)<>0 then begin if e then print(" entered at line ") else print(" at line "); print_int(saved(-1)); end; exit:end; @ The |group_trace| procedure is called when a new level of grouping begins (|e=false|) or ends (|e=true|) with |saved(-1)| containing the line number. @= @!stat procedure group_trace(@!e:boolean); begin begin_diagnostic; print_char("{"); if e then print("leaving ") else print("entering "); print_group(e); print_char("}"); end_diagnostic(false); end; tats @ The \.{\\currentgrouplevel} and \.{\\currentgrouptype} commands return the current level of grouping and the type of the current group respectively. @d group_level_code=glue_val+5 {code for \.{\\currentgrouplevel}} @d group_type_code=glue_val+6 {code for \.{\\currentgrouptype}} @= primitive("currentgrouplevel",last_item,group_level_code); @!@:current_group_level_}{\.{\\currentgrouplevel} primitive@> primitive("currentgrouptype",last_item,group_type_code); @!@:current_group_type_}{\.{\\currentgrouptype} primitive@> @ @= group_level_code: print_esc("currentgrouplevel"); group_type_code: print_esc("currentgrouptype"); @ @= else if cur_chr=group_level_code then cur_val:=cur_level-level_one else if cur_chr=group_type_code then cur_val:=cur_group @ The \.{\\showgroups} command displays all currently active grouping levels. @d show_groups=4 { \.{\\showgroups} } @= primitive("showgroups",xray,show_groups); @!@:show_groups_}{\.{\\showgroups} primitive@> @ @= show_groups:print_esc("showgroups"); @ @= show_groups: begin begin_diagnostic; show_save_groups; end; @ The modifications of \TeX\ required for the display produced by the |show_save_groups| procedure were first discussed by Donald~E. Knuth in {\sl TUGboat\/} {\bf 11}, 165--170 and 499--511, 1990. @^Knuth, Donald Ervin@> In order to understand a group type we also have to know its mode. Since unrestricted horizontal modes are not associated with grouping, they are skipped when traversing the semantic nest. @= procedure show_save_groups; label found1,found2,found,done; var p:0..nest_size; {index into |nest|} @!m:-mmode..mmode; {mode} @!v:0..save_size; {saved value of |save_ptr|} @!l:quarterword; {saved value of |cur_level|} @!c:group_code; {saved value of |cur_group|} @!a:-1..1; {to keep track of alignments} @!i:integer; @!j:quarterword; @!s:str_number; begin p:=nest_ptr; nest[p]:=cur_list; {put the top level into the array} v:=save_ptr; l:=cur_level; c:=cur_group; save_ptr:=cur_boundary; decr(cur_level);@/ a:=1; print_nl(""); print_ln; loop@+begin print_nl("### "); print_group(true); if cur_group=bottom_level then goto done; repeat m:=nest[p].mode_field; if p>0 then decr(p) else m:=vmode; until m<>hmode; print(" ("); case cur_group of simple_group: begin incr(p); goto found2; end; hbox_group,adjusted_hbox_group: s:="hbox"; vbox_group: s:="vbox"; vtop_group: s:="vtop"; align_group: if a=0 then begin if m=-vmode then s:="halign" else s:="valign"; a:=1; goto found1; end else begin if a=1 then print("align entry") else print_esc("cr"); if p>=a then p:=p-a; a:=0; goto found; end; no_align_group: begin incr(p); a:=-1; print_esc("noalign"); goto found2; end; output_group: begin print_esc("output"); goto found; end; math_group: goto found2; disc_group,math_choice_group: begin if cur_group=disc_group then print_esc("discretionary") else print_esc("mathchoice"); for i:=1 to 3 do if i<=saved(-2) then print("{}"); goto found2; end; insert_group: begin if saved(-2)=255 then print_esc("vadjust") else begin print_esc("insert"); print_int(saved(-2)); end; goto found2; end; vcenter_group: begin s:="vcenter"; goto found1; end; semi_simple_group: begin incr(p); print_esc("begingroup"); goto found; end; math_shift_group: begin if m=mmode then print_char("$") else if nest[p].mode_field=mmode then begin print_cmd_chr(eq_no,saved(-2)); goto found; end; print_char("$"); goto found; end; math_left_group: begin if type(nest[p+1].eTeX_aux_field)=left_noad then print_esc("left") else print_esc("middle"); goto found; end; end; {there are no other cases} @; found1: print_esc(s); @; found2: print_char("{"); found: print_char(")"); decr(cur_level); cur_group:=save_level(save_ptr); save_ptr:=save_index(save_ptr) end; done: save_ptr:=v; cur_level:=l; cur_group:=c; end; @ @= if saved(-2)<>0 then begin print_char(" "); if saved(-3)=exactly then print("to") else print("spread"); print_scaled(saved(-2)); print("pt"); end @ @= i:=saved(-4); if i<>0 then if i0 then print_cmd_chr(j,0) else print_cmd_chr(j,1); print_scaled(abs(i)); print("pt"); end else if i255 then begin print_esc("global"); i:=i-256; end; print_esc("setbox"); print_int(i); print_char("="); end else print_cmd_chr(leader_ship,i-(leader_flag-a_leaders)) @ The |scan_general_text| procedure is much like |scan_toks(false,false)|, but will be invoked via |expand|, i.e., recursively. @^recursion@> @= procedure@?scan_general_text; forward;@t\2@> @ The token list (balanced text) created by |scan_general_text| begins at |link(temp_head)| and ends at |cur_val|. (If |cur_val=temp_head|, the list is empty.) @= procedure scan_general_text; label found; var s:normal..absorbing; {to save |scanner_status|} @!w:pointer; {to save |warning_index|} @!d:pointer; {to save |def_ref|} @!p:pointer; {tail of the token list being built} @!q:pointer; {new node being added to the token list via |store_new_token|} @!unbalance:halfword; {number of unmatched left braces} begin s:=scanner_status; w:=warning_index; d:=def_ref; scanner_status:=absorbing; warning_index:=cur_cs; def_ref:=get_avail; token_ref_count(def_ref):=null; p:=def_ref; scan_left_brace; {remove the compulsory left brace} unbalance:=1; loop@+ begin get_token; if cur_tok= primitive("showtokens",xray,show_tokens); @!@:show_tokens_}{\.{\\showtokens} primitive@> @ @= show_tokens:print_esc("showtokens"); @ The \.{\\unexpanded} primitive prevents expansion of tokens much as the result from \.{\\the} applied to a token variable. The \.{\\detokenize} primitive converts a token list into a list of character tokens much as if the token list were written to a file. We use the fact that the command modifiers for \.{\\unexpanded} and \.{\\detokenize} are odd whereas those for \.{\\the} and \.{\\showthe} are even. @= primitive("unexpanded",the,1);@/ @!@:unexpanded_}{\.{\\unexpanded} primitive@> primitive("detokenize",the,show_tokens);@/ @!@:detokenize_}{\.{\\detokenize} primitive@> @ @= else if chr_code=1 then print_esc("unexpanded") else print_esc("detokenize") @ @= if odd(cur_chr) then begin c:=cur_chr; scan_general_text; if c=1 then the_toks:=cur_val else begin old_setting:=selector; selector:=new_string; b:=pool_ptr; p:=get_avail; link(p):=link(temp_head); token_show(p); flush_list(p); selector:=old_setting; the_toks:=str_toks(b); end; return; end @ The \.{\\interactionmode} primitive allows to query and set the interaction mode. @= primitive("interactionmode",set_page_int,2); @!@:interaction_mode_}{\.{\\interactionmode} primitive@> @ @= else if chr_code=2 then print_esc("interactionmode") @ @= else if m=2 then cur_val:=interaction @ @= procedure@?new_interaction; forward;@t\2@> @ @= else if c=2 then begin if (cur_valerror_stop_mode) then begin print_err("Bad interaction mode"); @.Bad interaction mode@> help2("Modes are 0=batch, 1=nonstop, 2=scroll, and")@/ ("3=errorstop. Proceed, and I'll ignore this case."); int_error(cur_val); end else begin cur_chr:=cur_val; new_interaction; end; end @ The |middle| feature of \eTeX\ allows one ore several \.{\\middle} delimiters to appear between \.{\\left} and \.{\\right}. @= primitive("middle",left_right,middle_noad); @!@:middle_}{\.{\\middle} primitive@> @ @= else if chr_code=middle_noad then print_esc("middle") @ In constructions such as $$\vbox{\halign{\.{#}\hfil\cr {}\\vbox to \\vsize\{\cr \hskip 25pt \\vskip 0pt plus 0.0001fil\cr \hskip 25pt ...\cr \hskip 25pt \\vfil\\penalty-200\\vfilneg\cr \hskip 25pt ...\}\cr}}$$ the stretch components of \.{\\vfil} and \.{\\vfilneg} compensate; in standard \TeX\ they may get modified in order to prevent arithmetic overflow during |ship_out| when each of them is multiplied by a large |glue_set| value. In \eTeX\ the conversion from stretch or shrink components of glue to \.{DVI} units is performed by the |do_glue| function defined below. In extended mode the |do_glue| function adds up the relevant stretch (or shrink) components of consecutive glue nodes and converts the glue nodes into equivalent kern nodes; during this process glue specifications may be recycled. The accumulated stretch or shrink is then multiplied by |glue_set(this_box)| and returned as result. Since one and the same box may be used several times inside leaders the result is also added to the width of the first or only kern node; the subtype of the glue node(s) remains unchanged. The consecutive glue nodes may be separated by insert, mark, adjust, kern, and penalty nodes. @d add_glue(#)==#:=#+do_glue(this_box,p) @# @d add_stretch_shrink== {accumulate stretch or shrink amount} if g_sign=stretching then begin if stretch_order(g)=g_order then s:=s+stretch(g); end else begin if shrink_order(g)=g_order then s:=s-shrink(g); end @= function do_glue(@!this_box,@!p:pointer):scaled; label continue, next_p, done; var q:pointer; {list traverser} @!g_order: glue_ord; {applicable order of infinity for glue} @!g_sign: normal..shrinking; {selects type of glue} @!s:scaled; {accumulated stretch or shrink} @!glue_temp:real; {glue value before rounding} begin g_order:=glue_order(this_box); g_sign:=glue_sign(this_box); s:=0; add_stretch_shrink; if not eTeX_ex or(subtype(p)>=a_leaders) then goto done; q:=p; continue: type(q):=kern_node; width(q):=width(g); fast_delete_glue_ref(g); next_p: q:=link(q); if (q<>null) and not is_char_node(q) then case type(q) of ins_node,mark_node,adjust_node,kern_node,penalty_node: goto next_p; glue_node: if subtype(q)0 then begin vet_glue(float(glue_set(this_box))*s); s:=round(glue_temp); @^real multiplication@> if type(p)=kern_node then width(p):=width(p)+s; end; do_glue:=s; end; @ The optional |TeXXeT| feature of \eTeX\ contains the code for mixed left-to-right and right-to-left typesetting. This code is inspired by but different from \TeXeT\ as presented by Donald~E. Knuth and Pierre MacKay in {\sl TUGboat\/} {\bf 8}, 14--25, 1987. @^Knuth, Donald Ervin@> @^MacKay, Pierre@> In order to avoid confusion with \TeXeT\ the present implementation of mixed direction typesetting is called \TeXXeT. It differs from \TeXeT\ in several important aspects: (1)~Right-to-left text is reversed explicitely by the |ship_out| routine and is written to a normal \.{DVI} file without any |begin_reflect| or |end_reflect| commands; (2)~a |math_node| is (ab)used instead of a |whatsit_node| to record the \.{\\beginL}, \.{\\endL}, \.{\\beginR}, and \.{\\endR} text direction primitives in order to keep the influence on the line breaking algorithm for pure left-to-right text as small as possible; (3)~right-to-left text interrupted by a displayed equation is automatically resumed after that equation; and (4)~the |valign| command code with a non-zero command modifier is (ab)used for the text direction primitives. Nevertheless there is a subtle difference between \TeX\ and \TeXXeT\ that may influence the line breaking algorithm for pure left-to-right text. When a paragraph containing math mode material is broken into lines \TeX\ may generate lines where math mode material is not enclosed by properly nested \.{\\mathon} and \.{\\mathoff} nodes. Unboxing such lines as part of a new paragraph may have the effect that hyphenation is attempted for `words' originating from math mode or that hyphenation is inhibited for words originating from horizontal mode. In \TeXXeT\ additional \.{\\beginM}, resp.\ \.{\\endM} math nodes are supplied at the start, resp.\ end of lines such that math mode material inside a horizontal list always starts with either \.{\\mathon} or \.{\\beginM} and ends with \.{\\mathoff} or \.{\\endM}. These additional nodes are transparent to operations such as \.{\\unskip}, \.{\\lastpenalty}, or \.{\\lastbox} but they do have the effect that hyphenation is never attempted for `words' originating from math mode and is never inhibited for words originating from horizontal mode. @d TeXXeT_state==eTeX_state(TeXXeT_code) @d TeXXeT_en==(TeXXeT_state>0) {is \TeXXeT\ enabled?} @= eTeX_state_code+TeXXeT_code:print_esc("TeXXeTstate"); @ @= primitive("TeXXeTstate",assign_int,eTeX_state_base+TeXXeT_code); @!@:TeXXeT_state_}{\.{\\TeXXeT_state} primitive@> primitive("beginL",valign,begin_L_code); @!@:beginL_}{\.{\\beginL} primitive@> primitive("endL",valign,end_L_code); @!@:endL_}{\.{\\endL} primitive@> primitive("beginR",valign,begin_R_code); @!@:beginR_}{\.{\\beginR} primitive@> primitive("endR",valign,end_R_code); @!@:endR_}{\.{\\endR} primitive@> @ @= else case chr_code of begin_L_code: print_esc("beginL"); end_L_code: print_esc("endL"); begin_R_code: print_esc("beginR"); othercases print_esc("endR") endcases @ @= if cur_chr>0 then begin if eTeX_enabled(TeXXeT_en,cur_cmd,cur_chr) then @.Improper \\beginL@> @.Improper \\endL@> @.Improper \\beginR@> @.Improper \\endR@> tail_append(new_math(0,cur_chr)); end else @ An hbox with subtype dlist will never be reversed, even when embedded in right-to-left text. @= if (type(p)=hlist_node)and(subtype(p)=dlist) then print(", display") @ A number of routines are based on a stack of one-word nodes whose |info| fields contain |end_M_code|, |end_L_code|, or |end_R_code|. The top of the stack is pointed to by |LR_ptr|. When the stack manipulation macros of this section are used below, variable |LR_ptr| might be the global variable declared here for |hpack| and |ship_out|, or might be local to |post_line_break|. @d put_LR(#)==begin temp_ptr:=get_avail; info(temp_ptr):=#; link(temp_ptr):=LR_ptr; LR_ptr:=temp_ptr; end @# @d push_LR(#)==put_LR(end_LR_type(#)) @# @d pop_LR==begin temp_ptr:=LR_ptr; LR_ptr:=link(temp_ptr); free_avail(temp_ptr); end @= @!LR_temp:pointer; {holds a temporarily removed \.{\\endM} node} @!LR_ptr:pointer; {stack of LR codes for |hpack|, |ship_out|, and |init_math|} @!LR_problems:integer; {counts missing begins and ends} @!cur_dir:small_number; {current text direction} @ @= LR_temp:=null; LR_ptr:=null; LR_problems:=0; cur_dir:=left_to_right; @ @= begin q:=link(temp_head); if LR_ptr<>null then begin temp_ptr:=LR_ptr; r:=q; repeat s:=new_math(0,begin_LR_type(info(temp_ptr))); link(s):=r; r:=s; temp_ptr:=link(temp_ptr); until temp_ptr=null; link(temp_head):=r; end; while q<>cur_break(cur_p) do begin if not is_char_node(q) then if type(q)=math_node then @; q:=link(q); end; end @ @= if end_LR(q) then begin if LR_ptr<>null then if info(LR_ptr)=end_LR_type(q) then pop_LR; end else push_LR(q) @ We use the fact that |q| now points to the node with \.{\\rightskip} glue. @= if LR_ptr<>null then begin s:=temp_head; r:=link(s); while r<>q do begin s:=r; r:=link(s); end; r:=LR_ptr; while r<>null do begin temp_ptr:=new_math(0,info(r)); link(s):=temp_ptr; s:=temp_ptr; r:=link(r); end; link(s):=q; end @ Special \.{\\beginM} and \.{\\endM} nodes are inserted in cases where math nodes are discarded during line breaking or end up in different lines. When the current lists ends with an \.{\\endM} node that node is temporarily removed and later reinserted when the last node is to be inspected or removed. A final \.{\\endM} preceded by a |char_node| will not be removed. @= procedure remove_end_M; var @!p:pointer; {runs through the current list} begin p:=head; while link(p)<>tail do p:=link(p); if not is_char_node(p) then begin LR_temp:=tail; link(p):=null; tail:=p; end; end; @ @= procedure insert_end_M; label done; var @!p:pointer; {runs through the current list} begin if not is_char_node(tail) then if (type(tail)=math_node)and(subtype(tail)=begin_M_code) then begin free_node(LR_temp,small_node_size); p:=head; while link(p)<>tail do p:=link(p); free_node(tail,small_node_size); link(p):=null; tail:=p; goto done; end; link(tail):=LR_temp; tail:=LR_temp; done: LR_temp:=null; end; @ @= put_LR(before) {this will never match} @ @= if end_LR(p) then if info(LR_ptr)=end_LR_type(p) then pop_LR else begin incr(LR_problems); type(p):=kern_node; subtype(p):=explicit; end else push_LR(p) @ @= begin if info(LR_ptr)<>before then begin while link(q)<>null do q:=link(q); repeat temp_ptr:=q; q:=new_math(0,info(LR_ptr)); link(temp_ptr):=q; LR_problems:=LR_problems+10000; pop_LR; until info(LR_ptr)=before; end; if LR_problems>0 then begin @; goto common_ending; end; pop_LR; if LR_ptr<>null then confusion("LR1"); @:this can't happen LR1}{\quad LR1@> end @ @= begin print_ln; print_nl("\endL or \endR problem (");@/ print_int(LR_problems div 10000); print(" missing, ");@/ print_int(LR_problems mod 10000); print(" extra");@/ LR_problems:=0; end @ Breaking a paragraph into lines while \TeXXeT\ is disabled may result in lines whith unpaired math nodes. Such hlists are silently accepted in the absence of text direction directives. @d LR_dir(#)==(subtype(#) div R_code) {text direction of a `math node'} @= begin if end_LR(p) then if info(LR_ptr)=end_LR_type(p) then pop_LR else begin if subtype(p)>L_code then incr(LR_problems); end else begin push_LR(p); if LR_dir(p)<>cur_dir then @; end; type(p):=kern_node; end @ @= begin while info(LR_ptr)<>before do begin if info(LR_ptr)>L_code then LR_problems:=LR_problems+10000; pop_LR; end; pop_LR; end @ @d edge_node=style_node {a |style_node| does not occur in hlists} @d edge_node_size=style_node_size {number of words in an edge node} @d edge_dist(#)==depth(#) {new |left_edge| position relative to |cur_h| (after |width| has been taken into account)} @= function new_edge(@!s:small_number;@!w:scaled):pointer; {create an edge node} var p:pointer; {the new node} begin p:=get_node(edge_node_size); type(p):=edge_node; subtype(p):=s; width(p):=w; edge_dist(p):=0; {the |edge_dist| field will be set later} new_edge:=p; end; @ @= edge_node: begin cur_h:=cur_h+width(p); left_edge:=cur_h+edge_dist(p); cur_dir:=subtype(p); end; @ We detach the hlist, start a new one consisting of just one kern node, append the reversed list, and set the width of the kern node. @= begin save_h:=cur_h; temp_ptr:=p; p:=new_kern(0); link(prev_p):=p; cur_h:=0; link(p):=reverse(this_box,null); width(p):=-cur_h; cur_h:=save_h; subtype(this_box):=reversed; end @ We detach the remainder of the hlist, replace the math node by an edge node, and append the reversed hlist segment to it; the tail of the reversed segment is another edge node and the remainder of the original list is attached to it. @= begin save_h:=cur_h; temp_ptr:=link(p); rule_wd:=width(p); free_node(p,small_node_size); cur_dir:=reflected; p:=new_edge(cur_dir,rule_wd); link(prev_p):=p; cur_h:=cur_h-left_edge+rule_wd; link(p):=reverse(this_box,new_edge(reflected,0)); edge_dist(p):=cur_h; cur_dir:=reflected; cur_h:=save_h; goto reswitch; end @ The |reverse| function defined here is responsible to reverse the nodes of an hlist (segment). The first parameter |this_box| is the enclosing hlist node, the second parameter |t| is to become the tail of the reversed list, and the global variable |temp_ptr| is the head of the list to be reversed. We remove nodes from the original list and add them to the head of the new one. @= function reverse(@!this_box,@!t:pointer):pointer; label reswitch,next_p,done; var l:pointer; {the new list} @!p:pointer; {the current node} @!q:pointer; {the next node} @!g_sign: normal..shrinking; {selects type of glue} @!m,@!n:halfword; {count of unmatched math nodes} begin g_sign:=glue_sign(this_box); l:=t; p:=temp_ptr; m:=min_halfword; n:=min_halfword; loop@+ begin while p<>null do @; if (t=null)and(m=min_halfword)and(n=min_halfword) then goto done; p:=new_math(0,info(LR_ptr)); LR_problems:=LR_problems+10000; {manufacture one missing math node} end; done:reverse:=l; end; @ @= reswitch: if is_char_node(p) then repeat f:=font(p); c:=character(p); cur_h:=cur_h+char_width(f)(char_info(f)(c)); q:=link(p); link(p):=l; l:=p; p:=q; until not is_char_node(p) else @ @ @= begin q:=link(p); case type(p) of hlist_node,vlist_node,rule_node,kern_node: rule_wd:=width(p); @t\4@>@@; edge_node: confusion("LR2"); @:this can't happen LR2}{\quad LR2@> othercases goto next_p endcases;@/ cur_h:=cur_h+rule_wd; next_p: link(p):=l; if type(p)=kern_node then if (rule_wd=0)or(l=null) then begin free_node(p,small_node_size); p:=l; end; l:=p; p:=q; end @ Here we have to remember that |add_glue| may have converted the glue node into a kern node. If this is not the case we try to covert the glue node into a rule node. @= glue_node: begin g:=glue_ptr(p); rule_wd:=width(g); if g_sign<>normal then add_glue(rule_wd); if subtype(p)>=a_leaders then begin temp_ptr:=leader_ptr(p); if type(temp_ptr)=rule_node then begin delete_glue_ref(g); free_node(p,small_node_size); p:=temp_ptr; width(p):=rule_wd; end; end; end; @ A ligature node is replaced by a char node. @= ligature_node: begin flush_node_list(lig_ptr(p)); temp_ptr:=p; p:=get_avail; mem[p]:=mem[lig_char(temp_ptr)]; link(p):=q; free_node(temp_ptr,small_node_size); goto reswitch; end; @ Math nodes in an inner reflected segment are modified, those at the outer level are changed into kern nodes. @= math_node: begin rule_wd:=width(p); if end_LR(p) then if info(LR_ptr)<>end_LR_type(p) then begin type(p):=kern_node; incr(LR_problems); end else begin pop_LR; if n>min_halfword then begin decr(n); decr(subtype(p)); {change |after| into |before|} end else begin type(p):=kern_node; if m>min_halfword then decr(m) else @; end; end else begin push_LR(p); if (n>min_halfword)or(LR_dir(p)<>cur_dir) then begin incr(n); incr(subtype(p)); {change |before| into |after|} end else begin type(p):=kern_node; incr(m); end; end; end; @ Finally we have found the end of the hlist segment to be reversed; the final math node is released and the remaining list attached to the edge node terminating the reversed segment. @= begin free_node(p,small_node_size); link(t):=q; width(t):=rule_wd; edge_dist(t):=-cur_h-rule_wd; goto done; end @ @= begin if LR_problems>0 then begin @; print_char(")"); print_ln; end; if (LR_ptr<>null)or(cur_dir<>left_to_right) then confusion("LR3"); @:this can't happen LR3}{\quad LR3@> end @ Some special actions are required for displayed equation in paragraphs with mixed direction texts. First of all we have to set the text direction preceding the display. @= if LR_save=null then x:=0 else if info(LR_save)>=R_code then x:=-1@+else x:=1 @ @= begin pop_nest; @; end @ When calculating the natural width, |w|, of the final line preceding the display, we may have to copy all or part of its hlist. We copy, however, only those parts of the original list that are relevant for the computation of |pre_display_size|. @^data structure assumptions@> @= procedure just_copy(@!p,@!h,@!t:pointer); label found,not_found; var @!r:pointer; {current node being fabricated for new list} @!words:0..5; {number of words remaining to be copied} begin while p<>null do begin words:=1; {this setting occurs in more branches than any other} if is_char_node(p) then r:=get_avail else case type(p) of hlist_node,vlist_node: begin r:=get_node(box_node_size); mem[r+6]:=mem[p+6]; mem[r+5]:=mem[p+5]; {copy the last two words} words:=5; list_ptr(r):=null; {this affects |mem[r+5]|} end; rule_node: begin r:=get_node(rule_node_size); words:=rule_node_size; end; ligature_node: begin r:=get_avail; {only |font| and |character| are needed} mem[r]:=mem[lig_char(p)]; goto found; end; kern_node,math_node: begin r:=get_node(small_node_size); words:=small_node_size; end; glue_node: begin r:=get_node(small_node_size); add_glue_ref(glue_ptr(p)); glue_ptr(r):=glue_ptr(p); leader_ptr(r):=null; end; whatsit_node:@; othercases goto not_found endcases; while words>0 do begin decr(words); mem[r+words]:=mem[p+words]; end; found: link(h):=r; h:=r; not_found: p:=link(p); end; link(h):=t; end; @ When the final line ends with R-text, the value |w| refers to the line reflected with respect to the left edge of the enclosing vertical list. @= if eTeX_ex then @; v:=shift_amount(just_box); @; if x>=0 then begin p:=list_ptr(just_box); link(temp_head):=null; end else begin v:=-v-width(just_box); p:=new_math(0,begin_L_code); link(temp_head):=p; just_copy(list_ptr(just_box),p,new_math(0,end_L_code)); cur_dir:=right_to_left; end; v:=v+2*quad(cur_font); if TeXXeT_en then @ @ @= if TeXXeT_en then begin while LR_ptr<>null do pop_LR; if LR_problems<>0 then begin w:=max_dimen; LR_problems:=0; end; end; cur_dir:=left_to_right; flush_node_list(link(temp_head)) @ In the presence of text direction directives we assume that any LR problems have been fixed by the |hpack| routine. If the final line contains, however, text direction directives while \TeXXeT\ is disabled, then we set |w:=max_dimen|. @= math_node: begin d:=width(p); if TeXXeT_en then @ else if subtype(p)>=L_code then begin w:=max_dimen; goto done; end; end; edge_node: begin d:=width(p); cur_dir:=subtype(p); end; @ @= if end_LR(p) then begin if info(LR_ptr)=end_LR_type(p) then pop_LR else if subtype(p)>L_code then begin w:=max_dimen; goto done; end end else begin push_LR(p); if LR_dir(p)<>cur_dir then begin just_reverse(p); p:=temp_head; end; end @ @= procedure just_reverse(@!p:pointer); label found,done; var l:pointer; {the new list} @!t:pointer; {tail of reversed segment} @!q:pointer; {the next node} @!m,@!n:halfword; {count of unmatched math nodes} begin m:=min_halfword; n:=min_halfword; if link(temp_head)=null then begin just_copy(link(p),temp_head,null); q:=link(temp_head); end else begin q:=link(p); link(p):=null; flush_node_list(link(temp_head)); end; t:=new_edge(cur_dir,0); l:=t; cur_dir:=reflected; while q<>null do if is_char_node(q) then repeat p:=q; q:=link(p); link(p):=l; l:=p; until not is_char_node(q) else begin p:=q; q:=link(p); if type(p)=math_node then @; link(p):=l; l:=p; end; goto done; found:width(t):=width(p); link(t):=q; free_node(p,small_node_size); done:link(temp_head):=l; end; @ @= if end_LR(p) then if info(LR_ptr)<>end_LR_type(p) then begin type(p):=kern_node; incr(LR_problems); end else begin pop_LR; if n>min_halfword then begin decr(n); decr(subtype(p)); {change |after| into |before|} end else begin if m>min_halfword then decr(m)@+else goto found; type(p):=kern_node; end; end else begin push_LR(p); if (n>min_halfword)or(LR_dir(p)<>cur_dir) then begin incr(n); incr(subtype(p)); {change |before| into |after|} end else begin type(p):=kern_node; incr(m); end; end @ The prototype box is an hlist node with the width, glue set, and shift amount of |just_box|, i.e., the last line preceding the display. Its hlist reflects the current \.{\\leftskip} and \.{\\rightskip}. @= begin if right_skip=zero_glue then j:=new_kern(0) else j:=new_param_glue(right_skip_code); if left_skip=zero_glue then p:=new_kern(0) else p:=new_param_glue(left_skip_code); link(p):=j; j:=new_null_box; width(j):=width(just_box); shift_amount(j):=shift_amount(just_box); list_ptr(j):=p; glue_order(j):=glue_order(just_box); glue_sign(j):=glue_sign(just_box); glue_set(j):=glue_set(just_box); end @ At the end of a displayed equation we retrieve the prototype box. @= @!j:pointer; {prototype box} @ @= if mode=mmode then j:=LR_box @ @= flush_node_list(j) @ The |app_display| procedure used to append the displayed equation and\slash or equation number to the current vertical list has three parameters: the prototype box, the hbox to be appended, and the displacement of the hbox in the display line. @= procedure app_display(@!j,@!b:pointer;@!d:scaled); var z:scaled; {width of the line} @!s:scaled; {move the line right this much} @!e:scaled; {distance from right edge of box to end of line} @!x:integer; {|pre_display_direction|} @!p,@!q,@!r,@!t,@!u:pointer; {for list manipulation} begin s:=display_indent; x:=pre_display_direction; if x=0 then shift_amount(b):=s+d else begin z:=display_width; p:=b; @; @; end; append_to_vlist(b); end; @ Here we construct the hlist for the display, starting with node |p| and ending with node |q|. We also set |d| and |e| to the amount of kerning to be added before and after the hlist (adjusted for the prototype box). @= if x>0 then e:=z-d-width(p) else begin e:=d; d:=z-e-width(p); end; if j<>null then begin b:=copy_node_list(j); height(b):=height(p); depth(b):=depth(p); s:=s-shift_amount(b); d:=d+s; e:=e+width(b)-z-s; end; if subtype(p)=dlist then q:=p {display or equation number} else begin {display and equation number} r:=list_ptr(p); free_node(p,box_node_size); if r=null then confusion("LR4"); if x>0 then begin p:=r; repeat q:=r; r:=link(r); {find tail of list} until r=null; end else begin p:=null; q:=r; repeat t:=link(r); link(r):=p; p:=r; r:=t; {reverse list} until r=null; end; end @ In the presence of a prototype box we use its shift amount and width to adjust the values of kerning and add these values to the glue nodes inserted to cancel the \.{\\leftskip} and \.{\\rightskip}. If there is no prototype box (because the display is preceded by an empty paragraph), or if the skip parameters are zero, we just add kerns. The |cancel_glue| macro creates and links a glue node that is, together with another glue node, equivalent to a given amount of kerning. We can use |j| as temporary pointer, since all we need is |j<>null|. @d cancel_glue(#)==j:=new_skip_param(#); cancel_glue_cont @d cancel_glue_cont(#)==link(#):=j; cancel_glue_cont_cont @d cancel_glue_cont_cont(#)==link(j):=#; cancel_glue_end @d cancel_glue_end(#)==j:=glue_ptr(#); cancel_glue_end_end @d cancel_glue_end_end(#)== stretch_order(temp_ptr):=stretch_order(j); shrink_order(temp_ptr):=shrink_order(j); width(temp_ptr):=#-width(j); stretch(temp_ptr):=-stretch(j); shrink(temp_ptr):=-shrink(j) @= if j=null then begin r:=new_kern(0); t:=new_kern(0); {the widths will be set later} end else begin r:=list_ptr(b); t:=link(r); end; u:=new_math(0,end_M_code); if type(t)=glue_node then {|t| is \.{\\rightskip} glue} begin cancel_glue(right_skip_code)(q)(u)(t)(e); link(u):=t; end else begin width(t):=e; link(t):=u; link(q):=t; end; u:=new_math(0,begin_M_code); if type(r)=glue_node then {|r| is \.{\\leftskip} glue} begin cancel_glue(left_skip_code)(u)(p)(r)(d); link(r):=u; end else begin width(r):=d; link(r):=p; link(u):=r; if j=null then begin b:=hpack(u,natural); shift_amount(b):=s; end else list_ptr(b):=u; end @ The |scan_tokens| feature of \eTeX\ defines the \.{\\scantokens} primitive. @= primitive("scantokens",input,2); @!@:scan_tokens_}{\.{\\scantokens} primitive@> @ @= else if chr_code=2 then print_esc("scantokens") @ @= else if cur_chr=2 then pseudo_start @ The global variable |pseudo_files| is used to maintain a stack of pseudo files. The |info| field of each pseudo file points to a linked list of variable size nodes representing lines not yet processed: the |info| field of the first word contains the size of this node, all the following words contain ASCII codes. @= @!pseudo_files:pointer; {stack of pseudo files} @ @= pseudo_files:=null; @ The |pseudo_start| procedure initiates reading from a pseudo file. @= procedure@?pseudo_start; forward;@t\2@> @ @= procedure pseudo_start; var old_setting:0..max_selector; {holds |selector| setting} @!s:str_number; {string to be converted into a pseudo file} @!l,@!m:pool_pointer; {indices into |str_pool|} @!p,@!q,@!r:pointer; {for list construction} @!w: four_quarters; {four ASCII codes} @!nl,@!sz:integer; begin scan_general_text; old_setting:=selector; selector:=new_string; token_show(temp_head); selector:=old_setting; flush_list(link(temp_head)); str_room(1); s:=make_string; @; flush_string; @; end; @ @= str_pool[pool_ptr]:=si(" "); l:=str_start[s]; nl:=si(new_line_char); p:=get_avail; q:=p; while lnl) do incr(l); sz:=(l-m+7)div 4; if sz=1 then sz:=2; r:=get_node(sz); link(q):=r; q:=r; info(q):=hi(sz); while sz>2 do begin decr(sz); incr(r); w.b0:=qi(so(str_pool[m])); w.b1:=qi(so(str_pool[m+1])); w.b2:=qi(so(str_pool[m+2])); w.b3:=qi(so(str_pool[m+3])); mem[r].qqqq:=w; m:=m+4; end; w.b0:=qi(" "); w.b1:=qi(" "); w.b2:=qi(" "); w.b3:=qi(" "); if l>m then begin w.b0:=qi(so(str_pool[m])); if l>m+1 then begin w.b1:=qi(so(str_pool[m+1])); if l>m+2 then begin w.b2:=qi(so(str_pool[m+2])); if l>m+3 then w.b3:=qi(so(str_pool[m+3])); end; end; end; mem[r+1].qqqq:=w; if str_pool[l]=nl then incr(l); end; info(p):=link(p); link(p):=pseudo_files; pseudo_files:=p @ @= begin_file_reading; {set up |cur_file| and new level of input} line:=0; limit:=start; loc:=limit+1; {force line read} if tracing_scan_tokens>0 then begin if term_offset>max_print_line-3 then print_ln else if (term_offset>0)or(file_offset>0) then print_char(" "); name:=19; print("( "); incr(open_parens); update_terminal; end else name:=18 @ Here we read a line from the current pseudo file into |buffer|. @= function pseudo_input: boolean; {inputs the next line or returns |false|} var p:pointer; {current line from pseudo file} @!sz:integer; {size of node |p|} @!w:four_quarters; {four ASCII codes} @!r:pointer; {loop index} begin last:=first; {cf.\ Matthew 19\thinspace:\thinspace30} p:=info(pseudo_files); if p=null then pseudo_input:=false else begin info(pseudo_files):=link(p); sz:=ho(info(p)); if 4*sz-3>=buf_size-last then @; last:=first; for r:=p+1 to p+sz-1 do begin w:=mem[r].qqqq; buffer[last]:=w.b0; buffer[last+1]:=w.b1; buffer[last+2]:=w.b2; buffer[last+3]:=w.b3; last:=last+4; end; if last>=max_buf_stack then max_buf_stack:=last+1; while (last>first)and(buffer[last-1]=" ") do decr(last); free_node(p,sz); pseudo_input:=true; end; end; @ When we are done with a pseudo file we `close' it. @= procedure pseudo_close; {close the top level pseudo file} var p,@!q: pointer; begin p:=link(pseudo_files); q:=info(pseudo_files); free_avail(pseudo_files); pseudo_files:=p; while q<>null do begin p:=q; q:=link(p); free_node(p,ho(info(p))); end; end; @ @= while pseudo_files<>null do pseudo_close; {flush pseudo files} @ @= primitive("readline",read_to_cs,1);@/ @!@:read_line_}{\.{\\readline} primitive@> @ @= else print_esc("readline") @ @= if j=1 then begin while loc<=limit do {current line not yet finished} begin cur_chr:=buffer[loc]; incr(loc); if cur_chr=" " then cur_tok:=space_token @+else cur_tok:=cur_chr+other_token; store_new_token(cur_tok); end; goto done; end @ The |cond| feature of \eTeX\ defines additional conditionals as well as the \.{\\unless} prefix. @d if_def_code=17 { `\.{\\ifdefined}' } @d if_cs_code=18 { `\.{\\ifcsname}' } @= primitive("unless",expand_after,1);@/ @!@:unless_}{\.{\\unless} primitive@> primitive("ifdefined",if_test,if_def_code); @!@:if_defined_}{\.{\\ifdefined} primitive@> primitive("ifcsname",if_test,if_cs_code); @!@:if_cs_name_}{\.{\\ifcsname} primitive@> @ @= else print_esc("unless") @ @= if_def_code:print_esc("ifdefined"); if_cs_code:print_esc("ifcsname"); @ The result of a boolean condition is reversed when the conditional is preceded by \.{\\unless}. @= begin get_token; if (cur_cmd=if_test)and(cur_chr<>if_case_code) then begin cur_chr:=cur_chr+unless_code; goto reswitch; end; print_err("You can't use `"); print_esc("unless"); print("' before `"); @.You can't use \\unless...@> print_cmd_chr(cur_cmd,cur_chr); print_char("'"); help1("Continue, and I'll forget that it ever happened."); back_error; end @ The conditional \.{\\ifdefined} tests if a control sequence is defined. We need to reset |scanner_status|, since \.{\\outer} control sequences are allowed, but we might be scanning a macro definition or preamble. @= if_def_code:begin save_scanner_status:=scanner_status; scanner_status:=normal; get_next; b:=(cur_cmd<>undefined_cs); scanner_status:=save_scanner_status; end; @ The conditional \.{\\ifcsname} is equivalent to \.{\{\\expandafter} \.{\}\\expandafter} \.{\\ifdefined} \.{\\csname}, except that no new control sequence will be entered into the hash table (once all tokens preceding the mandatory \.{\\endcsname} have been expanded). @= if_cs_code:begin n:=get_avail; p:=n; {head of the list of characters} repeat get_x_token; if cur_cs=0 then store_new_token(cur_tok); until cur_cs<>0; if cur_cmd<>end_cs_name then @; @; flush_list(n); b:=(eq_type(cur_cs)<>undefined_cs); end; @ @= m:=first; p:=link(n); while p<>null do begin if m>=max_buf_stack then begin max_buf_stack:=m+1; if max_buf_stack=buf_size then overflow("buffer size",buf_size); @:TeX capacity exceeded buffer size}{\quad buffer size@> end; buffer[m]:=info(p) mod @'400; incr(m); p:=link(p); end; if m>first+1 then cur_cs:=id_lookup(first,m-first) {|no_new_control_sequence| is |true|} else if m=first then cur_cs:=null_cs {the list is empty} else cur_cs:=single_base+buffer[first] {the list has length one} @ The |protected| feature of \eTeX\ defines the \.{\\protected} prefix command for macro definitions. Such macros are protected against expansions when lists of expanded tokens are built, e.g., for \.{\\edef} or during \.{\\write}. @= primitive("protected",prefix,8); @!@:protected_}{\.{\\protected} primitive@> @ @= else if chr_code=8 then print_esc("protected") @ \eTeX\ supports 256~classes of marks where \.{\\marks0} is a synonym for \.{\\mark}. Similarly there are 256~classes of topmarks etc. @= primitive("marks",mark,marks_code); @!@:marks_}{\.{\\marks} primitive@> primitive("topmarks",top_bot_mark,top_mark_code+marks_code); @!@:top_marks_}{\.{\\topmarks} primitive@> primitive("firstmarks",top_bot_mark,first_mark_code+marks_code); @!@:first_marks_}{\.{\\firstmarks} primitive@> primitive("botmarks",top_bot_mark,bot_mark_code+marks_code); @!@:bot_marks_}{\.{\\botmarks} primitive@> primitive("splitfirstmarks",top_bot_mark,split_first_mark_code+marks_code); @!@:split_first_marks_}{\.{\\splitfirstmarks} primitive@> primitive("splitbotmarks",top_bot_mark,split_bot_mark_code+marks_code); @!@:split_bot_marks_}{\.{\\splitbotmarks} primitive@> @ We have to declare |mark_class| as local variable for |initialize|. @= @!mark_class:eight_bits; {a mark class} @* \[54] System-dependent changes. @z @x l.24904 This section should be replaced, if necessary, by any special modifications of the program that are necessary to make \TeX\ 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. @y Here are the remaining changes to the program that are necessary to make \TeX\ work on VMS. Note that especial care has been taken not to introduce any new sections; therefore, everything up to this point has had the same section numbers as the canonical versions in ``\TeX: The Program''. Firstly, putting the cart before the horse, this is how we can return the final status of \TeX\ to the operating system, in such a way that DCL command procedures and the like can determine whether the run of \TeX\ was successfull or not. We use the \.{\$EXIT} system service; the value of its parameter is @.{\$}EXIT@> given by an appropriate symbolic constant taken from the \.{starlet} library. We also take this opportunity to call the |symbol_jobname| routine (defined below). @d VAX_exit==@=$exit@> @d VAX_ss_normal==@= sts$k_success @> @d VAX_ss_ignore==@= sts$m_inhib_msg @> @d VAX_ss_warning==@= sts$k_warning+sts$m_inhib_msg @> @d VAX_ss_error==@= sts$k_error+sts$m_inhib_msg @> @d VAX_ss_fatal==@= sts$k_severe+sts$m_inhib_msg @> @= symbol_jobname; case history of { Issue an appropriate VAX exit status } spotless: VAX_exit(VAX_ss_normal); { Everything OK! } warning_issued: VAX_exit(VAX_ss_warning); error_message_issued: VAX_exit(VAX_ss_error); fatal_error_stop: VAX_exit(VAX_ss_fatal) endcases @ |symbol_jobname| is a routine which takes the |job_name| string and the numeric portion of the extension of the output file and writes that information to the DCL symbol given by \.{/JOBNAME\_SYMBOL}. @./JOBNAME_SYMBOL@> The code here is based on code donated by Jim Walker of South Carolina University. @d VAX_set_symbol == @= lib$set_symbol @> @= procedure@?VAX_set_symbol(VAX_immed symbol: descr_ptr; VAX_immed value_string: descr_ptr; tbl_ind: integer := VAX_immed 0); external; @t\2@> @# procedure symbol_jobname; var tmp_descr: descr_ptr; begin tmp_descr:=nil; if job_qual then begin str_to_descr(job_name,tmp_descr); VAX_set_symbol (VAX_stdescr l_jobname, tmp_descr, 2); end; end; @ Support is provided for the \.{REVIEW} mode of DEC's Language-sensitive editor, @^Language-sensitive editor@> @^LSE@> by generating a \.{.dia} file. Any output sent via this routine is also repeated to that file if the global |copy_err| is |print_it|: if the characters are being ``repeated'' to produce a \.{label} for a \.{region/text} directive, then characters will only be copied if no more than |label_max| have been output; this is controlled by |label_size|. Negative values for this variable always permit printing. Since \TeX\ produces its error messages by many separate calls to various printing routines, we accumulate the full text in the \PASCAL\ internal file |temp_file| when |copy_err| is set to |save_it|. This file can later be |reset| and ``played back'' by standard \PASCAL\ routines. @d label_max=14 @= case copy_err of print_it: begin if label_size<> 0 then diag_char(s); if label_size>0 then decr(label_size) end; ignore_it: do_nothing; save_it: temp_char(s) endcases @ We introduce here variables which control the action of error reporting routines. When error message display is commenced, the variable |copy_err| is set to |save_it|: this causes parts of the error message to be saved in the internal file |temp_file|, which is rewound at this point. Certain parts of the error message are not so saved (|copy_err=ignore_it|). This variable is also used to cause messages to be written (|copy_err=print_it|) to the diagnostics file |diag_file|. Since VAX-\PASCAL\ supports proper enumeration types, we don't bother with defining numeric constants for this. When information is being written to the |diag_file|, we restrict the ``label'' portion of a diagnostic message to |label_max| characters, to preserve on-screen alignment in LSEdit's \.{REVIEW} buffer. Characters are only output through to the |diag_file| if |label_size| is non-zero, and this variable is decremented after each character has been output if it is positive. Thus negative values of |label_size| do not impose any restriction on the amount of text that may be output to the |diag_file|. @= @!copy_err:(ignore_it,print_it,save_it); @!label_size:-1..label_max; {Restricts ``printing'' in the \.{.dia} file} @ After the terminating period has been written, |copy_err| is reset to prevent further output to |temp_file|, which is also reset, ready to be ``replayed'' into the diagnostics file itself. This code is also used during initialization, and also before prompting for a new file name when \TeX\ has been unable to find a users' file. @= copy_err:=ignore_it; reset(temp_file) {Full \TeX\ message in |temp_file|} @ Every error message that \TeX\ creates is ``wrapped'' into a \.{diagnostic} environment for use by LSE's \.{REVIEW} mode. This is the text generated for the start of such an environment. @= wdiag_ln('!'); wdiag_ln(' start diagnostic') @ And this finishes off the \.{diagnostic} environment: we copy into it the informational part of \TeX's own error message. @= wdiag(' message "%TEX-E-TEXERROR, '); while not eof(temp_file) do begin wdiag(temp_file^); get(temp_file) end; wdiag_ln('"'); wdiag_ln(' end diagnostic') @ If the error report arises within the expansion of a macro, \TeX\ will report the expansions of all macros and arguments involved: each such line of information is used in the diagnostics file as a \.{label} (in the terminology of LSE's \.{REVIEW} mode). This is how we start it off, and ensure that no more than |label_max| characters are printed, thus preserving alignment of the text within the \.{\$REVIEW} buffer. @= wdiag(' region/text/label="'); copy_err:=print_it; label_size:=label_max @ The rest of the context (display of macro expansions, or whatever) forms the remainder of the diagnostic region label. @= copy_err:=ignore_it; wdiag('" "') @ On the other hand, if \TeX's error report specifies a location within a source file, the diagnostic region generated in the diagnostics file reports that location thus: @= wdiag(' region/file/primary '); diag_print(name); wdiag_ln(' -'); {Continuation line follows} wdiag_ln(' /line=',line:1,'/column_range=(1,65535)') @ Whenever |show_context| involves printing out a token list, we arrange to capture the printed tokens for our diagnostic file. @= wdiag(' region/text/label="'); copy_err:=print_it; label_size:=label_max @ As we write out the second line of the original source, split at the point of error detection, we don't want to include within the diagnostic file the newline nor the leading spaces. This looks like horrible duplication of code, but remember that |copy_err=print_it| \&{only} if a diagnostic file is being generated. @= if copy_err=print_it then begin copy_err:=ignore_it; print_ln; for q:=1 to n do print_char(" "); {print |n| spaces to begin line~2} copy_err:=print_it end else begin print_ln; for q:=1 to n do print_char(" "); {print |n| spaces to begin line~2} end @ After we've completed the display of the error context, we are able to complete the diagnostic region within the diagnostics file. @= if copy_err=print_it then begin wdiag('"/line=1/column_range=('); n:=n-l; wdiag_ln(n+1:1,',65535)'); copy_err:=ignore_it; end else wdiag_ln(' region/nested/column=',loc-start+1:1) @ When we are writing the remainder of the context to the terminal and/or transcript file, we need to ensure that it is also \&{all} copied to the diagnostics file. The diagnostic is completed by ``playing back'' the contents of the |temp_file|, which contains \TeX's error message. @= begin copy_err:=print_it; label_size:=-1 end @ When the \.{\string\show} primitive is used, it will later involve the display of a token; the latter would cause output to be written to the |temp_file| used for accumulating error messages for the diagnostics file, so we ensure here that the file will not be overfilled. @= copy_err:=ignore_it; rewrite(temp_file); {Internal file will later be |reset|} @ The |open_diag_file| routine is used to open a file into which error diagnostics are written to support DEC's Language-sensitive Editor (LSEdit). @^Language-sensitive editor@> @^LSE@> These may be used by the latter to locate the editor at the position within the source file at which an error has been detected. @= procedure open_diag_file; begin pack_job_name(".dia"); if pack_default_name(diag_qual,diagf_name,diagf_len) then begin while not a_open_out(diag_file) do prompt_file_name("diagnostics file name",".dia"); diag_name:=a_make_name_string(diag_file); wdiag_ln('start module'); end else diag_name:="."; clear_default_name; end; @ Here are a number of variables used during the initial extraction of the command line and its qualifiers. Firstly, we require separate flags for each of the possible qualifiers. We also need to declare those variables associated with support for the diagnostics file, utilized by LSEdit. @^Language-sensitive editor@> @^LSE@> When \TeX\ is producing error messages, they are created in ``dribs and drabs''; we utilize a \PASCAL\ `internal' file |temp_file| to accumulate the whole message for transfer to the diagnostics file. This mechanism is also used to create a command line by means of which an editor can be invoked by the user answering `\.e' in response to \TeX's error prompt. Since such invocation of an editor will disrupt access to the values associated with any qualifiers on the \.{TEX} command, we have to provide storage space for any values provided with those qualifiers, so that they may be read during the initialization phase, in preparation for use later (in some cases, much later) in the program. For each such piece of text, we need somewhere to save it, and somewhere else to record its length, for use with |pack_default_name|. @= @!format_qual, @!dvi_qual, @!cmd_line_present, @!continue_qual, @!eight_qual, @!job_qual,@!batch_qual, @!log_qual, @!diag_qual, @!edit_qual : boolean; @# @!diag_file : alpha_file; @!diag_name : str_number; @!temp_file : alpha_file; @# @!logf_name, @!diagf_name, @!edit_name, @!l_jobname, @!dvif_name : packed array[1..file_name_size] of char; @!logf_len, @!diagf_len, @!edit_len, @!l_len_name, @!dvif_len : file_size; @ Since we provide a command-line qualifier which will ``preload'' a format file, it would be best to extract all the qualifiers before the |banner| gets printed, so that the correct preloaded format can be displayed (it will never {\it really\/} be preloaded, but a VAX doesn't take long to read a \.{.FMT} file!) In fact, it is {\it essential\/} that all command-line qualifiers be read at this stage; the reader might imagine that extraction of qualifiers and their values could be deferred until the point at which the qualifier is used, but any intervening activation of another image (for example, an editor) results in the information being wiped out. The |cmd_line_present| flag will later avoid clearing the |buffer| if a command-line has already been ``read'' into it. We can control \TeX's operation in |batch_mode| through the \.{/BATCH} qualifier. At this point, we also initialize |copy_err|, which controls the insertion into the diagnostics file of text being (pseudo)printed in traditional \TeX\ error message. @= diag_name := 0; get_command_line; if batch_qual then interaction:=batch_mode; copy_err:=ignore_it@; @ For interacting with a user-supplied command line, we need to call the VAX standard library routines \.{CLI\$PRESENT}, \.{CLI\$GET\_VALUE}, \.{CLI\$DCL\_PARSE} and \.{LIB\$GET\_FOREIGN}. @.CLI{\$}PRESENT@> @.CLI{\$}GET_VALUE@> @.CLI{\$}DCL_PARSE@> @.LIB{\$}GET_FOREIGN@> This is a definition of their external interfaces: note the application of the `external' attribute, and use of the |extern| directive. @d VAX_external==@= external@> @d VAX_asynchronous==@= asynchronous@> @d VAX_cli_dcl_parse==@= cli$dcl_parse@> @d VAX_lib_get_foreign==@= lib$get_foreign@> @d VAX_lib_sig_to_ret==@= lib$sig_to_ret@> @d VAX_establish==@= establish@> @= [VAX_external] function VAX_cli_present(@/ VAX_stdescr @!entity: [VAX_volatile,VAX_readonly] packed array [l1..u1:integer] of char := VAX_immed 0) : integer; @/ extern;@;@t\2@>@# [VAX_external] function VAX_cli_get_value(@/ VAX_stdescr @!entity: [VAX_volatile,VAX_readonly] packed array [l1..u1:integer] of char := VAX_immed 0; VAX_stdescr @!returns: [VAX_volatile] packed array [l2..u2:integer] of char := VAX_immed 0; var @!retlen: [VAX_volatile] sixteen_bits := VAX_immed 0):integer; @/ extern;@;@t\2@>@# [VAX_external] function VAX_cli_dcl_parse(@/ VAX_stdescr @!cmdline: [VAX_volatile,VAX_readonly] packed array [l1..u1:integer] of char := VAX_immed 0; VAX_immed @!cld_table: [VAX_volatile,VAX_readonly] VAX_unsigned := VAX_immed 0):integer; @/ extern;@;@t\2@>@# [VAX_external] function VAX_lib_get_foreign(@/ VAX_stdescr @!cmdlin: [VAX_volatile] packed array [l1..u1:integer] of char := VAX_immed 0; VAX_stdescr @!prompt: [VAX_volatile] packed array [l2..u2:integer] of char := VAX_immed 0; var @!len: [VAX_volatile] sixteen_bits := VAX_immed 0; var @!flag: [VAX_volatile] integer := VAX_immed 0) :integer; @/ extern;@;@t\2@>@# [VAX_external, VAX_asynchronous] function VAX_lib_sig_to_ret(@/ VAX_ref @!signal_args: [VAX_volatile,VAX_unsafe] array [l1..u1:integer] of [VAX_byte] eight_bits; VAX_ref @!mechan_args: [VAX_volatile,VAX_unsafe] array [l2..u2:integer] of [VAX_byte] eight_bits) :integer; @/ extern; @ The following global symbol is used to refer to the command definition table linked into the \TeX\ program @d eTeX_CLD_table == @=ETEX_CLI@> @= @!eTeX_CLD_table : [VAX_external, VAX_readonly] VAX_unsigned; @ The |init_cli| function is invoked right at the beginning of \TeX, only preceded by the terminal output initialization. Its purpose is to make sure that the DCL command interface is available. This function checks, if the program was invoked by the DCL command specified through |verb_name| and that a command qualifier specified by |qual_name| is present (or defaulted) in the command description. For the second test, a small subroutine |check_cli| is needed, because of the "caller--callee" dependence required by the user error handler facility. The |verb_name| string supplied to |init_cli| by the caller must not exceed a length of 4, otherwise the comparison with the "last DCL command" does never succeed, because the DCL parser truncates commands to a length of 4! The test item |qual_name| should be a specific, non-negatable command qualifier for the verb |verb_name|, which is set by default in the command description. If either of these two tests fail, it can be assumed that the program was invoked as a foreign command (or started by the RUN command). If this case, the command line tail is fetched with the \.{LIB\$GET\_FOREIGN} runtime functions and parsed internally, using the \.{CLI\$DCL\_PARSE} utility routine and the command table linked into the program executable, whose name is supplied by the |table| formal parameter. @.LIB{\$}GET_FOREIGN@> @.CLI{\$}DCL_PARSE@> @= function init_cli( var @!table:[VAX_readonly] VAX_unsigned; @!verb_name:[VAX_readonly] packed array[l1..u1:integer] of char; @!qual_name:[VAX_readonly] packed array[l2..u2:integer] of char ): integer; label exit; var command_line: packed array[1..256] of char; @!len: sixteen_bits; @!sts: integer; function check_cli( @!unique_def_qual:[VAX_readonly] packed array[l1..u1:integer] of char ): integer; begin VAX_establish(VAX_lib_sig_to_ret); check_cli := VAX_cli_present(unique_def_qual); end; begin sts := VAX_cli_get_value('$VERB',command_line,len); if (odd(sts) and (len > 0)) then if (VAX_substr(command_line,1,len) = verb_name) then if (odd(check_cli(qual_name))) then begin init_cli := 1; return; end; VAX_lib_get_foreign(command_line,,len); {prepend |verb_name| plus a blank to |command_line|} command_line := verb_name + ' ' + VAX_substr(command_line,1,len); init_cli := VAX_cli_dcl_parse(command_line, VAX_address_of(table)); exit:end; @ Logically, the following procedure belongs with |init_terminal|; however, we can't declare it there because it calls functions which don't get declared until later, so we'll stuff it in just before the main program starts. If an editor is invoked later, its use of the command-line interface parsing routines will ``disable communications'', so we'd better extract any values associated with qualifiers now. The various flags are set or cleared according as to whether the associated qualifier is or is not present. @= procedure get_command_line; var qual_argument: packed array[1..256] of char; @!len: sixteen_bits; @!i: integer; @!j: 0..buf_size; begin cmd_line_present := odd(VAX_cli_present('COMMAND_LINE')); edit_qual := odd(VAX_cli_present('EDITOR')); if edit_qual then VAX_cli_get_value('EDITOR',edit_name,edit_len); job_qual:=odd(VAX_cli_present('JOBNAME_SYMBOL')); if job_qual then VAX_cli_get_value('JOBNAME_SYMBOL',l_jobname,l_len_name); continue_qual := odd(VAX_cli_present('CONTINUE')); batch_qual := odd(VAX_cli_present('BATCH')); dvi_qual := odd(VAX_cli_present('OUTPUT')); if dvi_qual then VAX_cli_get_value('OUTPUT',dvif_name,dvif_len); log_qual := odd(VAX_cli_present('LOG_FILE')); if log_qual then VAX_cli_get_value('LOG_FILE',logf_name,logf_len); diag_qual := odd(VAX_cli_present('DIAGNOSTICS')); if diag_qual then VAX_cli_get_value('DIAGNOSTICS',diagf_name,diagf_len); format_qual := odd(VAX_cli_present('FORMAT')); if format_qual then begin VAX_cli_get_value('FORMAT',qual_argument,len); loc := 0; buffer[0] := xord['&']; j := 1; for i := 1 to len do begin buffer[j] := xord[qual_argument[i]]; incr(j) end; buffer[j] := xord[' ']; { |open_fmt_file| requires space after name } if format_ident <> 0 then initialize; if not open_fmt_file then goto final_end; if not load_fmt_file then begin w_close(fmt_file); goto final_end; end; w_close(fmt_file); end; end; @ Here are the things we need for |byte_file| and |word_file| files: @= @!tfm_count: 0..VAX_block_length; @!fmt_count: 0..VAX_block_length; @ Here's the interrupt stuff. At this point, we define some attributes for specifying particular sizes and alignments of numerical quantities in VAX-\PASCAL. @d VAX_word==@= word @> @d VAX_longword==@= long @> @d VAX_byte==@= byte @> @d VAX_unsigned==@= unsigned @> @= @!signed_halfword=[VAX_word] -32768..32767; @!sixteen_bits=[VAX_word] 0..65535; @!file_size=[VAX_word] 0..file_name_size; @# @!VAX_F_float = packed record {Bit pattern layout of F-Floating Reals} @!Frac1 : 0..127; {the 7 MSBits of the mantissa} @!Expo : 0..255; {8 bit exponent} @!Sign : boolean; {1 sign bit} @!Frac : 0..65535; {the 16 lower bits of the mantissa} end; @ @= @!res: [VAX_volatile] integer; @!tt_chan: [VAX_volatile] signed_halfword; @ @= [VAX_asynchronous] procedure @!ctrlc_rout; begin interrupt:=1; enable_control_C; end; @ Here is the stuff for magic file operations. @d VAX_FAB_type==@= FAB$type @> @d VAX_RAB_type==@= RAB$type @> @d VAX_NAM_type==@= NAM$type @> @= @!unsafe_file = [VAX_unsafe] file of char; @!FAB_ptr = ^VAX_FAB_type; @!RAB_ptr = ^VAX_RAB_type; @!NAM_ptr = ^VAX_NAM_type; @!chrptr = ^char; @ We supply the following two routines to be used (in a call of the VAX-\PASCAL\ |open| procedure) as a |user_action| function. When called from within the |open| routine, the addresses of the |FAB| and |RAB| allocated to the file are passed to such a function, along with the file variable; the latter is tagged as `unsafe' to prevent undesirable compiler optimizations. The |user_reset| function, used to open files for reading, performs wild card expansion on the file specification and opens the first matching file. Both |user_action| functions copy the fully qualified name of the file that was actually opened into the global variable |last_name|. Additionally, the basename part of the filename is available in the string variable |last_basename|. The latter string is converted to lowercase; to comply with normal usage on other (case-sensitive) operating systems. The two external functions |VAX_PAS_FAB| and |VAX_PAS_RAB| permit access by the program to these structures after the file has been opened. @d VAX_rms_parse==@=$parse@> @d VAX_rms_search==@=$search@> @d VAX_rms_create==@=$create@> @d VAX_rms_connect==@=$connect@> @d VAX_rms_open==@=$open@> @# @d VAX_FAB_V_NAM== @=FAB$V_NAM@> @d VAX_FAB_L_NAM== @=FAB$L_NAM@> @d VAX_NAM_B_RSL== @=NAM$B_RSL@> @d VAX_NAM_L_RSA== @=NAM$L_RSA@> @d VAX_NAM_B_NAME== @=NAM$B_NAME@> @d VAX_NAM_L_NAME== @=NAM$L_NAME@> @= function user_reset (var FAB:VAX_FAB_type; var RAB:VAX_RAB_type; var F:unsafe_file):integer; label done; var sts:integer; @!NAM:NAM_ptr; @!p:chrptr; @!i:integer; @!ichr:integer; begin last_length:=0; sts:=VAX_rms_parse(FAB); if not odd(sts) then goto done; sts:=VAX_rms_search(FAB); if odd(sts) then FAB.VAX_FAB_V_NAM:=true; {Use |NAM| block in |VAX_rms_open| call!} sts:=VAX_rms_open(FAB); if not odd(sts) then goto done; sts:=VAX_rms_connect(RAB); if not odd(sts) then goto done; NAM:=FAB.VAX_FAB_L_NAM::NAM_ptr; if NAM=nil then goto done; last_length:=NAM^.VAX_NAM_B_RSL; for i:=1 to last_length do begin p:=(NAM^.VAX_NAM_L_RSA::integer+i-1)::chrptr; last_name[i]:=p^; end; last_basenam_len:=NAM^.VAX_NAM_B_NAME; for i:=1 to last_basenam_len do begin p:=(NAM^.VAX_NAM_L_NAME::integer+i-1)::chrptr; ichr:=ord(p^); if (ichr > 64) and (ichr < 91) then ichr := ichr+32; last_basename[i]:=chr(ichr); end; done: user_reset:=sts; end; @# function user_rewrite (var FAB:VAX_FAB_type; var RAB:VAX_RAB_type; var F:unsafe_file):integer; label done; var sts:integer; @!NAM:NAM_ptr; @!p:chrptr; @!i:integer; @!ichr:integer; begin sts:=VAX_rms_create(FAB); if not odd(sts) then goto done; sts:=VAX_rms_connect(RAB); if not odd(sts) then goto done; NAM:=FAB.VAX_FAB_L_NAM::NAM_ptr; if NAM=nil then goto done; last_length:=NAM^.VAX_NAM_B_RSL; for i:=1 to last_length do begin p:=(NAM^.VAX_NAM_L_RSA::integer+i-1)::chrptr; last_name[i]:=p^; end; last_basenam_len:=NAM^.VAX_NAM_B_NAME; for i:=1 to last_basenam_len do begin p:=(NAM^.VAX_NAM_L_NAME::integer+i-1)::chrptr; ichr:=ord(p^); if (ichr > 64) and (ichr < 91) then ichr := ichr+32; last_basename[i]:=chr(ichr); end; done: user_rewrite:=sts; end; @# function VAX_PAS_FAB(var foobar:unsafe_file):FAB_ptr; extern;@;@t\2@>@/ function VAX_PAS_RAB(var foobar:unsafe_file):RAB_ptr; extern; @ @= @!in_FAB,out_FAB: FAB_ptr; @!in_RAB,out_RAB: RAB_ptr; @!last_length: integer; @!last_name:packed array[1..file_name_size] of char; @!last_basenam_len: integer; @!last_basename:packed array[1..file_name_size] of char; @ The following procedure is used to translate any logical name that may appear as its parameter into its equivalence string and makes use of the \.{\$TRNLNM} @.{\$}TRNLNM@> system service in place of the obsolete \.{\$TRNLOG}. If the content of the @.{\$}TRNLOG@> buffer is a logical name, it is replaced by its equivalence string and the routine returns |true|. If no translation can be found, the result is |false|, and the original string is left unchanged. The VAX-\PASCAL\ procedure |substr| is used to extract a substring into the |varying| array which is passed to the system service, whilst another VAX-specific function |iaddress| is used to obtain the address of various data items to fill in the |item_list|. @d VAX_trnlnm==@= $trnlnm@> @d VAX_lnm_case_blind==@= lnm$m_case_blind @> @d VAX_lnm_string==@= lnm$_string @> @# @d VAX_substr==@= substr@> @d VAX_address_of==@= iaddress@> @= function translate ( var t : packed array [l1..u1 : integer] of char; var len : signed_halfword): boolean; var @!s: varying[file_name_size] of char; @!trnlnm_return: integer; {what did the \.{\$TRNLNM} return?} @!return_length: [VAX_volatile] integer; @!attributes: unsigned; @!item_list: [VAX_volatile] array [0..1] of VMS_item_list; begin s:=VAX_substr(t,1,len); attributes := VAX_lnm_case_blind; return_length := 0; with item_list[0] do begin buffer_length := file_name_size; item_code := VAX_lnm_string; buffer_addr := VAX_address_of(t); ret_len_addr := VAX_address_of(return_length); end; item_list[1].next_item := 0; trnlnm_return := VAX_trnlnm(attributes,'LNM$DCL_LOGICAL',s,,item_list); len := return_length; translate := trnlnm_return=VAX_ss_normal; end; @ Here is a new type introduced to support \.{\$TRNLNM}. Many VMS system @.{\$}TRNLNM@> services make use of an |item_list| to pass information in and out. An |item_list| consists of a number of |item_list| elements, with each element containing the following fields: \centerline{\vtop{\offinterlineskip\hrule \halign{\vrule#\hskip2pt&\strut#\hfil&#\hfil&#\hfil&\hskip2pt\vrule#\cr height2pt&\omit&\omit&\omit&\cr &\hfil Name & \hfil Type & \hfil Usage&\cr height2pt&\omit&\omit&\omit&\cr \noalign{\hrule} height2pt&\omit&\omit&\omit&\cr &|buffer_length| & 16-bit word & Size of buffer&\cr &|item_code| & unsigned 16-bit word & Code for desired operation&\cr &|buffer_address| & Pointer to char & Address of buffer&\cr &|ret_len_addr| & Pointer to integer & To receive length of translation&\cr height2pt&\omit&\omit&\omit&\cr} \hrule }} This structure is overlaid with a single 32-bit integer whose use is solely to hold the value zero indicating the end of the list. @== @!VMS_item_list = packed record case boolean of true: ( @!buffer_length : sixteen_bits;@/ @!item_code : sixteen_bits;@/ @!buffer_addr : [VAX_longword] integer;@/ @!ret_len_addr : [VAX_longword] integer); false: ( @!next_item : [VAX_longword] integer) end; @ If the user, in response to \TeX's error message, elects to edit the source file, then we have to find some method of invoking an editor. The simplest solution, under VMS, is simply to spawn a sub-process, but this is expensive in terms of image activation and might leave the sub-process short of page file quota, since the latter is shared by all processes in the current `job'. Therefore, where possible, we invoke a ``callable'' editor, which merely requires that we find the relevant editor's entry point in an installed shareable image. However, the library routine which can perform this trick returns the entry point as an address, and yet we want the \PASCAL\ code to think that it's invoking the editor through a procedure call, passing appropriate parameter(s). The callable versions of LSEdit @^Language-sensitive editor@> @^LSE@> and TPU each require a single parameter which is @^TPU@> @.EDIT/TPU@> a string similar to the DCL command that could be used to invoke the non-callable versions. In the case of EDT @^EDT@> @.EDIT/EDT@> @^Callable editors@> and TECO, @^TECO@> @.EDIT/TECO@> the first parameter gives the name of the file to be edited, the second (if used) names the output file, whilst the third can specify the name of a command file. Both editors can also take further parameters, and their meanings differ, but luckily we don't need any of these other parameters! Unfortunately, \PASCAL\ provides no mechanism by which a routine, which has amongst its formal parameters one which is in turn another routine, may be called with anything but the name of an \\{actual} routine (with congruent parameters) substitued for that formal parameter. Therefore, it is not permissible to pass the address of the routine instead and yet that is all that we have available! We therefore provide a procedure which calls, in turn, the actual editor ``procedure'', and resorting to subterfuge, invoke a rather useful VAX Library Routine: @d VAX_lib_callg==@= lib$callg@> @= [VAX_external] function VAX_lib_callg (@/ VAX_immed arg_list : [VAX_longword] integer; VAX_immed user_proc: [VAX_longword] integer) : integer; extern;@t\2@>@# function call_editor ( @!proc: [VAX_longword] integer; @!param_1, @!param_3 : [VAX_volatile] descr_ptr ) : integer; var @!call_G_descriptor : packed array [1..4] of [VAX_longword] integer; begin call_G_descriptor[1] := 1; {Number of arguments} call_G_descriptor[2] := param_1::integer; {DCL-like command line or name of file to be edited} if param_3 <> nil then begin call_G_descriptor[1] := 3; {EDT and TECO require more arguments} call_G_descriptor[3] := 0; {Default the output file name} call_G_descriptor[4] := param_3::integer; {Editor command file} end; call_editor:=VAX_lib_callg(VAX_address_of(call_G_descriptor),proc) end; @ Here is the interface to two routines from the run-time library to handle dynamic strings. Also, we declare here the interface to the \.{LIB\$SIGNAL} @.LIB{\$}SIGNAL@> library function, because we don't have much else to fall back on if an error crops up whilst allocating strings! @d str_allocate ==@= str$get1_dx@> @d str_release ==@= str$free1_dx@> @d lib_signal ==@= lib$signal@> @d VAX_char_string==@= dsc$k_dtype_t @> @d VAX_class_S==@= dsc$k_class_s @> @d VAX_class_D==@= dsc$k_class_d @> @= [VAX_external, VAX_asynchronous] function str_allocate(@/ VAX_ref alloc : [VAX_readonly] sixteen_bits; VAX_immed descrp : descr_ptr ) : integer; extern;@t\2@> @# [VAX_external, VAX_asynchronous] function str_release(@/ VAX_immed descrp : descr_ptr ) : integer; extern;@t\2@> @# [VAX_external, VAX_asynchronous] procedure lib_signal(@/ VAX_immed cond_code: integer; VAX_immed num_of_args: integer := VAX_immed 0; VAX_immed fao_argument: [@=list,unsafe@>] integer ); extern; @ Some editors require either command or file specifications to be passed to them as parameters, which in turn requires that they be passed in the form of string descriptors. Many of the strings that we have to deal with are held within \TeX's string pool. This routine converts a \.{WEB}-type string (from the pool) into an appropriate VAX-\PASCAL\ string descriptor. Any existing string described by |dynam_str| is returned to the operating system and a new string allocated to reflect the actual length of the string in |pool_string|. @= procedure str_to_descr( @!pool_string : str_number; var @!dynam_str : [VAX_volatile] descr_ptr); var @!ch_ptr, @!str_stat : integer; @!str_size : sixteen_bits; @!ch_ctr : chrptr; begin if dynam_str = nil then begin new( dynam_str ); with dynam_str^ do begin len := 0; desc_type := VAX_char_string; desc_class := VAX_class_D; string := 0 end; end else if dynam_str^.len <> 0 then begin str_stat := str_release( dynam_str ); if not odd(str_stat) then lib_signal(str_stat) end; ch_ptr := str_start[pool_string]; str_size := str_start[pool_string+1]-str_start[pool_string]; str_stat := str_allocate(str_size,dynam_str); if not odd(str_stat) then lib_signal(str_stat); ch_ctr := dynam_str^.string :: chrptr; while str_size>0 do begin ch_ctr^ := xchr[so(str_pool[ch_ptr])]; ch_ctr := (ch_ctr::integer + 1)::chrptr; incr(ch_ptr); decr(str_size) end; end; @ Here is where we declare a structure to hold a VMS Descriptor. We could just have used one of the definitions in the \.{STARLET} library that we've inherited, but declaring it here is an aid to understanding. \centerline{\vtop{\offinterlineskip\hrule \halign{\vrule#\hskip2pt&\strut#\hfil&#\hfil&#\hfil&\hskip2pt\vrule#\cr height2pt&\omit&\omit&\omit&\cr &\hfil Name & \hfil Type & \hfil Usage&\cr height2pt&\omit&\omit&\omit&\cr \noalign{\hrule} height2pt&\omit&\omit&\omit&\cr &|len| & 16-bit word & Elements in the array&\cr &|desc_type| & unsigned 8-bit byte & Type of items in array&\cr &|desc_class| & unsigned 8-bit byte & \\{e.g.} Fixed, Varying, Dynamic&\cr &|string| & Pointer to char & Address of first item in array&\cr height2pt&\omit&\omit&\omit&\cr} \hrule }} It also makes life much easier, when passing dynamic strings as parameters, especially to system services and library routines which expect to be passed the address of such a descriptor, to have a type which is a pointer to such a descriptor, and then pass the pointer's value by immediate parameter-passing mechanism. @= @!descr_type = packed record {A VAX-descriptor object} @!len : sixteen_bits; @!desc_type : eight_bits; @!desc_class: eight_bits; @!string : [VAX_longword] integer; end; @!descr_ptr = ^descr_type; @ Here is a procedure to dispose of dynamically-allocated strings when they are no longer required. @= procedure release ( @!string : descr_ptr ); var str_stat : integer; begin if string <> nil then begin str_stat := str_release( string ); if not odd(str_stat) then lib_signal( str_stat ); dispose(string); string := nil; end; end; @ This version of \TeX\ supports various editors; that required by the user must be specified by the qualifier \.{/EDITOR} which is set by default to \.{TEX\_EDIT} (which should be defined as a VMS logical name, in analogy with @.TEX_EDIT@> @.MAIL{\$}EDIT@> \.{MAIL\$EDIT}---in fact some system managers may want to set the default in the CLD file to {\it be\/} \.{MAIL\$EDIT}). If this qualifier specifies one of the strings `\.{Callable\_LSE}', `\.{Callable\_TPU}', `\.{Callable\_EDT} @.Callable_xxx@> or `\.{Callable\_TECO}' (or a logical name translating to one of those strings), the appropriate editor is invoked from its callable shared image. Any other value for \.{/EDITOR} is treated as a DCL command, and a sub-process is spawned in which the command is executed; the name of the file to be edited, together with the location of the error, are passed as parameters to this DCL command, which will most usefully, therefore, be defined to invoke a command procedure. Because the original version of this change file simply used \.{TEX\_EDIT} directly and this is the default value, the remainder of this exposition will simply refer to the value of \.{/EDITOR} as \.{TEX\_EDIT}. Here is a data structure which holds details of the supported callable editors: @= @!editor_ident = record @!cmd_offset : integer; @!image, @!entry, @!quitting, @!exiting, @!cmd_text: str_number; @!start_qual, @!EDT_like : boolean; @!logical : packed array[1..file_name_size] of char; end; @ We need a suitably sized array of such structures: @d max_editor=4 @# @d LSE_editor=1 @d TPU_editor=2 @d EDT_editor=3 @d TECO_editor=4 @= @!editor : packed array [1..max_editor] of editor_ident; @ And we needs must initialize them: @= with editor[LSE_editor] do begin logical := 'CALLABLE_LSE'; image := "LSESHR"; entry := "LSE$LSE"; quitting := "TPU$_QUITTING"; exiting := "TPU$_EXITING"; cmd_text := "LSEdit"; cmd_offset := 0; start_qual:=true; EDT_like := false; end; with editor[TPU_editor] do begin logical := 'CALLABLE_TPU'; image := "TPUSHR"; entry := "TPU$TPU"; quitting := "TPU$_QUITTING"; exiting := "TPU$_EXITING"; cmd_text := "EDIT/TPU"; cmd_offset := 5; {Actual command expected by \.{TPU\$TPU} omits \.{EDIT/}} start_qual:=true; EDT_like := false; end; with editor[EDT_editor] do begin logical := 'CALLABLE_EDT'; image := "EDTSHR"; entry := "EDT$EDIT"; quitting := 0; exiting := 0; cmd_text := "EDIT/EDT"; cmd_offset := 0; start_qual:=false; EDT_like := true; end; with editor[TECO_editor] do begin logical := 'CALLABLE_TECO'; image := "TECOSHR"; entry := "TECO$EDIT"; quitting := 0; exiting := 0; cmd_text := "EDIT/TECO"; cmd_offset := 0; start_qual := false; EDT_like := true; end; @ When we invoke an editor, there are three (possibly more?) potential outcomes: (1) The editor cannot be invoked --- perhaps we should find some other method; (2) The user makes no change to the file (quits); (3) The use produces a new version of the file. This type allows us to discriminate between these outcomes: @= @!edit_result = (failed,quit,edited); @ If the user elects to edit the relevant input file in response to an error message, we prefer to use an editor provided as a ``callable image'', since this saves the overhead of spawning a sub-process. DEC provide callable versions of EDT, @^EDT@> @.EDIT/EDT@> @^Callable editors@> TPU, @^TPU@> @.EDIT/TPU@> LSEdit (the language-sensitive editor, highly recommended for \LaTeX), @^Language-sensitive editor@> @^LSE@> and even for that editor beloved of many, TECO. @^TECO@> @.EDIT/TECO@> To activate such a callable image, we need to load it into the process's \.{P0} space, and determine its entry point before transferring control to it with appropriate parameters. If it proves impossible to load a suitable callable image, we can adopt the expedient of spawning a new (DCL) sub-process, and pass to it the command to be executed. When such a spawned sub-process is given a single command to execute, the exit status of that command is passed back to the parent process when the sub-process exits. In most useful applications of such a sub-process, the ``command'' to be executed will be a DCL command procedure; the code below will accept an exit status of $1$ as indicating that an edit has taken place, the value $0$ (which is of warning severity level) as showing that the edit was aborted (the user quit), and any other value will be interpreted as indicative of a failure of the sub-process to perform editing. The official definition of \.{LIB\$SPAWN} has about a dozen parameters, but @.LIB{\$}SPAWN@> since all of them are optional, and we only need to pass a command (which is the first parameter) and get back the completion status (which is the seventh), we'll pretend that it only takes seven parameters. @d VAX_find_image ==@= lib$find_image_symbol@> @d VAX_lib_spawn ==@= lib$spawn@> @= [VAX_external] function VAX_find_image (@/ VAX_immed @!filenm : descr_ptr; VAX_immed @!symbol : descr_ptr; VAX_ref @!symbol_value : [VAX_volatile,VAX_longword] integer; VAX_immed @!image_name : descr_ptr := VAX_immed 0) : integer; @/ extern;@t\2@> @# [VAX_external] function VAX_lib_spawn (@/ VAX_immed @!cmd : descr_ptr; VAX_immed @!sys_input : descr_ptr := VAX_immed 0; VAX_immed @!sys_output : descr_ptr := VAX_immed 0; VAX_ref @!flags : [VAX_longword] integer := VAX_immed 0; VAX_immed @!prcnm : descr_ptr := VAX_immed 0; VAX_ref @!pid : [VAX_longword] integer := VAX_immed 0; VAX_ref @!status : [VAX_longword] integer := VAX_immed 0 ): integer; @/ extern;@t\2@> @# function Edit ( @!filenm, @!cmd_file : str_number; @!editor : editor_ident ): edit_result; var @!edit_command_line : descr_ptr; @!char_ct : sixteen_bits; @!editor_entry : integer; @!editor_status, @!str_stat : integer; @!ch_ptr : chrptr; @!quit_status, @!exit_status : integer; @!image_symbol, @!entry_point, @!bad_symbol, @!good_symbol : descr_ptr; @!edit_file, @!edit_cmd : descr_ptr; begin @; edit_command_line := nil; @; edit_file:=nil; edit_cmd:=nil; if editor.EDT_like then {Such editors take \\{filenames} as parameters} begin str_to_descr(filenm,edit_file); str_to_descr(cmd_file,edit_cmd); end; Edit := failed; {Assume the worst!} editor_status := 4; {Neither edited nor quitted} quit_status := 0; {Users' command procedures can return this for quitting} exit_status := VAX_ss_normal; @; if editor.image <> 0 then {Possibly callable} begin if VAX_find_image(image_symbol,entry_point,editor_entry)=VAX_ss_normal then @ else editor.image := 0 {Indicate inability to invoke shareable image} end; if editor.image = 0 then {Use non-shareable-image editing} str_stat:=VAX_lib_spawn(cmd:=edit_command_line,status:=editor_status); @; @ end; @ The data structure |editor| contains pool strings giving the name of the required shareable image and the names of symbols which are to be sought for in it. This is where we translate those strings into dynamic ones to be passed to \.{LIB\$FIND\_IMAGE\_SYMBOL} @.LIB{\$}FIND_IMAGE_SYMBOL@> @== image_symbol := nil; entry_point := nil; bad_symbol := nil; good_symbol := nil; str_to_descr(editor.image,image_symbol); str_to_descr(editor.entry,entry_point); str_to_descr(editor.quitting,bad_symbol); str_to_descr(editor.exiting,good_symbol) @ If we're to invoke a callable editor, we have now obtained its entry point, which will have caused its image to be loaded into the process's \.{P0} space. Now we find within the image the values associated with the symbols which indicate whether the editor was used to create a new file or whether the use quit without creating a new file (only possible for LSEdit and TPU; with EDT and TECO, we assume that any successful exit resulted in the creation of a new file). @= begin @; if editor.EDT_like then editor_status:=call_editor(editor_entry,edit_file,edit_cmd) else @; end @ Just to keep things tidy, we dispose of all dynamic strings used by |Edit| before exit; this ensures that repeated invocation of an editor will not result in the ``eating up'' of virtual memory. @= release(image_symbol); release(entry_point); release(bad_symbol); release(good_symbol); release(edit_command_line); release(edit_file); release(edit_cmd); @ After the editor, whether running in a spawned sub-process or as a callable version in a shared image, has returned control to \TeX, we attempt to interpret its exit status. Having removed any flag instructing the CLI to ignore the error status (because the editor will have reported such an error already), we attempt to match the exit status against the values which we have preset as indicating normal exit or quit from the editor. Any other value will leave the value |failed| to be returned by |Edit|: this should cause \TeX\ to inform the user that the edit will have to be performed ``off-line''. @= if editor_status>=VAX_ss_ignore then editor_status:=editor_status-VAX_ss_ignore; if editor_status = exit_status then Edit := edited else if editor_status = quit_status then Edit := quit @ As well as containing the entry point at which the callable editor should be entered, its image file may also contain global symbols which give the exit status which will be returned by the editor if the user exits successfully, having written a new file, or quits without writing a new file. We extract the values of these symbols so that this status can be interpreted on exit from this procedure |Edit|. @= if editor.quitting<>0 then if not odd(VAX_find_image(image_symbol,bad_symbol,quit_status)) then quit_status := VAX_ss_normal; if editor.exiting<>0 then if not odd(VAX_find_image(image_symbol,good_symbol,exit_status)) then exit_status := VAX_ss_normal @ If we're invoking the callable version of TPU, we have to remove the `\.{EDIT/}' from the `\.{EDIT/TPU...}' command that we've constructed in |edit_command_line|. This code removes the first |editor.cmd_offset| characters of the command by overwriting with spaces, which achieves the desired effect. We then invoke the editor through |call_editor|. @= begin ch_ptr := edit_command_line^.string :: chrptr; for char_ct := 1 to editor.cmd_offset do begin ch_ptr^ := ' '; {Expunge the first |cmd_offset| characters} ch_ptr := (ch_ptr::integer + 1)::chrptr end; editor_status:=call_editor(editor_entry,edit_command_line,nil); end @ So far, we've managed to construct in the |temp_file| a command to be passed to the callable editor (through appropriate diversion to that \PASCAL\ internal file during the analysis of the logical \.{TEX\_EDIT}). So that we can allocate @.TEX_EDIT@> an appropriately sized dynamic string and its descriptor to be passed to the callable image, we need initially to determine how long that command really is: @= reset(temp_file); char_ct:=1; while not eof(temp_file) do begin get(temp_file); incr(char_ct) end @ Now we can allocate the dynamic string to hold the editor command, and copy the latter into it. Perhaps it might be thought that this could be simplified, because we could ``replay'' the command from the |temp_file| into a pool string by setting |selector| to |new_string| and then using |str_to_descr|: however, I'm not sure that this would be safe if in so doing we exceeded the allocated string pool, so we're going to do a bit more work! @= new( edit_command_line ); with edit_command_line^ do begin len := 0; desc_type := VAX_char_string; desc_class := VAX_class_D; string := 0 end; str_stat := str_allocate( char_ct, edit_command_line ); if not odd(str_stat) then lib_signal(str_stat); ch_ptr := edit_command_line^.string::chrptr; reset(temp_file); while not eof(temp_file) do begin ch_ptr^ := temp_file^; get(temp_file); ch_ptr := (ch_ptr::integer + 1)::chrptr end @ Certain VAX callable editors (\.{LSE} and \.{TPU}) accept a qualifier which may be used to specify the row and column number at which the editor's cursor is to be positioned. This routine adds suitable characters to the editor command line currently under construction in |temp_file|. @= procedure edit_locate(@!line, @!col : integer); begin print("/START_POSITION=("); print_int(line); print_char(","); print_int(col); print(") ") end; @ The function |edit_file| is called from the error reporting routine with the context of an input file and the line number as parameters. It forms a command for the desired editor (making using of |temp_file| and various of the error printing routines). The function returns |true| if it was able to invoke an editor. If |false| is returned, the user-interface routine should tell the user what and where to edit, and exit from \TeX. First of all, we need to make a forward declaration in order that the code which interprets the user's response can be compiled to call this procedure. @= function edit_file( @!stack_item : in_state_record; line : integer ) : boolean; forward; @ But the function itself needs to {\it follow\/} all those declared in \.{WEB} modules, so we put it just before the main program itself. To determine what name to use in invoking the editor, this function attempts to translate the value of \.{/EDITOR}; if the translation is recognized, then we'll use that as the value, otherwise, we'll use the value given by \.{/EDITOR}. If the editing of the file has (or could have) created a new version of the source file, then steps are taken to ensure that further edits all access the newly created file(s) rather than the original. @= function edit_file; {|( @!stack_item : in_state_record; line : integer ) : boolean|} var @!equivalence : packed array[1..file_name_size] of char; @!equv_len : signed_halfword; @!old_setting : integer; @!edit_status : edit_result; @!edit_ctr : integer; @!edit_found : integer; @@; begin old_setting:=selector; selector:=log_only; edit_file := false; edit_status:=failed; {Assume the worst!} equivalence:=edit_name; equv_len:=edit_len; if edit_qual then if equivalence[equv_len]=':' then begin equivalence[equv_len]:=' '; decr(equv_len); edit_qual:=translate(equivalence,equv_len); end; if edit_qual then @; if edit_status<>failed then begin edit_file := true; if edit_status=edited then @ end; selector:=old_setting; end; @ If the logical \.{TEX\_EDIT} has a suitable translation, we attempt to @.TEX_EDIT@> identify the ``preferred'' editors (preferred in the sense that they can be invoked from a shareable image, without the overhead of spawning a new process). @= begin print_nl("Issuing the following command:"); @.Issuing the following command:@> @; @; if edit_found<>0 then @ else @; copy_err:=ignore_it; selector:=old_setting; end @ The equivalence string for \.{TEX\_EDIT} needs to be converted to upper-case, @.TEX_EDIT@> to ensure that it may be matched to the names of the preferred editors. @= for edit_ctr:=1 to equv_len do if equivalence[edit_ctr] in ['a'..'z'] then equivalence[edit_ctr] := xchr[xord[equivalence[edit_ctr]]+"A"-"a"] @ Now that we have the equivalence string in upper-case, we attempt to match it with the names of the preferred editors in the data structure |editor|. For testing equality between two strings, we use VAX-\PASCAL's |index| function. @d VAX_index==@= index@> @= edit_ctr:=1; edit_found:=0; while (edit_ctr<=max_editor) and (edit_found=0) do begin if VAX_index(editor[edit_ctr].logical,equivalence) = 1 then edit_found:=edit_ctr; incr(edit_ctr) end; @ Well, we now know that the user wishes to use one of the supported \\{callable} editors. So the next move is to construct suitable command strings and invoke the editor from the appropriate shareable image. @= with editor[edit_found] do begin rewrite(temp_file); copy_err:=save_it; print_nl(cmd_text); if start_qual then with stack_item do edit_locate(line,loc_field-start_field+1); if edit_found=EDT_editor then @; if edit_found=TECO_editor then @; print(stack_item.name_field); copy_err:=ignore_it; selector:=old_setting; if EDT_like then begin edit_status := Edit(stack_item.name_field,cmd_file,editor[edit_found]); @ end else edit_status := Edit(0,0,editor[edit_found]); end @ The common-or-garden \.{EDT} editor doesn't have a qualifier to specify the starting position, so we create a small command file, and specify its name on the \.{/COMMAND} qualifier for \.{EDT} The command file contains line-mode commands to position the cursor appropriately. Strictly speaking, it is illegal to issue a \.{CHANGE} command (which is the only one that accepts no-keypad commands to position the cursor) except from a terminal, and \.{EDT} will display a message about this when it executes the command from the command file; however, it \\{does} honour the command correctly, so the end does justify the means! It seemed too complicated to try to delve into \TeX's ``whatsits'', and yet we'll want to use |print|, etc, to transfer text into the file. Previous versions of the VMS implementation tried to create the file using the first free |file| in the array |write_file|. But this approach failed, if \TeX\ has got all sixteen available files in use (although this is a rare case). To prevent this interference with TeX's own use of its output streams, the present solution uses a dedicated file |edcmd_file| for creating the editor command file. It is access by setting |selector| to the additional mode |edcmd_write|. @= begin name_of_file:='TEX_EDTINI'; default_name:='.EDT'; if a_open_out(edcmd_file) then begin cmd_file:=make_name_string; equivalence:='EDTINI'; equv_len:=6; {If it's defined} if not translate(equivalence,equv_len) then equv_len:=6; copy_err:=ignore_it; selector:=edcmd_write; print("SHOW COMMAND"); print_ln; print("CHANGE "); print_int(line); print_char(";"); with stack_item do print_int(loc_field-start_field); print("(+C)"); print_ln; print("SET MODE CHANGE"); print_ln; print("SET COMMAND "); for kkk:=1 to equv_len do print_char(xord[equivalence[kkk]]); a_close(edcmd_file); copy_err:=save_it; selector:=log_only; print("/COMMAND="); print(cmd_file); print_char(" "); end end @ Here are the other variables used in the above module: @= @!kkk : integer; @!cmd_file : str_number; @ Neither does the \.{TECO} editor accept such a qualifier, so again we create a suitable command file. @= begin name_of_file:='TEX_TECOINI'; default_name:='.TEC'; if a_open_out(edcmd_file) then begin cmd_file:=make_name_string; equivalence:='TEC$INIT'; equv_len:=8; {If it's defined} copy_err:=ignore_it; selector:=edcmd_write; if translate(equivalence,equv_len) then begin if equivalence[1]='$' then begin print("EI"); for kkk:=2 to equv_len do print_char(xord[equivalence[kkk]]); end else for kkk:=1 to equv_len do print_char(xord[equivalence[kkk]]); print_char(@"1B); print_ln; end; print("@@^U1/"); print_int(line); print("U0"); with stack_item do print_int(loc_field-start_field); print("U20U1<(Q1+1)U1(Q0-Q1-1):;L(.-Z)""LF>'^E""L(Q1+1)U1'P>Q2CT/"); print_char(@"1B); print_char(@"1B); print_ln; a_close(edcmd_file); copy_err:=save_it; selector:=log_only; print("/COMMAND="); print(cmd_file); print_char(" "); @ end end @ Unfortunately, the present version (V40.36) of \.{TECO} does not appear to make use of the third parameter (which is supposed to be the name of an initialization file). Therefore, we create (or redefine) the logical name \.{TEC\$INIT}, which the callable version of TECO will then use. Afterwards, of @.TEC{\$}INIT@> course, we have to put things back as they were, since otherwise a further invocation of the editor would introduce a circularity. The requirement for \.{TEC\$INIT} is that its first character shall be a dollar sign (`\.\$') to indicate that the rest of the logical name gives the name of a file to be used for initialization. @d VAX_create_logical==@= $crelnm@> @= begin TECO_cmd := '$'; kkk:=str_start[cmd_file]; while kkk= @!TECO_cmd : [VAX_volatile] varying [file_name_size] of char; @!item_list : [VAX_volatile] array [0..1] of VMS_item_list; @ After \.{EDT} or \.{TECO} has completed its editing, we are at liberty to delete the command file that was used to direct the cursor to the appropriate place. We've got the full file specification saved up from when the file was created, so we can go ahead and use the VAX-\PASCAL\ |delete_file| command to remove the file. @d VAX_delete_logical==@= $dellnm@> @d VAX_delete_file ==@= delete_file@> @= begin if edit_found=TECO_editor then begin if equv_len>0 then begin with item_list[0] do begin buffer_length := equv_len; item_code := VAX_lnm_string; buffer_addr := VAX_address_of(equivalence); ret_len_addr := 0; end; item_list[1].next_item := 0; VAX_create_logical(,'LNM$PROCESS_TABLE','TEC$INIT',,item_list); end else VAX_delete_logical('LNM$PROCESS_TABLE','TEC$INIT'); end; VAX_delete_file(last_name) end @ Once a source file has been edited, any further calls of an editor should access the latest version of the source file, rather than that first opened by \TeX. Therefore, as a crude approximation to this desired outcome, we truncate the file specification held in the pool by substituting spaces for the `\.;' and any characters that follow it in there. (This is a good approximation, since generally any revised file will have been written out to the next higher version number, and the method adopted is easier than trying to shuffle all of the pool down to fill the vacant space.) @= begin had_semicolon := false; for next_ch := str_start[stack_item.name_field] to str_start[stack_item.name_field+1]-1 do begin if str_pool[next_ch] = si(";") then had_semicolon := true; if had_semicolon then str_pool[next_ch] := si(" ") end; end @ Here's the necessary global variables for the previous module: @= @!next_ch : pool_pointer; @!had_semicolon : boolean; @ If we were unable to recognize the equivalence string for the \.{TEX\_EDIT} @.TEX_EDIT@> logical name, it's assumed to be a DCL command (most probably preceded by an `\.@@' to invoke a command procedure). The command will be run in a sub-process, and provided with three parameters: the name of the file to be edited, and the row and column numbers (starting from 1) of the file at which the error was detected. The following code constructs the requisite DCL command ready to be passed to the spawned sub-process by procedure |Edit|. As for the callable editors above, this command is constructed in the \PASCAL\ internal file |temp_file|, using various print commands. @= begin rewrite(temp_file); copy_err:=save_it; print_ln; for kkk:=1 to equv_len do print(xord[equivalence[kkk]]); print(" "); print(stack_item.name_field); print(" "); print_int(line); print(" "); with stack_item do print_int(loc_field-start_field+1); edit_status := Edit(0,0,empty_editor); end @ Here's a dummy |editor| structure to be passed to |Edit| for non-callable editors: @= @!empty_editor : editor_ident; @ and its initialization: @= with empty_editor do begin logical := ''; image := 0; entry := 0; quitting := 0; exiting := 0; cmd_text := 0; cmd_offset := 0; start_qual := false; EDT_like := false; end; @z