% This program by E. W.(Wayne) Sewell is not copyrighted % and can be used freely. % Version 1.0 was released in March, 1987. \font\ninerm=cmr9 \def\mod{{\hbox{Modula-2}}} \def\title{SCANTEX} %\hsize 84mm %\input webdoub \def\contentspagenumber{1} % should be odd \def\topofcontents{\null\vfill \centerline{\titlefont The {\ttitlefont \title} processor} \vskip 15pt \centerline{(Version 1.0)} \vfill} \pageno=\contentspagenumber \advance\pageno by 1 @* Introduction. This program takes a \TeX\ file generated by \.{WEAVE} and strips out the sections which have not been changed, outputting the changed sections to a second, greatly reduced \TeX\ file. The index, section names, and table of contents are dropped as well. @^Modula-2@> The program uses a few features of the \mod\ compiler used in its development (Logitech MS-DOS) @^Logitech@> @^MS-DOS@> that may need to be changed in other installations. System-dependent portions of \.{SCANTEX} can be identified by looking at the entries for `system dependencies' in the index below. @!@^system dependencies@> The ``banner line'' defined here should be changed whenever \.{SCANTEX} is modified. @d banner=='This is SCANTEX, Version 1.0' @ The program begins with a fairly normal header, made up of pieces that @^system dependencies@> will mostly be filled in later. The \TeX\ input comes from file |TeX_file| @^Modula-2@> and the new \TeX\ output goes to file |out_TeX_file|. Unlike Pascal, \mod\ does not require the constant, type, and variable sections to be placed here in the program header in a rigidly specified order, but we will do it anyway, since \.{WEB} makes it so easy. @p module scan_TeX; @@; const @@/ type @@/ var @@/ @ This procedure initializes the module. @p procedure initialize; var @@/ begin @@/ @; end initialize; @ A few macro definitions for low-level output instructions are introduced here. All of the terminal-oriented commands in the remainder of the module will be stated in terms of simple primitives. The boxes signify words that must not be forced to @^Modula-2@> uppercase when the program is \.{MANGLED}, since \mod\ is case-sensitive. @^system dependencies@> @d pr_char==@= Write @> (* library procedure to output a character *) @d pr_string==@= WriteString @> (* library procedure to output a string *) @d rd_string==@= ReadString @> (* library procedure to input a string *) @d pr_card==@= WriteCard @> (* library procedure to output a cardinal number *) @d new_line==@= WriteLn @> (*a new line *) @d print_string(#)==pr_string(#) (*put a given string to the terminal*) @d read_string(#)==rd_string(#) (*read a given string from the terminal *) @d print_cardinal(#)==pr_card(#,1) (*put a given cardinal to the terminal, in decimal notation, using only as many digit positions as necessary*) @d print_ln(#)==pr_string(#); new_line; (*put a given string to the terminal, followed by a new line *) @d print_char(#)==pr_char(#) (*put a given character to the terminal*) @= from @= InOut @> import pr_string, rd_string, pr_char, pr_card, new_line; @.InOut@> @ Let's define a few constants. @= @!buf_size=1000; (*maximum length of input line*) @!file_name_len=200; (*length of a file name*) @ A global variable called |history| will contain one of four values at the end of every run: |spotless| means that no unusual messages were printed; |harmless_message| means that a message of possible interest was printed but no real errors were detected; |error_message| means that at least one error was found; |fatal_message| means that the program terminated abnormally. The value of |history| does not influence the behavior of the program; it is simply computed for the convenience of systems that might want to use such information. We don't really have to worry about errors too much in this particular program because the input is machine-generated (by \.{WEAVE}). The error likeliest to occur is failure during file opens. @d mark_harmless==if history=spotless then history:=harmless_message; end; @d mark_error==history:=error_message @d mark_fatal==history:=fatal_message @d err_print(#)==print_ln(#); mark_error; @= @!error_level = (@!spotless,@!harmless_message,@!error_message,@!fatal_message); @ @=@!history:error_level; (*how bad was this run?*) @ @=history:=spotless; @ Some implementations may wish to pass the value of the |history| variable to the operating system so that it can be used to govern whether or not other programs are started. The |doscall| procedure passes a program status @^MS-DOS@> value back to DOS. We use |fatal_error| to terminate the program abnormally. @^system dependencies@> @d print_fatal_message== @| print_string('(That was a fatal '); @| print_ln('error, my friend.)') @d fatal_error(#)==mark_fatal;print_ln(#); print_fatal_message; doscall (@"4C,history); @= doscall (@"4C,history); @ If we are going to use |doscall| from the Logitech library we have to import it from the module |system|. @^MS-DOS@> @^Logitech@> @^system dependencies@> @= from system import @!doscall; @* File Handling. Here we define the symbols for use with file handling. @d lookup==@= Lookup @> (* library procedure to open a file *) @d close==@= Close @> (* library procedure to close a file *) @d failure(#)==(#.@=res@> <> @=done@>) (* last file operation sucessful ? *) @d abort_if_open_error(#)== if failure(#) then print_string('unable to open '); fatal_error(filename); end; @.Unable to open...@> @d open_input_file(#)== lookup(#,filename,false); abort_if_open_error(#) @d open_output_file(#)== lookup(#,filename,true); abort_if_open_error(#) @d close_file(#)==close(#); @d end_file==@=eof@> @d null_char==@= nul @> @d end_of_line(#)==(ch = eol) @d end_of_file(#)==(#.end_file) @d read_char==@= ReadChar @> @d write_char==@= WriteChar @> @d input_char(#)==read_char(#,ch); @d output_char(#)==write_char(#,ch) @d read_ln(#)==while not end_of_line(#) do input_char(#); end; @d write_ln(#)==write_char(#,eol); @d text_file==@= File @> @^system dependencies@> @= from @= FileSystem @> import lookup,@=Response@>,read_char,write_char, text_file, close;@/ @.FileSystem@> from ascii import @!eol,@!null_char; @ Input goes into an array called |buffer|. @=@!buffer: array[0..buf_size] of char; @!TeX_file,@!out_TeX_file : text_file; @ The |input_ln| procedure brings the next line of input from the specified file into the |buffer| array and returns the value |true|, unless the file has already been entirely read, in which case it returns |false|. Under normal conditions, we will never reach true end of file, for reasons discussed in later sections, but we will handle it anyway. Trailing blanks are ignored and the global variable |limit| is set to the length of the @^system dependencies@> line. The value of |limit| must be strictly less than |buf_size|. @p procedure input_ln(var f:text_file):boolean; (*inputs a line or returns |false|*) var @!final_limit:[0..buf_size]; (*|limit| without trailing blanks*) @!ch : char; (* current input character *) @!line_pres : boolean; (* temporary result of procedure *) begin limit:=0; final_limit:=0; if end_of_file(f) then line_pres:=false else input_char(f); while not end_of_line(f) do if ch = null_char then return false end; buffer[limit]:=ch; inc(limit); if buffer[limit-1]<>' ' then final_limit:=limit end; if limit=buf_size then read_ln(f); dec(limit); err_print('! Input line too long'); @.Input line too long@> return true ; end; input_char(f); end; read_ln(f); limit:=final_limit; line_pres := true; end; return line_pres ; end input_ln; @ The |output_ln| procedure writes the next line of output from the |buffer| array to the specified file. @p procedure output_ln(var f:text_file); (*outputs a line *) var @!ch : char; (* current output character *) @!temp : [0..buf_size]; begin if limit > 0 then for temp := 0 to limit - 1 do ch := buffer[temp]; output_char(f); end; end; write_ln(f); end output_ln; @ We define |filename| local to the initalization procedure because it is used only during file open. @= @!filename : array [0..file_name_len-1] of char; @ In this section we open both of the files. @d next_file(#)== filename[0]:= ' '; print_ln(#); read_string(filename); new_line; print_ln(filename); new_line; @= next_file('TeX file:'); open_input_file(TeX_file); next_file('output TeX file:'); open_output_file(out_TeX_file); @ Here we initialize most of the variables. The |output_enabled| flag is initialized to |true| so that the lines in the header of the \.{WEAVE}-generated \TeX\ file, known as ``limbo text'', are picked up in addition to the changed sections. @= TeX_line:=0;out_TeX_line:=0; limit:=0; buffer[0]:=' '; input_has_ended:=false; output_enabled := true; @ @= @!TeX_line:cardinal; (*the number of the current line in the main \TeX\ file *) @!out_TeX_line:cardinal; (*the number of the line in the output \TeX\ file *) @!limit:[0..buf_size]; (*the last character position occupied in the buffer*) @!input_has_ended: boolean; (*there is no more input*) @!output_enabled: boolean; (* we are copying input lines to output *) @* Main Input Loop. This is the main processing loop of \.{SCANTEX}. We simply read lines until end of file. The |get_line| procedure will determine the setting of the |output_enabled| flag. If set, we copy the line to the output file. @= while not input_has_ended do get_line; if output_enabled then output_ln(out_TeX_file); inc(out_TeX_line); end; end; @ The |get_line| procedure is called to read in the next line and scan it. We will output an ``I'm alive!'' dot to the terminal every 100 input lines and a new line every 2000. @p procedure get_line; (*inputs the next line*) var @!keep_looking : boolean;@!temp_index : cardinal; begin input_has_ended:=not input_ln(TeX_file); if input_has_ended then output_enabled := false; return; else inc(TeX_line); if (TeX_line mod 100) = 0 then print_char('.'); if (TeX_line mod 2000) = 0 then new_line; end; end; @; buffer[limit]:=' '; end; end get_line; @ In this section we determine whether the current line is the beginning of a section (`\.{\\M}' or `\.{\\N}' at beginning of line, followed immediately by a section number) and, if so, whether the section has been modified (`\.{\\*.}' following the number). We update the |output_enabled| flag according. Additionally, the index section (`\.{\\inx}') is considered end of file. If it is detected, we set the flag |input_has_ended| to terminate the program and set |output_enabled| to false to keep the \.{\\inx} command from being copied to the output file. @d numeric_digit_at(#)==( (buffer[#] <= '9') @| and @| (buffer[#] >= '0') ) @d third_char_matches(#)==(buffer[temp_index+2] = #) @d second_char_matches(#)==(buffer[temp_index+1] = #) @| and @| third_char_matches @d char_matches(#)==(buffer[temp_index] = #) @d three_chars_match(#)==char_matches(#) @| and @| second_char_matches @= temp_index := 1; if (limit > 3) and (buffer[0] = '\') then if ( char_matches('M') or char_matches('N') ) and numeric_digit_at(2) then @; elsif three_chars_match('i')('n')('x') then output_enabled := false; input_has_ended := true; end; end; @ Starting at the first digit of the section number, search for `\.{\\*.}', which indicates that this is a changed section. Discontinue the search if `\.{\\*.}'is found or the current position is no longer a numeric digit, which means we have moved past the section number without finding it. @= output_enabled := false; keep_looking := true; temp_index := 3; while (not output_enabled) and keep_looking do @| output_enabled := @| three_chars_match('\')('*')('.'); @| keep_looking := numeric_digit_at(temp_index) ; inc(temp_index); end; @ The command to generate the table of contents (`\.{\\con}') is normally the last line in a \TeX\ file generated by \.{WEAVE}. Part of its function is to terminate \TeX\ gracefully by generating a `\.{\\bye}' command or equivalent after generating the contents. Since we are dropping the `\.{\\con}' command, we must issue the `\.{\\bye}' command directly, just before closing the input and output files. @= buffer[0]:='\'; buffer[1]:='b'; buffer[2]:='y'; buffer[3]:='e'; limit:=4; output_ln(out_TeX_file); inc(out_TeX_line); close_file(TeX_file); close_file(out_TeX_file); @* Main Program. This is the main program. @p begin print_ln(banner); initialize; @; @; @; @; @; end scan_TeX. @ Here we simply report the history to the user. @= case history of spotless: @| print_ln('(No errors were found.)')| harmless_message: @| print_string('(Did you see the '); @| print_ln('warning message above?)')| error_message: @| print_string('(Pardon me, but I think I '); @| print_ln('spotted something wrong.)')| fatal_message: print_fatal_message end; (*there are no other cases*) @ @= new_line; print_ln('Line count statistics:'); print_cardinal(TeX_line); print_ln(' lines in input TeX file'); print_cardinal(out_TeX_line); print_ln(' lines in output TeX file'); @* Index.