/*****************************************************************************/
/* Program name:  metamath                                                   */
/* Copyright (C) 2021 NORMAN MEGILL  nm at alum.mit.edu  http://metamath.org */
/* License terms:  GNU General Public License Version 2 or any later version */
/*****************************************************************************/
/*34567890123456 (79-character line to adjust editor window) 2345678901234567*/

/* Copyright notice:  All code in this program that was written by Norman
   Megill is public domain.  However, the project includes code contributions
   from other people which may be GPL licensed.  For more details see:
   https://github.com/metamath/metamath-exe/issues/7#issuecomment-675555069 */

/* The overall functionality of the modules is as follows:
    metamath.c - Contains main(); executes or calls commands
    mmcmdl.c - Command line interpreter
    mmcmds.c - Extends metamath.c command() to execute SHOW and other
               commands; added after command() became too bloated (still is:)
    mmdata.c - Defines global data structures and manipulates arrays
               with functions similar to BASIC string functions;
               memory management; converts between proof formats
    mmhlpa.c - The help file, part 1.
    mmhlpb.c - The help file, part 2.
    mminou.c - Basic input and output interface
    mmmaci.c - THINK C Macintosh interface (obsolete)
    mmpars.c - Parses the source file
    mmpfas.c - Proof Assistant
    mmunif.c - Unification algorithm for Proof Assistant
    mmutil.c - Miscellaneous I/O utilities (reserved for future use)
    mmveri.c - Proof verifier for source file
    mmvstr.c - BASIC-like string functions
    mmwtex.c - LaTeX/HTML source generation
    mmword.c - File revision utility (for TOOLS> UPDATE) (not generally useful)
*/

/* Compilation instructions (gcc on Unix/Linus/Cygwin, lcc on Windows):
   1. Make sure each .c file above is present in the compilation directory and
      that each .c file (except metamath.c) has its corresponding .h file
      present.
   2. In the directory where these files are present, type:
         gcc m*.c -o metamath
   3. For full error checking, use:
         gcc m*.c -o metamath -O2 -Wall -Wextra -Wmissing-prototypes \
             -Wmissing-declarations -Wshadow -Wpointer-arith -Wcast-align \
             -Wredundant-decls -Wnested-externs -Winline -Wno-long-long \
             -Wconversion -Wstrict-prototypes -std=c99 -pedantic -Wunused-result
      Note: gcc 4.9.2 (on Debian) fails with "unknown type name `ssize_t'" if
      -std=c99 is used, so omit -std=c99 to work around this problem.
   4. For faster runtime, use these gcc options:
         gcc m*.c -o metamath -O3 -funroll-loops -finline-functions \
             -fomit-frame-pointer -Wall -std=c99 -pedantic -fno-strict-overflow
   5. The Windows version in the download was compiled with lcc-win32 version 3.8:
         lc -O m*.c -o metamath.exe
   6. On Linux, if you have autoconf, automake, and a C compiler, you
      can compile with the command "autoreconf -i && ./configure && make".
      See the README.TXT file for more information.
*/



#define MVERSION "0.198 7-Aug-2021"
/* 0.198 nm 7-Aug-2021 mmpars.c - Fix cosmetic bug in WRITE SOURCE ... /REWRAP
   that prevented end of sentence (e.g. period) from appearing in column 79,
   thus causing some lines to be shorter than necessary. */
/* 0.197 nm 2-Aug-2021 mmpars.c - put two spaces between $c,v on same line
   in /rewrap; mmwtex.c mmhlpa.c mminou.c - minor edits */
/* 0.196 nm 31-Dec-2020 metamath.c mmpars.c - fix bug that deleted comments
   that were followed by ${, $}, $c, $v, $d on the same line */
/* 0.195 nm 30-Dec-2020 metamath.c - temporarily disable /REWRAP until bug fixed
   27-Sep-2020 nm mmwtex.c - prevent "htmlexturl" links from wrapping */
/* 0.194 26-Dec-2020 nm mmwtex.c - add keyword "htmlexturl" to $t
   statement in .mm file */
/* 0.193 12-Sep-2020 nm mmcmds.c mmdata.c,h mmwtex.c,h mmhlpa.c - make the
   output of /EXTRACT stable in the sense that, with the same <label-list>
   parameter, extract(extract(file)) = extract(file) except that the date
   stamp at the top will be updated.  (The first extraction even if "*" will
   usually be different because it discards non-relevant content.  Note that
   the include file directives "$( $[ Begin..." etc. and comments with "$j" are
   currently discarded.) */
/* 0.192 4-Sep-2020 nm metamath.c - fix bug */
/* 0.191 4-Sep-2020 nm metamath.c - add comment close */
/* 0.190 4-Sep-2020 nm mmcmds.c - fix bug in writeExtractedSource() */
/* 0.189 4-Sep-2020 nm mmhlpa.c - add help for WRITE SOURCE .. /EXTRACT ...
   24-Aug-2020 nm metamath.c mmcmdl.c mmcmds.c,h mmdata.c,h mmhlpa.c
     mmpars.c mmpfas.c mmunif.c mmwtex.c,h - Added
     WRITE SOURCE ... /EXTRACT ... */
/* 0.188 23-Aug-2020 nm mmwtex.c, mmhlpa.c Added CONCLUSION FACT INTRODUCTION
     PARAGRAPH SCOLIA SCOLION SUBSECTION TABLE to [bib] keywords */
/* 0.187 15-Aug-2020 nm All m*.c, m*.h - put "g_" in front of all global
     variable names e.g. "statements" becomes "g_statements"; also capitalized
     1st letter of original name in case of global structs e.g. "statement"
     becomes "g_Statement".
   9-Aug-2020 nm mmcmdl.c, mmhlpa.c - add HELP BIBLIOGRAPHY */
/* 0.186 8-Aug-2020 nm mmwtex.c, mmhlpa.c - add CONJECTURE, RESULT to [bib]
     keywords
   8-Aug-2020 nm mmpfas.c, metamath.c - print message when IMPROVE or
     MINIMIZE_WITH uses another mathbox */
/* 0.185 5-Aug-2020 nm metamath.c mmcmdl.c mmhlpb.c mmpfas.c,h mmcmds.c
     mmwtex.c,h - add /INCLUDE_MATHBOXES to to IMPROVE; notify user upon ASSIGN
     from another mathbox.
   18-Jul-2020 nm mmcmds.c, mmdata.c, mmhlpb.c, metamath.c - "PROVE =" will now
     resume the previous MM-PA session if there was one; allow "~" to start/end
     with blank (meaning first/last statement); add "@1234" */
/* 0.184 17-Jul-2020 nm metamath.c mmcmdl.c mmcmds.c,h mmhlpb.c mmwtex.c,h -
     add checking for mathbox independence to VERIFY MARKUP; add /MATHBOX_SKIP
   4-Jul-2020 nm mmwtex.c - correct error msg for missing althtmldef
   3-Jul-2020 nm metamath.c, mmhlpa.c - allow space in TOOLS> BREAK */
/* 0.183 30-Jun-2020 30-Jun-2020 nm mmpars.c - refine prevention of
     WRITE SOURCE.../REWRAP from modifying comments containing "<HTML>"
     (specifically, remove indentation alignment).
   25-Jun-2020 nm metamath.c, mmcmds.c,h mmcmdl.c mmhlpb.c - add underscore
     checking in VERIFY MARKUP and add /UNDERSCORE_SKIP qualifier; also check
     for trailing space on lines.
   20-Jun-2020 nm mmcmds.c - check for discouragement tags in *ALT, *OLD
     labels in VERIFY MARKUP.
   19-Jun-2020 nm mminou.c,h, metamath.c, mmwtex.c - dynamically allocate
     buffer in print2() using vsnprintf() to calculate size needed
   18-Jun-2020 nm mmpars.c - remove error check for $e <- $f assignments.  See
     https://groups.google.com/d/msg/metamath/Cx_d84uorf8/0FrNYTM9BAAJ */
/* 0.182 12-Apr-2020 nm mmwtex.c, mmphlpa.c - add "Claim" to bib ref types */
/* 0.181 12-Feb-2020 nm (reported by David Starner) metamath.c - fix bug causing
     new axioms to be used by MINIMIZE_WITH */
/* 0.180 10-Dec-2019 nm (bj 13-Sep-2019) mmpars.c - fix "line 0" in error msg
     when label clashes with math symbol
   8-Dec-2019 nm (bj 13-Oct-2019) mmhlpa.c - improve TOOLS> HELP INSERT, DELETE
   8-Dec-2019 nm (bj 19-Sep-2019) mminou.c - change bug 1511 to error message
   30-Nov-2019 nm (bj 12-Oct-2019) mmwtex.c - trigger Most Recent link on
     mmtheorems.html when there is a mathbox statement (currently set.mm and
     iset.mm).
   30-Nov-2019 nm (bj 13-Sep-2019) mmhlpa.c - improve help for TOOLS> DELETE and
     SUBSTITUTE.
   30-Nov-2019 nm (bj 13-Sep-2019) mmwtex.c - change "g_htmlHome" in warnings to
     "htmlhome". */
/* 0.179 29-Nov-2019 nm (bj 22-Sep-2019) metamath.c - MINIMIZE_WITH axiom trace
     now starts from current NEW_PROOF instead of SAVEd proof.
   23-Nov-2019 nm (bj 4-Oct-2019) metamath.c - make sure traceback flags are
     cleared after MINIMIZE_WITH
   20-Nov-2019 nm mmhlpa.c - add url pointer to HELP WRITE SOURCE /SPLIT
   18-Nov-2019 nm mmhlpa.c - clarify HELP WRITE SOURCE /REWRAP
   15-Oct-2019 nm mmdata.c - add bug check info for user
   14-Oct-2019 nm mmcmds.c - use '|->' (not 'e.') as syntax hint for maps-to
   14-Oct-2019 nm mmwtex.c - remove extraneous </TD> */
/* 0.178 10-Aug-2019 nm mminou.c - eliminate redundant fopen in fSafeOpen
   6-Aug-2019 nm mmwtex.c,h, mmcmds.c - Add error check for >1 line
     section name or missing closing decoration line in getSectionHeadings()
   4-Aug-2019 nm mmhlpb.c, mmcmdl.c, metamath.c - Add /ALLOW_NEW_AXIOMS,
     renamed /ALLOW_GROWTH to /MAY_GROW
   17-Jul-2019 nm mmcmdl.c, mmhlpa.c, metamath.c - Add /NO_VERSIONING to
     WRITE THEOREM_LIST
   17-Jul-2019 nm metamath.c - Change line of dashes between SHOW STATEMENT
     output from hardcoded 79 to current g_screenWidth */
/* 0.177 27-Apr-2019 nm mmcmds.c -"set" -> "setvar" in htmlAllowedSubst.
   mmhlpb.c - fix typos in HELP IMPROVE. */
/* 0.176 25-Mar-2019 nm metamath.c mmcmds.h mmcmds.c mmcmdl.c mmhlpb.c -
   add /TOP_DATE_SKIP to VERIFY MARKUP */
/* 0.175 8-Mar-2019 nm mmvstr.c - eliminate warning in gcc 8.3 (patch
   provided by David Starner) */
/* 0.174 22-Feb-2019 nm mmwtex.c - fix erroneous warning when using "[["
   bracket escape in comment */
/* 0.173 3-Feb-2019 nm mmwtex.c - fix infinite loop when "[" was the first
   character in a comment */
/* 0.172 25-Jan-2019 nm mmwtex.c - comment out bug 2343 trap (not a bug) */
/* 0.171 13-Dec-2018 nm metamath.c, mmcmdl.c, mmhlpa.c, mmcmds.c,h, mmwtex.c,h
   - add fine-grained qualfiers to MARKUP command */
/* 0.170 12-Dec-2018 nm mmwtex.c - restore line accidentally deleted in 0.169 */
/* 0.169 10-Dec-2018 nm metamath.c, mmcmds.c,h, mmcmdl.c, mmpars.c, mmhlpa.c,
   mmwtex.c - Add MARKUP command.
   9-Dec-2018 nm mmwtex.c - escape literal "[" with "[[" in comments. */
/* 0.168 8-Dec-2018 nm metamath.c - validate that /NO_REPEATED_STEPS is used
   only with /LEMMON.
   8-Dec-2018 nm mmcmds.c - fix bug #256 reported by Jim Kingdon
   (https://github.com/metamath/set.mm/issues/497). */
/* 0.167 13-Nov-2018 nm mmcmds.c - SHOW TRACE_BACK .../COUNT now uses proof
   the way it's stored (previously, it always uncompressed the proof).  The
   new step count (for compressed proofs) corresponds to the step count the
   user would see on the web pages.
   12-Nov-2018 nm mmcmds.c - added unlimited precision arithmetic
   for SHOW TRACE_BACK .../COUNT/ESSENTIAL */
/* 0.166 31-Oct-2018 nm mmwtex.c - workaround Chrome anchor bug
   30-Oct-2018 nm mmcmds.c - put "This theorem is referenced by" after
   axioms and definitions used in HTML; use "(None)" instead of suppressing
   line if nothing is referenced */
/* 0.165 20-Oct-2018 nm mmwtex.c - added ~ mmtheorems#abc type anchor
   in TOC details.  mmwtex.c - fix bug (reported by Benoit Jubin) that
   changes "_" in labels to subscript.  mmcmdl.c - remove unused COMPLETE
   qualifier from SHOW PROOF.  mmwtex.c - enhance special cases of web page
   spacing identified by Benoit Jubin */
/* 0.164 5-Sep-2018 nm mmwtex.c, mmhlpb.c - added NOTE to bib keywords
   14-Aug-2018 nm metamath.c - added defaultScrollMode to prevent
   SET SCROLL CONTINUOUS from reverting to PROMPTED after a SUBMIT command */
/* 0.163 4-Aug-2018 nm mmwtex.c - removed 2nd "sandbox:bighdr" anchor
   in mmtheorems.html; removed Firefox and IE references; changed breadcrumb
   font to be consistent with other pages; put asterisk next to TOC entries
   that have associated comments */
/* FOR FUTURE REFERENCE: search for "Thierry" in mmwtex.c to modify the link
   to tirix.org structured proof site */
/* 0.162-thierry 3-Jun-2018 nm mmwtex.c - add link to tirix.org structured
   proofs */
/* 0.162 3-Jun-2018 nm mmpars.c - re-enabled error check for $c not in
   outermost scope.  mmhlpa.c mmhlpb.c- improve some help messages.
   mmwtex.c - added "OBSERVATION", "PROOF", AND "STATEMENT" keywords for
   WRITE BIBLIOGRAPHY */
/* 0.161 2-Feb-2018 nm mmpars.c,h mmcmds.c mmwtex.c - fix wrong file name
   and line number in error messages */
/* 0.160 24-Jan-2018 nm mmpars.c - fix bug introduced in version 0.158 */
/* 0.159 23-Jan-2018 nm mmpars.c - fix crash due to missing include file */
/* 0.158 22-Jan-2018 nm mminou.c - strip CRs from Windows SUBMIT files
   run on Linux */
/* 0.157 15-Jan-2018 nm Major rewrite of READ-related functions.
     Added HELP MARKUP.
   9-Jan-2018 nm Track line numbers for error messages in included files
   1-Jan-2018 nm Changed HOME_DIRECTORY to ROOT_DIRECTORY
   31-Dec-2017 nm metamath.c mmcmdl.c,h mmpars.c,h mmcmds.c,h mminou.c,h
     mmwtex.c mmhlpb.c mmdata.h - add virtual includes "$( Begin $[...$] $)",
     $( End $[...$] $)", "$( Skip $[...$] $)" */
/* 0.156 8-Dec-2017 nm mmwtex.c - fix bug that incorrectly gave "verify markup"
   errors when there was a mathbox statement without an "extended" section */
/* 0.155 8-Oct-2017 nm mmcmdl.c - restore accidentally removed HELP HTML;
   mmhlpb.c mmwtex.c mmwtex.h,c mmcmds.c metamath.c - improve HELP and make
   other cosmetic changes per Benoit Jubin's suggestions */
/* 0.154 2-Oct-2017 nm mmunif.h,c mmcmds.c - add 2 more variables to ERASE;
   metamath.c mmcmdl.c - remove obsolete OPEN/CLOSE HTML; mmhlpa.c mmhlpb.c -
   fix typos reported by Benoit Jubin */
/* 0.153 1-Oct-2017 nm mmunif.c,h mmcmds.c - Re-initialize internal nmbrStrings
   in unify() after 'erase' command reported by Benoit Jubin */
/* 0.152 26-Sep-2017 nm mmcmds.c - change default links from mpegif to mpeuni;
   metamath.c - enforce minimum screen width = 3 to prevent crash reported
   by Benoit Jubin */
/* 0.151 20-Sep-2017 nm mmwtex.c - better matching to insert space between
   A and y in "E. x e. ran A y R x" to prevent spurious spaces in thms rncoeq,
   dfiun3g as reported by Benoit Jubin */
/* 0.150 26-Aug-2017 nm mmcmds.c,mmwtex.h - fix hyperlink for Distinct variable
   etc. lists so that it will point to mmset.html on other Explorers like NF.
   Move the "Dummy variables..." to print after the "Proof of Theorem..."
   line. */
/* 0.149 21-Aug-2017 nm mmwtex.c,h mmcmds.c mmhlpb.c - add a subsubsection
     "tiny" header with separator "-.-." to table of contents and theorem list;
     see HELP WRITE THEOREM_LIST
   21-Aug-2017 nm mmcmds.c - remove bug check 255
   19-Aug-2017 nm mmcmds.c - change mmset.html links to
     ../mpeuni/mmset.html so they will work in NF Explorer etc. */
/* 0.148 14-Aug-2017 nm mmcmds.c - hyperlink "Dummy variable(s)" */
/* 0.147 13-Aug-2017 nm mmcmds.c,h - add "Dummy variable x is distinct from all
   other variables." to proof web page */
/* 0.146 26-Jun-2017 nm mmwtex.c - fix handling of local labels in
     'show proof.../tex' (bug 2341 reported by Eric Parfitt) */
/* 0.145 16-Jun-2017 nm metamath.c mmpars.c - fix bug 1741 during
     MINIMIZE_WITH; mmpfas.c - make duplicate bug numbers unique; mmhlpa.c
     mmhlpb.c - adjust to prevent lcc compiler "Function too big for the
     optimizer"
   29-May-2017 nm mmwtex.c mmhlpa.c - take out extraneous  <HTML>...</HTML>
     markup tags in HTML output so w3c validator will pass */
/* 0.144 15-May-2017 nm metamath.c mmcmds.c - add "(Revised by..." tag for
     conversion of legacy .mm's if there is a 2nd date under the proof */
/* 0.143 14-May-2017 nm metamath.c mmdata.c,h mmcmdl.c mmcmds.c mmhlpb.c -
     added SET CONTRIBUTOR; for missing "(Contributed by..." use date
     below proof if it exists, otherwise use today's date, in order to update
     old .mm files.
   14-May-2017 Ari Ferrera - mmcmds.c - fix memory leaks in ERASE */
/* 0.142 12-May-2017 nm metamath.c mmdata.c,h mmcmds.c - added
     "#define DATE_BELOW_PROOF" in mmdata.h that if uncommented, will enable
     use of the (soon-to-be obsolete) date below the proof
   4-May-2017 Ari Ferrera - mmcmds.c metamath.c mmdata.c mmcmdl.c
     mminou.c mminou.h mmcmdl.h mmdata.h - fixed memory leaks and warnings
     found by valgrind.
   3-May-2017 nm - metamath.c mmdata.c,h mmcmds.c,h mmpars.c,h mmhlpb.c
     mmcmdl.c mmwtex.c - added xxChanged flags to statement structure so
     that any part of the source can be changed;  removed /CLEAN qualifier
     of WRITE SOURCE; automatically put "(Contributed by ?who?..." during
     SAVE NEW_PROOF or SAVE PROOF when it is missing; more VERIFY MARKUP
     checking. */
/* 0.141 2-May-2017 nm mmdata.c, metamath.c, mmcmds.c, mmhlpb.c - use
   getContrib() date for WRITE RECENT instead of date below proof.  This lets
   us list recent $a's as well as $p's.  Also, add caching to getContrib() for
   speedup. */
/* 0.140 1-May-2017 nm mmwtex.c, mmcmds.c, metamath.c - fix some LaTeX issues
   reported by Ari Ferrera */
/* 0.139 2-Jan-2017 nm metamath.c - print only one line for
     'save proof * /compressed/fast' */
/* 0.138 26-Dec-2016 nm mmwtex.c - remove extraneous </TD> causing w3c
   validation failure; put space after 1st x in "F/ x x = x";
   mmcmds.c - added checking for lines > 79 chars in VERIFY MARKUP;
   mmcmds.c, mmcmdl.c, metamath.c, mmhlpb.c, mmcmds.h - added /VERBOSE to
   VERIFY MARKUP */
/* 0.137 20-Dec-2016 nm mmcmds.c - check ax-XXX $a vs axXXX $p label convention
     in 'verify markup'
   18-Dec-2016 nm mmwtex.c, mmpars.c, mmdata.h - use true "header area"
     between successive $a/$p for getSectionHeadings()  mmcmds.c - add
     header comment checking
   13-Dec-2016 nm mmdata.c,h - enhanced compareDates() to treat empty string as
     older date.
   13-Dec-2016 nm metamath.c, mmcmds.c - moved mm* and Microsoft illegal file
     name label check to verifyMarkup() (the VERIFY MARKUP command) instead of
     checking on READ; added check of set.mm Version date to verifyMarkup().
   13-Dec-2016 nm mmwtex.c,h - don't treat bracketed description text with
     space as a bib label; add labelMatch parameter to writeBibliography() */
/* 0.136 10-Oct-2016 mminou.c - fix resource leak bug reported by David
   Binderman */
/* 0.135 11-Sep-2016, 14-Sep-2016 metamath.c, mmpfas.c,h, mmdata.c,h,
   mmpars.c,h mmcmds.c, mmcmdl.c, mmhlpb.c - added EXPAND command */
/* 0.134 16-Aug-2016 mmwtex.c - added breadcrumbs to theorem pages;
   metamath.c, mmcmdl.c, mmhlpb.c, mminou.c,.h - added /TIME to SAVE PROOF,
   SHOW STATEMENT.../[ALT}HTML, MINIMIZE_WITH */
/* 0.133 13-Aug-2016 mmwtex.c - improve mobile display with <head> tag
   mmpars.c - use updated Macintosh information */
/* 0.132 10-Jul-2016 metamath.c, mmcmdl.c, mmcmds.c,.h, mmdata.c,.h, mmhlpb.c,
   mmpfas.c - change "restricted" to "discouraged" to match set.mm markup
   tags; add SET DISCOURAGEMENT OFF|ON (default ON) to turn off blocking for
   convenience of advanced users
   6-Jul-2016 metamath.c - add "(void)" in front of "system(...)" to
   suppress -Wunused-result warning */
/* 0.131 10-Jun-2016 mminou.c - reverted change of 22-May-2016 because
   'minimize_with' depends on error message in string to prevent DV violations.
   Todo:  write a DV-checking routine for 'minimize_with', then revert
   the 22-May-2016 fix for bug 126 (which only occurs when writing web
   pages for .mm file with errors).
   9-Jun-2016 mmcmdl.c, metamath.c - added _EXIT_PA for use with
   scripts that will give an error message outside of MM-PA> rather
   than exiting metamath */
/* 0.130 25-May-2016 mmpars.c - workaround clang warning about j = j;
      mmvstr.c - workaround gcc -Wstrict-overflow warning */
/* 0.129 24-May-2016 mmdata.c - fix bug 1393 */
/* 0.128 22-May-2016 mminou.c - fixed error message going to html page
      instead of to screen, triggering bug 126. */
/* 0.127 10-May-2016 metamath.c, mmcmdl.c, mmhlpb.c - added /OVERRIDE to
      PROVE */
/* 0.126 3-May-2016 metamath.c, mmdata.h, mmdata.c, mmcmds.h, mmcmds.c,
      mmcmdl.c, mmhlpb.c, mmpars.c - added getMarkupFlag() in mmdata.c;
      Added /OVERRIDE added to ASSIGN, REPLACE, IMPROVE, MINIMIZE_WITH,
      SAVE NEW_PROOF;  PROVE gives warning about SAVE NEW_PROOF for locked
      proof.  Added SHOW RESTRICTED command.
   3-May-2016 m*.c - fix numerous conversion warnings provided by gcc 5.3.0 */
/* 0.125 10-Mar-2016 mmpars.c - fixed bug parsing /EXPLICIT/PACKED format
   8-Mar-2016 nm mmdata.c - added "#nnn" to SHOW STATEMENT etc. to reference
      statement number e.g. SHOW STATEMENT #58 shows a1i in set.mm.
   7-Mar-2016 nm mmwtex.c - added space between } and { in HTML output
   6-Mar-2016 nm mmpars.c - disabled wrapping of formula lines in
       WRITE SOURCE.../REWRAP
   2-Mar-2016 nm metamat.c, mmcmdl.c, mmhlpb.c - added /FAST to
       SAVE PROOF, SHOW PROOF */
/* 0.123 25-Jan-2016 nm mmpars.c, mmdata.h, mmdata.c, mmpfas.c, mmcmds.,
   metamath.c, mmcmdl.c, mmwtex.c - unlocked SHOW PROOF.../PACKED,
   added SHOW PROOF.../EXPLICIT */
/* 0.122 14-Jan-2016 nm metamath.c, mmcmds.c, mmwtex.c, mmwtex.h - surrounded
      math HTML output with "<SPAN [g_htmlFont]>...</SPAN>; added htmlcss and
      htmlfont $t commands
   10-Jan-2016 nm mmwtex.c - delete duplicate -4px style; metamath.c -
     add &nbsp; after char on mmascii.html
   3-Jan-2016 nm mmwtex.c - fix bug when doing SHOW STATEMENT * /ALT_HTML after
   VERIFY MARKUP */
/* 0.121 17-Nov-2015 nm metamath.c, mmcmdl.h, mmcmdl.c, mmcmds.h, mmcmds.c,
       mmwtex.h, mmwtex.c, mmdata.h, mmdata.c -
   1. Moved WRITE BIBLIOGRAPHY code from metamath.c to its own function in
      mmwtex.c; moved qsortStringCmp() from metamath.c to mmdata.c
   2. Added $t, comment markup, and bibliography checking to VERIFY MARKUP
   3. Added options to bug() bug-check interception to select aborting,
      stepping to next bug, or ignoring subsequent bugs
   4. SHOW STATEMENT can now use both /HTML and /ALT_HTML in same session
   5. Added /HTML, /ALT_HTML to WRITE THEOREM_LIST and
      WRITE RECENT_ADDITIONS */
/* 0.120 7-Nov-2015 nm metamath.c, mmcmdl.c, mmpars.c - add VERIFY MARKUP
   4-Nov-2015 nm metamath.c, mmcmds.c/h, mmdata.c/h - move getDescription,
       getSourceIndentation from mmcmds.c to mmdata.c.
       metamath.c, mmdata.c - add and call parseDate() instead of in-line
       code; add getContrib(), getProofDate(), buildDate(), compareDates(). */
/* 0.119 18-Oct-2015 nm mmwtex.c - add summary TOC to Theorem List; improve
       math symbol GIF image alignment
   2-Oct-2015 nm metamath.c, mmpfas.c, mmwtex.c - fix miscellaneous small
       bugs or quirks */
/* 0.118 18-Jul-2015 nm metamath.c, mmcmds.h, mmcmds.c, mmcmdl.c, mmhlpb.h,
   mmhlpb.c - added /TO qualifier to SHOW TRACE_BACK.  See
   HELP SHOW TRACE_BACK. */
/* 0.117 30-May-2015
   1. nm mmwtex.c - move <A NAME... tag to math symbol cell in proof pages so
      hyperlink will jump to top of cell (reported by Alan Sare)
   2. daw mmpfas.c - add INLINE speedup if compiler permits
   3. nm metamath.c, mminou.c, mmwtex.c, mmpfas.c - fix clang -Wall warnings
      (reported by David A. Wheeler) */
/* 0.116 9-May-2015 nm mmwtex.c - adjust paragraph break in 'write th';
   Statement List renamed Theorem List;  prevent space in after paragraph
   in Theorem List; remove stray "(";  put header and header comment
   in same table cell; fix <TITLE> of Theorem List pages */
/* 0.115 8-May-2015 nm mmwtex.c - added section header comments to
       WRITE THEOREM_LIST and broke out Table of Contents page
   24-Apr-2015 nm metamath.c - add # bytes to end of "---Clip out the proof";
       reverted to no blank lines there (see 0.113 item 3) */
/* 0.114 22-Apr-2015 nm mmcmds.c - put [-1], [-2],... offsets on 'show
   new_proof/unknown' */
/* 0.113 19-Apr-2015 so, nm metamath.c, mmdata.c
   1. SHOW LABEL % will show statements with changed proofs
   2. SHOW LABEL = will show the statement being proved in MM-PA
   3. Added blank lines before, after "---------Clip out the proof" proof
   4. Generate date only if the proof is complete */
/* 0.112 15-Apr-2015 nm metamath.c - fix bug 1121 (reported by S. O'Rear);
   mwtex.c - add "img { margin-bottom: -4px }" to CSS to align symbol GIFs;
   mwtex.c - remove some hard coding for set.mm, for use with new nf.mm;
   metamath.c - fix comment parsing in WRITE BIBLIOGRAPHY to ignore
   math symbols  */
/* 0.111 22-Nov-2014 nm metamath.c, mmcmds.c, mmcmdl.c, mmhlpb.c - added
   /NO_NEW_AXIOMS_FROM qualifier to MINIMIZE_WITH; see HELP MINIMIZE_WITH.
   21-Nov-2014 Stepan O'Rear mmdata.c, mmhlpb.c - added ~ label range specifier
   to wildcards; see HELP SEARCH */
/* 0.110 2-Nov-2014 nm mmcmds.c - fixed bug 1114 (reported by Stefan O'Rear);
   metamath.c, mmhlpb.c - added "SHOW STATEMENT =" to show the statement
   being proved in MM-PA (based on patch submitted by Stefan O'Rear) */
/* 0.109 20-Aug-2014 nm mmwtex.c - fix corrupted HTML caused by misinterpreting
   math symbols as comment markup (math symbols with _ [ ] or ~).  Also,
   allow https:// as well as http:// in ~ label markup.
   11-Jul-2014 wl mmdata.c - fix obscure crash in debugging mode db9 */
/* 0.108 25-Jun-2014 nm
   (1) metamath.c, mmcmdl.c, mmhlpb.c - MINIMIZE_WITH now checks the size
   of the compressed proof, prevents $d violations, and tries forward and
   reverse statment scanning order; /NO_DISTINCT, /BRIEF, /REVERSE
   qualifiers were removed.
   (2) mminou.c - prevent hard breaks (in the middle of a word) in too-long
   lines (e.g. long URLs) in WRITE SOURCE .../REWRAP; just overflow the
   screen width instead.
   (3) mmpfas.c - fixed memory leak in replaceStatement()
   (4) mmpfas.c - suppress inf. growth with MINIMIZE_WITH idi/ALLOW_GROWTH */
/* 0.107 21-Jun-2014 nm metamath.c, mmcmdl.c, mmhlpb.c - added /SIZE qualifier
   to SHOW PROOF; added SHOW ELAPSED_TIME; mmwtex.c - reformatted WRITE
   THEOREM_LIST output; now "$(", newline, "######" starts a "part" */
/* 0.106 30-Mar-2014 nm mmwtex.c - fix bug introduced by 0.105 that disabled
   hyperlinks on literature refs in HTML comment.  metamath.c - improve
   messages */
/* 0.105 15-Feb-2014 nm mmwtex.c - prevented illegal LaTeX output for certain
   special characters in comments. */
/* 0.104 14-Feb-2014 nm mmwtex.c - fixed bug 2312, mmcmds.c - enhanced ASSIGN
   error message. */
/* 0.103 4-Jan-2014 nm mmcmds.c,h - added "Allowed substitution hints" below
   the "Distinct variable groups" list on generated web pages
   mmwtex.c - added "*" to indicate DV's occur in Statement List entries. */
/* 0.102 2-Jan-2014 nm mminou.c - made compressed proof line wrapping more
   uniform at start of compressed part of proof */
/* 0.101 27-Dec-2013 nm mmdata.h,c, mminou.c, mmcmdl.c, mmhlpb.c, mmvstr.c -
   Improved storage efficiency of /COMPRESSED proofs (but with 20% slower run
   time); added /OLD_COMPRESSION to specify old algorithm; removed end-of-line
   space after label list in old algorithm; fixed linput() bug */
/* 0.100 30-Nov-2013 nm mmpfas.c - reversed statement scan order in
   proveFloating(), to speed up SHOW STATEMENT df-* /HTML; metamath.c - remove
   the unknown date place holder in SAVE NEW_PROOF; Wolf Lammen mmvstr.c -
   some cleanup */
/* 0.07.99 1-Nov-2013 nm metamath.c, mmpfas.h,c, mmcmdl.h,c, mmhlpa.c,
   mmhlpb.c - added UNDO, REDO, SET UNDO commands (see HELP UNDO) */
/* 0.07.98 30-Oct-2013 Wolf Lammen mmvstr.c,h, mmiou.c, mmpars.c,
   mmdata.c  - improve code style and program structure */
/* 0.07.97 20-Oct-2013 Wolf Lammen mmvstr.c,h, metamath.c - improved linput();
   nm mmcmds.c, mmdata.c - tolerate bad proofs in SHOW TRACE_BACK etc. */
/* 0.07.96 20-Sep-2013 Wolf Lammen mmvstr.c - revised cat();
   nm mmwtex.c, mminou.c - change a print2 to printLongLine to fix bug 1150 */
/* 0.07.95 18-Sep-2013 Wolf Lammen mmvstr.c - optimized cat();
   nm metamath.c, mmcmds.c, mmdata.c, mmpars.c, mmpfas.c, mmvstr.c,
   mmwtex.c - suppress some clang warnings */
/* 0.07.94 28-Aug-2013 Alexey Merkulov mmcmds.c, mmpars.c - fixed several
   memory leaks found by valgrind --leak-check=full --show-possibly-lost=no */
/* 0.07.93 8-Jul-2013 Wolf Lammen mmvstr.c - simplified let() function;
   also many minor changes in m*.c and m*.h to assist future refactoring */
/* 0.07.92 28-Jun-2013 nm metamath.c mmcmds.c,h mmcmdl.c mmhlpb.c- added
   /NO_REPEATED_STEPS for /LEMMON mode of SHOW PROOF, SHOW NEW_PROOF.
   This reverts the /LEMMON mode default display change of 31-Jan-2010
   and invokes it when desired via /NO_REPEATED_STEPS. */
/* 0.07.91 20-May-2013 nm metamath.c mmpfas.c,h mmcmds.c,h mmcmdl.c
   mmhlpb.c- added /FORBID qualifier to MINIMIZE_WITH */
/* 0.07.90 19-May-2013 nm metamath.c mmcmds.c mmcmdl.c mmhlpb.c - added /MATCH
   qualifier to SHOW TRACE_BACK */
/* 0.07.88 18-Nov-2012 nm mmcmds.c - fixed bug 243 */
/* 0.07.87 17-Nov-2012 nm mmwtex.c - fixed formatting problem when label
   markup ends a comment in SHOW PROOF ... /HTML */
/* 0.07.86 27-Oct-2012 nm mmcmds.c, mmwtex.c, mmwtex.h - fixed ERASE bug
   caused by imperfect re-initialization; reported by Wolf Lammen */
/* 0.07.85 10-Oct-2012 nm metamath.c, mmcmdl.c, mmwtex.c, mmwtex.h, mmhlpb.c -
   added /SHOW_LEMMAS to WRITE THEOREM_LIST to bypass lemma math suppression */
/* 0.07.84 9-Oct-2012 nm mmcmds.c - fixed bug in getStatementNum() */
/* 0.07.83 19-Sep-2012 nm mmwtex.c - fixed bug reported by Wolf Lammen */
/* 0.07.82 16-Sep-2012 nm metamath.c, mmpfas.c - fixed REPLACE infinite loop;
   improved REPLACE message for shared dummy variables */
/* 0.07.81 14-Sep-2012 nm metamath.c, mmcmds.c, mmcmds.h, mmcmdl.c, mmhlpb.c
   - added FIRST, LAST, +nn, -nn where missing from ASSIGN, REPLACE,
   IMPROVE, LET STEP.  Wildcards are allowed for PROVE, ASSIGN, REPLACE
   labels provided there is a unique match. */
/* 0.07.80 4-Sep-2012 nm metamath.c, mmpfas.c, mmpfas.h, mmcmdl.c, mmhlpb.c
   - added / 1, / 2, / 3, / SUBPROOFS to IMPROVE to specify search level */
/* 0.07.79 31-Aug-2012 nm m*.c - clean up some gcc warnings */
/* 0.07.78 28-Aug-2012 nm mmpfas.c - fix bug in 0.07.77. */
/* 0.07.77 25-Aug-2012 nm metamath.c, mmpfas.c - Enhanced IMPROVE algorithm to
   allow non-shared dummy variables in unknown steps */
/* 0.07.76 22-Aug-2012 nm metamath.c, mmpfas.c, mmcmdl.c, mmhlpb.c -
   Enhanced IMPROVE algorithm to also try REPLACE algorithm */
/* 0.07.75 14-Aug-2012 nm metamath.c - MINIMIZE_WITH now checks current
   mathbox (but not other mathboxes) even if /INCLUDE_MATHBOXES is omitted */
/* 0.07.74 18-Mar-2012 nm mmwtex.c, mmcmds.c, metamath.c - improved texToken()
   error message */
/* 0.07.73 26-Dec-2011 nm mmwtex.c, mmpars.c - added <HTML>...</HTML> in
   comments for passing through raw HTML code into HTML files generated with
   SHOw STATEMENT xxx / HTML */
/* 0.07.72 25-Dec-2011 nm (obsolete) */
/* 0.07.71 10-Nov-2011 nm metamath.c, mmcmdl.c - added /REV to MINIMIZE_WITH */
/* 0.07.70 6-Aug-2011 nm mmwtex.c - fix handling of double quotes inside
   of htmldef strings to match spec in Metamath book Appendix A p. 156 */
/* 0.07.69 9-Jul-2011 nm mmpars.c, mmvstr.c - Untab file in WRITE SOURCE
   ... /REWRAP */
/* 0.07.68 3-Jul-2011 nm metamath.c, mminou.h, mminou.c - Nested SUBMIT calls
   (SUBMIT calls inside of a SUBMIT command file) are now allowed.
   Also, mmdata.c - fix memory leak. */
/* 0.07.67 2-Jul-2011 nm metamath.c, mmcmdl.c, mmhlpa.c - Added TAG command
   to TOOLS.  See HELP TAG under TOOLS.  (The old special-purpose TAG command
   was renamed to UPDATE.) */
/* 0.07.66 1-Jul-2011 nm metamath.c, mmcmds.c, mmpars.c, mmpars.h - Added code
   to strip spurious "$( [?] $)" in WRITE SOURCE ... /CLEAN output */
/* 0.07.65 30-Jun-2011 nm mmwtex.c - Prevent processing [...] bibliography
   brackets inside of `...` math strings in comments. */
/* 0.07.64 28-Jun-2011 nm metamath.c, mmcmdl.c - Added /INCLUDE_MATHBOXES
   qualifier to MINIMIZE_WITH; without it, MINIMIZE_WITH * will skip
   checking user mathboxes. */
/* 0.07.63 26-Jun-2011 nm mmwtex.c - check that .gifs exist for htmldefs */
/* 0.07.62 18-Jun-2011 nm mmpars.c - fixed bug where redeclaration of active
   $v was not detected */
/* 0.07.61 12-Jun-2011 nm mmpfas.c, mmcmds.c, metamath.c, mmhlpb.c - added
   /FORMAT and /REWRAP qualifiers to WRITE SOURCE to format according to set.mm
   conventions - set HELP WRITE SOURCE */
/* 0.07.60 7-Jun-2011 nm mmpfas.c - fixed bug 1805 which occurred when doing
   MINIMIZE_WITH weq/ALLOW_GROWTH after DELETE DELETE FLOATING_HYPOTHESES */
/* 0.07.59 11-Dec-2010 nm mmpfas.c - increased default SET SEARCH_LIMIT from
   10000 to 25000 to accomodate df-plig web page in set.mm */
/* 0.07.58 9-Dec-2010 nm mmpars.c - detect if same symbol is used with both
   $c and $v, in order to conform with Metamath spec */
/* 0.07.57 19-Oct-2010 nm mmpars.c - fix bug causing incorrect line count
   for error messages when non-ASCII character was found; mminou.h -
   increase SET WIDTH maximum from 999 to 9999 */
/* 0.07.56 27-Sep-2010 nm mmpars.c, mmpfas.c - check for $a's with
   one token e.g. "$a wff $."; if found, turn SET EMPTY_SUBSTITUTION ON
   automatically.  (Suggested by Mel O'Cat; patent pending.) */
/* 0.07.55 26-Sep-2010 nm mmunif.c, mmcmds.c, mmunif.h - check for mismatched
   brackets in all $a's, so that if there are any, the bracket matching
   algorithm (for fewer ambiguous unifications) in mmunif.c will be turned
   off. */
/* 0.07.54 25-Sep-2010 nm mmpars.c - added $f checking to conform to the
   current Metamath spec, so footnote 2 on p. 92 of Metamath book is
   no longer applicable. */
/* 0.07.53 24-Sep-2010 nm mmveri.c - fixed bug(2106), reported by Michal
   Burger */
/* 0.07.52 14-Sep-2010 nm metamath.c, mmwtex.h, mmwtex.c, mmcmds.c,
   mmcmdl.c, mmhlpb.c - rewrote the LaTeX output for easier hand-editing
   and embedding in LaTeX documents.  The old LaTeX output is still
   available with /OLD_TEX on OPEN TEX, SHOW STATEMENT, and SHOW PROOF,
   but it is obsolete and will be deleted eventually if no one objects.  The
   new /TEX output also replaces the old /SIMPLE_TEX, which was removed. */
/* 0.07.51 9-Sep-2010 Stefan Allen mmwtex.c - put hyperlinks on hypothesis
   label references in SHOW STATEMENT * /HTML, ALT_HTML output */
/* 0.07.50 21-Feb-2010 nm mminou.c - "./metamath < empty", where "empty" is a
   0-byte file, now exits metamath instead of producing an infinite loop.
   Also, ^D now exits metamath.  Reported by Cai Yufei */
/* 0.07.49 31-Jan-2010 nm mmcmds.c - Lemmon-style proofs (SHOW PROOF xxx
   /LEMON/RENUMBER) no longer have steps with dummy labels; instead, steps
   are now the same as in HTML page proofs.  (There is a line to comment
   out if you want to revert to old behavior.) */
/* 0.07.48 11-Sep-2009 nm mmpars.c, mm, mmvstr.c, mmdata.c - Added detection of
   non-whitespace around keywords (mmpars.c); small changes to eliminate
   warnings in gcc 3.4.4 (mmvstr.c, mmdata.c) */
/* 0.07.47 2-Aug-2009 nm mmwtex.c, mmwtex.h - added user name to mathbox
   pages */
/* 0.07.46 24-Jul-2009 nm metamath.c, mmwtex.c - changed name of sandbox
   to "mathbox" */
/* 0.07.45 15-Jun-2009 nm metamath.c, mmhlpb.c - put "!" before each line of
   SET ECHO ON output to make them easy to identity for creating scripts */
/* 0.07.44 12-May-2009 Stefan Allan, nm metamath.c, mmcmdl.c, mmwtex.c -
   added SHOW STATEMENT / MNEMONICS - see HELP SHOW STATEMENT */
/* 0.07.43 29-Aug-2008 nm mmwtex.c - workaround for Unicode huge font bug in
   FireFox 3 */
/* 0.07.42 8-Aug-2008 nm metamath.c - added sandbox, Hilbert Space colors to
   Definition List */
/* 0.07.41 29-Jul-2008 nm metamath.c, mmwtex.h, mmwtex.c - Added handling of
   sandbox section of Metamath Proof Explorer web pages */
/* 0.07.40 6-Jul-2008 nm metamath.c, mmcmdl.c, mmhlpa.c, mmhlpb.c - Added
   / NO_VERSIONING qualifier for SHOW STATEMENT, so website can be regenerated
   in place with less temporary space required.  Also, the wildcard trigger
   for mmdefinitions.html, etc. is more flexible (see HELP HTML). */
/* 0.07.39 21-May-2008 nm metamath.c, mmhlpb.c - Added wildcard handling to
   statement label in SHOW TRACE_BACK.  All wildcards now allow
   comma-separated lists [i.e. matchesList() instead of matches()] */
/* 0.07.38 26-Apr-2008 nm metamath.c, mmdata.h, mmdata.c, mmvstr.c, mmhlpb.c -
   Enhanced / EXCEPT qualifier for MINIMIZE_WITH to handle comma-separated
   wildcard list. */
/* 0.07.37 14-Apr-2008 nm metamath.c, mmcmdl.c, mmhlpb.c - Added / JOIN
   qualifier to SEARCH. */
/* 0.07.36 7-Jan-2008 nm metamath.c, mmcmdl.c, mmhlpb.c - Added wildcard
   handling for labels in SHOW USAGE. */
/* 0.07.35 2-Jan-2008 nm mmcmdl.c, metamath.c, mmhlpb.c - Changed keywords
   COMPACT to PACKED and COLUMN to START_COLUMN so that SAVE/SHOW proof can use
   C to abbreviate COMPRESSED.  (PACKED format is supported but "unofficial,"
   used mainly for debugging purposes, and is not listed in HELP SAVE
   PROOF.) */
/* 0.07.34 19-Nov-2007 nm mmwtex.c, mminou.c - Added tooltips to proof step
   hyperlinks in SHOW STATEMENT.../HTML,ALT_HTML output (suggested by Reinder
   Verlinde) */
/* 0.07.33 19-Jul-2007 nm mminou.c, mmvstr.c, mmdata.c, mmword.c - added fflush
   after each printf() call for proper behavior inside emacs (suggested by
   Frederic Line) */
/* 0.07.32 29-Apr-2007 nm mminou.c - fSafeOpen now stops at gap; e.g. if ~2
   doesn't exist, ~1 will be renamed to ~2, but any ~3, etc. are not touched */
/* 0.07.31 5-Apr-2007 nm mmwtex.c - Don't make "_" in hyperlink a subscript */
/* 0.07.30 8-Feb-2007 nm mmcmds.c, mmwtex.c Added HTML statement number info to
   SHOW STATEMENT.../FULL; friendlier "Contents+1" link in mmtheorems*.html */
/* 0.07.29 6-Feb-2007 Jason Orendorff mmpfas.c - Patch to eliminate the
   duplicate "Exceeded trial limit at step n" messages */
/* 0.07.28 22-Dec-2006 nm mmhlpb.c - Added info on quotes to HELP LET */
/* 0.07.27 23-Oct-2006 nm metamath.c, mminou.c, mmhlpa.c, mmhlpb.c - Added
   / SILENT qualifier to SUBMIT command */
/* 0.07.26 12-Oct-2006 nm mminou.c - Fixed bug when SUBMIT file was missing
   a new-line at end of file (reported by Marnix Klooster) */
/* 0.07.25 10-Oct-2006 nm metamath.c - Fixed bug invoking TOOLS as a ./metamath
   command-line argument */
/* 0.07.24 25-Sep-2006 nm mmcmdl.c Fixed bug in
   SHOW NEW_PROOF/START_COLUMN nn/LEM */
/* 0.07.23 31-Aug-2006 nm mmwtex.c - Added Home and Contents links to bottom of
   WRITE THEOREM_LIST pages */
/* 0.07.22 26-Aug-2006 nm metamath.c, mmcmdl.c, mmhlpb.c - Changed 'IMPROVE
   STEP <step>' to 'IMPROVE <step>' for user convenience and to be consistent
   with ASSIGN <step> */
/* 0.07.21 20-Aug-2006 nm mmwtex.c - Revised small colored numbers so that all
   colors have the same grayscale brightness.. */
/* 0.07.20 19-Aug-2006 nm mmpars.c - Made the error "Required hypotheses may
   not be explicitly declared" in a compressed proof non-severe, so that we
   can still SAVE the proof to reformat and recover it. */
/* 0.07.19 11-Aug-06 nm mmcmds.c - "Distinct variable group(s)" is now
   "group" or "groups" as appropriate. */
/* 0.07.18 31-Jul-06 nm mmwtex.c - added table to contents to p.1 of output of
   WRITE THEOREM_LIST command. */
/* 0.07.17 4-Jun-06 nm mmpars.c - do not allow labels to match math symbols
   (new spec proposed by O'Cat).   mmwtex.c - made theorem name 1st in title,
   for readability in Firefox tabs. */
/* 0.07.16 16-Apr-06 nm metamath.c, mmcmdl.c, mmpfas.c, mmhlpb.c - allow step
   to be negative (relative to end of proof) for ASSIGN, UNIFY, and LET STEP
   (see their HELPs).  Added INITIALIZE USER to delete LET STEP assignments
   (see HELP INITIALIZE).  Fixed bug in LET STEP (mmpfas.c). */
/* 0.07.15 10-Apr-06 nm metamath.c, mmvstr.c - change dates from 2-digit to
   4-digit year; make compatible with older 2-digit year. */
/* 0.07.14 21-Mar-06 nm mmpars.c - fix bug 1722 when compressed proof has
   "Z" at beginning of proof instead of after a proof step. */
/* 0.07.13 3-Feb-06 nm mmpfas.c - minor improvement to MINIMIZE_WITH */
/* 0.07.12 30-Jan-06 nm metamath.c, mmcmds.c, mmdata.c, mmdata.h, mmhlpa.c,
   mmhlpb.c - added "?" wildcard to match single character. See HELP SEARCH. */
/* 0.07.11 7-Jan-06 nm metamath.c, mmcmdl.c, mmhlpb.c - added EXCEPT qualifier
   to MINIMIZE_WITH */
/* 0.07.10 28-Dec-05 nm metamath.c, mmcmds.c - cosmetic tweaks */
/* 0.07.10 11-Dec-05 nm metamath.c, mmcmdl.c, mmhlpb.c - added ASSIGN FIRST
   and IMPROVE FIRST commands.  Also enhanced READ error message */
/* 0.07.9 1-Dec-05 nm mmvstr.c - added comment on how to make portable */
/* 0.07.9 18-Nov-05 nm metamath.c, mminou.c, mminou.h, mmcmdl.c, mmhlpb.c -
   added SET HEIGHT command; changed SET SCREEN_WIDTH to SET WIDTH; changed
   SET HENTY_FILTER to SET JEREMY_HENTY_FILTER (to make H for HEIGHT
   unambiguous); added HELP for SET JEREMY_HENTY_FILTER */
/* 0.07.8 15-Nov-05 nm mmunif.c - now detects wrong order in bracket matching
   heuristic to further reduce ambiguous unifications in Proof Assistant */
/* 0.07.7 12-Nov-05 nm mmunif.c - add "[","]" and "[_","]_" bracket matching
   heuristic to reduce ambiguous unifications in Proof Assistant.
   mmwtex.c - added heuristic for HTML spacing after "sum_" token. */
/* 0.07.6 15-Oct-05 nm mmcmds.c,mmpars.c - fixed compressed proof algorithm
   to match spec in book (with new algorithm due to Marnix Klooster).
   Users are warned to convert proofs when the old compression is found. */
/* 0.07.5 6-Oct-05 nm mmpars.c - fixed bug that reset "severe error in
   proof" flag when a proof with severe error also had unknown steps */
/* 0.07.4 1-Oct-05 nm mmcmds.c - ignored bug 235, which could happen for
   non-standard logics */
/* 0.07.3 17-Sep-05 nm mmpars.c - reinstated duplicate local label checking to
   conform to strict spec */
/* 0.07.2 19-Aug-05 nm mmwtex.c - suppressed math content for lemmas in
   WRITE THEOREMS output */
/* 0.07.1 28-Jul-05 nm Added SIMPLE_TEX qualifier to SHOW STATEMENT */
/* 0.07:  Official 0.07 22-Jun-05 corresponding to Metamath book */
/* 0.07x:  Fixed to work with AMD64 with 64-bit longs by
   Waldek Hebisch; deleted unused stuff in mmdata.c */
/* 0.07w:  .mm date format like "$( [7-Sep-04] $)" is now
   generated and permitted (old one is tolerated too for compatibility) */
/* Metamath Proof Verifier - main program */
/* See the book "Metamath" for description of Metamath and run instructions */

/*****************************************************************************/


/*----------------------------------------------------------------------*/


#include <string.h>
#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
#include <ctype.h>
#include <stdarg.h>
/* #include <time.h> */ /* 21-Jun-2014 nm For ELAPSED_TIME */
#ifdef THINK_C
#include <console.h>
#endif
#include "mmutil.h"
#include "mmvstr.h"
#include "mmdata.h"
#include "mmcmdl.h"
#include "mmcmds.h"
#include "mmhlpa.h"
#include "mmhlpb.h"
#include "mminou.h"
#include "mmpars.h"
#include "mmveri.h"
#include "mmpfas.h"
#include "mmunif.h"
#include "mmword.h"
#include "mmwtex.h"
#ifdef THINK_C
#include "mmmaci.h"
#endif

void command(int argc, char *argv[]);

int main(int argc, char *argv[])
{

/* argc is the number of arguments; argv points to an array containing them */
#ifdef THINK_C
/* Set console attributes */
console_options.pause_atexit = 0; /* No pause at exit */
console_options.title = (unsigned char*)"\pMetamath";
#endif

#ifdef THINK_C
  /* The standard stream triggers the console package to initialize the
     Macintosh Toolbox managers and use the console interface.  cshow must
     be called before using our own window to prevent crashing (THINK C
     Standard Library Reference p. 43). */
  cshow(stdout);
  /* Initialize MacIntosh interface */
  /*ToolBoxInit(); */ /* cshow did this automatically */
  /* Display opening window */
  /*
  WindowInit();
  DrawMyPicture();
  */
  /* Wait for mouse click or key */
  /*while (!Button());*/
#endif


  /****** If g_listMode is set to 1 here, the startup will be Text Tools
          utilities, and Metamath will be disabled ***************************/
  /* (Historically, this mode was used for the creation of a stand-alone
     "TOOLS>" utility for people not interested in Metamath.  This utility
     was named "LIST.EXE", "tools.exe", and "tools" on VMS, DOS, and Unix
     platforms respectively.  The UPDATE command of TOOLS (mmword.c) was
     custom-written in accordance with the version control requirements of a
     company that used it; it documents the differences between two versions
     of a program as C-style comments embedded in the newer version.) */
  g_listMode = 0; /* Force Metamath mode as startup */


  g_toolsMode = g_listMode;

  if (!g_listMode) {
    /*print2("Metamath - Version %s\n", MVERSION);*/
    print2("Metamath - Version %s%s", MVERSION, space(27 - (long)strlen(MVERSION)));
  }
  /* if (argc < 2) */ print2("Type HELP for help, EXIT to exit.\n");

  /* Allocate big arrays */
  initBigArrays();

  /* 14-May-2017 nm */
  /* Set the default contributor */
  let(&g_contributorName, DEFAULT_CONTRIBUTOR);

  /* Process a command line until EXIT */
  command(argc, argv);

  /* Close logging command file */
  if (g_listMode && g_listFile_fp != NULL) {
    fclose(g_listFile_fp);
  }

  return 0;

}




void command(int argc, char *argv[])
{
  /* Command line user interface -- this is an infinite loop; it fetches and
     processes a command; returns only if the command is 'EXIT' or 'QUIT' and
     never returns otherwise. */
  long argsProcessed = 0;  /* Number of argv initial command-line
                                     arguments processed so far */

  long /*c,*/ i, j, k, m, l, n, p, q, r, s /*,tokenNum*/;
  long stmt, step;
  int subType = 0;
#define SYNTAX 4
  vstring str1 = "", str2 = "", str3 = "", str4 = "", str5= "";
  nmbrString *nmbrTmpPtr; /* Pointer only; not allocated directly */
  nmbrString *nmbrTmp = NULL_NMBRSTRING;
  nmbrString *nmbrSaveProof = NULL_NMBRSTRING;
  /*pntrString *pntrTmpPtr;*/ /* Pointer only; not allocated directly */
  pntrString *pntrTmp = NULL_PNTRSTRING;
  pntrString *expandedProof = NULL_PNTRSTRING;
  flag tmpFlag;

  /* 1-Nov-2013 nm proofSavedFlag tells us there was at least one
     SAVE NEW_PROOF during the MM-PA session while the UNDO stack wasn't
     empty, meaning that "UNDO stack empty" is no longer a reliable indication
     that the proof wasn't changed.  It is cleared upon entering MM-PA, and
     set by SAVE NEW_PROOF. */
  flag proofSavedFlag = 0;

  /* Variables for SHOW PROOF */
  flag pipFlag; /* Proof-in-progress flag */
  long outStatement; /* Statement for SHOW PROOF or SHOW NEW_PROOF */
  flag explicitTargets; /* For SAVE PROOF /EXPLICIT */
  long startStep; long endStep;
  /* long startIndent; */
  long endIndent; /* Also for SHOW TRACE_BACK */
  flag essentialFlag; /* Also for SHOW TRACE_BACK */
  flag renumberFlag; /* Flag to use essential step numbering */
  flag unknownFlag;
  flag notUnifiedFlag;
  flag reverseFlag;
  long detailStep;
  flag noIndentFlag; /* Flag to use non-indented display */
  long splitColumn; /* Column at which formula starts in nonindented display */
  flag skipRepeatedSteps; /* NO_REPEATED_STEPS qualifier */ /* 28-Jun-2013 nm */
  flag texFlag; /* Flag for TeX */
  flag saveFlag; /* Flag to save in source */
  flag fastFlag; /* Flag for SAVE PROOF.../FAST */ /* 2-Jan-2017 nm */
  long indentation; /* Number of spaces to indent proof */
  vstring labelMatch = ""; /* SHOW PROOF <label> argument */

  flag axiomFlag; /* For SHOW TRACE_BACK */
  flag treeFlag; /* For SHOW TRACE_BACK */ /* 19-May-2013 nm */
  flag countStepsFlag; /* For SHOW TRACE_BACK */ /* 19-May-2013 nm */
  flag matchFlag; /* For SHOW TRACE_BACK */ /* 19-May-2013 nm */
  vstring matchList = "";  /* For SHOW TRACE_BACK */ /* 19-May-2013 nm */
  vstring traceToList = ""; /* For SHOW TRACE_BACK */ /* 18-Jul-2015 nm */
  flag recursiveFlag; /* For SHOW USAGE */
  long fromLine, toLine; /* For TYPE, SEARCH */
  flag joinFlag; /* For SEARCH */
  long searchWindow; /* For SEARCH */
  FILE *type_fp; /* For TYPE, SEARCH */
  long maxEssential; /* For MATCH */
  nmbrString *essentialFlags = NULL_NMBRSTRING;
                                            /* For ASSIGN/IMPROVE FIRST/LAST */
  long improveDepth; /* For IMPROVE */
  flag searchAlg; /* For IMPROVE */ /* 22-Aug-2012 nm */
  flag searchUnkSubproofs;  /* For IMPROVE */ /* 4-Sep-2012 nm */
  flag dummyVarIsoFlag; /* For IMPROVE */ /* 25-Aug-2012 nm */
  long improveAllIter; /* For IMPROVE ALL */ /* 25-Aug-2012 nm */
  flag proofStepUnk; /* For IMPROVE ALL */ /* 25-Aug-2012 nm */

  flag texHeaderFlag; /* For OPEN TEX, CLOSE TEX */
  flag commentOnlyFlag; /* For SHOW STATEMENT */
  flag briefFlag; /* For SHOW STATEMENT */
  flag linearFlag; /* For SHOW LABELS */
  vstring bgcolor = ""; /* For SHOW STATEMENT definition list */
                                                            /* 8-Aug-2008 nm */

  flag verboseMode, mayGrowFlag /*, noDistinctFlag*/; /* For MINIMIZE_WITH */
  long prntStatus; /* For MINIMIZE_WITH */
  flag hasWildCard; /* For MINIMIZE_WITH */
  long exceptPos; /* For MINIMIZE_WITH */
  flag mathboxFlag; /* For MINIMIZE_WITH */ /* 28-Jun-2011 nm */
  long thisMathboxStartStmt; /* For MINIMIZE_WITH */ /* 14-Aug-2012 nm */
  flag forwFlag; /* For MINIMIZE_WITH */ /* 11-Nov-2011 nm */
  long forbidMatchPos;  /* For MINIMIZE_WITH */ /* 20-May-2013 nm */
  vstring forbidMatchList = "";  /* For MINIMIZE_WITH */ /* 20-May-2013 nm */
  long noNewAxiomsMatchPos;  /* For NO_NEW_AXIOMS_FROM */ /* 22-Nov-2014 nm */
  vstring noNewAxiomsMatchList = "";  /* For NO_NEW_AXIOMS_FROM */ /* 22-Nov-2014 */
  long allowNewAxiomsMatchPos;  /* For NO_NEW_AXIOMS_FROM */ /* 4-Aug-2019 nm */
  vstring allowNewAxiomsMatchList = "";  /* For NO_NEW_AXIOMS_FROM */ /* 4-Aug-2019 */
  vstring traceProofFlags = ""; /* For NO_NEW_AXIOMS_FROM */ /* 22-Nov-2014 nm */
  vstring traceTrialFlags = ""; /* For NO_NEW_AXIOMS_FROM */ /* 22-Nov-2014 nm */
  flag overrideFlag; /* For discouraged statement /OVERRIDE */ /* 3-May-2016 nm */

  struct pip_struct saveProofForReverting = {
       NULL_NMBRSTRING, NULL_PNTRSTRING, NULL_PNTRSTRING, NULL_PNTRSTRING };
                                   /* For MINIMIZE_WITH */ /* 20-May-2013 nm */
  long origCompressedLength; /* For MINIMIZE_WITH */ /* 25-Jun-2014 nm */
  long oldCompressedLength = 0; /* For MINIMIZE_WITH */ /* 25-Jun-2014 nm */
  long newCompressedLength = 0; /* For MINIMIZE_WITH */ /* 25-Jun-2014 nm */
  long forwardCompressedLength = 0; /* For MINIMIZE_WITH */ /* 25-Jun-2014 nm */
  long forwardLength = 0; /* For MINIMIZE_WITH */ /* 25-Jun-2014 nm */
  vstring saveZappedProofSectionPtr; /* Pointer only */ /* For MINIMIZE_WITH */
  long saveZappedProofSectionLen; /* For MINIMIZE_WITH */ /* 25-Jun-2014 nm */
  flag saveZappedProofSectionChanged; /* For MINIMIZE_WITH */ /* 16-Jun-2017 nm */

  struct pip_struct saveOrigProof = {
       NULL_NMBRSTRING, NULL_PNTRSTRING, NULL_PNTRSTRING, NULL_PNTRSTRING };
                                   /* For MINIMIZE_WITH */ /* 25-Jun-2014 nm */
  struct pip_struct save1stPassProof = {
       NULL_NMBRSTRING, NULL_PNTRSTRING, NULL_PNTRSTRING, NULL_PNTRSTRING };
                                   /* For MINIMIZE_WITH */ /* 25-Jun-2014 nm */
  long forwRevPass; /* 1 = forward pass */ /* 25-Jun-2014 nm */

  long sourceStatement; /* For EXPAND */ /* 11-Sep-2016 nm */

  flag showLemmas; /* For WRITE THEOREM_LIST */ /* 10-Oct-2012 nm */
  flag noVersioning; /* For WRITE THEOREM_LIST & others */ /* 17-Jul-2019 nm */
  long theoremsPerPage; /* For WRITE THEOREM_LIST */ /* 17-Jul-2019 nm */

  /* g_toolsMode-specific variables */
  flag commandProcessedFlag = 0; /* Set when the first command line processed;
                                    used to exit shell command line mode */
  FILE *list1_fp;
  FILE *list2_fp;
  FILE *list3_fp;
  vstring list2_fname = "", list2_ftmpname = "";
  vstring list3_ftmpname = "";
  vstring oldstr = "", newstr = "";
  long lines, changedLines, oldChangedLines, twoMatches, p1, p2;
  long firstChangedLine;
  flag cmdMode, changedFlag, outMsgFlag;
  double sum;
  vstring bufferedLine = "";
  vstring tagStartMatch = "";  /* 2-Jul-2011 nm For TAG command */
  long tagStartCount = 0;      /* 2-Jul-2011 nm For TAG command */
  vstring tagEndMatch = "";    /* 2-Jul-2011 nm For TAG command */
  long tagEndCount = 0;        /* 2-Jul-2011 nm For TAG command */
  long tagStartCounter = 0;    /* 2-Jul-2011 nm For TAG command */
  long tagEndCounter = 0;      /* 2-Jul-2011 nm For TAG command */

  /* 21-Jun-2014 */
  /* 16-Aug-2016 nm Now in getElapasedTime() */
  /* clock_t timePrevious = 0; */  /* For SHOW ELAPSED_TIME command */
  /* clock_t timeNow = 0; */       /* For SHOW ELAPSED_TIME command */
  /* 16-Aug-2016 nm */
  double timeTotal = 0;
  double timeIncr = 0;
  flag printTime;  /* Set by "/ TIME" in SAVE PROOF and others */

  /* 14-Aug-2018 nm */
  flag defaultScrollMode = 1; /* Default to prompted mode */

  /* Initialization to avoid compiler warning (should not be theoretically
     necessary) */
  p = 0;
  q = 0;
  s = 0;
  texHeaderFlag = 0;
  firstChangedLine = 0;
  tagStartCount = 0;           /* 2-Jul-2011 nm For TAG command */
  tagEndCount = 0;             /* 2-Jul-2011 nm For TAG command */
  tagStartCounter = 0;         /* 2-Jul-2011 nm For TAG command */
  tagEndCounter = 0;           /* 2-Jul-2011 nm For TAG command */

  while (1) {

    if (g_listMode) {
      /* If called from the OS shell with arguments, do one command
         then exit program. */
      /* (However, let a SUBMIT job complete) */
      if (argc > 1 && commandProcessedFlag &&
             g_commandFileNestingLevel == 0) return;
    }

    g_errorCount = 0; /* Reset error count before each read or proof parse. */

    /* Deallocate stuff that may have been used in previous pass */
    let(&str1,"");
    let(&str2,"");
    let(&str3,"");
    let(&str4,"");
    let(&str5,"");
    nmbrLet(&nmbrTmp, NULL_NMBRSTRING);
    pntrLet(&pntrTmp, NULL_PNTRSTRING);
    nmbrLet(&nmbrSaveProof, NULL_NMBRSTRING);
    nmbrLet(&essentialFlags, NULL_NMBRSTRING);
    j = nmbrLen(g_rawArgNmbr);
    if (j != g_rawArgs) bug(1110);
    j = pntrLen(g_rawArgPntr);
    if (j != g_rawArgs) bug(1111);
    g_rawArgs = 0;
    for (i = 0; i < j; i++) let((vstring *)(&g_rawArgPntr[i]), "");
    pntrLet(&g_rawArgPntr, NULL_PNTRSTRING);
    nmbrLet(&g_rawArgNmbr, NULL_NMBRSTRING);
    j = pntrLen(g_fullArg);
    for (i = 0; i < j; i++) let((vstring *)(&g_fullArg[i]),"");
    pntrLet(&g_fullArg,NULL_PNTRSTRING);
    j = pntrLen(expandedProof);
    if (j) {
      for (i = 0; i < j; i++) {
        let((vstring *)(&expandedProof[i]),"");
      }
     pntrLet(&expandedProof,NULL_PNTRSTRING);
    }

    let(&list2_fname, "");
    let(&list2_ftmpname, "");
    let(&list3_ftmpname, "");
    let(&oldstr, "");
    let(&newstr, "");
    let(&labelMatch, "");
    /* (End of space deallocation) */

    g_midiFlag = 0; /* 8/28/00 Initialize here in case SHOW PROOF exits early */

    if (g_memoryStatus) {
      /*??? Change to user-friendly message */
#ifdef THINK_C
      print2("Memory:  string %ld xxxString %ld free %ld\n",db,db3,(long)FreeMem());
      getPoolStats(&i, &j, &k);
      print2("Pool:  free alloc %ld  used alloc %ld  used actual %ld\n",i,j,k);
#else
      print2("Memory:  string %ld xxxString %ld\n",db,db3);
#endif
      getPoolStats(&i, &j, &k);
      print2("Pool:  free alloc %ld  used alloc %ld  used actual %ld\n",i,j,k);
    }

    if (!g_toolsMode) {
      if (g_PFASmode) {
        let(&g_commandPrompt,"MM-PA> ");
      } else {
        let(&g_commandPrompt,"MM> ");
      }
    } else {
      if (g_listMode) {
        let(&g_commandPrompt,"Tools> ");
      } else {
        let(&g_commandPrompt,"TOOLS> ");
      }
    }

    let(&g_commandLine,""); /* Deallocate previous contents */

    if (!commandProcessedFlag && argc > 1 && argsProcessed < argc - 1
        && g_commandFileNestingLevel == 0) {
      /* if (g_toolsMode) { */  /* 10-Oct-2006 nm Fix bug: changed to g_listMode */
      if (g_listMode) {
        /* If program was compiled in TOOLS mode, the command-line argument
           is assumed to be a single TOOLS command; build the equivalent
           TOOLS command */
        for (i = 1; i < argc; i++) {
          argsProcessed++;
          /* Put quotes around an argument with spaces or tabs or quotes
             or empty string */
          if (instr(1, argv[i], " ") || instr(1, argv[i], "\t")
              || instr(1, argv[i], "\"") || instr(1, argv[i], "'")
              || (argv[i])[0] == 0) {
            /* If it contains a double quote, use a single quote */
            if (instr(1, argv[i], "\"")) {
              let(&str1, cat("'", argv[i], "'", NULL));
            } else {
              /* (??? (TODO)Case of both ' and " is not handled) */
              let(&str1, cat("\"", argv[i], "\"", NULL));
            }
          } else {
            let(&str1, argv[i]);
          }
          let(&g_commandLine, cat(g_commandLine, (i == 1) ? "" : " ", str1, NULL));
        }
      } else {
        /* If program was compiled in default (Metamath) mode, each command-line
           argument is considered a full Metamath command.  User is responsible
           for ensuring necessary quotes around arguments are passed in. */
        argsProcessed++;
        g_scrollMode = 0; /* Set continuous scrolling until completed */
        let(&g_commandLine, cat(g_commandLine, argv[argsProcessed], NULL));
        if (argc == 2 && instr(1, argv[1], " ") == 0) {
          /* Assume the user intended a READ command.  This special mode allows
             invocation via "metamath xxx.mm". */
          if (instr(1, g_commandLine, "\"") || instr(1, g_commandLine, "'")) {
            /* If it already has quotes don't put quotes */
            let(&g_commandLine, cat("READ ", g_commandLine, NULL));
          } else {
            /* Put quotes so / won't be interpreted as qualifier separator */
            let(&g_commandLine, cat("READ \"", g_commandLine, "\"", NULL));
          }

          /***** 3-Jan-2017 nm  This is now done with SET ROOT_DIRECTORY
          /@ 31-Dec-2017 nm @/
          /@ This block of code can be removed without side effects @/
          /@ "Hidden" hack for NM's convenience. :)  If there is an =, change
             it to space.  This lets users invoke with "metamath set.mm/h=test"
             or "metamath mbox/aa.mm/home=test" to specify an implicit read with
             /ROOT_DIRECTORY without having a space in the argument (a space
             means don't assume a read command). @/
          i = instr(1, g_commandLine, "=");
          if (i != 0) {
            /@ Change 'READ "set.mm/h=test"' to 'READ "set.mm" / "h" "test"' @/
            let(&g_commandLine, cat(left(g_commandLine, i - 1), "\" \"",
                right(g_commandLine, i + 1), NULL));
            while (i > 0) {
              if (g_commandLine[i - 1] == '/') {
                let(&g_commandLine, cat(left(g_commandLine, i - 1),
                    "\" / \"", right(g_commandLine, i + 1), NULL));
                break;
              }
              i--;
            }
            /@ Change ' to " to prevent mismatched quotes @/
            i = (long)strlen(g_commandLine);
            while (i > 0) {
              if (g_commandLine[i - 1] == '\'') {
                g_commandLine[i - 1] = '"';
              }
              i--;
            }
          } /@ if i != 0 (end of 31-Dec-2017 NM hack) @/
          */

        }
      }
      print2("%s\n", cat(g_commandPrompt, g_commandLine, NULL));
    } else {
      /* Get command from user input or SUBMIT script file */
      g_commandLine = cmdInput1(g_commandPrompt);
    }
    if (argsProcessed == argc && !commandProcessedFlag) {
      commandProcessedFlag = 1;
      g_scrollMode = defaultScrollMode; /* Set prompted (default) scroll mode */
    }
    if (argsProcessed == argc - 1) {
      argsProcessed++; /* Indicates restore scroll mode next time around */
      if (g_toolsMode) {
        /* If program was compiled in TOOLS mode, we're only going to execute
           one command; set flag to exit next time around */
        commandProcessedFlag = 1;
      }
    }

    /* See if it's an operating system command */
    /* (This is a command line that begins with a quote) */
    if (g_commandLine[0] == '\'' || g_commandLine[0] == '\"') {
      /* See if this computer has this feature */
      if (!system(NULL)) {
        print2("?This computer does not accept an operating system command.\n");
        continue;
      } else {
        /* Strip off quote and trailing quote if any */
        let(&str1, right(g_commandLine, 2));
        if (g_commandLine[0]) { /* (Prevent stray pointer if empty string) */
          if (g_commandLine[0] == g_commandLine[strlen(g_commandLine) - 1]) {
            let(&str1, left(str1, (long)(strlen(str1)) - 1));
          }
        }
        /* Do the operating system command */
        (void)system(str1);
#ifdef VAXC
        printf("\n"); /* Last line from VAX doesn't have new line */
#endif
        continue;
      }
    }

    parseCommandLine(g_commandLine);
    if (g_rawArgs == 0) {
      continue; /* Empty or comment line */
    }
    if (!processCommandLine()) {
      continue;
    }

    if (g_commandEcho || (g_toolsMode && g_listFile_fp != NULL)) {
      /* Build the complete command and print it for the user */
      k = pntrLen(g_fullArg);
      let(&str1,"");
      for (i = 0; i < k; i++) {
        if (instr(1, g_fullArg[i], " ") || instr(1, g_fullArg[i], "\t")
            || instr(1, g_fullArg[i], "\"") || instr(1, g_fullArg[i], "'")
            || ((char *)(g_fullArg[i]))[0] == 0) {
          /* If the argument has spaces or tabs or quotes
             or is empty string, put quotes around it */
          if (instr(1, g_fullArg[i], "\"")) {
            let(&str1, cat(str1, "'", g_fullArg[i], "' ", NULL));
          } else {
            /* (???Case of both ' and " is not handled) */
            let(&str1, cat(str1, "\"", g_fullArg[i], "\" ", NULL));
          }
        } else {
          let(&str1, cat(str1, g_fullArg[i], " ", NULL));
        }
      }
      let(&str1, left(str1, (long)(strlen(str1)) - 1)); /* Trim trailing spc */
      if (g_toolsMode && g_listFile_fp != NULL) {
        /* Put line in list.tmp as command */
        fprintf(g_listFile_fp, "%s\n", str1);  /* Print to list command file */
      }
      if (g_commandEcho) {
        /* 15-Jun-2009 nm Added code line below */
        /* Put special character "!" before line for easier extraction to
           build SUBMIT files; see also SET ECHO ON output below */
        let(&str1, cat("!", str1, NULL));
        /* The tilde is a special flag for printLongLine to print a
           tilde before the carriage return in a split line, not after */
        printLongLine(str1, "~", " ");
      }
    }

    if (cmdMatches("BEEP") || cmdMatches("B")) {
      /* Print a bell (if user types ahead "B", the bell lets him know when
         his command is finished - useful for long-running commands */
      print2("%c",7);
      continue;
    }

    if (cmdMatches("HELP")) {
      /* Build the complete command */
      k = pntrLen(g_fullArg);
      let(&str1,"");
      for (i = 0; i < k; i++) {
        let(&str1, cat(str1, g_fullArg[i], " ", NULL));
      }
      let(&str1, left(str1, (long)(strlen(str1)) - 1));
      if (g_toolsMode) {
        help0(str1);
        help1(str1);
      } else {
        help1(str1);
        help2(str1);
        help3(str1); /* 18-Jul-2015 nm */
      }
      continue;
    }


    if (cmdMatches("SET SCROLL")) {
      if (cmdMatches("SET SCROLL CONTINUOUS")) {
        defaultScrollMode = 0;
        g_scrollMode = 0;
        print2("Continuous scrolling is now in effect.\n");
      } else {
        defaultScrollMode = 1;
        g_scrollMode = 1;
        print2("Prompted scrolling is now in effect.\n");
      }
      continue;
    }

    if (cmdMatches("EXIT") || cmdMatches("QUIT")
        || cmdMatches("_EXIT_PA")) { /* 9-Jun-2016 - for MM-PA> exit
            in scripts, so it will error out in MM> (if for some reason
            MM-PA wasn't entered) instead of exiting metamath */
    /*???        || !strcmp(cmd,"^Z")) { */

      /* 9-Jun-2016 */
      if (cmdMatches("_EXIT_PA")) {
        if (!g_PFASmode || (g_toolsMode && !g_listMode)) bug(1127);
                 /* mmcmdl.c should have caught this */
      }

      if (g_toolsMode && !g_listMode) {
        /* Quitting tools command from within Metamath */
        if (!g_PFASmode) {
          print2(
 "Exiting the Text Tools.  Type EXIT again to exit Metamath.\n");
        } else {
          print2(
 "Exiting the Text Tools.  Type EXIT again to exit the Proof Assistant.\n");
        }
        g_toolsMode = 0;
        continue;
      }

      if (g_PFASmode) {

        if (g_proofChanged &&
              /* If g_proofChanged, but the UNDO stack is empty (and
                 there were no other conditions such as stack overflow),
                 the proof didn't really change, so it is safe to
                 exit MM-PA without warning */
              (processUndoStack(NULL, PUS_GET_STATUS, "", 0)
                 /* However, if the proof was saved earlier, UNDO stack
                    empty no longer indicates proof didn't change */
                 || proofSavedFlag)) {
          print2(
              "Warning:  You have not saved changes to the proof of \"%s\".\n",
              g_Statement[g_proveStatement].labelName); /* 21-Jan-06 nm */
          /* 17-Aug-04 nm Added / FORCE qualifier */
          if (switchPos("/ FORCE") == 0) {
            str1 = cmdInput1("Do you want to EXIT anyway (Y, N) <N>? ");
            if (str1[0] != 'y' && str1[0] != 'Y') {
              print2("Use SAVE NEW_PROOF to save the proof.\n");
              continue;
            }
          } else {
            /* User specified / FORCE, so answer question automatically */
            print2("Do you want to EXIT anyway (Y, N) <N>? Y\n");
          }
        }

        g_proofChanged = 0;
        processUndoStack(NULL, PUS_INIT, "", 0);
        proofSavedFlag = 0; /* Will become 1 if proof is ever saved */

        print2(
 "Exiting the Proof Assistant.  Type EXIT again to exit Metamath.\n");

        /* Deallocate proof structure */
        deallocProofStruct(&g_ProofInProgress); /* 20-May-2013 nm */

        /**** old deallocation before 20-May-2013
        i = nmbrLen(g_ProofInProgress.proof);
        nmbrLet(&g_ProofInProgress.proof, NULL_NMBRSTRING);
        for (j = 0; j < i; j++) {
          nmbrLet((nmbrString **)(&((g_ProofInProgress.target)[j])),
              NULL_NMBRSTRING);
          nmbrLet((nmbrString **)(&((g_ProofInProgress.source)[j])),
              NULL_NMBRSTRING);
          nmbrLet((nmbrString **)(&((g_ProofInProgress.user)[j])),
              NULL_NMBRSTRING);
        }
        pntrLet(&g_ProofInProgress.target, NULL_PNTRSTRING);
        pntrLet(&g_ProofInProgress.source, NULL_PNTRSTRING);
        pntrLet(&g_ProofInProgress.user, NULL_PNTRSTRING);
        *** end of old code before 20-May-2013 */

        g_PFASmode = 0;
        continue;
      } else {
        if (g_sourceChanged) {
          print2("Warning:  You have not saved changes to the source.\n");
          /* 17-Aug-04 nm Added / FORCE qualifier */
          if (switchPos("/ FORCE") == 0) {
            str1 = cmdInput1("Do you want to EXIT anyway (Y, N) <N>? ");
            if (str1[0] != 'y' && str1[0] != 'Y') {
              print2("Use WRITE SOURCE to save the changes.\n");
              continue;
            }
          } else {
            /* User specified / FORCE, so answer question automatically */
            print2("Do you want to EXIT anyway (Y, N) <N>? Y\n");
          }
          g_sourceChanged = 0;
        }

        if (g_texFileOpenFlag) {
          print2("The %s file \"%s\" was closed.\n",
              g_htmlFlag ? "HTML" : "LaTeX", g_texFileName);
          printTexTrailer(texHeaderFlag);
          fclose(g_texFilePtr);
          g_texFileOpenFlag = 0;
        }
        if (g_logFileOpenFlag) {
          print2("The log file \"%s\" was closed %s %s.\n",g_logFileName,
              date(),time_());
          fclose(g_logFilePtr);
          g_logFileOpenFlag = 0;
        }

        /* 4-May-2017 Ari Ferrera */
        /* Free remaining allocations before exiting */
        freeCommandLine();
        freeInOu();
        memFreePoolPurge(0);
        eraseSource();
        freeData(); /* Call AFTER eraseSource()(->initBigArrays->malloc) */
        let(&g_commandPrompt,"");
        let(&g_commandLine,"");
        let(&g_input_fn,"");
        let(&g_contributorName, ""); /* 14-May-2017 nm */

        return; /* Exit from program */
      }
    }

    if (cmdMatches("SUBMIT")) {
      if (g_commandFileNestingLevel == MAX_COMMAND_FILE_NESTING) {
        printf("?The SUBMIT nesting level has been exceeded.\n");
        continue;
      }
      g_commandFilePtr[g_commandFileNestingLevel + 1] = fSafeOpen(g_fullArg[1], "r",
          0/*noVersioningFlag*/);
      if (!g_commandFilePtr[g_commandFileNestingLevel + 1]) continue;
                                      /* Couldn't open (err msg was provided) */
      g_commandFileNestingLevel++;
      g_commandFileName[g_commandFileNestingLevel] = ""; /* Initialize if nec. */
      let(&g_commandFileName[g_commandFileNestingLevel], g_fullArg[1]);

      /* 23-Oct-2006 nm Added / SILENT */
      g_commandFileSilent[g_commandFileNestingLevel] = 0;
      if (switchPos("/ SILENT")
          || g_commandFileSilentFlag /* Propagate silence from outer level */) {
        g_commandFileSilent[g_commandFileNestingLevel] = 1;
      } else {
        g_commandFileSilent[g_commandFileNestingLevel] = 0;
      }
      g_commandFileSilentFlag = g_commandFileSilent[g_commandFileNestingLevel];
      if (!g_commandFileSilentFlag)
        print2("Taking command lines from file \"%s\"...\n",
            g_commandFileName[g_commandFileNestingLevel]);

      continue;
    }

    if (g_toolsMode) {
      /* Start of g_toolsMode-specific commands */
#define ADD_MODE 1
#define DELETE_MODE 2
#define CLEAN_MODE 3
#define SUBSTITUTE_MODE 4
#define SWAP_MODE 5
#define INSERT_MODE 6
#define BREAK_MODE 7
#define BUILD_MODE 8
#define MATCH_MODE 9
#define RIGHT_MODE 10
#define TAG_MODE 11  /* 2-Jul-2011 nm Added TAG command */
      cmdMode = 0;
      if (cmdMatches("ADD")) cmdMode = ADD_MODE;
      else if (cmdMatches("DELETE")) cmdMode = DELETE_MODE;
      else if (cmdMatches("CLEAN")) cmdMode = CLEAN_MODE;
      else if (cmdMatches("SUBSTITUTE") || cmdMatches("S"))
        cmdMode = SUBSTITUTE_MODE;
      else if (cmdMatches("SWAP")) cmdMode = SWAP_MODE;
      else if (cmdMatches("INSERT")) cmdMode = INSERT_MODE;
      else if (cmdMatches("BREAK")) cmdMode = BREAK_MODE;
      else if (cmdMatches("BUILD")) cmdMode = BUILD_MODE;
      else if (cmdMatches("MATCH")) cmdMode = MATCH_MODE;
      else if (cmdMatches("RIGHT")) cmdMode = RIGHT_MODE;
      else if (cmdMatches("TAG")) cmdMode = TAG_MODE;
      if (cmdMode) {
        list1_fp = fSafeOpen(g_fullArg[1], "r", 0/*noVersioningFlag*/);
        if (!list1_fp) continue; /* Couldn't open it (error msg was provided) */
        if (cmdMode == RIGHT_MODE) {
          /* Find the longest line */
          p = 0;
          while (linput(list1_fp, NULL, &str1)) {
            if (p < (signed)(strlen(str1))) p = (long)(strlen(str1));
          }
          rewind(list1_fp);
        }
        let(&list2_fname, g_fullArg[1]);
        if (list2_fname[strlen(list2_fname) - 2] == '~') {
          let(&list2_fname, left(list2_fname, (long)(strlen(list2_fname)) - 2));
          print2("The output file will be called %s.\n", list2_fname);
        }
        let(&list2_ftmpname, "");
        list2_ftmpname = fGetTmpName("zz~tools");
        list2_fp = fSafeOpen(list2_ftmpname, "w", 0/*noVersioningFlag*/);
        if (!list2_fp) continue; /* Couldn't open it (error msg was provided) */
        lines = 0;
        changedLines = 0;
        twoMatches = 0;
        changedFlag = 0;
        outMsgFlag = 0;
        switch (cmdMode) {
          case ADD_MODE:
            break;
          case TAG_MODE:  /* 2-Jul-2011 nm Added TAG command */
            let(&tagStartMatch, g_fullArg[4]);
            tagStartCount = (long)val(g_fullArg[5]);
            if (tagStartCount == 0) tagStartCount = 1; /* Default */
            let(&tagEndMatch, g_fullArg[6]);
            tagEndCount = (long)val(g_fullArg[7]);
            if (tagEndCount == 0) tagEndCount = 1; /* Default */
            tagStartCounter = 0;
            tagEndCounter = 0;
            break;
          case DELETE_MODE:
            break;
          case CLEAN_MODE:
            let(&str4, edit(g_fullArg[2], 32));
            q = 0;
            if (instr(1, str4, "P") > 0) q = q + 1;
            if (instr(1, str4, "D") > 0) q = q + 2;
            if (instr(1, str4, "G") > 0) q = q + 4;
            if (instr(1, str4, "B") > 0) q = q + 8;
            if (instr(1, str4, "R") > 0) q = q + 16;
            if (instr(1, str4, "C") > 0) q = q + 32;
            if (instr(1, str4, "E") > 0) q = q + 128;
            if (instr(1, str4, "Q") > 0) q = q + 256;
            if (instr(1, str4, "L") > 0) q = q + 512;
            if (instr(1, str4, "T") > 0) q = q + 1024;
            if (instr(1, str4, "U") > 0) q = q + 2048;
            if (instr(1, str4, "V") > 0) q = q + 4096;
            break;
          case SUBSTITUTE_MODE:
            let(&newstr, g_fullArg[3]); /* The replacement string */
            if (((vstring)(g_fullArg[4]))[0] == 'A' ||
                ((vstring)(g_fullArg[4]))[0] == 'a') { /* ALL */
              q = -1;
            } else {
              q = (long)val(g_fullArg[4]);
              if (q == 0) q = 1;    /* The occurrence # of string to subst */
            }
            s = instr(1, g_fullArg[2], "\\n");
            if (s) {
              /*s = 1;*/ /* Replace lf flag */
              q = 1; /* Only 1st occurrence makes sense in this mode */
            }
            if (!strcmp(g_fullArg[3], "\\n")) {
              let(&newstr, "\n"); /* Replace with lf */
            }
            break;
          case SWAP_MODE:
            break;
          case INSERT_MODE:
            p = (long)val(g_fullArg[3]);
            break;
          case BREAK_MODE:
            outMsgFlag = 1;
            break;
          case BUILD_MODE:
            let(&str4, "");
            outMsgFlag = 1;
            break;
          case MATCH_MODE:
            outMsgFlag = 1;
        } /* End switch */
        let(&bufferedLine, "");
        /*
        while (linput(list1_fp, NULL, &str1)) {
        */
        while (1) {
          if (bufferedLine[0]) {
            /* Get input from buffered line (from rejected \n replacement) */
            let(&str1, bufferedLine);
            let(&bufferedLine, "");
          } else {
            if (!linput(list1_fp, NULL, &str1)) break;
          }
          lines++;
          oldChangedLines = changedLines;
          let(&str2, str1);
          switch (cmdMode) {
            case ADD_MODE:
              let(&str2, cat(g_fullArg[2], str1, g_fullArg[3], NULL));
              if (strcmp(str1, str2)) changedLines++;
              break;
            case TAG_MODE:   /* 2-Jul-2011 nm Added TAG command */
              if (tagStartCounter < tagStartCount) {
                if (instr(1, str1, tagStartMatch)) tagStartCounter++;
              }
              if (tagStartCounter == tagStartCount &&
                  tagEndCounter < tagEndCount) { /* We're in tagging range */
                let(&str2, cat(g_fullArg[2], str1, g_fullArg[3], NULL));
                if (strcmp(str1, str2)) changedLines++;
                if (instr(1, str1, tagEndMatch)) tagEndCounter++;
              }
              break;
            case DELETE_MODE:
              p1 = instr(1, str1, g_fullArg[2]);
              if (strlen(g_fullArg[2]) == 0) p1 = 1;
              p2 = instr(p1, str1, g_fullArg[3]);
              if (strlen(g_fullArg[3]) == 0) p2 = (long)strlen(str1) + 1;
              if (p1 != 0 && p2 != 0) {
                let(&str2, cat(left(str1, p1 - 1), right(str1, p2
                    + (long)strlen(g_fullArg[3])), NULL));
                changedLines++;
              }
              break;
            case CLEAN_MODE:
              if (q) {
                let(&str2, edit(str1, q));
                if (strcmp(str1, str2)) changedLines++;
              }
              break;
            case SUBSTITUTE_MODE:
              let(&str2, str1);
              p = 0;
              p1 = 0;

              k = 1;
              /* See if an additional match on line is required */
              if (((vstring)(g_fullArg[5]))[0] != 0) {
                if (!instr(1, str2, g_fullArg[5])) {
                  /* No match on line; prevent any substitution */
                  k = 0;
                }
              }

              if (s && k) { /* We're asked to replace a newline char */
                /* Read in the next line */
                /*
                if (linput(list1_fp, NULL, &str4)) {
                  let(&str2, cat(str1, "\\n", str4, NULL));
                */
                if (linput(list1_fp, NULL, &bufferedLine)) {
                  /* Join the next line and see if the string matches */
                  if (instr(1, cat(str1, "\\n", bufferedLine, NULL),
                      g_fullArg[2])) {
                    let(&str2, cat(str1, "\\n", bufferedLine, NULL));
                    let(&bufferedLine, "");
                  } else {
                    k = 0; /* No match - leave bufferedLine for next pass */
                  }
                } else { /* EOF reached */
                  print2("Warning: file %s has an odd number of lines\n",
                      g_fullArg[1]);
                }
              }

              while (k) {
                p1 = instr(p1 + 1, str2, g_fullArg[2]);
                if (!p1) break;
                p++;
                if (p == q || q == -1) {
                  let(&str2, cat(left(str2, p1 - 1), newstr,
                      right(str2, p1 + (long)strlen(g_fullArg[2])), NULL));
                  if (newstr[0] == '\n') {
                    /* Replacement string is an lf */
                    lines++;
                    changedLines++;
                  }
                  /* 14-Sep-2010 nm Continue the search after the replacement
                     string, so that "SUBST 1.tmp abbb ab a ''" will change
                     "abbbab" to "abba" rather than "aa" */
                  p1 = p1 + (long)strlen(newstr) - 1;
                  /* p1 = p1 - (long)strlen(g_fullArg[2]) + (long)strlen(newstr); */ /* bad */
                  if (q != -1) break;
                }
              }
              if (strcmp(str1, str2)) changedLines++;
              break;
            case SWAP_MODE:
              p1 = instr(1, str1, g_fullArg[2]);
              if (p1) {
                p2 = instr(p1 + 1, str1, g_fullArg[2]);
                if (p2) twoMatches++;
                let(&str2, cat(right(str1, p1) + (long)strlen(g_fullArg[2]),
                    g_fullArg[2], left(str1, p1 - 1), NULL));
                if (strcmp(str1, str2)) changedLines++;
              }
              break;
            case INSERT_MODE:
              if ((signed)(strlen(str2)) < p - 1)
                let(&str2, cat(str2, space(p - 1 - (long)strlen(str2)), NULL));
              let(&str2, cat(left(str2, p - 1), g_fullArg[2],
                  right(str2, p), NULL));
              if (strcmp(str1, str2)) changedLines++;
              break;
            case BREAK_MODE:
              let(&str2, str1);
              changedLines++;
              for (i = 0; i < (signed)(strlen(g_fullArg[2])); i++) {
                p = 0;
                while (1) {
                  p = instr(p + 1, str2, chr(((vstring)(g_fullArg[2]))[i]));
                  if (!p) break;
                  /* Put spaces arount special one-char tokens */
                  let(&str2, cat(left(str2, p - 1), " ",
                      mid(str2, p, 1),
                      " ", right(str2, p + 1), NULL));
                  /*p++;*/
                  /* Even though space is always a separator, it can be used
                     to suppress all default tokens.  Go past 2nd space to prevent
                     infinite loop in that case. */
                  p += 2; /* 3-Jul-2020 nm */
                }
              }
              let(&str2, edit(str2, 8 + 16 + 128)); /* Reduce & trim spaces */
              for (p = (long)strlen(str2) - 1; p >= 0; p--) {
                if (str2[p] == ' ') {
                  str2[p] = '\n';
                  changedLines++;
                }
              }
              if (!str2[0]) changedLines--; /* Don't output blank line */
              break;
            case BUILD_MODE:
              if (str2[0] != 0) { /* Ignore blank lines */
                if (str4[0] == 0) {
                  let(&str4, str2);
                } else {
                  if ((long)strlen(str4) + (long)strlen(str2) > 72) {
                    let(&str4, cat(str4, "\n", str2, NULL));
                    changedLines++;
                  } else {
                    let(&str4, cat(str4, " ", str2, NULL));
                  }
                }
                p = instr(1, str4, "\n");
                if (p) {
                  let(&str2, left(str4, p - 1));
                  let(&str4, right(str4, p + 1));
                } else {
                  let(&str2, "");
                }
              }
              break;
            case MATCH_MODE:
              if (((vstring)(g_fullArg[2]))[0] == 0) {
                /* Match any non-blank line */
                p = str1[0];
              } else {
                p = instr(1, str1, g_fullArg[2]);
              }
              if (((vstring)(g_fullArg[3]))[0] == 'n' ||
                  ((vstring)(g_fullArg[3]))[0] == 'N') {
                p = !p;
              }
              if (p) changedLines++;
              break;
            case RIGHT_MODE:
              let(&str2, cat(space(p - (long)strlen(str2)), str2, NULL));
              if (strcmp(str1, str2)) changedLines++;
              break;
          } /* End switch(cmdMode) */
          if (lines == 1) let(&str3, left(str2, 79)); /* For msg */
          if (oldChangedLines != changedLines && !changedFlag) {
            changedFlag = 1;
            let(&str3, left(str2, 79)); /* For msg */
            firstChangedLine = lines;
            if ((cmdMode == SUBSTITUTE_MODE && newstr[0] == '\n')
                || cmdMode == BUILD_MODE) /* Joining lines */ {
              firstChangedLine = 1; /* Better message */
            }
          }
          if (((cmdMode != BUILD_MODE && cmdMode != BREAK_MODE)
              || str2[0] != 0)
              && (cmdMode != MATCH_MODE || p))
            fprintf(list2_fp, "%s\n", str2);
        } /* Next input line */
        if (cmdMode == BUILD_MODE) {
          if (str4[0]) {
            /* Output last partial line */
            fprintf(list2_fp, "%s\n", str4);
            changedLines++;
            if (!str3[0]) {
              let(&str3, str4); /* For msg */
            }
          }
        }
        /* Remove any lines after lf for readability of msg */
        p = instr(1, str3, "\n");
        if (p) let(&str3, left(str3, p - 1));
        if (!outMsgFlag) {
          /* 18-Aug-2011 nm Make message depend on line counts */
          if (!changedFlag) {
            if (!lines) {
              print2("The file %s has no lines.\n", g_fullArg[1]);
            } else {
              print2(
"The file %s has %ld line%s; none were changed.  First line:\n",
                list2_fname, lines, (lines == 1) ? "" : "s");
              print2("%s\n", str3);
            }
          } else {
            print2(
"The file %s has %ld line%s; %ld w%s changed.  First changed line is %ld:\n",
                list2_fname,
                lines,  (lines == 1) ? "" : "s",
                changedLines,  (changedLines == 1) ? "as" : "ere",
                firstChangedLine);
            print2("%s\n", str3);
          }
          if (twoMatches > 0) {
            /* For SWAP command */
            print2(
"Warning:  %ld line%s more than one \"%s\".  The first one was used.\n",
                twoMatches, (twoMatches == 1) ? " has" : "s have", g_fullArg[2]);
          }
        } else {
          /* if (changedLines == 0) let(&str3, ""); */
          print2(
"The input had %ld line%s, the output has %ld line%s.%s\n",
              lines, (lines == 1) ? "" : "s",
              changedLines, (changedLines == 1) ? "" : "s",
              (changedLines == 0) ? "" : " First output line:");
          if (changedLines != 0) print2("%s\n", str3);
        }
        fclose(list1_fp);
        fclose(list2_fp);
        fSafeRename(list2_ftmpname, list2_fname);
        /* Deallocate string memory */
        let(&tagStartMatch, "");  /* 2-Jul-2011 nm Added TAG command */
        let(&tagEndMatch, "");  /* 2-Jul-2011 nm Added TAG command */
        continue;
      } /* end if cmdMode for ADD, etc. */

#define SORT_MODE 1
#define UNDUPLICATE_MODE 2
#define DUPLICATE_MODE 3
#define UNIQUE_MODE 4
#define REVERSE_MODE 5
      cmdMode = 0;
      if (cmdMatches("SORT")) cmdMode = SORT_MODE;
      else if (cmdMatches("UNDUPLICATE")) cmdMode = UNDUPLICATE_MODE;
      else if (cmdMatches("DUPLICATE")) cmdMode = DUPLICATE_MODE;
      else if (cmdMatches("UNIQUE")) cmdMode = UNIQUE_MODE;
      else if (cmdMatches("REVERSE")) cmdMode = REVERSE_MODE;
      if (cmdMode) {
        list1_fp = fSafeOpen(g_fullArg[1], "r", 0/*noVersioningFlag*/);
        if (!list1_fp) continue; /* Couldn't open it (error msg was provided) */
        let(&list2_fname, g_fullArg[1]);
        if (list2_fname[strlen(list2_fname) - 2] == '~') {
          let(&list2_fname, left(list2_fname, (long)strlen(list2_fname) - 2));
          print2("The output file will be called %s.\n", list2_fname);
        }
        let(&list2_ftmpname, "");
        list2_ftmpname = fGetTmpName("zz~tools");
        list2_fp = fSafeOpen(list2_ftmpname, "w", 0/*noVersioningFlag*/);
        if (!list2_fp) continue; /* Couldn't open it (error msg was provided) */

        /* Count the lines */
        lines = 0;
        while (linput(list1_fp, NULL, &str1)) lines++;
        if (cmdMode != SORT_MODE  && cmdMode != REVERSE_MODE) {
          print2("The input file has %ld lines.\n", lines);
        }

        /* Close and reopen the input file */
        fclose(list1_fp);
        list1_fp = fSafeOpen(g_fullArg[1], "r", 0/*noVersioningFlag*/);
        /* Allocate memory */
        pntrLet(&pntrTmp, pntrSpace(lines));
        /* Assign the lines to string array */
        for (i = 0; i < lines; i++) linput(list1_fp, NULL,
            (vstring *)(&pntrTmp[i]));

        /* Sort */
        if (cmdMode != REVERSE_MODE) {
          if (cmdMode == SORT_MODE) {
            g_qsortKey = g_fullArg[2]; /* Do not deallocate! */
          } else {
            g_qsortKey = "";
          }
          qsort(pntrTmp, (size_t)lines, sizeof(void *), qsortStringCmp);
        } else { /* Reverse the lines */
          for (i = lines / 2; i < lines; i++) {
            g_qsortKey = pntrTmp[i]; /* Use g_qsortKey as handy tmp var here */
            pntrTmp[i] = pntrTmp[lines - 1 - i];
            pntrTmp[lines - 1 - i] = g_qsortKey;
          }
        }

        /* Output sorted lines */
        changedLines = 0;
        let(&str3, "");
        for (i = 0; i < lines; i++) {
          j = 0; /* Flag that line should be printed */
          switch (cmdMode) {
            case SORT_MODE:
            case REVERSE_MODE:
              j = 1;
              break;
            case UNDUPLICATE_MODE:
              if (i == 0) {
                j = 1;
              } else {
                if (strcmp((vstring)(pntrTmp[i - 1]), (vstring)(pntrTmp[i]))) {
                  j = 1;
                }
              }
              break;
            case DUPLICATE_MODE:
              if (i > 0) {
                if (!strcmp((vstring)(pntrTmp[i - 1]), (vstring)(pntrTmp[i]))) {
                  if (i == lines - 1) {
                    j = 1;
                  } else {
                    if (strcmp((vstring)(pntrTmp[i]),
                        (vstring)(pntrTmp[i + 1]))) {
                      j = 1;
                    }
                  }
                }
              }
              break;
            case UNIQUE_MODE:
              if (i < lines - 1) {
                if (strcmp((vstring)(pntrTmp[i]), (vstring)(pntrTmp[i + 1]))) {
                  if (i == 0) {
                    j = 1;
                  } else {
                    if (strcmp((vstring)(pntrTmp[i - 1]),
                        (vstring)(pntrTmp[i]))) {
                      j = 1;
                    }
                  }
                }
              } else {
                if (i == 0) {
                  j = 1;
                } else {
                  if (strcmp((vstring)(pntrTmp[i - 1]),
                        (vstring)(pntrTmp[i]))) {
                      j = 1;
                  }
                }
              }
              break;
          } /* end switch (cmdMode) */
          if (j) {
            fprintf(list2_fp, "%s\n", (vstring)(pntrTmp[i]));
            changedLines++;
            if (changedLines == 1)
              let(&str3, left((vstring)(pntrTmp[i]), 79));
          }
        } /* next i */
        print2("The output file has %ld lines.  The first line is:\n",
            changedLines);
        print2("%s\n", str3);

        /* Deallocate memory */
        for (i = 0; i < lines; i++) let((vstring *)(&pntrTmp[i]), "");
        pntrLet(&pntrTmp,NULL_PNTRSTRING);

        fclose(list1_fp);
        fclose(list2_fp);
        fSafeRename(list2_ftmpname, list2_fname);
        continue;
      } /* end if cmdMode for SORT, etc. */

      if (cmdMatches("PARALLEL")) {
        list1_fp = fSafeOpen(g_fullArg[1], "r", 0/*noVersioningFlag*/);
        list2_fp = fSafeOpen(g_fullArg[2], "r", 0/*noVersioningFlag*/);
        if (!list1_fp) continue; /* Couldn't open it (error msg was provided) */
        if (!list2_fp) continue; /* Couldn't open it (error msg was provided) */
        let(&list3_ftmpname, "");
        list3_ftmpname = fGetTmpName("zz~tools");
        list3_fp = fSafeOpen(list3_ftmpname, "w", 0/*noVersioningFlag*/);
        if (!list3_fp) continue; /* Couldn't open it (error msg was provided) */

        p1 = 1; p2 = 1; /* not eof */
        p = 0; q = 0; /* lines */
        j = 0; /* 1st line flag */
        let(&str3, "");
        while (1) {
          let(&str1, "");
          if (p1) {
            p1 = linput(list1_fp, NULL, &str1);
            if (p1) p++;
            else let(&str1, "");
          }
          let(&str2, "");
          if (p2) {
            p2 = linput(list2_fp, NULL, &str2);
            if (p2) q++;
            else let(&str2, "");
          }
          if (!p1 && !p2) break;
          let(&str4, cat(str1, g_fullArg[4], str2, NULL));
          fprintf(list3_fp, "%s\n", str4);
          if (!j) {
            let(&str3, str4); /* Save 1st line for msg */
            j = 1;
          }
        }
        if (p == q) {
          print2(
"The input files each had %ld lines.  The first output line is:\n", p);
        } else {
          print2(
"Warning: file \"%s\" had %ld lines while file \"%s\" had %ld lines.\n",
              g_fullArg[1], p, g_fullArg[2], q);
          if (p < q) p = q;
          print2("The output file \"%s\" has %ld lines.  The first line is:\n",
              g_fullArg[3], p);
        }
        print2("%s\n", str3);

        fclose(list1_fp);
        fclose(list2_fp);
        fclose(list3_fp);
        fSafeRename(list3_ftmpname, g_fullArg[3]);
        continue;
      }


      if (cmdMatches("NUMBER")) {
        list1_fp = fSafeOpen(g_fullArg[1], "w", 0/*noVersioningFlag*/);
        if (!list1_fp) continue; /* Couldn't open it (error msg was provided) */
        j = (long)strlen(str(val(g_fullArg[2])));
        k = (long)strlen(str(val(g_fullArg[3])));
        if (k > j) j = k;
        for (i = (long)val(g_fullArg[2]); i <= val(g_fullArg[3]);
            i = i + (long)val(g_fullArg[4])) {
          let(&str1, str((double)i));
          fprintf(list1_fp, "%s\n", str1);
        }
        fclose(list1_fp);
        continue;
      }

      if (cmdMatches("COUNT")) {
        list1_fp = fSafeOpen(g_fullArg[1], "r", 0/*noVersioningFlag*/);
        if (!list1_fp) continue; /* Couldn't open it (error msg was provided) */
        p1 = 0;
        p2 = 0;
        lines = 0;
        q = 0; /* Longest line length */
        i = 0; /* First longest line */
        j = 0; /* Number of longest lines */
        sum = 0.0; /* Sum of numeric content of lines */
        firstChangedLine = 0;
        while (linput(list1_fp, NULL, &str1)) {
          lines++;

          /* Longest line */
          if (q < (signed)(strlen(str1))) {
            q = (long)strlen(str1);
            let(&str4, str1);
            i = lines;
            j = 0;
          }

          if (q == (signed)(strlen(str1))) {
            j++;
          }

          if (instr(1, str1, g_fullArg[2])) {
            if (!firstChangedLine) {
              firstChangedLine = lines;
              let(&str3, str1);
            }
            p1++;
            p = 0;
            while (1) {
              p = instr(p + 1, str1, g_fullArg[2]);
              if (!p) break;
              p2++;
            }
          }
          sum = sum + val(str1);
        }
        print2(
"The file has %ld lines.  The string \"%s\" occurs %ld times on %ld lines.\n",
            lines, g_fullArg[2], p2, p1);
        if (firstChangedLine) {
          print2("The first occurrence is on line %ld:\n", firstChangedLine);
          print2("%s\n", str3);
        }
        print2(
"The first longest line (out of %ld) is line %ld and has %ld characters:\n",
            j, i, q);
        printLongLine(str4, "    "/*startNextLine*/, ""/*breakMatch*/);
            /* breakMatch empty means break line anywhere */  /* 6-Dec-03 */
 /* print2("If each line were a number, their sum would be %s\n", str((double)sum)); */
        printLongLine(cat(
            "Stripping all but digits, \".\", and \"-\", the sum of lines is ",
            str((double)sum), NULL), "    ", " ");
        fclose(list1_fp);
        continue;
      }

      if (cmdMatches("TYPE") || cmdMatches("T")) {
        list1_fp = fSafeOpen(g_fullArg[1], "r", 0/*noVersioningFlag*/);
        if (!list1_fp) continue; /* Couldn't open it (error msg was provided) */
        if (g_rawArgs == 2) {
          n = 10;
        } else {
          if (((vstring)(g_fullArg[2]))[0] == 'A' ||
              ((vstring)(g_fullArg[2]))[0] == 'a') { /* ALL */
            n = -1;
          } else {
            n = (long)val(g_fullArg[2]);
          }
        }
        for (i = 0; i < n || n == -1; i++) {
          if (!linput(list1_fp, NULL, &str1)) break;
          if (!print2("%s\n", str1)) break;
        }
        fclose(list1_fp);
        continue;
      } /* end TYPE */

      if (cmdMatches("UPDATE")) {
        list1_fp = fSafeOpen(g_fullArg[1], "r", 0/*noVersioningFlag*/);
        list2_fp = fSafeOpen(g_fullArg[2], "r", 0/*noVersioningFlag*/);
        if (!list1_fp) continue; /* Couldn't open it (error msg was provided) */
        if (!list2_fp) continue; /* Couldn't open it (error msg was provided) */
        if (!getRevision(g_fullArg[4])) {
          print2(
"?The revision tag must be of the form /*nn*/ or /*#nn*/.  Please try again.\n");
          continue;
        }
        let(&list3_ftmpname, "");
        list3_ftmpname = fGetTmpName("zz~tools");
        list3_fp = fSafeOpen(list3_ftmpname, "w", 0/*noVersioningFlag*/);
        if (!list3_fp) continue; /* Couldn't open it (error msg was provided) */

        revise(list1_fp, list2_fp, list3_fp, g_fullArg[4],
            (long)val(g_fullArg[5]));

        fSafeRename(list3_ftmpname, g_fullArg[3]);
        continue;
      }

      if (cmdMatches("COPY") || cmdMatches("C")) {
        let(&list2_ftmpname, "");
        list2_ftmpname = fGetTmpName("zz~tools");
        list2_fp = fSafeOpen(list2_ftmpname, "w", 0/*noVersioningFlag*/);
        if (!list2_fp) continue; /* Couldn't open it (error msg was provided) */
        let(&str4, cat(g_fullArg[1], ",", NULL));
        lines = 0;
        j = 0; /* Error flag */
        while (1) {
          if (!str4[0]) break; /* Done scanning list */
          p = instr(1, str4, ",");
          let(&str3, left(str4, p - 1));
          let(&str4, right(str4, p + 1));
          list1_fp = fSafeOpen((str3), "r", 0/*noVersioningFlag*/);
          if (!list1_fp) { /* Couldn't open it (error msg was provided) */
            j = 1; /* Error flag */
            break;
          }
          n = 0;
          while (linput(list1_fp, NULL, &str1)) {
            lines++; n++;
            fprintf(list2_fp, "%s\n", str1);
          }
          if (instr(1, g_fullArg[1], ",")) { /* More than 1 input file */
            print2("The input file \"%s\" has %ld lines.\n", str3, n);
          }
          fclose(list1_fp);
        }
        if (j) continue; /* One of the input files couldn't be opened */
        fclose(list2_fp);
        print2("The output file \"%s\" has %ld lines.\n", g_fullArg[2], lines);
        fSafeRename(list2_ftmpname, g_fullArg[2]);
        continue;
      }

      print2("?This command has not been implemented yet.\n");
      continue;
    } /* End of g_toolsMode-specific commands */

    if (cmdMatches("TOOLS")) {
      print2(
"Entering the Text Tools utilities.  Type HELP for help, EXIT to exit.\n");
      g_toolsMode = 1;
      continue;
    }

    if (cmdMatches("READ")) {
      /*if (g_statements) {*/
      /* 31-Dec-2017 nm */
      /* We can't use 'statements > 0' for the test since the source
         could be just a comment */
      if (g_sourceHasBeenRead == 1) {
        printLongLine(cat(
            "?Sorry, reading of more than one source file is not allowed.  ",
            "The file \"", g_input_fn, "\" has already been READ in.  ",
                                                             /* 11-Dec-05 nm */
            "You may type ERASE to start over.  Note that additional source ",
        "files may be included in the source file with \"$[ <filename> $]\".",
                                                             /* 11-Dec-05 nm */
            NULL),"  "," ");
        continue;
      }

      let(&g_input_fn, g_fullArg[1]);

      /***** 3-Jan-2017 nm  This is now done with SET ROOT_DIRECTORY
      /@ 31-Dec-2017 nm - Added ROOT_DIRECTORY switch @/
      /@ TODO - remove this and just use SET ROOT_DIRECTORY instead @/
      i = switchPos("/ ROOT_DIRECTORY"); /@ Statement match to skip @/
      if (i != 0) {
        let(&g_rootDirectory, edit(g_fullArg[i + 1], 2/@discard spaces,tabs@/));
        if (g_rootDirectory[0] != 0) {  /@ Not an empty directory path @/
          /@ Add trailing "/" to g_rootDirectory if missing @/
          if (instr(1, g_rootDirectory, "\\") != 0
              || instr(1, g_input_fn, "\\") != 0 ) {
            /@ Using Windows-style path (not really supported, but at least
               make full path consistent) @/
            if (g_rootDirectory[strlen(g_rootDirectory) - 1] != '\\') {
              let(&g_rootDirectory, cat(g_rootDirectory, "\\", NULL));
            }
          } else {
            if (g_rootDirectory[strlen(g_rootDirectory) - 1] != '/') {
              let(&g_rootDirectory, cat(g_rootDirectory, "/", NULL));
            }
          }
        }
      /@
      } else {
        let(&g_rootDirectory, "");
      @/
      }
      */

      let(&str1, cat(g_rootDirectory, g_input_fn, NULL));
      g_input_fp = fSafeOpen(str1, "r", 0/*noVersioningFlag*/);
      if (!g_input_fp) continue; /* Couldn't open it (error msg was provided
                                     by fSafeOpen) */
      fclose(g_input_fp);

      readInput();
      /*g_sourceHasBeenRead = 1;*/ /* Global variable - set in readInput() */

      if (switchPos("/ VERIFY")) {
        verifyProofs("*",1); /* Parse and verify */
      } else {
        /* verifyProofs("*",0); */ /* Parse only (for gross error checking) */
      }

      /* 13-Dec-2016 nm Moved to verifyMarkup in mmcmds.c */
      /*
      /@ 10/21/02 - detect Microsoft bugs reported by several users, when the
         HTML output files are named "con.html" etc. @/
      /@ If we want a standard error message underlining token, this could go
         in mmpars.c @/
      /@ From Microsoft's site:
         "The following reserved words cannot be used as the name of a file:
         CON, PRN, AUX, CLOCK$, NUL, COM1, COM2, COM3, COM4, COM5, COM6, COM7,
         COM8, COM9, LPT1, LPT2, LPT3, LPT4, LPT5, LPT6, LPT7, LPT8, and LPT9.
         Also, reserved words followed by an extension - for example,
         NUL.tx7 - are invalid file names." @/
      /@ Check for labels that will lead to illegal Microsoft file names for
         Windows users.  Don't bother checking CLOCK$ since $ is already
         illegal @/
      let(&str1, cat(
         ",CON,PRN,AUX,NUL,COM1,COM2,COM3,COM4,COM5,COM6,COM7,",
         "COM8,COM9,LPT1,LPT2,LPT3,LPT4,LPT5,LPT6,LPT7,LPT8,LPT9,", NULL));
      for (i = 1; i <= g_statements; i++) {
        let(&str2, cat(",", edit(g_Statement[i].labelName, 32/@uppercase@/), ",",
            NULL));
        if (instr(1, str1, str2) ||
            /@ 5-Jan-04 mm@.html is reserved for mmtheorems.html, etc. @/
            !strcmp(",MM", left(str2, 3))) {
          print2("\n");
          assignStmtFileAndLineNum(j); /@ 9-Jan-2018 nm @/
          printLongLine(cat("?Warning in statement \"",
              g_Statement[i].labelName, "\" at line ",
              str((double)(g_Statement[i].lineNum)),
              " in file \"", g_Statement[i].fileName,
              "\".  To workaround a Microsoft operating system limitation, the",
              " the following reserved words cannot be used for label names:",
              " CON, PRN, AUX, CLOCK$, NUL, COM1, COM2, COM3, COM4, COM5,",
              " COM6, COM7, COM8, COM9, LPT1, LPT2, LPT3, LPT4, LPT5, LPT6,",
              " LPT7, LPT8, and LPT9.  Also, \"mm@.html\" is reserved for",
              " Metamath file names.  Use another name for this label.", NULL),
              "", " ");
          g_errorCount++;
        }
      }
      /@ 10/21/02 end @/
      */

      if (g_sourceHasBeenRead == 1) {  /* 9-Jan-2018 nm */
        if (!g_errorCount) {
          let(&str1, "No errors were found.");
          if (!switchPos("/ VERIFY")) {
              let(&str1, cat(str1,
         "  However, proofs were not checked.  Type VERIFY PROOF *",
         " if you want to check them.",
              NULL));
          }
          printLongLine(str1, "", " ");
        } else {
          print2("\n");
          if (g_errorCount == 1) {
            print2("One error was found.\n");
          } else {
            print2("%ld errors were found.\n", (long)g_errorCount);
          }
        }
      } /* g_sourceHasBeenRead == 1 */

      continue;
    }

    if (cmdMatches("WRITE SOURCE")) {
      let(&g_output_fn, g_fullArg[2]);

      /********* Deleted 28-Dec-2013 nm Now opened in writeSource()
      g_output_fp = fSafeOpen(g_output_fn, "w", 0/@noVersioningFlag@/);
      if (!g_output_fp) continue; /@ Couldn't open it (error msg was provided)@/
      ********/

      /******* Deleted 3-May-2017 nm
      /@ Added 24-Oct-03 nm @/
      if (switchPos("/ CLEAN") > 0) {
        c = 1; /@ Clean out any proof-in-progress (that user has flagged
                        with a ? in its date comment field) @/
      } else {
        c = 0; /@ Output all proofs (normal) @/
      }
      *******/

      /********* Deleted 3-May-2017 nm
      writeSource((char)c, (char)r); /@ Added arg 24-Oct-03 nm 12-Jun-2011 nm @/
      fclose(g_output_fp);
      if (c == 0) g_sourceChanged = 0; /@ Don't unset flag if CLEAN option
                                    since some new proofs may not be saved. @/
      ***********/


      /***** 3-Jan-2017 nm  This is now done with SET ROOT_DIRECTORY
      /@ 31-Dec-2017 nm - Added ROOT_DIRECTORY switch @/
      /@ TODO - remove this qualifier; too confusing.
         Use SET ROOT_DIRECTORY instead. @/
      i = switchPos("/ ROOT_DIRECTORY"); /@ Statement match to skip @/
      if (i != 0) {
        let(&g_rootDirectory, edit(g_fullArg[i + 1], 2/@discard spaces,tabs@/));
        /@ Add trailing "/" to g_rootDirectory if missing @/
        if (instr(1, g_rootDirectory, "\\") != 0
            || instr(1, g_input_fn, "\\") != 0 ) {
          /@ Using Windows-style path (not really supported, but at least
             make full path consistent) @/
          if (g_rootDirectory[strlen(g_rootDirectory) - 1] != '\\') {
            let(&g_rootDirectory, cat(g_rootDirectory, "\\", NULL));
          }
        } else {
          if (g_rootDirectory[strlen(g_rootDirectory) - 1] != '/') {
            let(&g_rootDirectory, cat(g_rootDirectory, "/", NULL));
          }
        }
      /@
      } else {
        let(&g_rootDirectory, "");
      @/
      }
      */

      /* Added 12-Jun-2011 nm */
      if (switchPos("/ REWRAP") > 0) {
        r = 2; /* Re-wrap then format (more aggressive than / FORMAT) */
      } else if (switchPos("/ FORMAT") > 0) {
        r = 1; /* Format output according to set.mm standard */
      } else {
        r = 0; /* Keep formatting as-is */
      }

      /* 24-Aug-2020 nm */
      i = switchPos("/ EXTRACT");
      if (i > 0) {
        let(&str1, g_fullArg[i + 1]); /* List of labels */
        if (r > 0
            || switchPos("/ SPLIT") > 0
            || switchPos("/ KEEP_INCLUDES") > 0) {
          print2(
"?You may not use / SPLIT, / REWRAP, or / KEEP_INCLUDES with / EXTRACT.\n");
          continue;
        }
      } else {
        let(&str1, ""); /* Empty string means full db */
      }

      /* 3-May-2017 nm */
      writeSource((char)r, /* Rewrap type */ /* Added arg 12-Jun-2011 nm */
        ((switchPos("/ SPLIT") > 0) ? 1 : 0),          /* 31-Dec-2017 nm */
        ((switchPos("/ NO_VERSIONING") > 0) ? 1 : 0),  /* 31-Dec-2017 nm */
        ((switchPos("/ KEEP_INCLUDES") > 0) ? 1 : 0),   /* 31-Dec-2017 nm */
        str1 /* Label list to extract */  /* 24-Aug-2020 nm */
          );
      /*fclose(g_output_fp);*/
      g_sourceChanged = 0;

      let(&str1, ""); /* Deallocate */ /* 24-Aug-2020 nm */
      continue;
    } /* End of WRITE SOURCE */


    if (cmdMatches("WRITE THEOREM_LIST")) {
      /* 4-Dec-03 - Write out an HTML summary of the theorems to
         mmtheorems.html, mmtheorems1.html,... */
      /* THEOREMS_PER_PAGE is the default number of proof descriptions to output. */
#define THEOREMS_PER_PAGE 100
      /* theoremsPerPage is the actual number of proof descriptions to output. */
      /* See if the user overrode the default. */
      i = switchPos("/ THEOREMS_PER_PAGE");
      if (i != 0) {
        theoremsPerPage = (long)val(g_fullArg[i + 1]); /* Use user's value */
      } else {
        theoremsPerPage = THEOREMS_PER_PAGE; /* Use the default value */
      }
      showLemmas = (switchPos("/ SHOW_LEMMAS") != 0);
      noVersioning = (switchPos("/ NO_VERSIONING") != 0);

      /**** 17-Nov-2015 nm Deleted, no longer need this restriction
      if (!g_texDefsRead) {
        g_htmlFlag = 1;
        print2("Reading definitions from $t statement of %s...\n", g_input_fn);
        if (2/@error@/ == readTexDefs(0 /@ 1 = check errors only @/,
            0 /@ 1 = no GIF file existence check @/ )) {
          continue; /@ An error occurred @/
        }
      } else {
        /@ Current limitation - can only read def's from .mm file once @/
        if (!g_htmlFlag) {
          print2("?You cannot use both LaTeX and HTML in the same session.\n");
          print2(
              "You must EXIT and restart Metamath to switch to the other.\n");
          continue;
        }
      }
      *** end of 17-Nov-2015 deletion */

      g_htmlFlag = 1;
      /* If not specified, for backwards compatibility in scripts
         leave g_altHtmlFlag at current value */
      if (switchPos("/ HTML") != 0) {
        if (switchPos("/ ALT_HTML") != 0) {
          print2("?Please specify only one of / HTML and / ALT_HTML.\n");
          continue;
        }
        g_altHtmlFlag = 0;
      } else {
        if (switchPos("/ ALT_HTML") != 0) g_altHtmlFlag = 1;
      }

      if (2/*error*/ == readTexDefs(0 /* 1 = check errors only */,
          0 /* 1 = no GIF file existence check */ )) {
        continue; /* An error occurred */
      }

      /* Output the theorem list */
      writeTheoremList(theoremsPerPage, showLemmas,
          noVersioning); /* (located in mmwtex.c) */
      continue;
    }  /* End of "WRITE THEOREM_LIST" */


    if (cmdMatches("WRITE BIBLIOGRAPHY")) {
      /* 10/10/02 */
      /* This command builds the bibliographical cross-references to various
         textbooks and updates the user-specified file normally called
         mmbiblio.html. */

      /* 17-Nov-2015 nm code moved to writeBibliography() in mmwtex.c */
      /* (Also called by verifyMarkup() in mmcmds.c for error checking) */
      writeBibliography(g_fullArg[2],
          "*", /* labelMatch - all labels */
          0,  /* 1 = no output, just warning msgs if any */
          0); /* 1 = ignore missing external files (gifs, bib, etc.) */
      continue;
    }  /* End of "WRITE BIBLIOGRAPHY" */


    if (cmdMatches("WRITE RECENT_ADDITIONS")) {
      /* 18-Sep-03 -
         This utility creates a list of recent proof descriptions and updates
         the user-specified file normally called mmrecent.html.
       */

      /* RECENT_COUNT is the default number of proof descriptions to output. */
#define RECENT_COUNT 100
      /* i is the actual number of proof descriptions to output. */
      /* See if the user overrode the default. */
      i = switchPos("/ LIMIT");
      if (i) {
        i = (long)val(g_fullArg[i + 1]); /* Use user's value */
      } else {
        i = RECENT_COUNT; /* Use the default value */
      }

      /**** 17-Nov-2015 nm Deleted because readTeXDefs() now handles everything
      if (!g_texDefsRead) {
        g_htmlFlag = 1;
        print2("Reading definitions from $t statement of %s...\n", g_input_fn);
        if (2/@error@/ == readTexDefs(0 /@ 1 = check errors only @/,
            0 /@ 1 = no GIF file existence check @/  )) {
          continue; /@ An error occurred @/
        }
      } else {
        /@ Current limitation - can only read def's from .mm file once @/
        if (!g_htmlFlag) {
          print2("?You cannot use both LaTeX and HTML in the same session.\n");
          print2(
              "You must EXIT and restart Metamath to switch to the other.\n");
          continue;
        }
      }
      **** end of 17-Nov-2015 deletion */

      g_htmlFlag = 1;
      /* If not specified, for backwards compatibility in scripts
         leave g_altHtmlFlag at current value */
      if (switchPos("/ HTML") != 0) {
        if (switchPos("/ ALT_HTML") != 0) {
          print2("?Please specify only one of / HTML and / ALT_HTML.\n");
          continue;
        }
        g_altHtmlFlag = 0;
      } else {
        if (switchPos("/ ALT_HTML") != 0) g_altHtmlFlag = 1;
      }

      /* readTexDefs() rereads based on changed in g_htmlFlag, g_altHtmlFlag */
      if (2/*error*/ == readTexDefs(0 /* 1 = check errors only */,
          0 /* 1 = no GIF file existence check */  )) {
        continue; /* An error occurred */
      }

      tmpFlag = 0; /* Error flag to recover input file */
      list1_fp = fSafeOpen(g_fullArg[2], "r", 0/*noVersioningFlag*/);
      if (list1_fp == NULL) {
        /* Couldn't open it (error msg was provided)*/
        continue;
      }
      fclose(list1_fp);
      /* This will rename the input mmrecent.html as mmrecent.html~1 */
      list2_fp = fSafeOpen(g_fullArg[2], "w", 0/*noVersioningFlag*/);
      if (list2_fp == NULL) {
          /* Couldn't open it (error msg was provided)*/
        continue;
      }
      /* Note: in older versions the "~1" string was OS-dependent, but we
         don't support VAX or THINK C anymore...  Anyway we reopen it
         here with the renamed file in case the OS won't let us rename
         an opened file during the fSafeOpen for write above. */
      list1_fp = fSafeOpen(cat(g_fullArg[2], "~1", NULL), "r",
          0/*noVersioningFlag*/);
      if (list1_fp == NULL) bug(1117);

      /* Transfer the input file up to the special "<!-- #START# -->" comment */
      while (1) {
        if (!linput(list1_fp, NULL, &str1)) {
          print2(
"?Error: Could not find \"<!-- #START# -->\" line in input file \"%s\".\n",
              g_fullArg[2]);
          tmpFlag = 1; /* Error flag to recover input file */
          break;
        }
        /* 13-May-04 nm Put in "last updated" stamp */
        if (!strcmp(left(str1, 21), "<!-- last updated -->")) {
          let(&str1, cat(left(str1, 21), " <I>Last updated on ", date(),
          /* ??Future: make "EDT"/"EST" or other automatic */
          /*  " at ", time_(), " EST.</I>", NULL)); */
          /* 10-Nov-04 nm Just make it "ET" for "Eastern Time" */
            " at ", time_(), " ET.</I>", NULL));
        }
        fprintf(list2_fp, "%s\n", str1);
        if (!strcmp(str1, "<!-- #START# -->")) break;
      }
      if (tmpFlag) goto wrrecent_error;

      /* Get and parse today's date */
      parseDate(date(), &k /*dd*/, &l /*mmm*/, &m /*yyyy*/); /* 4-Nov-2015 */

      /************ Deleted 4-Nov-2015
      let(&str1, date());
      j = instr(1, str1, "-");
      k = (long)val(left(str1, j - 1)); /@ Day @/
#define MONTHS "JanFebMarAprMayJunJulAugSepOctNovDec"
      l = ((instr(1, MONTHS, mid(str1, j + 1, 3)) - 1) / 3) + 1; /@ 1 = Jan @/
      m = str1[j + 6]; /@ Character after 2-digit year @/  /@ nm 10-Apr-06 @/
      if (m == ' ' || m == ']') {                          /@ nm 10-Apr-06 @/
        /@ Handle 2-digit year @/
        m = (long)val(mid(str1, j + 5, 2));  /@ Year @/
#define START_YEAR 93 /@ Earliest 19xx year in set.mm database @/
        if (m < START_YEAR) {
          m = m + 2000;
        } else {
          m = m + 1900;
        }
      } else {                                            /@ nm 10-Apr-06 @/
        /@ Handle 4-digit year @/                         /@ nm 10-Apr-06 @/
        m = (long)val(mid(str1, j + 5, 4));  /@ Year @/         /@ nm 10-Apr-06 @/
      }                                                   /@ nm 10-Apr-06 @/
      *********** end of 4-Nov-2015 deletion */

#define START_YEAR 93 /* Earliest 19xx year in set.mm database */
      n = 0; /* Count of how many output so far */
      while (n < i /*RECENT_COUNT*/ && m > START_YEAR + 1900 - 1) {

        /* Build date string to match */
        /* 4-Nov-2015 nm */
        /*buildDate(k, l, m, &str5);*/
        buildDate(k, l, m, &str1); /* 2-May-2017 nm */

        /***** Deleted 2-May-2017 nm - we no longer match date below proof
        let(&str5, cat("$([", str5, "]$)", NULL));
        /@ 2-digit year is obsolete, but keep for backwards compatibility @/
        let(&str1, cat(left(str5, (long)strlen(str5) - 7),
            right(str5, (long)strlen(str5) - 4), NULL));
        *******/

        /************ Deleted 4-Nov-2015
#define MONTHS "JanFebMarAprMayJunJulAugSepOctNovDec"
        /@ Match for 2-digit year OBSOLETE @/
        let(&str1, cat("$([", str((double)k), "-", mid(MONTHS, 3 @ l - 2, 3), "-",
            right(str((double)m), 3), "]$)", NULL));
        /@ Match for 4-digit year @/  /@ nm 10-Apr-06 @/
        let(&str5, cat("$([", str((double)k), "-", mid(MONTHS, 3 @ l - 2, 3), "-",
            str((double)m), "]$)", NULL));
        *********** end of 4-Nov-2015 deletion */

        for (stmt = g_statements; stmt >= 1; stmt--) {

          if (g_Statement[stmt].type != (char)p_
                && g_Statement[stmt].type != (char)a_) {
            continue;
          }

          /******** Deleted 2-May-2017 nm
          /@ Get the comment section after the statement @/
          let(&str2, space(g_Statement[stmt + 1].labelSectionLen));
          memcpy(str2, g_Statement[stmt + 1].labelSectionPtr,
              (size_t)(g_Statement[stmt + 1].labelSectionLen));
          p = instr(1, str2, "$)");
          let(&str2, left(str2, p + 1)); /@ Get 1st comment (if any) @/
          let(&str2, edit(str2, 2)); /@ Discard spaces @/
          ******** end of 2-May-2017 deletion */
          /* 2-May-2017 nm */
          /******* Deleted 3-May-2017 nm
          /@ In the call below, str3 is a dummy variable for a placeholder
             (its value will be undefined because of multiple occurrences) @/
          getContrib(stmt/@stmtNum@/, &str3, &str3, &str3, &str3, &str3, &str3,
              &str2, /@mostRecentDate@/
              0, /@printErrorsFlag@/
              1); /@normal mode@/
          **********/
          /* 3-May-2017 nm */
          let(&str2, "");
          str2 = getContrib(stmt/*stmtNum*/, MOST_RECENT_DATE);

          /* See if the date comment matches */
          /*if (instr(1, str2, str1) || instr(1, str2, str5)) {*/ /* 10-Apr-06 */
          if (!strcmp(str2, str1)) { /* 2-May-2017 nm */
            /* We have a match, so increment the match count */
            n++;
            let(&str3, "");
            str3 = getDescription(stmt);
            let(&str4, "");
            str4 = pinkHTML(stmt); /* Get little pink number */
            /* Output the description comment */
            /* Break up long lines for text editors with printLongLine */
            let(&g_printString, "");
            g_outputToString = 1;
            print2("\n"); /* Blank line for HTML human readability */
            printLongLine(cat(

                /*
                (stmt < g_extHtmlStmt) ?
                     "<TR>" :
                     cat("<TR BGCOLOR=", PURPLISH_BIBLIO_COLOR, ">", NULL),
                */

                /* 29-Jul-2008 nm Sandbox stuff */
                (stmt < g_extHtmlStmt)
                   ? "<TR>"
                   : (stmt < g_mathboxStmt)
                       ? cat("<TR BGCOLOR=", PURPLISH_BIBLIO_COLOR, ">",
                           NULL)
                       : cat("<TR BGCOLOR=", SANDBOX_COLOR, ">", NULL),

                "<TD NOWRAP>",  /* IE breaks up the date */
                /* mid(str1, 4, (long)strlen(str1) - 6), */ /* Date */
                /* Use 4-digit year */   /* 10-Apr-06 */
                /* mid(str5, 4, (long)strlen(str5) - 6), */ /* Date */ /* 10-Apr-06 */
                str2, /* Date */ /* 2-May-2017 nm */
                "</TD><TD ALIGN=CENTER><A HREF=\"",
                g_Statement[stmt].labelName, ".html\">",
                g_Statement[stmt].labelName, "</A>",
                str4, "</TD><TD ALIGN=LEFT>", NULL),  /* Description */
                            /* 28-Dec-05 nm Added ALIGN=LEFT for IE */
              " ",  /* Start continuation line with space */
              "\""); /* Don't break inside quotes e.g. "Arial Narrow" */

            g_showStatement = stmt; /* For printTexComment */
            g_outputToString = 0; /* For printTexComment */
            g_texFilePtr = list2_fp;
            /* 18-Sep-03 ???Future - make this just return a string??? */
            /* printTexComment(str3, 0); */
            /* 17-Nov-2015 nm Added 3rd & 4th arguments */
            printTexComment(str3,              /* Sends result to g_texFilePtr */
                0, /* 1 = htmlCenterFlag */
                PROCESS_EVERYTHING, /* actionBits */ /* 13-Dec-2018 nm */
                0  /* 1 = noFileCheck */ );
            g_texFilePtr = NULL;
            g_outputToString = 1; /* Restore after printTexComment */

            /* Get HTML hypotheses => assertion */
            let(&str4, "");
            str4 = getTexOrHtmlHypAndAssertion(stmt); /* In mmwtex.c */
            printLongLine(cat("</TD></TR><TR",

                  /*
                  (s < g_extHtmlStmt) ?
                       ">" :
                       cat(" BGCOLOR=", PURPLISH_BIBLIO_COLOR, ">", NULL),
                  */

                  /* 29-Jul-2008 nm Sandbox stuff */
                  (stmt < g_extHtmlStmt)
                     ? ">"
                     : (stmt < g_mathboxStmt)
                         ? cat(" BGCOLOR=", PURPLISH_BIBLIO_COLOR, ">",
                             NULL)
                         : cat(" BGCOLOR=", SANDBOX_COLOR, ">", NULL),

                /*** old
                "<TD BGCOLOR=white>&nbsp;</TD><TD COLSPAN=2 ALIGN=CENTER>",
                str4, "</TD></TR>", NULL),
                ****/
                /* 27-Oct-03 nm */
                "<TD COLSPAN=3 ALIGN=CENTER>",
                str4, "</TD></TR>", NULL),

                " ",  /* Start continuation line with space */
                "\""); /* Don't break inside quotes e.g. "Arial Narrow" */

            g_outputToString = 0;
            fprintf(list2_fp, "%s", g_printString);
            let(&g_printString, "");

            if (n >= i /*RECENT_COUNT*/) break; /* We're done */

            /* 27-Oct-03 nm Put separator row if not last theorem */
            g_outputToString = 1;
            printLongLine(cat("<TR BGCOLOR=white><TD COLSPAN=3>",
                "<FONT SIZE=-3>&nbsp;</FONT></TD></TR>", NULL),
                " ",  /* Start continuation line with space */
                "\""); /* Don't break inside quotes e.g. "Arial Narrow" */

            /* 29-Jul-04 nm Put the previous, current, and next statement
               labels in HTML comments so a script can use them to update
               web site incrementally.  This would be done by searching
               for "For script" and gather label between = and --> then
               regenerate just those statements.  Previous and next labels
               are included to prevent dead links if they don't exist yet. */
            /* This section can be deleted without side effects */
            /* Find the previous statement with a web page */
            j = 0;
            for (q = stmt - 1; q >= 1; q--) {
              if (g_Statement[q].type == (char)p_ ||
                  g_Statement[q].type == (char)a_ ) {
                j = q;
                break;
              }
            }
            /* 13-Dec-2018 nm This isn't used anywhere yet.  But fix error
               in current label and also identify previous, current, next */
            if (j) print2("<!-- For script: previous = %s -->\n",
                g_Statement[j].labelName);
            /* Current statement */
            print2("<!-- For script: current = %s -->\n",
                g_Statement[stmt].labelName);
            /* Find the next statement with a web page */
            j = 0;
            for (q = stmt + 1; q <= g_statements; q++) {
              if (g_Statement[q].type == (char)p_ ||
                  g_Statement[q].type == (char)a_ ) {
                j = q;
                break;
              }
            }
            if (j) print2("<!-- For script: next = %s -->\n",
                g_Statement[j].labelName);
            /* End of 29-Jul-04 section */

            g_outputToString = 0;
            fprintf(list2_fp, "%s", g_printString);
            let(&g_printString, "");

          }
        } /* Next stmt - statement number */
        /* Decrement date */
        if (k > 1) {
          k--; /* Decrement day */
        } else {
          k = 31; /* Non-existent day 31's will never match, which is OK */
          if (l > 1) {
            l--; /* Decrement month */
          } else {
            l = 12; /* Dec */
            m --; /* Decrement year */
          }
        }
      } /* next while - Scan next date */


      /* Discard the input file up to the special "<!-- #END# -->" comment */
      while (1) {
        if (!linput(list1_fp, NULL, &str1)) {
          print2(
"?Error: Could not find \"<!-- #END# -->\" line in input file \"%s\".\n",
              g_fullArg[2]);
          tmpFlag = 1; /* Error flag to recover input file */
          break;
        }
        if (!strcmp(str1, "<!-- #END# -->")) {
          fprintf(list2_fp, "%s\n", str1);
          break;
        }
      }
      if (tmpFlag) goto wrrecent_error;

      /* Transfer the rest of the input file */
      while (1) {
        if (!linput(list1_fp, NULL, &str1)) {
          break;
        }

        /* Update the date stamp at the bottom of the HTML page. */
        /* This is just a nicety; no error check is done. */
        if (!strcmp("This page was last updated on ", left(str1, 30))) {
          let(&str1, cat(left(str1, 30), date(), ".", NULL));
        }

        fprintf(list2_fp, "%s\n", str1);
      }

      print2("The %ld most recent theorem(s) were written.\n", n);

     wrrecent_error:
      fclose(list1_fp);
      fclose(list2_fp);
      if (tmpFlag) {
        /* Recover input files in case of error */
        remove(g_fullArg[2]);  /* Delete output file */
        rename(cat(g_fullArg[2], "~1", NULL), g_fullArg[2]);
            /* Restore input file name */
        print2("?The file \"%s\" was not modified.\n", g_fullArg[2]);
      }
      continue;
    }  /* End of "WRITE RECENT_ADDITIONS" */


    if (cmdMatches("SHOW LABELS")) {
      linearFlag = 0;
      if (switchPos("/ LINEAR")) linearFlag = 1;
      if (switchPos("/ ALL")) {
        m = 1;  /* Include $e, $f statements */
        print2(
"The labels that match are shown with statement number, label, and type.\n");
      } else {
        m = 0;  /* Show $a, $p only */
        print2(
"The assertions that match are shown with statement number, label, and type.\n");
      }
      j = 0;
      k = 0;
      let(&str2, ""); /* Line so far */
#define COL 20 /* Column width */
#define MIN_SPACE 2 /* At least this many spaces between columns */
      for (i = 1; i <= g_statements; i++) {
        if (!g_Statement[i].labelName[0]) continue; /* No label */
        if (!m && g_Statement[i].type != (char)p_ &&
            g_Statement[i].type != (char)a_) continue; /* No /ALL switch */
        /* 30-Jan-06 nm Added single-character-match wildcard argument */
        if (!matchesList(g_Statement[i].labelName, g_fullArg[2], '*', '?')) {
          continue;
        }

        /* 2-Oct-2015 nm */
        let(&str1, cat(str((double)i), " ",
            g_Statement[i].labelName, " $", chr(g_Statement[i].type),
            NULL));
        if (!str2[0]) {
          j = 0; /* # of fields on line so far */
        }
        k = ((long)strlen(str2) + MIN_SPACE > j * COL)
            ? (long)strlen(str2) + MIN_SPACE : j * COL;
                /* Position before new str1 starts */
        if (k + (long)strlen(str1) > g_screenWidth || linearFlag) {
          if (j == 0) {
            /* In case of huge label, force it out anyway */
            printLongLine(str1, "", " ");
          } else {
            /* Line width exceeded, postpone adding str1 */
            print2("%s\n", str2);
            let(&str2, str1);
            j = 1;
          }
        } else {
          /* Add new field to line */
          if (j == 0) {
            let(&str2, str1); /* Don't put space before 1st label on line */
          } else {
            let(&str2, cat(str2, space(k - (long)strlen(str2)), str1, NULL));
          }
          j++;
        }

        /* 2-Oct-2015 nm Deleted
        let(&str1,cat(str((double)i)," ",
            g_Statement[i].labelName," $",chr(g_Statement[i].type)," ",NULL));
#define COL 19 /@ Characters per column @/
        if (j + (long)strlen(str1) > MAX_LEN
            || (linearFlag && j != 0)) { /@ j != 0 to suppress 1st CR @/
          print2("\n");
          j = 0;
          k = 0;
        }
        if (strlen(str1) > COL || linearFlag) {
          j = j + (long)strlen(str1);
          k = k + (long)strlen(str1) - COL;
          print2(str1);
        } else {
          if (k == 0) {
            j = j + COL;
            print2("%s%s",str1,space(COL - (long)strlen(str1)));
          } else {
            k = k - (COL - (long)strlen(str1));
            if (k > 0) {
              print2(str1);
              j = j + (long)strlen(str1);
            } else {
              print2("%s%s",str1,space(COL - (long)strlen(str1)));
              j = j + COL;
              k = 0;
            }
          }
        }
        */

      } /* next i */
      /* print2("\n"); */
      if (str2[0]) {
        print2("%s\n", str2);
        let(&str2, "");
      }
      let(&str1, "");
      continue;
    }

    if (cmdMatches("SHOW DISCOURAGED")) {  /* was SHOW RESTRICTED */
      showDiscouraged(); /* In mmcmds.c */
      continue;
    }

    if (cmdMatches("SHOW SOURCE")) {

      /* 14-Sep-2012 nm */
      /* Currently, SHOW SOURCE only handles one statement at a time,
         so use getStatementNum().  Eventually, SHOW SOURCE may become
         obsolete; I don't think anyone uses it. */
      s = getStatementNum(g_fullArg[2],
          1/*startStmt*/,
          g_statements + 1  /*maxStmt*/,
          1/*aAllowed*/,
          1/*pAllowed*/,
          1/*eAllowed*/,
          1/*fAllowed*/,
          0/*efOnlyForMaxStmt*/,
          1/*uniqueFlag*/);
      if (s == -1) {
        continue; /* Error msg was provided */
      }
      g_showStatement = s; /* Update for future defaults */

      /*********** 14-Sep-2012 replaced by getStatementNum()
      for (i = 1; i <= g_statements; i++) {
        if (!strcmp(g_fullArg[2],g_Statement[i].labelName)) break;
      }
      if (i > g_statements) {
        printLongLine(cat("?There is no statement with label \"",
            g_fullArg[2], "\".  ",
            "Use SHOW LABELS for a list of valid labels.", NULL), "", " ");
        g_showStatement = 0;
        continue;
      }
      g_showStatement = i;
      ************** end 14-Sep-2012 *******/

      let(&str1, "");
      str1 = outputStatement(g_showStatement, /*0, 3-May-2017 */ /* cleanFlag */
          0 /* reformatFlag */);
      let(&str1,edit(str1,128)); /* Trim trailing spaces */
      if (str1[strlen(str1)-1] == '\n') let(&str1, left(str1,
          (long)strlen(str1) - 1));
      printLongLine(str1, "", "");
      let(&str1,""); /* Deallocate vstring */
      continue;
    } /* if (cmdMatches("SHOW SOURCE")) */


    if (cmdMatches("SHOW STATEMENT") && (
        switchPos("/ HTML")
        || switchPos("/ BRIEF_HTML")
        || switchPos("/ ALT_HTML")
        || switchPos("/ BRIEF_ALT_HTML"))) {
      /* Special processing for the / HTML qualifiers - for each matching
         statement, a .html file is opened, the statement is output,
         and depending on statement type a proof or other information
         is output. */
      /* if (g_rawArgs != 5) { */  /* obsolete */

      /* 16-Aug-2016 nm */
      noVersioning = (switchPos("/ NO_VERSIONING") != 0);
      i = 5;  /* # arguments with only / HTML or / ALT_HTML */
      if (noVersioning) i = i + 2;
      if (switchPos("/ TIME")) i = i + 2;
      if (g_rawArgs != i) {
        printLongLine(cat("?The HTML qualifiers may not be combined with",
            " others except / NO_VERSIONING and / TIME.\n", NULL), "  ", " ");
        continue;
      }

      /* 16-Aug-2016 nm */
      printTime = 0;
      if (switchPos("/ TIME") != 0) {
        printTime = 1;
      }

      /*** 17-Nov-2014 nm This restriction has been removed.
      if (g_texDefsRead) {
        /@ Current limitation - can only read def's from .mm file once @/
        if (!g_htmlFlag) {
          print2("?You cannot use both LaTeX and HTML in the same session.\n");
          print2(
              "You must EXIT and restart Metamath to switch to the other.\n");
          goto htmlDone;
        } else {
          if ((switchPos("/ ALT_HTML") || switchPos("/ BRIEF_ALT_HTML"))
              == (g_altHtmlFlag == 0)) {
            print2(
              "?You cannot use both HTML and ALT_HTML in the same session.\n");
            print2(
              "You must EXIT and restart Metamath to switch to the other.\n");
            goto htmlDone;
          }
        }
      }
      *** End 17-Nov-2014 deletion */

      g_htmlFlag = 1; /* 17-Nov-2015 nm */

      if (switchPos("/ BRIEF_HTML") || switchPos("/ BRIEF_ALT_HTML")) {
        if (strcmp(g_fullArg[2], "*")) {
          print2(
              "?For BRIEF_HTML or BRIEF_ALT_HTML, the label must be \"*\"\n");
          goto htmlDone;
        }
        g_briefHtmlFlag = 1;
      } else {
        g_briefHtmlFlag = 0;
      }

      if (switchPos("/ ALT_HTML") || switchPos("/ BRIEF_ALT_HTML")) {
        g_altHtmlFlag = 1;
      } else {
        g_altHtmlFlag = 0;
      }

      q = 0;

      /* Special feature:  if the match statement starts with "*", we
         will also output mmascii.html, mmtheoremsall.html, and
         mmdefinitions.html.  So, with
                 SHOW STATEMENT * / HTML
         these will be output plus all statements; with
                 SHOW STATEMENT *! / HTML
         these will be output with no statements (since ! is illegal in a
         statement label); with
                 SHOW STATEMENT ?* / HTML
         all statements will be output, but without mmascii.html etc. */
      /* if (instr(1, g_fullArg[2], "*") || g_briefHtmlFlag) { */  /* obsolete */
      if (((char *)(g_fullArg[2]))[0] == '*' || g_briefHtmlFlag) {
                                                            /* 6-Jul-2008 nm */
        s = -2; /* -2 is for ASCII table; -1 is for theorems;
                    0 is for definitions */
      } else {
        s = 1;
      }

      for (s = s + 0; s <= g_statements; s++) {

        if (s > 0 && g_briefHtmlFlag) break; /* Only do summaries */

        /*
           s = -2:  mmascii.html
           s = -1:  mmtheoremsall.html (used to be mmtheorems.html)
           s = 0:   mmdefinitions.html
           s > 0:   normal statement
        */

        if (s > 0) {
          if (!g_Statement[s].labelName[0]) continue; /* No label */
          /* 30-Jan-06 nm Added single-character-match wildcard argument */
          if (!matchesList(g_Statement[s].labelName, g_fullArg[2], '*', '?'))
            continue;
          if (g_Statement[s].type != (char)a_
              && g_Statement[s].type != (char)p_) continue;
        }

        q = 1; /* Flag that at least one matching statement was found */

        if (s > 0) {
          g_showStatement = s;
        } else {
          /* We set it to 1 here so we will output the Metamath Proof
             Explorer and not the Hilbert Space Explorer header for
             definitions and theorems lists, when g_showStatement is
             compared to g_extHtmlStmt in printTexHeader in mmwtex.c */
          g_showStatement = 1;
        }


        /*** Open the html file ***/
        g_htmlFlag = 1;
        /* Open the html output file */
        switch (s) {
          case -2:
            let(&g_texFileName, "mmascii.html");
            break;
          case -1:
            let(&g_texFileName, "mmtheoremsall.html");
            break;
          case 0:
            let(&g_texFileName, "mmdefinitions.html");
            break;
          default:
            let(&g_texFileName, cat(g_Statement[g_showStatement].labelName, ".html",
                NULL));
        }
        print2("Creating HTML file \"%s\"...\n", g_texFileName);
        g_texFilePtr = fSafeOpen(g_texFileName, "w",    /* 17-Jul-2019 nm */
            noVersioning /*noVersioningFlag*/);
        /****** old code before 17-Jul-2019 *******
        if (switchPos("/ NO_VERSIONING") == 0) {
          g_texFilePtr = fSafeOpen(g_texFileName, "w", 0/@noVersioningFlag@/);
        } else {
          /@ 6-Jul-2008 nm Added / NO_VERSIONING @/
          /@ Don't create the backup versions ~1, ~2,... @/
          g_texFilePtr = fopen(g_texFileName, "w");
          if (!g_texFilePtr) print2("?Could not open the file \"%s\".\n",
              g_texFileName);
        }
        ********* end of old code before 17-Jul-2019 *******/
        if (!g_texFilePtr) goto htmlDone; /* Couldn't open it (err msg was
            provided) */
        g_texFileOpenFlag = 1;
        printTexHeader((s > 0) ? 1 : 0 /*texHeaderFlag*/);
        if (!g_texDefsRead) {
          /* 9/6/03 If there was an error reading the $t xx.mm statement,
             g_texDefsRead won't be set, and we should close out file and skip
             further processing.  Otherwise we will be attempting to process
             uninitialized htmldef arrays and such. */
          print2("?HTML generation was aborted due to the error above.\n");
          s = g_statements + 1; /* To force loop to exit */
          goto ABORT_S; /* Go to end of loop where file is closed out */
        }

        if (s <= 0) {
          g_outputToString = 1;
          if (s == -2) {
            printLongLine(cat("<CENTER><FONT COLOR=", GREEN_TITLE_COLOR,
                "><B>",
                "Symbol to ASCII Correspondence for Text-Only Browsers",
                " (in order of appearance in $c and $v statements",
                     " in the database)",
                "</B></FONT></CENTER><P>", NULL), "", "\"");
          }
          /* 13-Oct-2006 nm todo - </CENTER> still appears - where is it? */
          if (!g_briefHtmlFlag) print2("<CENTER>\n");
          print2("<TABLE BORDER CELLSPACING=0 BGCOLOR=%s\n",
              MINT_BACKGROUND_COLOR);
          /* For bobby.cast.org approval */
          switch (s) {
            case -2:
              print2("SUMMARY=\"Symbol to ASCII correspondences\">\n");
              break;
            case -1:
              print2("SUMMARY=\"List of theorems\">\n");
              break;
            case 0:
              print2("SUMMARY=\"List of syntax, axioms and definitions\">\n");
              break;
          }
          switch (s) {
            case -2:
              print2("<TR ALIGN=LEFT><TD><B>\n");
              break;
            case -1:
              print2(
         "<CAPTION><B>List of Theorems</B></CAPTION><TR ALIGN=LEFT><TD><B>\n");
              break;
            case 0:
              printLongLine(cat(
/*"<CAPTION><B>List of Syntax (not <FONT COLOR=\"#00CC00\">|-&nbsp;</FONT>), ",*/
                  /* 2/9/02 (in case |- suppressed) */
                  "<CAPTION><B>List of Syntax, ",
                  "Axioms (<FONT COLOR=", GREEN_TITLE_COLOR, ">ax-</FONT>) and",
                  " Definitions (<FONT COLOR=", GREEN_TITLE_COLOR,
                  ">df-</FONT>)", "</B></CAPTION><TR ALIGN=LEFT><TD><B>",
                  NULL), "", "\"");
              break;
          }
          switch (s) {
            case -2:
              print2("Symbol</B></TD><TD><B>ASCII\n");
              break;
            case -1:
              print2(
                  "Ref</B></TD><TD><B>Description\n");
              break;
            case 0:
              printLongLine(cat(
                  "Ref</B></TD><TD><B>",
                "Expression (see link for any distinct variable requirements)",
                NULL), "", "\"");
              break;
          }
          print2("</B></TD></TR>\n");
          m = 0; /* Statement number map */
          let(&str3, ""); /* For storing ASCII token list in s=-2 mode */
          let(&bgcolor, MINT_BACKGROUND_COLOR); /* 8-Aug-2008 nm Initialize */
          for (i = 1; i <= g_statements; i++) {

            /* 8-Aug-2008 nm Commented out: */
            /*
            if (i == g_extHtmlStmt && s != -2) {
              / * Print a row that identifies the start of the extended
                 database (e.g. Hilbert Space Explorer) * /
              printLongLine(cat(
                  "<TR><TD COLSPAN=2 ALIGN=CENTER><A NAME=\"startext\"></A>",
                  "The list of syntax, axioms (ax-) and definitions (df-) for",
                  " the <B><FONT COLOR=", GREEN_TITLE_COLOR, ">",
                  g_extHtmlTitle,
                  "</FONT></B> starts here</TD></TR>", NULL), "", "\"");
            }
            */

            /* 8-Aug-2008 nm */
            if (s != -2 && (i == g_extHtmlStmt || i == g_mathboxStmt)) {
              /* Print a row that identifies the start of the extended
                 database (e.g. Hilbert Space Explorer) or the user
                 sandboxes */
              if (i == g_extHtmlStmt) {
                let(&bgcolor, PURPLISH_BIBLIO_COLOR);
              } else {
                let(&bgcolor, SANDBOX_COLOR);
              }
              printLongLine(cat("<TR BGCOLOR=", bgcolor,
                  "><TD COLSPAN=2 ALIGN=CENTER><A NAME=\"startext\"></A>",
                  "The list of syntax, axioms (ax-) and definitions (df-) for",
                  " the <B><FONT COLOR=", GREEN_TITLE_COLOR, ">",
                  (i == g_extHtmlStmt) ?
                    g_extHtmlTitle :
                    /*"User Sandboxes",*/
                    /* 24-Jul-2009 nm Changed name of sandbox to "mathbox" */
                    "User Mathboxes",
                  "</FONT></B> starts here</TD></TR>", NULL), "", "\"");
            }

            if (g_Statement[i].type == (char)p_ ||
                g_Statement[i].type == (char)a_ ) m++;
            if ((s == -1 && g_Statement[i].type != (char)p_)
                || (s == 0 && g_Statement[i].type != (char)a_)
                || (s == -2 && g_Statement[i].type != (char)c_
                    && g_Statement[i].type != (char)v_)
                ) continue;
            switch (s) {
              case -2:
                /* Print symbol to ASCII table entry */
                /* It's a $c or $v statement, so each token generates a
                   table row */
                for (j = 0; j < g_Statement[i].mathStringLen; j++) {
                  let(&str1, g_MathToken[(g_Statement[i].mathString)[j]].tokenName);
                  /* Output each token only once in case of multiple decl. */
                  if (!instr(1, str3, cat(" ", str1, " ", NULL))) {
                    let(&str3, cat(str3, " ", str1, " ", NULL));
                    let(&str2, "");
                    str2 = tokenToTex(g_MathToken[(g_Statement[i].mathString)[j]
                        ].tokenName, i/*stmt# for error msgs*/);
                    /* 2/9/02  Skip any tokens (such as |- in QL Explorer) that
                       may be suppressed */
                    if (!str2[0]) continue;
                    /* Convert special characters to HTML entities */
                    for (k = 0; k < (signed)(strlen(str1)); k++) {
                      if (str1[k] == '&') {
                        let(&str1, cat(left(str1, k), "&amp;",
                            right(str1, k + 2), NULL));
                        k = k + 4;
                      }
                      if (str1[k] == '<') {
                        let(&str1, cat(left(str1, k), "&lt;",
                            right(str1, k + 2), NULL));
                        k = k + 3;
                      }
                      if (str1[k] == '>') {
                        let(&str1, cat(left(str1, k), "&gt;",
                            right(str1, k + 2), NULL));
                        k = k + 3;
                      }
                    } /* next k */
                    printLongLine(cat("<TR ALIGN=LEFT><TD>",
                        (g_altHtmlFlag ? cat("<SPAN ", g_htmlFont, ">", NULL) : ""),
                                                           /* 14-Jan-2016 nm */
                        str2,
                        (g_altHtmlFlag ? "</SPAN>" : ""),    /* 14-Jan-2016 nm */
                        "&nbsp;", /* 10-Jan-2016 nm This will prevent a
                                     -4px shifted image from overlapping the
                                     lower border of the table cell */
                        "</TD><TD><TT>",
                        str1,
                        "</TT></TD></TR>", NULL), "", "\"");
                  }
                } /* next j */
                /* Close out the string now to prevent memory overflow */
                fprintf(g_texFilePtr, "%s", g_printString);
                let(&g_printString, "");
                break;
              case -1: /* Falls through to next case */
              case 0:
                /* Count the number of essential hypotheses k */
                /* Not needed anymore??? since getTexOrHtmlHypAndAssertion() */
                /*
                k = 0;
                j = nmbrLen(g_Statement[i].reqHypList);
                for (n = 0; n < j; n++) {
                  if (g_Statement[g_Statement[i].reqHypList[n]].type
                      == (char)e_) {
                    k++;
                  }
                }
                */
                let(&str1, "");
                if (s == 0 || g_briefHtmlFlag) {
                  let(&str1, "");
                  /* 18-Sep-03 Get HTML hypotheses => assertion */
                  str1 = getTexOrHtmlHypAndAssertion(i);
                                                /* In mmwtex.c */
                  let(&str1, cat(str1, "</TD></TR>", NULL));
                }

                /* 13-Oct-2006 nm Made some changes to BRIEF_HTML/_ALT_HTML
                   to use its mmtheoremsall.html output for the Palm PDA */
                if (g_briefHtmlFlag) {
                  /* Get page number in mmtheorems*.html of WRITE THEOREMS */
                  k = ((g_Statement[i].pinkNumber - 1) /
                      THEOREMS_PER_PAGE) + 1; /* Page # */
                  let(&str2, cat("<TR ALIGN=LEFT><TD ALIGN=LEFT>",
                      /*"<FONT COLOR=\"#FA8072\">",*/
                      "<FONT COLOR=ORANGE>",
                      str((double)(g_Statement[i].pinkNumber)), "</FONT> ",
                      "<FONT COLOR=GREEN><A HREF=\"",
                      "mmtheorems", (k == 1) ? "" : str((double)k), ".html#",
                      g_Statement[i].labelName,
                      "\">", g_Statement[i].labelName,
                      "</A></FONT>", NULL));
                  let(&str1, cat(str2, " ", str1, NULL));
                } else {
                  /* Get little pink (or rainbow-colored) number */
                  let(&str4, "");
                  str4 = pinkHTML(i);
                  let(&str2, cat("<TR BGCOLOR=", bgcolor, /* 8-Aug-2008 nm */
                      " ALIGN=LEFT><TD><A HREF=\"",
                      g_Statement[i].labelName,
                      ".html\">", g_Statement[i].labelName,
                      "</A>", str4, NULL));
                  let(&str1, cat(str2, "</TD><TD>", str1, NULL));
                }
                /* End of 13-Oct-2006 changed section */

                print2("\n");  /* New line for HTML source readability */
                printLongLine(str1, "", "\"");

                if (s == 0 || g_briefHtmlFlag) {
                              /* Set s == 0 here for Web site version,
                                 s == s for symbol version of theorem list */
                  /* The below has been replaced by
                     getTexOrHtmlHypAndAssertion(i) above. */
                  /*printTexLongMath(g_Statement[i].mathString, "", "", 0, 0);*/
                  /*g_outputToString = 1;*/ /* Is reset by printTexLongMath */
                } else {
                  /* Theorems are listed w/ description; otherwise file is too
                     big for convenience */
                  let(&str1, "");
                  str1 = getDescription(i);
                  if (strlen(str1) > 29)
                    let(&str1, cat(left(str1, 26), "...", NULL));
                  let(&str1, cat(str1, "</TD></TR>", NULL));
                  printLongLine(str1, "", "\"");
                }
                /* Close out the string now to prevent overflow */
                fprintf(g_texFilePtr, "%s", g_printString);
                let(&g_printString, "");
                break;
            } /* end switch */
          } /* next i (statement number) */
          /* print2("</TABLE></CENTER>\n"); */ /* 8/8/03 Removed - already
              done somewhere else, causing validator.w3.org to fail */
          g_outputToString = 0;  /* closing will write out the string */
          let(&bgcolor, ""); /* Deallocate (to improve fragmentation) */

        } else { /* s > 0 */

          /* 16-Aug-2016 nm */
          if (printTime == 1) {
            getRunTime(&timeIncr);  /* This call just resets the time */
          }

          /*** Output the html statement body ***/
          typeStatement(g_showStatement,
              0 /*briefFlag*/,
              0 /*commentOnlyFlag*/,
              1 /*texFlag*/,   /* means latex or html */
              1 /*g_htmlFlag*/);

          /* 16-Aug-2016 nm */
          if (printTime == 1) {
            getRunTime(&timeIncr);
            print2("SHOW STATEMENT run time = %6.2f sec for \"%s\"\n",
                timeIncr,
                g_texFileName);
          }

        } /* if s <= 0 */

       ABORT_S:
        /*** Close the html file ***/
        printTexTrailer(1 /*texHeaderFlag*/);
        fclose(g_texFilePtr);
        g_texFileOpenFlag = 0;
        let(&g_texFileName,"");

      } /* next s */

      if (!q) {
        /* No matching statement was found */
        printLongLine(cat("?There is no statement whose label matches \"",
            g_fullArg[2], "\".  ",
            "Use SHOW LABELS for a list of valid labels.", NULL), "", " ");
        continue;
      }

      /* Complete the command processing to bypass normal SHOW STATEMENT
         (non-html) below. */
     htmlDone:
      continue;
    } /* if (cmdMatches("SHOW STATEMENT") && switchPos("/ HTML")...) */


    /******* Section for / MNEMONICS added 12-May-2009 by Stefan Allan *****/
    /* Write mnemosyne.txt */
    if (cmdMatches("SHOW STATEMENT") && switchPos("/ MNEMONICS")) {

      /*** 17-Nov-2015 nm Deleted - removed g_htmlFlag, g_altHtmlFlag
            change prohibition
      if (!g_texDefsRead) {
        g_htmlFlag = 1;     /@ Use HTML, not TeX section @/
        g_altHtmlFlag = 1;  /@ Use Unicode, not GIF @/
        print2("Reading definitions from $t statement of %s...\n", g_input_fn);
        if (2/@error@/ == readTexDefs(0 /@ 1 = check errors only @/,
            0 /@ 1 = no GIF file existence check @/  )) {
          print2(
          "?There was an error in the $t comment's LaTeX/HTML definitions.\n");
          print2("?HTML generation was aborted due to the error above.\n");
          continue; /@ An error occurred @/
        }
      } else {
        /@ Current limitation - can only read def's from .mm file once @/
        if (!g_htmlFlag) {
          print2("?You cannot use both LaTeX and HTML in the same session.\n");
          print2(
              "You must EXIT and restart Metamath to switch to the other.\n");
          continue;
        } else {
          if (!g_altHtmlFlag) {
            print2(
              "?You cannot use both HTML and ALT_HTML in the same session.\n");
            print2(
              "You must EXIT and restart Metamath to switch to the other.\n");
            continue;
          }
        }
      }
      *** end of 17-Nov-2015 deletion */

      g_htmlFlag = 1;     /* Use HTML, not TeX section */
      g_altHtmlFlag = 1;  /* Use Unicode, not GIF */
      /* readTexDefs() rereads based on changes to g_htmlFlag, g_altHtmlFlag */
      if (2/*error*/ == readTexDefs(0 /* 1 = check errors only */,
          0 /* 1 = no GIF file existence check */  )) {
        continue; /* An error occurred */
      }

      let(&g_texFileName, "mnemosyne.txt");
      g_texFilePtr = fSafeOpen(g_texFileName, "w", 0/*noVersioningFlag*/);
      if (!g_texFilePtr) {
        /* Couldn't open file; error message was provided by fSafeOpen */
        continue;
      }
      print2("Creating Mnemosyne file \"%s\"...\n", g_texFileName);

      for (s = 1; s <= g_statements; s++) {
        g_showStatement = s;
/*
        if (strcmp("|-", g_MathToken[
         (g_Statement[g_showStatement].mathString)[0]].tokenName)) {
           subType = SYNTAX;
        }
*/
        if (!g_Statement[s].labelName[0]) continue; /* No label */
        /* 30-Jan-06 nm Added single-character-match wildcard argument */
        if (!matchesList(g_Statement[s].labelName, g_fullArg[2], '*', '?'))
          continue;
        if (g_Statement[s].type != (char)a_
            && g_Statement[s].type != (char)p_)
          continue;

        let(&str1, cat("<CENTER><B><FONT SIZE=\"+1\">",
            " <FONT COLOR=", GREEN_TITLE_COLOR,
            " SIZE = \"+3\">", g_Statement[g_showStatement].labelName,
            "</FONT></FONT></B>", "</CENTER>", NULL));
        fprintf(g_texFilePtr, "%s", str1);

        let(&str1, cat("<TABLE>",NULL));
        fprintf(g_texFilePtr, "%s", str1);

        j = nmbrLen(g_Statement[g_showStatement].reqHypList);
        for (i = 0; i < j; i++) {
          k = g_Statement[g_showStatement].reqHypList[i];
          if (g_Statement[k].type != (char)e_
              && !(subType == SYNTAX
              && g_Statement[k].type == (char)f_))
            continue;

          let(&str1, cat("<TR ALIGN=LEFT><TD><FONT SIZE=\"+2\">",
              g_Statement[k].labelName, "</FONT></TD><TD><FONT SIZE=\"+2\">",
              NULL));
          fprintf(g_texFilePtr, "%s", str1);

          /* Print hypothesis */
          let(&str1, ""); /* Free any previous allocation to str1 */
          /* getTexLongMath does not return a temporary allocation; must
             assign str1 directly, not with let().  It will be deallocated
             with the next let(&str1,...). */
          str1 = getTexLongMath(g_Statement[k].mathString,
              k/*stmt# for err msgs*/);
          fprintf(g_texFilePtr, "%s</FONT></TD>", str1);
        }


        let(&str1, "</TABLE>");
        fprintf(g_texFilePtr, "%s", str1);

        let(&str1, "<BR><FONT SIZE=\"+2\">What is the conclusion?</FONT>");
        fprintf(g_texFilePtr, "%s\n", str1);

        let(&str1, "<FONT SIZE=\"+3\">");
        fprintf(g_texFilePtr, "%s", str1);

        let(&str1, ""); /* Free any previous allocation to str1 */
        /* getTexLongMath does not return a temporary allocation */
        str1 = getTexLongMath(g_Statement[s].mathString, s);
        fprintf(g_texFilePtr, "%s", str1);

        let(&str1, "</FONT>");
        fprintf(g_texFilePtr, "%s\n",str1);
      } /*  for(s=1;s<g_statements;++s) */

      fclose(g_texFilePtr);
      g_texFileOpenFlag = 0;
      let(&g_texFileName,"");
      let(&str1,"");
      let(&str2,"");

      continue;
    } /* if (cmdMatches("SHOW STATEMENT") && switchPos("/ MNEMONICS")) */
    /** End of section for / MNEMONICS added 12-May-2009 by Stefan Allan *****/

    /* If we get here, the user did not specify one of the qualifiers /HTML,
       /BRIEF_HTML, /ALT_HTML, or /BRIEF_ALT_HTML */
    if (cmdMatches("SHOW STATEMENT") && !switchPos("/ HTML")) {

      texFlag = 0;
      /* 14-Sep-2010 nm Added OLD_TEX */
      if (switchPos("/ TEX") || switchPos("/ OLD_TEX")
          || switchPos("/ HTML"))
        texFlag = 1;

      briefFlag = 1;
      g_oldTexFlag = 0;
      if (switchPos("/ TEX")) briefFlag = 0;
      /* 14-Sep-2010 nm Added OLD_TEX */
      if (switchPos("/ OLD_TEX")) briefFlag = 0;
      if (switchPos("/ OLD_TEX")) g_oldTexFlag = 1;
      if (switchPos("/ FULL")) briefFlag = 0;

      commentOnlyFlag = 0;
      if (switchPos("/ COMMENT")) {
        commentOnlyFlag = 1;
        briefFlag = 1;
      }


      if (switchPos("/ FULL")) {
        briefFlag = 0;
        commentOnlyFlag = 0;
      }

      if (texFlag) {
        if (!g_texFileOpenFlag) {
          print2(
      "?You have not opened a %s file.  Use the OPEN TEX command first.\n",
              g_htmlFlag ? "HTML" : "LaTeX");
          continue;
        }
      }

      if (texFlag && (commentOnlyFlag || briefFlag)) {
        print2("?TEX qualifier should be used alone\n");
        continue;
      }

      q = 0;

      for (s = 1; s <= g_statements; s++) {
        if (!g_Statement[s].labelName[0]) continue; /* No label */
        /* We are not in MM-PA mode, or the statement isn't "=" */
        /* 30-Jan-06 nm Added single-character-match wildcard argument */
        if (!matchesList(g_Statement[s].labelName, g_fullArg[2], '*', '?'))
          continue;

        if (briefFlag || commentOnlyFlag || texFlag) {
          /* For brief or comment qualifier, if label has wildcards,
             show only $p and $a's */
          /* 30-Jan-06 nm Added single-character-match wildcard argument */
          if (g_Statement[s].type != (char)p_
              && g_Statement[s].type != (char)a_ && (instr(1, g_fullArg[2], "*")
                  || instr(1, g_fullArg[2], "?")))
            continue;
        }

        if (q && !texFlag) {
          /* 17-Jul-2019 nm Changed "79" to "g_screenWidth" */
          if (!print2("%s\n", string(g_screenWidth, '-'))) /* Put line between
                                                   statements */
            break; /* Break for speedup if user quit */
        }
        if (texFlag) print2("Outputting statement \"%s\"...\n",
            g_Statement[s].labelName);

        q = 1; /* Flag that at least one matching statement was found */

        g_showStatement = s;


        typeStatement(g_showStatement,
            briefFlag,
            commentOnlyFlag,
            texFlag,
            g_htmlFlag);
      } /* Next s */

      if (!q) {
        /* No matching statement was found */
        printLongLine(cat("?There is no statement whose label matches \"",
            g_fullArg[2], "\".  ",
            "Use SHOW LABELS for a list of valid labels.", NULL), "", " ");
        continue;
      }

      if (texFlag && !g_htmlFlag) {
        print2("The LaTeX source was written to \"%s\".\n", g_texFileName);
        /* 14-Sep-2010 nm Added OLD_TEX */
        g_oldTexFlag = 0;
      }
      continue;
    } /* (cmdMatches("SHOW STATEMENT") && !switchPos("/ HTML")) */

    if (cmdMatches("SHOW SETTINGS")) {
      print2("Metamath settings on %s at %s:\n",date(),time_());
      if (g_commandEcho) {
        print2("(SET ECHO...) Command ECHO is ON.\n");
      } else {
        print2("(SET ECHO...) Command ECHO is OFF.\n");
      }
      if (defaultScrollMode == 1) {
        print2("(SET SCROLL...) SCROLLing mode is PROMPTED.\n");
      } else {
        print2("(SET SCROLL...) SCROLLing mode is CONTINUOUS.\n");
      }
      print2("(SET WIDTH...) Screen display WIDTH is %ld.\n", g_screenWidth);
      print2("(SET HEIGHT...) Screen display HEIGHT is %ld.\n",
          g_screenHeight + 1);
      if (g_sourceHasBeenRead == 1) {
        print2("(READ...) %ld statements have been read from \"%s\".\n",
            g_statements, g_input_fn);
      } else {
        print2("(READ...) No source file has been read in yet.\n");
      }
      print2("(SET ROOT_DIRECTORY...) Root directory is \"%s\".\n",
          g_rootDirectory);
      print2(
     "(SET DISCOURAGEMENT...) Blocking based on \"discouraged\" tags is %s.\n",
          (g_globalDiscouragement ? "ON" : "OFF"));
      print2(
     "(SET CONTRIBUTOR...) The current contributor is \"%s\".\n",
          g_contributorName);
      if (g_PFASmode) {
        print2("(PROVE...) The statement you are proving is \"%s\".\n",
            g_Statement[g_proveStatement].labelName);
      }
      print2("(SET UNDO...) The maximum number of UNDOs is %ld.\n",
          processUndoStack(NULL, PUS_GET_SIZE, "", 0));
      print2(
    "(SET UNIFICATION_TIMEOUT...) The unification timeout parameter is %ld.\n",
          g_userMaxUnifTrials);
      print2(
    "(SET SEARCH_LIMIT...) The SEARCH_LIMIT for the IMPROVE command is %ld.\n",
          g_userMaxProveFloat);
      if (g_minSubstLen) {
        print2(
     "(SET EMPTY_SUBSTITUTION...) EMPTY_SUBSTITUTION is not allowed (OFF).\n");
      } else {
        print2(
          "(SET EMPTY_SUBSTITUTION...) EMPTY_SUBSTITUTION is allowed (ON).\n");
      }
      if (g_hentyFilter) { /* 18-Nov-05 nm Added to the SHOW listing */
        print2(
              "(SET JEREMY_HENTY_FILTER...) The Henty filter is turned ON.\n");
      } else {
        print2(
             "(SET JEREMY_HENTY_FILTER...) The Henty filter is turned OFF.\n");
      }
      if (g_showStatement) {
        print2("(SHOW...) The default statement for SHOW commands is \"%s\".\n",
            g_Statement[g_showStatement].labelName);
      }
      if (g_logFileOpenFlag) {
        print2("(OPEN LOG...) The log file \"%s\" is open.\n", g_logFileName);
      } else {
        print2("(OPEN LOG...) No log file is currently open.\n");
      }
      if (g_texFileOpenFlag) {
        print2("The %s file \"%s\" is open.\n", g_htmlFlag ? "HTML" : "LaTeX",
            g_texFileName);
      }
      /* 17-Nov-2015 nm */
      print2(
  "(SHOW STATEMENT.../[TEX,HTML,ALT_HTML]) Current output mode is %s.\n",
          g_htmlFlag
             ? (g_altHtmlFlag ? "ALT_HTML" : "HTML")
             : "TEX (LaTeX)");
      /* 21-Jun-2014 */
      print2("The program is compiled for a %ld-bit CPU.\n",
          (long)(8 * sizeof(long)));
      print2(
 "sizeof(short)=%ld, sizeof(int)=%ld, sizeof(long)=%ld, sizeof(size_t)=%ld.\n",
        (long)(sizeof(short)),
        (long)(sizeof(int)), (long)(sizeof(long)), (long)(sizeof(size_t)));
      continue;
    }

    if (cmdMatches("SHOW MEMORY")) {
      /*print2("%ld bytes of data memory have been used.\n",db+db3);*/
      j = 32000000; /* The largest we'ed ever look for */
#ifdef THINK_C
      i = FreeMem();
#else
      i = getFreeSpace(j);
#endif
      if (i > j-3) {
        print2("At least %ld bytes of memory are free.\n",j);
      } else {
        print2("%ld bytes of memory are free.\n",i);
      }
      continue;
    }

    /* 21-Jun-2014 */
    if (cmdMatches("SHOW ELAPSED_TIME")) {
      timeTotal = getRunTime(&timeIncr);
      print2(
      "Time since last SHOW ELAPSED_TIME command = %6.2f s; total = %6.2f s\n",
          timeIncr, timeTotal);
      continue;
    } /* if (cmdMatches("SHOW ELAPSED_TIME")) */


    if (cmdMatches("SHOW TRACE_BACK")) {


      /* Pre-21-May-2008
        for (i = 1; i <= g_statements; i++) {
          if (!strcmp(g_fullArg[2],g_Statement[i].labelName)) break;
        }
        if (i > g_statements) {
          printLongLine(cat("?There is no statement with label \"",
              g_fullArg[2], "\".  ",
              "Use SHOW LABELS for a list of valid labels.", NULL), "", " ");
          g_showStatement = 0;
          continue;
        }
       */

      essentialFlag = 0;
      axiomFlag = 0;
      endIndent = 0;
      i = switchPos("/ ESSENTIAL");
      if (i) essentialFlag = 1; /* Limit trace to essential steps only */
      i = switchPos("/ ALL");
      if (i) essentialFlag = 0;
      i = switchPos("/ AXIOMS");
      if (i) axiomFlag = 1; /* Limit trace printout to axioms */
      i = switchPos("/ DEPTH"); /* Limit depth of printout */
      if (i) endIndent = (long)val(g_fullArg[i + 1]);

       /* 19-May-2013 nm */
      i = switchPos("/ COUNT_STEPS");
      countStepsFlag = (i != 0 ? 1 : 0);
      i = switchPos("/ TREE");
      treeFlag = (i != 0 ? 1 : 0);
      i = switchPos("/ MATCH");
      matchFlag = (i != 0 ? 1 : 0);
      if (matchFlag) {
        let(&matchList, g_fullArg[i + 1]);
      } else {
        let(&matchList, "");
      }
      i = switchPos("/ TO");
      if (i != 0) {
        let(&traceToList, g_fullArg[i + 1]);
      } else {
        let(&traceToList, "");
      }
      if (treeFlag) {
        if (axiomFlag) {
          print2(
              "(Note:  The AXIOMS switch is ignored in TREE mode.)\n");
        }
        if (countStepsFlag) {
          print2(
              "(Note:  The COUNT_STEPS switch is ignored in TREE mode.)\n");
        }
        if (matchFlag) {
          print2(
  "(Note: The MATCH list is ignored in TREE mode.)\n");
        }
      } else {
        if (endIndent != 0) {
          print2(
  "(Note:  The DEPTH is ignored if the TREE switch is not used.)\n");
        }
        if (countStepsFlag) {
          if (matchFlag) {
            print2(
  "(Note: The MATCH list is ignored in COUNT_STEPS mode.)\n");
          }
        }
      }

      /* 21-May-2008 nm Added wildcard handling */
      g_showStatement = 0;
      for (i = 1; i <= g_statements; i++) {
        if (g_Statement[i].type != (char)p_)
          continue; /* Not a $p statement; skip it */
        /* Wildcard matching */
        if (!matchesList(g_Statement[i].labelName, g_fullArg[2], '*', '?'))
          continue;

        g_showStatement = i;
        /*** start of 19-May-2013 deletion
        j = switchPos("/ TREE");
        if (j) {
          if (axiomFlag) {
            print2(
                "(Note:  The AXIOMS switch is ignored in TREE mode.)\n");
          }
          if (switchPos("/ COUNT_STEPS")) {
            print2(
                "(Note:  The COUNT_STEPS switch is ignored in TREE mode.)\n");
          }
          traceProofTree(g_showStatement, essentialFlag, endIndent);
        } else {
          if (endIndent != 0) {
           print2(
"(Note:  The DEPTH is ignored if the TREE switch is not used.)\n");
          }

          j = switchPos("/ COUNT_STEPS");
          if (j) {
            countSteps(g_showStatement, essentialFlag);
          } else {
            traceProof(g_showStatement, essentialFlag, axiomFlag);
          }
        }
        *** end of 19-May-2013 deletion */


        /* 19-May-2013 nm - move /TREE and /COUNT_STEPS to outside loop,
           assigning new variables, for cleaner code. */
        if (treeFlag) {
          traceProofTree(g_showStatement, essentialFlag, endIndent);
        } else {
          if (countStepsFlag) {
            countSteps(g_showStatement, essentialFlag);
          } else {
            traceProof(g_showStatement,
                essentialFlag,
                axiomFlag,
                matchList, /* 19-May-2013 nm */
                traceToList, /* 18-Jul-2015 nm */
                0 /* testOnlyFlag */ /* 20-May-2013 nm */);
          }
        }

      /* 21-May-2008 nm Added wildcard handling */
      } /* next i */
      if (g_showStatement == 0) {
        printLongLine(cat("?There are no $p labels matching \"",
            g_fullArg[2], "\".  ",
            "See HELP SHOW TRACE_BACK for matching rules.", NULL), "", " ");
      }

      let(&matchList, ""); /* Deallocate memory */
      let(&traceToList, ""); /* Deallocate memory */
      continue;
    } /* if (cmdMatches("SHOW TRACE_BACK")) */


    if (cmdMatches("SHOW USAGE")) {

      /* 7-Jan-2008 nm Added / ALL qualifier */
      if (switchPos("/ ALL")) {
        m = 1;  /* Always include $e, $f statements */
      } else {
        m = 0;  /* If wildcards are used, show $a, $p only */
      }

      g_showStatement = 0;
      for (i = 1; i <= g_statements; i++) { /* 7-Jan-2008 */

        /* 7-Jan-2008 nm Added wildcard handling */
        if (!g_Statement[i].labelName[0]) continue; /* No label */
        if (!m && g_Statement[i].type != (char)p_ &&
            g_Statement[i].type != (char)a_
            /* A wildcard-free user-specified statement is always matched even
               if it's a $e, i.e. it overrides omission of / ALL */
            && (instr(1, g_fullArg[2], "*")
              || instr(1, g_fullArg[2], "?")))
          continue; /* No /ALL switch and wildcard and not $p, $a */
        /* Wildcard matching */
        if (!matchesList(g_Statement[i].labelName, g_fullArg[2], '*', '?'))
          continue;

        g_showStatement = i;
        recursiveFlag = 0;
        j = switchPos("/ RECURSIVE");
        if (j) recursiveFlag = 1; /* Recursive (indirect) usage */
        j = switchPos("/ DIRECT");
        if (j) recursiveFlag = 0; /* Direct references only */

        let(&str1, "");
        str1 = traceUsage(g_showStatement,
            recursiveFlag,
            0 /* cutoffStmt */);



        /************* 18-Jul-2015 nm Start of deleted code ************/
        /*
        /@ Count the number of statements = # of spaces @/
        k = (long)strlen(str1) - (long)strlen(edit(str1, 2));

        if (!k) {
          printLongLine(cat("Statement \"",
              g_Statement[g_showStatement].labelName,
              "\" is not referenced in the proof of any statement.", NULL),
              "", " ");
        } else {
          if (recursiveFlag) {
            let(&str2, "\" directly or indirectly affects");
          } else {
            let(&str2, "\" is directly referenced in");
          }
          if (k == 1) {
            printLongLine(cat("Statement \"",
                g_Statement[g_showStatement].labelName,
                str2, " the proof of ",
                str((double)k), " statement:", NULL), "", " ");
          } else {
            printLongLine(cat("Statement \"",
                g_Statement[g_showStatement].labelName,
                str2, " the proofs of ",
                str((double)k), " statements:", NULL), "", " ");
          }
        }

        if (k) {
          let(&str1, cat(" ", str1, NULL));
        } else {
          let(&str1, "  (None)");
        }

        /@ Print the output @/
        printLongLine(str1, "  ", " ");
        */
        /********* 18-Jul-2015 nm End of deleted code ****************/


        /************* 18-Jul-2015 nm Start of new code ************/
        /* 18-Jul-2015 nm */
        /* str1[0] will be 'Y' or 'N' depending on whether there are any
           statements.  str1[i] will be 'Y' or 'N' depending on whether
           g_Statement[i] uses g_showStatement. */
        /* Count the number of statements k = # of 'Y' */
        k = 0;
        if (str1[0] == 'Y') {
          /* There is at least one statement using g_showStatement */
          for (j = g_showStatement + 1; j <= g_statements; j++) {
            if (str1[j] == 'Y') {
              k++;
            } else {
              if (str1[j] != 'N') bug(1124); /* Must be 'Y' or 'N' */
            }
          }
        } else {
          if (str1[0] != 'N') bug(1125); /* Must be 'Y' or 'N' */
        }

        if (k == 0) {
          printLongLine(cat("Statement \"",
              g_Statement[g_showStatement].labelName,
              "\" is not referenced in the proof of any statement.", NULL),
              "", " ");
        } else {
          if (recursiveFlag) {
            let(&str2, "\" directly or indirectly affects");
          } else {
            let(&str2, "\" is directly referenced in");
          }
          if (k == 1) {
            printLongLine(cat("Statement \"",
                g_Statement[g_showStatement].labelName,
                str2, " the proof of ",
                str((double)k), " statement:", NULL), "", " ");
          } else {
            printLongLine(cat("Statement \"",
                g_Statement[g_showStatement].labelName,
                str2, " the proofs of ",
                str((double)k), " statements:", NULL), "", " ");
          }
        }

        if (k != 0) {
          let(&str3, " "); /* Line buffer */
          for (j = g_showStatement + 1; j <= g_statements; j++) {
            if (str1[j] == 'Y') {
              /* Since the output list could be huge, don't build giant
                 string (very slow) but output it line by line */
              if ((long)strlen(str3) + 1 +
                  (long)strlen(g_Statement[j].labelName) > g_screenWidth) {
                /* Output and reset the line buffer */
                print2("%s\n", str3);
                let(&str3, " ");
              }
              let(&str3, cat(str3, " ", g_Statement[j].labelName, NULL));
            }
          }
          if (strlen(str3) > 1) print2("%s\n", str3);
          let(&str3, "");
        } else {
          print2("  (None)\n");
        } /* if (k != 0) */
        /********* 18-Jul-2015 nm End of new code ****************/


      } /* next i (statement matching wildcard list) */

      if (g_showStatement == 0) {
        printLongLine(cat("?There are no labels matching \"",
            g_fullArg[2], "\".  ",
            "See HELP SHOW USAGE for matching rules.", NULL), "", " ");
      }
      continue;
    } /* if cmdMatches("SHOW USAGE") */


    if (cmdMatches("SHOW PROOF")
        || cmdMatches("SHOW NEW_PROOF")
        || cmdMatches("SAVE PROOF")
        || cmdMatches("SAVE NEW_PROOF")
        || cmdMatches("MIDI")) {
      if (switchPos("/ HTML")) {
        print2("?HTML qualifier is obsolete - use SHOW STATEMENT * / HTML\n");
        continue;
      }

      /* 3-May-2016 nm */
      if (cmdMatches("SAVE NEW_PROOF")
          && getMarkupFlag(g_proveStatement, PROOF_DISCOURAGED)) {
        if (switchPos("/ OVERRIDE") == 0 && g_globalDiscouragement == 1) {
          /* print2("\n"); */ /* Enable for more emphasis */
          print2(
">>> ?Error: Attempt to overwrite a proof whose modification is discouraged.\n");
          print2(
   ">>> Use SAVE NEW_PROOF ... / OVERRIDE if you really want to do this.\n");
          /* print2("\n"); */ /* Enable for more emphasis */
          continue;
        } else {
          /* print2("\n"); */ /* Enable for more emphasis */
          print2(
">>> ?Warning: You are overwriting a proof whose modification is discouraged.\n");
          /* print2("\n"); */ /* Enable for more emphasis */
        }
      }

      if (cmdMatches("SHOW PROOF") || cmdMatches("SAVE PROOF")) {
        pipFlag = 0;
      } else {
        pipFlag = 1; /* Proof-in-progress (NEW_PROOF) flag */
      }
      if (cmdMatches("SHOW")) {
        saveFlag = 0;
      } else {
        saveFlag = 1; /* The command is SAVE PROOF */
      }

      /* 16-Aug-2016 nm */
      printTime = 0;
      if (switchPos("/ TIME") != 0) {  /* / TIME legal in SAVE mode only */
        printTime = 1;
      }

      /* 27-Dec-2013 nm */
      i = switchPos("/ OLD_COMPRESSION");
      if (i) {
        if (!switchPos("/ COMPRESSED")) {
          print2("?/ OLD_COMPRESSION must be accompanied by / COMPRESSED.\n");
          continue;
        }
      }

      /* 2-Mar-2016 nm */
      i = switchPos("/ FAST");
      if (i) {
        if (!switchPos("/ COMPRESSED") && !switchPos("/ PACKED")) {
          print2("?/ FAST must be accompanied by / COMPRESSED or / PACKED.\n");
          continue;
        }
        fastFlag = 1;
      } else {
        fastFlag = 0;
      }

      /* 2-Mar-2016 nm */
      if (switchPos("/ EXPLICIT")) {
        if (switchPos("/ COMPRESSED")) {
          print2("?/ COMPRESSED and / EXPLICIT may not be used together.\n");
          continue;
        } else if (switchPos("/ NORMAL")) {
          print2("?/ NORMAL and / EXPLICIT may not be used together.\n");
          continue;
        }
      }
      if (switchPos("/ NORMAL")) {
        if (switchPos("/ COMPRESSED")) {
          print2("?/ NORMAL and / COMPRESSED may not be used together.\n");
          continue;
        }
      }


      /* Establish defaults for omitted qualifiers */
      startStep = 0;
      endStep = 0;
      /* startIndent = 0; */ /* Not used */
      endIndent = 0;
      /*essentialFlag = 0;*/
      essentialFlag = 1; /* 10/9/99 - friendlier default */
      renumberFlag = 0;
      unknownFlag = 0;
      notUnifiedFlag = 0;
      reverseFlag = 0;
      detailStep = 0;
      noIndentFlag = 0;
      splitColumn = DEFAULT_COLUMN;
      skipRepeatedSteps = 0; /* 28-Jun-2013 nm */
      texFlag = 0;

      i = switchPos("/ FROM_STEP");
      if (i) startStep = (long)val(g_fullArg[i + 1]);
      i = switchPos("/ TO_STEP");
      if (i) endStep = (long)val(g_fullArg[i + 1]);
      i = switchPos("/ DEPTH");
      if (i) endIndent = (long)val(g_fullArg[i + 1]);
      /* 10/9/99 - ESSENTIAL is retained for downwards compatibility, but is
         now the default, so we ignore it. */
      /*
      i = switchPos("/ ESSENTIAL");
      if (i) essentialFlag = 1;
      */
      i = switchPos("/ ALL");
      if (i) essentialFlag = 0;
      if (i && switchPos("/ ESSENTIAL")) {
        print2("?You may not specify both / ESSENTIAL and / ALL.\n");
        continue;
      }
      i = switchPos("/ RENUMBER");
      if (i) renumberFlag = 1;
      i = switchPos("/ UNKNOWN");
      if (i) unknownFlag = 1;
      i = switchPos("/ NOT_UNIFIED"); /* pip mode only */
      if (i) notUnifiedFlag = 1;
      i = switchPos("/ REVERSE");
      if (i) reverseFlag = 1;
      i = switchPos("/ LEMMON");
      if (i) noIndentFlag = 1;
      i = switchPos("/ START_COLUMN");
      if (i) splitColumn = (long)val(g_fullArg[i + 1]);
      i = switchPos("/ NO_REPEATED_STEPS"); /* 28-Jun-2013 nm */
      if (i) skipRepeatedSteps = 1;         /* 28-Jun-2013 nm */

      /* 8-Dec-2018 nm */
      /* If NO_REPEATED_STEPS is specified, indentation (tree) mode will be
         misleading because a hypothesis assignment will be suppressed if the
         same assignment occurred earlier, i.e. it is no longer a "tree". */
      if (skipRepeatedSteps == 1 && noIndentFlag == 0) {
        print2("?You must specify / LEMMON with / NO_REPEATED_STEPS\n");
        continue;
      }

      i = switchPos("/ TEX") || switchPos("/ HTML")
          /* 14-Sep-2010 nm Added OLDE_TEX */
          || switchPos("/ OLD_TEX");
      if (i) texFlag = 1;

      /* 14-Sep-2010 nm Added OLD_TEX */
      g_oldTexFlag = 0;
      if (switchPos("/ OLD_TEX")) g_oldTexFlag = 1;

      if (cmdMatches("MIDI")) { /* 8/28/00 */
        g_midiFlag = 1;
        pipFlag = 0;
        saveFlag = 0;
        let(&labelMatch, g_fullArg[1]);
        i = switchPos("/ PARAMETER"); /* MIDI only */
        if (i) {
          let(&g_midiParam, g_fullArg[i + 1]);
        } else {
          let(&g_midiParam, "");
        }
      } else {
        g_midiFlag = 0;
        if (!pipFlag) let(&labelMatch, g_fullArg[2]);
      }


      if (texFlag) {
        if (!g_texFileOpenFlag) {
          print2(
     "?You have not opened a %s file.  Use the OPEN %s command first.\n",
              g_htmlFlag ? "HTML" : "LaTeX",
              g_htmlFlag ? "HTML" : "TEX");
          continue;
        }
        /**** this is now done after outputting
        print2("The %s source was written to \"%s\".\n",
            g_htmlFlag ? "HTML" : "LaTeX", g_texFileName);
        */
      }

      i = switchPos("/ DETAILED_STEP"); /* non-pip mode only */
      if (i) {
        detailStep = (long)val(g_fullArg[i + 1]);
        if (!detailStep) detailStep = -1; /* To use as flag; error message
                                             will occur in showDetailStep() */
      }

/*??? Need better warnings for switch combinations that don't make sense */

      /* 2-Jan-2017 nm */
      /* Print a single message for "/compressed/fast" */
      if (switchPos("/ COMPRESSED") && fastFlag
          && !strcmp("*", labelMatch)) {
        print2(
            "Reformatting and saving (but not recompressing) all proofs...\n");
      }


      q = 0;  /* Flag that at least one matching statement was found */
      for (stmt = 1; stmt <= g_statements; stmt++) {
        /* If pipFlag (NEW_PROOF), we will iterate exactly once.  This
           loop of course will be entered because there is a least one
           statement, and at the end of the s loop we break out of it. */
        /* If !pipFlag, get the next statement: */
        if (!pipFlag) {
          if (g_Statement[stmt].type != (char)p_) continue; /* Not $p */
          /* 30-Jan-06 nm Added single-character-match wildcard argument */
          if (!matchesList(g_Statement[stmt].labelName, labelMatch, '*', '?'))
            continue;
          g_showStatement = stmt;
        }

        q = 1; /* Flag that at least one matching statement was found */

        if (detailStep) {
          /* Show the details of just one step */
          showDetailStep(g_showStatement, detailStep);
          continue;
        }

        if (switchPos("/ STATEMENT_SUMMARY")) { /* non-pip mode only */
          /* Just summarize the statements used in the proof */
          proofStmtSumm(g_showStatement, essentialFlag, texFlag);
          continue;
        }

        /* 21-Jun-2014 */
        if (switchPos("/ SIZE")) { /* non-pip mode only */
          /* Just print the size of the stored proof and continue */
          let(&str1, space(g_Statement[g_showStatement].proofSectionLen));
          memcpy(str1, g_Statement[g_showStatement].proofSectionPtr,
              (size_t)(g_Statement[g_showStatement].proofSectionLen));
          n = instr(1, str1, "$.");
          if (n == 0) {
            /* The original source truncates the proof before $. */
            n = g_Statement[g_showStatement].proofSectionLen;
          } else {
            /* If a proof is saved, it includes the $. (Should we
               revisit or document better how/why this is done?) */
            n = n - 1;
          }
          print2("The proof source for \"%s\" has %ld characters.\n",
              g_Statement[g_showStatement].labelName, n);
          continue;
        }

        if (switchPos("/ PACKED") || switchPos("/ NORMAL") ||
            switchPos("/ COMPRESSED") || switchPos("/ EXPLICIT") || saveFlag) {
          /*??? Add error msg if other switches were specified. (Ignore them.)*/

          /* 16-Aug-2016 nm */
          if (saveFlag) {
            if (printTime == 1) {
              getRunTime(&timeIncr);  /* This call just resets the time */
            }
          }

          if (!pipFlag) {
            outStatement = g_showStatement;
          } else {
            outStatement = g_proveStatement;
          }

          explicitTargets = (switchPos("/ EXPLICIT") != 0) ? 1 : 0;

          /* Get the amount to indent the proof by */
          indentation = 2 + getSourceIndentation(outStatement);

          if (!pipFlag) {
            parseProof(g_showStatement);  /* Prints message if severe error */
            if (g_WrkProof.errorSeverity > 1) {  /* 21-Aug-04 nm */
                              /* Prevent bugtrap in nmbrSquishProof -> */
                              /* nmbrGetSubProofLen if proof corrupted */
              print2(
          "?The proof has a severe error and cannot be displayed or saved.\n");
              continue;
            }
            /* verifyProof(g_showStatement); */ /* Not necessary */
            if (fastFlag) { /* 2-Mar-2016 nm */
              /* 2-Mar-2016 nm */
              /* Use the proof as is */
              nmbrLet(&nmbrSaveProof, g_WrkProof.proofString);
            } else {
              /* Make sure the proof is uncompressed */
              nmbrLet(&nmbrSaveProof, nmbrUnsquishProof(g_WrkProof.proofString));
            }
          } else {
            nmbrLet(&nmbrSaveProof, g_ProofInProgress.proof);
          }
          if (switchPos("/ PACKED")  || switchPos("/ COMPRESSED")) {
            if (!fastFlag) { /* 2-Mar-2016 nm */
              nmbrLet(&nmbrSaveProof, nmbrSquishProof(nmbrSaveProof));
            }
          }

          if (switchPos("/ COMPRESSED")) {
            let(&str1, compressProof(nmbrSaveProof,
                outStatement, /* g_showStatement or g_proveStatement based on pipFlag */
                (switchPos("/ OLD_COMPRESSION")) ? 1 : 0  /* 27-Dec-2013 nm */
                ));
          } else {
            let(&str1, nmbrCvtRToVString(nmbrSaveProof,
                /* 25-Jan-2016 nm */
                explicitTargets, /*explicitTargets*/
                outStatement /*statemNum, used only if explicitTargets*/));
          }


          if (saveFlag) {
            /* ??? This is a problem when mixing html and save proof */
            if (g_printString[0]) bug(1114);
            let(&g_printString, "");

            /* Set flag for print2() to put output in g_printString instead
               of displaying it on the screen */
            g_outputToString = 1;

          } else {
            if (!print2("Proof of \"%s\":\n", g_Statement[outStatement].labelName))
              break; /* Break for speedup if user quit */
            print2(
"---------Clip out the proof below this line to put it in the source file:\n");
            /* 19-Apr-2015 so */
            /* 24-Apr-2015 nm Reverted */
            /*print2("\n");*/ /* Add a blank line to make clipping easier */
          }
          if (switchPos("/ COMPRESSED")) {
            printLongLine(cat(space(indentation), str1, " $.", NULL),
              space(indentation), "& "); /* "&" is special flag to break
                  compressed part of proof anywhere */
          } else {
            printLongLine(cat(space(indentation), str1," $.", NULL),
              space(indentation), " ");
          }

          /* 24-Apr-2015 nm */
          l = (long)(strlen(str1)); /* Save length for printout below */

          /* 3-May-2017 nm Rewrote the if block below */
          /* 12-Jun-2011 nm Removed pipFlag condition so that a date
             stamp will always be created if it doesn't exist */
          if /*(pipFlag)*/ (1  /* Add the date proof was created */
              /* 19-Apr-2015 so */
              /* SOREAR Only generate date if the proof looks complete.
                 This is not intended as a grading mechanism, just trying
                 to avoid premature output */
              && !nmbrElementIn(1, nmbrSaveProof, -(long)'?')) {

            /* 3-May-2017 nm */
            /* Add a "(Contributed by...)" date if it isn't there */
            let(&str2, "");
            str2 = getContrib(outStatement, CONTRIBUTOR);
            if (str2[0] == 0) { /* The is no contributor, so add one */

              /* 14-May-2017 nm */
              /* See if there is a date below the proof (for converting old
                 .mm files).  Someday this will be obsolete, with str3 and
                 str4 always returned as "". */
              getProofDate(outStatement, &str3, &str4);
              /* If there are two dates below the proof, the first on is
                 the revision date and the second the "Contributed by" date. */
              if (str4[0] != 0) { /* There are 2 dates below the proof */
                let(&str5, str3); /* 1st date is Revised by... */
                let(&str3, str4); /* 2nd date is Contributed by... */
              } else {
                let(&str5, "");
              }
              /* If there is no date below proof, use today's date */
              if (str3[0] == 0) let(&str3, date());
              let(&str4, cat("\n", space(indentation + 1),
                  /*"(Contributed by ?who?, ", date(), ".) ", NULL));*/
                  "(Contributed by ", g_contributorName,
                      ", ", str3, ".) ", NULL)); /* 14-May-2017 nm */
              /* If there is a 2nd date below proof, add a "(Revised by..."
                 tag */
              if (str5[0] != 0) {
                /* Use the DEFAULT_CONTRIBUTOR ?who? because we don't
                   know the reviser name (using the contributor name may
                   be incorrect).  Also, this will trigger a warning in
                   VERIFY MARKUP since it may be a proof shortener rather than
                   a reviser. */
                let(&str4, cat(str4, "\n", space(indentation + 1),
                    "(Revised by ", DEFAULT_CONTRIBUTOR,
                        ", ", str5, ".) ", NULL)); /* 15-May-2017 nm */
              }

              let(&str3, space(g_Statement[outStatement].labelSectionLen));
              /* str3 will have the statement's label section w/ comment */
              memcpy(str3, g_Statement[outStatement].labelSectionPtr,
                  (size_t)(g_Statement[outStatement].labelSectionLen));
              i = rinstr(str3, "$)");  /* The last "$)" occurrence */
              if (i != 0    /* A description comment exists */
                  && saveFlag) { /* and we are saving the proof */
                /* This isn't a perfect wrapping but we assume
                   'write source .../rewrap' will be done eventually. */
                /* str3 will have the updated comment */
                let(&str3, cat(left(str3, i - 1), str4, right(str3, i), NULL));
                if (g_Statement[outStatement].labelSectionChanged == 1) {
                  /* Deallocate old comment if not original source */
                  let(&str4, ""); /* Deallocate any previous str4 content */
                  str4 = g_Statement[outStatement].labelSectionPtr;
                  let(&str4, ""); /* Deallocate the old content */
                }
                /* Set flag that this is not the original source */
                g_Statement[outStatement].labelSectionChanged = 1;
                g_Statement[outStatement].labelSectionLen = (long)strlen(str3);
                /* We do a direct assignment instead of let(&...) because
                   labelSectionPtr may point to the middle of the giant input
                   file buffer, which we don't want to deallocate */
                g_Statement[outStatement].labelSectionPtr = str3;
                /* Reset str3 without deallocating with let(), since it
                   was assigned to labelSectionPtr */
                str3 = "";
                /* Reset the cache for this statement in getContrib() */
                str3 = getContrib(outStatement, GC_RESET_STMT);
              } /* if i != 0 */

#ifdef DATE_BELOW_PROOF /* 12-May-2017 nm */

              /* Add a date below the proof.  It actually goes in the
                 label section of the next statement; the proof section
                 is not changed. */
              /* (This will become obsolete eventually) */
              let(&str3, space(g_Statement[outStatement + 1].labelSectionLen));
              /* str3 will have the next statement's label section w/ comment */
              memcpy(str3, g_Statement[outStatement + 1].labelSectionPtr,
                  (size_t)(g_Statement[outStatement + 1].labelSectionLen));
              let(&str5, ""); /* We need to guarantee this
                                for the screen printout later */
              if (instr(1, str3, "$( [") == 0) {
                /* There is no date below proof (if there is, don't do
                   anything; if it is wrong, 'verify markup' will check it) */

                /* Save str5 for screen printout later! */
                let(&str5, cat(space(indentation),
                    "$( [", date(), "] $)", NULL)); /* str4 will be used
                                          for the screen printout later */

                if (saveFlag) { /* save proof, not show proof */

                  if (g_Statement[outStatement + 1].labelSectionChanged == 1) {
                    /* Deallocate old comment if not original source */
                    let(&str4, ""); /* Deallocate any previous str4 content */
                    str4 = g_Statement[outStatement + 1].labelSectionPtr;
                    let(&str4, ""); /* Deallocate the old content */
                  }
                  /* str3 starts after the "$." ending the proof, and should
                     start with "\n" */
                  let(&str3, edit(str3, 8/* Discard leading spaces and tabs */));
                  if (str3[0] != '\n') let(&str3, cat("\n", str3, NULL));
                  /* Add the date after the proof */
                  let(&str3, cat("\n", str5, str3, NULL));
                  /* Set flag that this is not the original source */
                  g_Statement[outStatement + 1].labelSectionChanged = 1;
                  g_Statement[outStatement + 1].labelSectionLen
                      = (long)strlen(str3);
                  /* We do a direct assignment instead of let(&...) because
                     labelSectionPtr may point to the middle of the giant input
                     file buffer, which we don't want to deallocate */
                  g_Statement[outStatement + 1].labelSectionPtr = str3;
                  /* Reset str3 without deallocating with let(), since it
                     was assigned to labelSectionPtr */
                  str3 = "";
                  /* Reset the cache for this statement in getContrib() */
                  str3 = getContrib(outStatement + 1, GC_RESET_STMT);
                } /* if saveFlag */
              } /* if (instr(1, str3, "$( [") == 0) */

#endif /* end #ifdef DATE_BELOW_PROOF */ /* 12-May-2017 nm */

            } /* if str2[0] == 0 */

            /* At this point, str4 contains the "$( [date] $)" comment
               if it would have been added to the saved proof, for use
               by "show proof" */


            /********* deleted 3-May-2017 - date below proof is obsolete
            /@ 12-Jun-2011 nm Removed pipFlag condition so that a date
               stamp will always be created if it doesn't exist @/
            if ( /@ pipFlag && @/ !instr(1, str2, "$([")
                /@ 7-Sep-04 Allow both "$([<date>])$" and "$( [<date>] )$" @/
                /@ 19-Apr-2015 so @/
                /@ SOREAR Only generate date if the proof looks complete.
                   This is not intended as a grading mechanism, just trying
                   to avoid premature output @/
                && !nmbrElementIn(1, nmbrSaveProof, -(long)'?')
                && !instr(1, str2, "$( [")) {
            /@ 6/13/98 end @/
              /@ No date stamp existed before.  Create one for today's
                 date.  Note that the characters after "$." at the end of
                 the proof normally go in the labelSection of the next
                 statement, but a special mode in outputStatement() (in
                 mmpars.c) will output the date stamp characters for a saved
                 proof. @/
              /@ print2("%s$([%s]$)\n", space(indentation), date()); @/
              /@ 4/23/04 nm Initialize with a "?" date followed by today's
                 date.  The "?" date can be edited by the user when the
                 proof is becomes "official." @/
              /@print2("%s$([?]$) $([%s]$)\n", space(indentation), date());@/
              /@ 7-Sep-04 Put space around "[<date>]" @/
              /@print2("%s$( [?] $) $( [%s] $)\n", space(indentation), date());@/
              /@ 30-Nov-2013 remove the unknown date placeholder @/
              print2("%s$( [%s] $)\n", space(indentation), date());
            } else {
              if (saveFlag && (instr(1, str2, "$([")
                  /@ 7-Sep-04 Allow both "$([<date>])$" and "$( [<date>] )$" @/
                  || instr(1, str2, "$( ["))) {
                /@ An old date stamp existed, and we're saving the proof to
                   the output file.  Make sure the indentation of the old
                   date stamp (which exists in the labelSection of the
                   next statement) matches the indentation of the saved
                   proof.  To do this, we "delete" the indentation spaces
                   on the old date in the labelSection of the next statement,
                   and we put the actual required indentation spaces at
                   the end of the saved proof.  This is done because the
                   labelSectionPtr of the next statement does not point to
                   an isolated string that can be allocated/deallocated but
                   rather to a place in the input source buffer. @/
                /@ Correct the indentation on old date @/
                while ((g_Statement[outStatement + 1].labelSectionPtr)[0] !=
                    '$') {
                  /@ "Delete" spaces before old date (by moving source
                     buffer pointer forward), and also "delete"
                     the \n that comes before those spaces @/
                  /@ If the proof is saved a 2nd time, this loop will
                     not be entered because the pointer will already be
                     at the "$". @/
                  (g_Statement[outStatement + 1].labelSectionPtr)++;
                  (g_Statement[outStatement + 1].labelSectionLen)--;
                }
                if (!g_outputToString) bug(1115);
                /@ The final \n will not appear in final output (done in
                   outputStatement() in mmpars.c) because the proofSectionLen
                   below is adjusted to omit it.  This will allow the
                   space(indentation) to appear before the old date without an
                   intervening \n. @/
                print2("%s\n", space(indentation));
              }
            }
            ******** end of deletion 3-May-2017 ******/
          } /* if / *(pipFlag)* / (1) */

          if (saveFlag) {
            g_sourceChanged = 1;
            g_proofChanged = 0;
            if (processUndoStack(NULL, PUS_GET_STATUS, "", 0)) {
              /* The UNDO stack may not be empty */
              proofSavedFlag = 1; /* UNDO stack empty no longer reliably
                             indicates that proof hasn't changed */
            }

            /******** deleted 3-May-2017 nm (before proofSectionChanged added)
            /@ ASCII 1 is a flag that string was allocated and not part of
               original source file text buffer @/
            let(&g_printString, cat(chr(1), "\n", g_printString, NULL));
            if (g_Statement[outStatement].proofSectionPtr[-1] == 1) {
              /@ Deallocate old proof if not original source @/
              let(&str1, ""); /@ Deallocate any previous str1 content @/
              str1 = g_Statement[outStatement].proofSectionPtr - 1;
              let(&str1, ""); /@ Deallocate the proof section @/
            }
            g_Statement[outStatement].proofSectionPtr = g_printString + 1;
            /@ Subtr 1 char for ASCII 1 at beg, 1 char for "\n" at the end
               (which is the first char of next statement's labelSection) @/
            g_Statement[outStatement].proofSectionLen
                = (long)strlen(g_printString) - 2;
            /@ Reset g_printString without deallocating @/
            g_printString = "";
            g_outputToString = 0;
            **********/

            /* 3-May-2017 nm */
            /* Add an initial \n which will go after the "$=" and the
               beginning of the proof */
            let(&g_printString, cat("\n", g_printString, NULL));
            if (g_Statement[outStatement].proofSectionChanged == 1) {
              /* Deallocate old proof if not original source */
              let(&str1, ""); /* Deallocate any previous str1 content */
              str1 = g_Statement[outStatement].proofSectionPtr;
              let(&str1, ""); /* Deallocate the proof section */
            }
            /* Set flag that this is not the original source */
            g_Statement[outStatement].proofSectionChanged = 1;
            if (strcmp(" $.\n",
                right(g_printString, (long)strlen(g_printString) - 3))) {
              bug(1128);
            }
            /* Note that g_printString ends with "$.\n", but those 3 characters
               should not be in the proofSection.  (The "$." keyword is
               added between proofSection and next labelSection when the
               output is written by writeOutput.)  Thus we subtract 3
               from the length.  But there is no need to truncate the
               string; later deallocation will take care of the whole
               string. */
            g_Statement[outStatement].proofSectionLen
                = (long)strlen(g_printString) - 3;
            /* We do a direct assignment instead of let(&...) because
               proofSectionPtr may point to the middle of the giant input
               file string, which we don't want to deallocate */
            g_Statement[outStatement].proofSectionPtr = g_printString;
            /* Reset g_printString without deallocating with let(), since it
               was assigned to proofSectionPtr */
            g_printString = "";
            g_outputToString = 0;


            if (!pipFlag) {
              if (!(fastFlag && !strcmp("*", labelMatch))) {
                printLongLine(
                    cat("The proof of \"", g_Statement[outStatement].labelName,
                    "\" has been reformatted and saved internally.",
                    NULL), "", " ");
              }
            } else {
              printLongLine(cat("The new proof of \"", g_Statement[outStatement].labelName,
                  "\" has been saved internally.",
                  NULL), "", " ");
            }

            /* 16-Aug-2016 nm */
            if (printTime == 1) {
              getRunTime(&timeIncr);
              print2("SAVE PROOF run time = %6.2f sec for \"%s\"\n", timeIncr,
                  g_Statement[outStatement].labelName);
            }

          } else {

#ifdef DATE_BELOW_PROOF /* 12-May-2017 nm */

            /* Print the date on the screen if it would be added to the file */
            if (str5[0] != 0) print2("%s\n", str5);

#endif /*#ifdef DATE_BELOW_PROOF*/ /* 12-May-2017 nm */

            /* 19-Apr-2015 so */
            /* 24-Apr-2015 nm Reverted */
            /*print2("\n");*/ /* Add a blank line to make clipping easier */
            print2(cat(
                "---------The proof of \"", g_Statement[outStatement].labelName,
                /* "\" to clip out ends above this line.\n",NULL)); */
                /* 24-Apr-2015 nm */
                "\" (", str((double)l), " bytes) ends above this line.\n", NULL));
          } /* End if saveFlag */
          nmbrLet(&nmbrSaveProof, NULL_NMBRSTRING);
          if (pipFlag) break; /* Only one iteration for NEW_PROOF stuff */
          continue;  /* to next s iteration */
        } /* end if (switchPos("/ PACKED") || switchPos("/ NORMAL") ||
            switchPos("/ COMPRESSED") || switchPos("/ EXPLICIT") || saveFlag) */

        if (saveFlag) bug(1112); /* Shouldn't get here */

        if (!pipFlag) {
          outStatement = g_showStatement;
        } else {
          outStatement = g_proveStatement;
        }
        if (texFlag) {
          g_outputToString = 1; /* Flag for print2 to add to g_printString */
          if (!g_htmlFlag) {
            if (!g_oldTexFlag) {
              /* 14-Sep-2010 nm */
              print2("\\begin{proof}\n");
              print2("\\begin{align}\n");
            } else {
              print2("\n");
              print2("\\vspace{1ex} %%1\n");
              printLongLine(cat("Proof of ",
                  "{\\tt ",
                  asciiToTt(g_Statement[outStatement].labelName),
                  "}:", NULL), "", " ");
              print2("\n");
              print2("\n");
            }
          } else { /* g_htmlFlag */
            bug(1118);
            /*???? The code below is obsolete - now down in show statement*/
            /*
            print2("<CENTER><TABLE BORDER CELLSPACING=0 BGCOLOR=%s>\n",
                MINT_BACKGROUND_COLOR);
            print2("<CAPTION><B>Proof of Theorem <FONT\n");
            printLongLine(cat("   COLOR=", GREEN_TITLE_COLOR, ">",
                asciiToTt(g_Statement[outStatement].labelName),
                "</FONT></B></CAPTION>", NULL), "", "\"");
            print2(
                "<TR><TD><B>Step</B></TD><TD><B>Hyp</B></TD><TD><B>Ref</B>\n");
            print2("</TD><TD><B>Expression</B></TD></TR>\n");
            */
          }
          g_outputToString = 0;
          /* 8/26/99: Obsolete: */
          /* printTexLongMath in typeProof will do this
          fprintf(g_texFilePtr, "%s", g_printString);
          let(&g_printString, "");
          */
          /* 8/26/99: printTeXLongMath now clears g_printString in LaTeX
             mode before starting its output, so we must put out the
             g_printString ourselves here */
          fprintf(g_texFilePtr, "%s", g_printString);
          let(&g_printString, ""); /* We'll clr it anyway */
        } else { /* !texFlag */
          /* Terminal output - display the statement if wildcard is used */
          if (!pipFlag) {
            /* 30-Jan-06 nm Added single-character-match wildcard argument */
            if (instr(1, labelMatch, "*") || instr(1, labelMatch, "?")) {
              if (!print2("Proof of \"%s\":\n", g_Statement[outStatement].labelName))
                break; /* Break for speedup if user quit */
            }
          }
        }


        if (texFlag) print2("Outputting proof of \"%s\"...\n",
            g_Statement[outStatement].labelName);

        typeProof(outStatement,
            pipFlag,
            startStep,
            endStep,
            endIndent,
            essentialFlag,

            /* 1-May-2017 nm */
            /* In SHOW PROOF xxx /TEX, we use renumber steps mode so that
               the first step is step 1.  The step number is checked for
               step 1 in mmwtex.c to prevent a spurious \\ (newline) at the
               start of the proof.  Note that
               SHOW PROOF is not available in HTML mode, so texFlag will
               always mean LaTeX mode here. */
            (texFlag ? 1 : renumberFlag),
            /*was: renumberFlag,*/

            unknownFlag,
            notUnifiedFlag,
            reverseFlag,

            /* 1-May-2017 nm */
            /* In SHOW PROOF xxx /TEX, we use Lemmon mode so that the
               hypothesis reference list will be available.  Note that
               SHOW PROOF is not available in HTML mode, so texFlag will
               always mean LaTeX mode here. */
            (texFlag ? 1 : noIndentFlag),
            /*was: noIndentFlag,*/

            splitColumn,
            skipRepeatedSteps, /* 28-Jun-2013 nm */
            texFlag,
            g_htmlFlag);
        if (texFlag) {
          if (!g_htmlFlag) {
            /* 14-Sep-2010 nm */
            if (!g_oldTexFlag) {
              g_outputToString = 1;
              print2("\\end{align}\n");
              print2("\\end{proof}\n");
              print2("\n");
              g_outputToString = 0;
              fprintf(g_texFilePtr, "%s", g_printString);
              let(&g_printString, "");
            } else {
            }
          } else { /* g_htmlFlag */
            g_outputToString = 1;
            print2("</TABLE>\n");
            print2("</CENTER>\n");
            /* print trailer will close out string later */
            g_outputToString = 0;
          }
        }

        /*???CLEAN UP */
        /*nmbrLet(&g_WrkProof.proofString, nmbrSaveProof);*/

        /*E*/ if (0) { /* for debugging: */
          printLongLine(nmbrCvtRToVString(g_WrkProof.proofString,
                /* 25-Jan-2016 nm */
                0, /*explicitTargets*/
                0 /*statemNum, used only if explicitTargets*/)," "," ");
          print2("\n");

          nmbrLet(&nmbrSaveProof, nmbrSquishProof(g_WrkProof.proofString));
          printLongLine(nmbrCvtRToVString(nmbrSaveProof,
                /* 25-Jan-2016 nm */
                0, /*explicitTargets*/
                0 /*statemNum, used only if explicitTargets*/)," "," ");
          print2("\n");

          nmbrLet(&nmbrTmp, nmbrUnsquishProof(nmbrSaveProof));
          printLongLine(nmbrCvtRToVString(nmbrTmp,
                /* 25-Jan-2016 nm */
                0, /*explicitTargets*/
                0 /*statemNum, used only if explicitTargets*/)," "," ");

          nmbrLet(&nmbrTmp, nmbrGetTargetHyp(nmbrSaveProof,g_showStatement));
          printLongLine(nmbrCvtAnyToVString(nmbrTmp)," "," "); print2("\n");

          nmbrLet(&nmbrTmp, nmbrGetEssential(nmbrSaveProof));
          printLongLine(nmbrCvtAnyToVString(nmbrTmp)," "," "); print2("\n");

          cleanWrkProof(); /* Deallocate verifyProof storage */
        } /* end debugging */

        if (pipFlag) break; /* Only one iteration for NEW_PROOF stuff */
      } /* Next stmt */
      if (!q) {
        /* No matching statement was found */
        printLongLine(cat("?There is no $p statement whose label matches \"",
            (cmdMatches("MIDI")) ? g_fullArg[1] : g_fullArg[2],
            "\".  ",
            "Use SHOW LABELS to see list of valid labels.", NULL), "", " ");
      } else {
        if (saveFlag) {
          print2("Remember to use WRITE SOURCE to save changes permanently.\n");
        }
        if (texFlag) {
          print2("The LaTeX source was written to \"%s\".\n", g_texFileName);
         /* 14-Sep-2010 nm Added OLD_TEX */
         g_oldTexFlag = 0;
        }
      }

      continue;
    } /* if (cmdMatches("SHOW/SAVE [NEW_]PROOF" or" MIDI") */


/*E*/ /*???????? DEBUG command for debugging only */
    if (cmdMatches("DBG")) {
      print2("DEBUGGING MODE IS FOR DEVELOPER'S USE ONLY!\n");
      print2("Argument:  %s\n", g_fullArg[1]);
      nmbrLet(&nmbrTmp, parseMathTokens(g_fullArg[1], g_proveStatement));
      for (j = 0; j < 3; j++) {
        print2("Trying depth %ld\n", j);
        nmbrTmpPtr = proveFloating(nmbrTmp, g_proveStatement, j, 0, 0,
            1/*overrideFlag*/, 1/*mathboxFlag*/);
        if (nmbrLen(nmbrTmpPtr)) break;
      }

      print2("Result:  %s\n", nmbrCvtRToVString(nmbrTmpPtr,
                /* 25-Jan-2016 nm */
                0, /*explicitTargets*/
                0 /*statemNum, used only if explicitTargets*/));
      nmbrLet(&nmbrTmpPtr, NULL_NMBRSTRING);

      continue;
    }
/*E*/ /*???????? DEBUG command for debugging only */

    if (cmdMatches("PROVE")) {

      /* 14-Sep-2012 nm */
      /* Get the unique statement matching the g_fullArg[1] pattern */
      i = getStatementNum(g_fullArg[1],
          1/*startStmt*/,
          g_statements + 1  /*maxStmt*/,
          0/*aAllowed*/,
          1/*pAllowed*/,
          0/*eAllowed*/,
          0/*fAllowed*/,
          0/*efOnlyForMaxStmt*/,
          1/*uniqueFlag*/);
      if (i == -1) {
        continue; /* Error msg was provided if not unique */
      }
      g_proveStatement = i;


      /* 10-May-2016 nm */
      /* 1 means to override usage locks */
      overrideFlag = ( (switchPos("/ OVERRIDE")) ? 1 : 0)
         || g_globalDiscouragement == 0;
      if (getMarkupFlag(g_proveStatement, PROOF_DISCOURAGED)) {
        if (overrideFlag == 0) {
          /* print2("\n"); */ /* Enable for more emphasis */
          print2(
 ">>> ?Error: Modification of this statement's proof is discouraged.\n"
              );
          print2(
 ">>> You must use PROVE ... / OVERRIDE to work on it.\n");
          /* print2("\n"); */ /* Enable for more emphasis */
          continue;
        }
      }


      /*********** 14-Sep-2012 replaced by getStatementNum()
      /@??? Make sure only $p statements are allowed. @/
      for (i = 1; i <= g_statements; i++) {
        if (!strcmp(g_fullArg[1],g_Statement[i].labelName)) break;
      }
      if (i > g_statements) {
        printLongLine(cat("?There is no statement with label \"",
            g_fullArg[1], "\".  ",
            "Use SHOW LABELS for a list of valid labels.", NULL), "", " ");
        g_proveStatement = 0;
        continue;
      }
      g_proveStatement = i;
      if (g_Statement[g_proveStatement].type != (char)p_) {
        printLongLine(cat("?Statement \"", g_fullArg[1],
            "\" is not a $p statement.", NULL), "", " ");
        g_proveStatement = 0;
        continue;
      }
      ************** end of 14-Sep-2012 deletion ************/

      print2(
"Entering the Proof Assistant.  HELP PROOF_ASSISTANT for help, EXIT to exit.\n");

      /* Obsolete:
      print2("You will be working on the proof of statement %s:\n",
          g_Statement[g_proveStatement].labelName);
      printLongLine(cat("  $p ", nmbrCvtMToVString(
          g_Statement[g_proveStatement].mathString), NULL), "    ", " ");
      */

      g_PFASmode = 1; /* Set mode for commands here and in mmcmdl.c */
      /* Note:  Proof Assistant mode can equivalently be determined by:
            nmbrLen(g_ProofInProgress.proof) != 0  */

      parseProof(g_proveStatement);
      verifyProof(g_proveStatement); /* Necessary to set RPN stack ptrs
                                      before calling cleanWrkProof() */
      if (g_WrkProof.errorSeverity > 1) {
        print2(
             "The starting proof has a severe error.  It will not be used.\n");
        nmbrLet(&nmbrSaveProof, nmbrAddElement(NULL_NMBRSTRING, -(long)'?'));
      } else {
        nmbrLet(&nmbrSaveProof, g_WrkProof.proofString);
      }
      cleanWrkProof(); /* Deallocate verifyProof storage */

      /* 11-Sep-2016 nm */
      /* Initialize the structure needed for the Proof Assistant */
      initProofStruct(&g_ProofInProgress, nmbrSaveProof, g_proveStatement);

      /****** 11-Sep-2016 nm Replaced by initProofStruct()
      /@ Right now, only non-packed proofs are handled. @/
      nmbrLet(&nmbrSaveProof, nmbrUnsquishProof(nmbrSaveProof));

      /@ Assign initial proof structure @/
      if (nmbrLen(g_ProofInProgress.proof)) bug(1113); /@ Should've been deall.@/
      nmbrLet(&g_ProofInProgress.proof, nmbrSaveProof);
      i = nmbrLen(g_ProofInProgress.proof);
      pntrLet(&g_ProofInProgress.target, pntrNSpace(i));
      pntrLet(&g_ProofInProgress.source, pntrNSpace(i));
      pntrLet(&g_ProofInProgress.user, pntrNSpace(i));
      nmbrLet((nmbrString @@)(&((g_ProofInProgress.target)[i - 1])),
          g_Statement[g_proveStatement].mathString);
      g_pipDummyVars = 0;

      /@ Assign known subproofs @/
      assignKnownSubProofs();

      /@ Initialize remaining steps @/
      for (j = 0; j < i/@proof length@/; j++) {
        if (!nmbrLen((g_ProofInProgress.source)[j])) {
          initStep(j);
        }
      }

      /@ Unify whatever can be unified @/
      autoUnify(0); /@ 0 means no "congrats" message @/
      **** end of 11-Sep-2016 deletion ************/

/*
      print2(
"Periodically save your work with SAVE NEW_PROOF and WRITE SOURCE.\n");
      print2(
"There is no UNDO command yet.  You can OPEN LOG to track what you've done.\n");
*/
      /* Show the user the statement to be proved */
      print2("You will be working on statement (from \"SHOW STATEMENT %s\"):\n",
          g_Statement[g_proveStatement].labelName);
      typeStatement(g_proveStatement /*g_showStatement*/,
          1 /*briefFlag*/,
          0 /*commentOnlyFlag*/,
          0 /*texFlag*/,
          0 /*g_htmlFlag*/);

      if (!nmbrElementIn(1, g_ProofInProgress.proof, -(long)'?')) {
        print2(
        "Note:  The proof you are starting with is already complete.\n");
      } else {

        print2(
     "Unknown step summary (from \"SHOW NEW_PROOF / UNKNOWN\"):\n");
        /* 6/14/98 - Automatically display new unknown steps
         ???Future - add switch to enable/defeat this */
        typeProof(g_proveStatement,
            1 /*pipFlag*/,
            0 /*startStep*/,
            0 /*endStep*/,
            0 /*endIndent*/,
            1 /*essentialFlag*/,
            0 /*renumberFlag*/,
            1 /*unknownFlag*/,
            0 /*notUnifiedFlag*/,
            0 /*reverseFlag*/,
            0 /*noIndentFlag*/,
            0 /*splitColumn*/,
            0 /*skipRepeatedSteps*/, /* 28-Jun-2013 nm */
            0 /*texFlag*/,
            0 /*g_htmlFlag*/);
        /* 6/14/98 end */
      }

      /* 3-May-2016 nm */
      if (getMarkupFlag(g_proveStatement, PROOF_DISCOURAGED)) {
        /* print2("\n"); */ /* Enable for more emphasis */
        print2(
">>> ?Warning: Modification of this statement's proof is discouraged.\n"
            );
        /*
        print2(
">>> You must use SAVE NEW_PROOF ... / OVERRIDE to save it.\n");
        */
        /* print2("\n"); */ /* Enable for more emphasis */
      }

      processUndoStack(NULL, PUS_INIT, "", 0); /* Optional? */
      /* Put the initial proof into the UNDO stack; we don't need
         the info string since it won't be undone */
      processUndoStack(&g_ProofInProgress, PUS_PUSH, "", 0);
      continue;
    }


    /* 1-Nov-2013 nm Added UNDO */
    if (cmdMatches("UNDO")) {
      processUndoStack(&g_ProofInProgress, PUS_UNDO, "", 0);
      g_proofChanged = 1; /* Maybe make this more intelligent some day */
      /* 6/14/98 - Automatically display new unknown steps
         ???Future - add switch to enable/defeat this */
      typeProof(g_proveStatement,
          1 /*pipFlag*/,
          0 /*startStep*/,
          0 /*endStep*/,
          0 /*endIndent*/,
          1 /*essentialFlag*/,
          0 /*renumberFlag*/,
          1 /*unknownFlag*/,
          0 /*notUnifiedFlag*/,
          0 /*reverseFlag*/,
          0 /*noIndentFlag*/,
          0 /*splitColumn*/,
          0 /*skipRepeatedSteps*/, /* 28-Jun-2013 nm */
          0 /*texFlag*/,
          0 /*g_htmlFlag*/);
      /* 6/14/98 end */
      continue;
    }

    /* 1-Nov-2013 nm Added REDO */
    if (cmdMatches("REDO")) {
      processUndoStack(&g_ProofInProgress, PUS_REDO, "", 0);
      g_proofChanged = 1; /* Maybe make this more intelligent some day */
      /* 6/14/98 - Automatically display new unknown steps
         ???Future - add switch to enable/defeat this */
      typeProof(g_proveStatement,
          1 /*pipFlag*/,
          0 /*startStep*/,
          0 /*endStep*/,
          0 /*endIndent*/,
          1 /*essentialFlag*/,
          0 /*renumberFlag*/,
          1 /*unknownFlag*/,
          0 /*notUnifiedFlag*/,
          0 /*reverseFlag*/,
          0 /*noIndentFlag*/,
          0 /*splitColumn*/,
          0 /*skipRepeatedSteps*/, /* 28-Jun-2013 nm */
          0 /*texFlag*/,
          0 /*g_htmlFlag*/);
      /* 6/14/98 end */
      continue;
    }

    if (cmdMatches("UNIFY")) {
      m = nmbrLen(g_ProofInProgress.proof); /* Original proof length */
      g_proofChangedFlag = 0;
      if (cmdMatches("UNIFY STEP")) {

        s = (long)val(g_fullArg[2]); /* Step number */
        if (s > m || s < 1) {
          print2("?The step must be in the range from 1 to %ld.\n", m);
          continue;
        }

        interactiveUnifyStep(s - 1, 1); /* 2nd arg. means print msg if
                                           already unified */

        /* (The interactiveUnifyStep handles all messages.) */
        /* print2("... */

        autoUnify(1);
        if (g_proofChangedFlag) {
          g_proofChanged = 1; /* Cumulative flag */
          processUndoStack(&g_ProofInProgress, PUS_PUSH, g_fullArgString, 0);
        }
        continue;
      }

      /* "UNIFY ALL" */
      if (!switchPos("/ INTERACTIVE")) {
        autoUnify(1);
        if (!g_proofChangedFlag) {
          print2("No new unifications were made.\n");
        } else {
          print2(
  "Steps were unified.  SHOW NEW_PROOF / NOT_UNIFIED to see any remaining.\n");
          g_proofChanged = 1; /* Cumulative flag */
          processUndoStack(&g_ProofInProgress, PUS_PUSH, g_fullArgString, 0);
        }
      } else {
        q = 0;
        while (1) {
          /* Repeat the unifications over and over until done, since
             a later unification may improve the ability of an aborted earlier
             one to be done without timeout */
          g_proofChangedFlag = 0; /* This flag is set by autoUnify() and
                                   interactiveUnifyStep() */
          autoUnify(0);
          for (s = m - 1; s >= 0; s--) {
            interactiveUnifyStep(s, 0); /* 2nd arg. means no msg if already
                                           unified */
          }
          autoUnify(1); /* 1 means print congratulations if complete */
          if (!g_proofChangedFlag) {
            if (!q) {
              print2("No new unifications were made.\n");
            } else {
              /* If q=1, then we are in the 2nd or later pass, which means
                 there was a change in the 1st pass. */
              print2(
  "Steps were unified.  SHOW NEW_PROOF / NOT_UNIFIED to see any remaining.\n");
              g_proofChanged = 1; /* Cumulative flag */
              processUndoStack(&g_ProofInProgress, PUS_PUSH, g_fullArgString, 0);
            }
            break; /* while (1) */
          } else {
            q = 1; /* Flag that we're starting a 2nd or later pass */
          }
        } /* End while (1) */
      }
      /* 6/14/98 - Automatically display new unknown steps
         ???Future - add switch to enable/defeat this */
      typeProof(g_proveStatement,
          1 /*pipFlag*/,
          0 /*startStep*/,
          0 /*endStep*/,
          0 /*endIndent*/,
          1 /*essentialFlag*/,
          0 /*renumberFlag*/,
          1 /*unknownFlag*/,
          0 /*notUnifiedFlag*/,
          0 /*reverseFlag*/,
          0 /*noIndentFlag*/,
          0 /*splitColumn*/,
          0 /*skipRepeatedSteps*/, /* 28-Jun-2013 nm */
          0 /*texFlag*/,
          0 /*g_htmlFlag*/);
      /* 6/14/98 end */
      continue;
    }

    if (cmdMatches("MATCH")) {

      maxEssential = -1; /* Default:  no maximum */
      i = switchPos("/ MAX_ESSENTIAL_HYP");
      if (i) maxEssential = (long)val(g_fullArg[i + 1]);

      if (cmdMatches("MATCH STEP")) {

        s = (long)val(g_fullArg[2]); /* Step number */
        m = nmbrLen(g_ProofInProgress.proof); /* Original proof length */
        if (s > m || s < 1) {
          print2("?The step must be in the range from 1 to %ld.\n", m);
          continue;
        }
        if ((g_ProofInProgress.proof)[s - 1] != -(long)'?') {
          print2(
    "?Step %ld is already assigned.  Only unknown steps can be matched.\n", s);
          continue;
        }

        interactiveMatch(s - 1, maxEssential);
        n = nmbrLen(g_ProofInProgress.proof); /* New proof length */
        if (n != m) {
          if (s != m) {
            printLongLine(cat("Steps ", str((double)s), ":",
                str((double)m), " are now ", str((double)(s - m + n)), ":",
                str((double)n), ".",
                NULL),
                "", " ");
          } else {
            printLongLine(cat("Step ", str((double)m), " is now step ", str((double)n), ".",
                NULL),
                "", " ");
          }
        }

        autoUnify(1);
        g_proofChanged = 1; /* Cumulative flag */
        /* 1-Nov-2013 nm Why is g_proofChanged set unconditionally above?
           Do we need the processUndoStack() call? */
        processUndoStack(&g_ProofInProgress, PUS_PUSH, g_fullArgString, 0);

        continue;
      } /* End if MATCH STEP */

      if (cmdMatches("MATCH ALL")) {

        m = nmbrLen(g_ProofInProgress.proof); /* Original proof length */

        k = 0;
        g_proofChangedFlag = 0;

        if (switchPos("/ ESSENTIAL")) {
          nmbrLet(&nmbrTmp, nmbrGetEssential(g_ProofInProgress.proof));
        }

        for (s = m; s > 0; s--) {
          /* Match only unknown steps */
          if ((g_ProofInProgress.proof)[s - 1] != -(long)'?') continue;
          /* Match only essential steps if specified */
          if (switchPos("/ ESSENTIAL")) {
            if (!nmbrTmp[s - 1]) continue;
          }

          interactiveMatch(s - 1, maxEssential);
          if (g_proofChangedFlag) {
            k = s; /* Save earliest step changed */
            g_proofChangedFlag = 0;
          }
          print2("\n");
        }
        if (k) {
          g_proofChangedFlag = 1; /* Restore it */
          g_proofChanged = 1; /* Cumulative flag */
          processUndoStack(&g_ProofInProgress, PUS_PUSH, g_fullArgString, 0);
          print2("Steps %ld and above have been renumbered.\n", k);
        }
        autoUnify(1);

        continue;
      } /* End if MATCH ALL */
    }

    if (cmdMatches("LET")) {

      g_errorCount = 0;
      nmbrLet(&nmbrTmp, parseMathTokens(g_fullArg[4], g_proveStatement));
      if (g_errorCount) {
        /* Parsing issued error message(s) */
        g_errorCount = 0;
        continue;
      }

      if (cmdMatches("LET VARIABLE")) {
        if (((vstring)(g_fullArg[2]))[0] != '$') {
          print2(
   "?The target variable must be of the form \"$<integer>\", e.g. \"$23\".\n");
          continue;
        }
        n = (long)val(right(g_fullArg[2], 2));
        if (n < 1 || n > g_pipDummyVars) {
          print2("?The target variable must be between $1 and $%ld.\n",
              g_pipDummyVars);
          continue;
        }

        replaceDummyVar(n, nmbrTmp);

        autoUnify(1);


        g_proofChangedFlag = 1; /* Flag to push 'undo' stack */
        g_proofChanged = 1; /* Cumulative flag */
        processUndoStack(&g_ProofInProgress, PUS_PUSH, g_fullArgString, 0);

      }

      if (cmdMatches("LET STEP")) {

        /* 14-Sep-2012 nm */
        s = getStepNum(g_fullArg[2], g_ProofInProgress.proof,
            0 /* ALL not allowed */);
        if (s == -1) continue;  /* Error; message was provided already */

        /************** 14-Sep-2012 replaced by getStepNum()
        s = (long)val(g_fullArg[2]); /@ Step number @/

        /@ 16-Apr-06 nm Added LET STEP n where n <= 0: 0 = last,
           -1 = penultimate, etc. _unknown_ step @/
        /@ Unlike ASSIGN LAST/FIRST and IMPROVE LAST/FIRST, it probably
           doesn't make sense to add LAST/FIRST to LET STEP since known
           steps can also be LET.  The main purpose of LET STEP n, n<=0, is
           to use with scripting for mmj2 imports. @/
        offset = 0;
        if (s <= 0) {
          offset = - s + 1;
          s = 1; /@ Temp. until we figure out which step @/
        }
        /@ End of 16-Apr-06 @/

        m = nmbrLen(g_ProofInProgress.proof); /@ Original proof length @/
        if (s > m || s < 1) {
          print2("?The step must be in the range from 1 to %ld.\n", m);
          continue;
        }

        /@ 16-Apr-06 nm Added LET STEP n where n <= 0: 0 = last,
           1 = penultimate, etc. _unknown_ step @/
        if (offset > 0) {  /@ step <= 0 @/
          /@ Get the essential step flags @/
          s = 0; /@ Use as flag that step was found @/
          nmbrLet(&essentialFlags, nmbrGetEssential(g_ProofInProgress.proof));
          /@ Scan proof backwards until last essential unknown step found @/
          /@ 16-Apr-06 - count back 'offset' unknown steps @/
          j = offset;
          for (i = m; i >= 1; i--) {
            if (essentialFlags[i - 1]
                && (g_ProofInProgress.proof)[i - 1] == -(long)'?') {
              j--;
              if (j == 0) {
                /@ Found it @/
                s = i;
                break;
              }
            }
          } /@ Next i @/
          if (s == 0) {
            if (offset == 1) {
              print2("?There are no unknown essential steps.\n");
            } else {
              print2("?There are not at least %ld unknown essential steps.\n",
                offset);
            }
            continue;
          }
        } /@ if offset > 0 @/
        /@ End of 16-Apr-06 @/
        ******************** end 14-Sep-2012 deletion ********/

        /* Check to see if the statement selected is allowed */
        if (!checkMStringMatch(nmbrTmp, s - 1)) {
          printLongLine(cat("?Step ", str((double)s), " cannot be unified with \"",
              nmbrCvtMToVString(nmbrTmp),"\".", NULL), " ", " ");
          continue;
        }

        /* Assign the user string */
        nmbrLet((nmbrString **)(&((g_ProofInProgress.user)[s - 1])), nmbrTmp);

        autoUnify(1);
        g_proofChangedFlag = 1; /* Flag to push 'undo' stack */
        g_proofChanged = 1; /* Cumulative flag */
        processUndoStack(&g_ProofInProgress, PUS_PUSH, g_fullArgString, 0);
      }
      /* 6/14/98 - Automatically display new unknown steps
         ???Future - add switch to enable/defeat this */
      typeProof(g_proveStatement,
          1 /*pipFlag*/,
          0 /*startStep*/,
          0 /*endStep*/,
          0 /*endIndent*/,
          1 /*essentialFlag*/,
          0 /*renumberFlag*/,
          1 /*unknownFlag*/,
          0 /*notUnifiedFlag*/,
          0 /*reverseFlag*/,
          0 /*noIndentFlag*/,
          0 /*splitColumn*/,
          0 /*skipRepeatedSteps*/, /* 28-Jun-2013 nm */
          0 /*texFlag*/,
          0 /*g_htmlFlag*/);
      /* 6/14/98 end */
      continue;
    }


    if (cmdMatches("ASSIGN")) {

      /* 10/4/99 - Added LAST - this means the last unknown step shown
         with SHOW NEW_PROOF/ESSENTIAL/UNKNOWN */
      /* 11-Dec-05 nm - Added FIRST - this means the first unknown step shown
         with SHOW NEW_PROOF/ESSENTIAL/UNKNOWN */
      /* 16-Apr-06 nm - Handle nonpositive step number: 0 = last,
         -1 = penultimate, etc.*/
      /* 14-Sep-2012 nm All the above now done by getStepNum() */
      s = getStepNum(g_fullArg[1], g_ProofInProgress.proof,
          0 /* ALL not allowed */);
      if (s == -1) continue;  /* Error; message was provided already */

      /* 3-May-2016 nm */
      /* 1 means to override usage locks */
      overrideFlag = ( (switchPos("/ OVERRIDE")) ? 1 : 0)
         || g_globalDiscouragement == 0;

      /****** replaced by getStepNum()  nm 14-Sep-2012
      offset = 0; /@ 16-Apr-06 @/
      let(&str1, g_fullArg[1]); /@ To avoid void pointer problems with g_fullArg @/
      if (toupper((unsigned char)(str1[0])) == 'L'
          || toupper((unsigned char)(str1[0])) == 'F') {
                                          /@ "ASSIGN LAST" or "ASSIGN FIRST" @/
                                          /@ 11-Dec-05 nm @/
        s = 1; /@ Temporary until we figure out which step @/
        offset = 1;          /@ 16-Apr-06 @/
      } else {
        s = (long)val(g_fullArg[1]); /@ Step number @/
        if (strcmp(g_fullArg[1], str((double)s))) {
          print2("?Expected either a number or FIRST or LAST after ASSIGN.\n");
                                                             /@ 11-Dec-05 nm @/
          continue;
        }
        if (s <= 0) {         /@ 16-Apr-06 @/
          offset = - s + 1;   /@ 16-Apr-06 @/
          s = 1; /@ Temporary until we figure out which step @/ /@ 16-Apr-06 @/
        }                     /@ 16-Apr-06 @/
      }
      ******************** end 14-Sep-2012 deletion ********/

      /* 14-Sep-2012 nm */
      /* Get the unique statement matching the g_fullArg[2] pattern */
      k = getStatementNum(g_fullArg[2],
          1/*startStmt*/,
          g_proveStatement  /*maxStmt*/,
          1/*aAllowed*/,
          1/*pAllowed*/,
          1/*eAllowed*/,
          1/*fAllowed*/,
          1/*efOnlyForMaxStmt*/,
          1/*uniqueFlag*/);
      if (k == -1) {
        continue; /* Error msg was provided if not unique */
      }

      /*********** 14-Sep-2012 replaced by getStatementNum()
      for (i = 1; i <= g_statements; i++) {
        if (!strcmp(g_fullArg[2], g_Statement[i].labelName)) {
          /@ If a $e or $f, it must be a hypothesis of the statement
             being proved @/
          if (g_Statement[i].type == (char)e_ || g_Statement[i].type == (char)f_){
            if (!nmbrElementIn(1, g_Statement[g_proveStatement].reqHypList, i) &&
                !nmbrElementIn(1, g_Statement[g_proveStatement].optHypList, i))
                continue;
          }
          break;
        }
      }
      if (i > g_statements) {
        printLongLine(cat("?The statement with label \"",
            g_fullArg[2],
            "\" was not found or is not a hypothesis of the statement ",
            "being proved.  ",
            "Use SHOW LABELS for a list of valid labels.", NULL), "", " ");
        continue;
      }
      k = i;

      if (k >= g_proveStatement) {
        print2(
   "?You must specify a statement that occurs earlier the one being proved.\n");
        continue;
      }
      ***************** end of 14-Sep-2012 deletion ************/

      /* 3-May-2016 nm */
      if (getMarkupFlag(k, USAGE_DISCOURAGED)) {
        if (overrideFlag == 0) {
          /* print2("\n"); */ /* Enable for more emphasis */
          print2(
">>> ?Error: Attempt to assign a statement whose usage is discouraged.\n");
          print2(
       ">>> Use ASSIGN ... / OVERRIDE if you really want to do this.\n");
          /* print2("\n"); */ /* Enable for more emphasis */
          continue;
        } else {
          /* print2("\n"); */ /* Enable for more emphasis */
          print2(
">>> ?Warning: You are assigning a statement whose usage is discouraged.\n");
          /* print2("\n"); */ /* Enable for more emphasis */
        }
      }

      m = nmbrLen(g_ProofInProgress.proof); /* Original proof length */


      /****** replaced by getStepNum()  nm 14-Sep-2012
      if (s > m || s < 1) {
        print2("?The step must be in the range from 1 to %ld.\n", m);
        continue;
      }

      /@ 10/4/99 - For ASSIGN FIRST/LAST command, figure out the last unknown
         essential step @/                      /@ 11-Dec-05 nm - Added LAST @/
      /@if (toupper(str1[0]) == 'L' || toupper(str1[0]) == 'F') {@/
                                /@ "ASSIGN LAST or FIRST" @/ /@ 11-Dec-05 nm @/
      if (offset > 0) {  /@ LAST, FIRST, or step <= 0 @/ /@ 16-Apr-06 @/
        /@ Get the essential step flags @/
        s = 0; /@ Use as flag that step was found @/
        nmbrLet(&essentialFlags, nmbrGetEssential(g_ProofInProgress.proof));
        /@ if (toupper((unsigned char)(str1[0])) == 'L') { @/
        if (toupper((unsigned char)(str1[0])) != 'F') {   /@ 16-Apr-06 @/
          /@ Scan proof backwards until last essential unknown step is found @/
          /@ 16-Apr-06 - count back 'offset' unknown steps @/
          j = offset;      /@ 16-Apr-06 @/
          for (i = m; i >= 1; i--) {
            if (essentialFlags[i - 1]
                && (g_ProofInProgress.proof)[i - 1] == -(long)'?') {
              j--;          /@ 16-Apr-06 @/
              if (j == 0) {  /@ 16-Apr-06 @/
                /@ Found it @/
                s = i;
                break;
              }             /@ 16-Apr-06 @/
            }
          } /@ Next i @/
        } else {
          /@ 11-Dec-05 nm Added ASSIGN FIRST @/
          /@ Scan proof forwards until first essential unknown step is found @/
          for (i = 1; i <= m; i++) {
            if (essentialFlags[i - 1]
                && (g_ProofInProgress.proof)[i - 1] == -(long)'?') {
              /@ Found it @/
              s = i;
              break;
            }
          } /@ Next i @/
        }
        if (s == 0) {
          if (offset == 1) {                                /@ 16-Apr-06 @/
            print2("?There are no unknown essential steps.\n");
          } else {                                          /@ 16-Apr-06 @/
            print2("?There are not at least %ld unknown essential steps.\n",
              offset);                                      /@ 16-Apr-06 @/
          }                                                 /@ 16-Apr-06 @/
          continue;
        }
      }
      ******************** end 14-Sep-2012 deletion ********/

      /* Check to see that the step is an unknown step */
      if ((g_ProofInProgress.proof)[s - 1] != -(long)'?') {
        print2(
        "?Step %ld is already assigned.  You can only assign unknown steps.\n"
            , s);
        continue;
      }

      /* Check to see if the statement selected is allowed */
      if (!checkStmtMatch(k, s - 1)) {
        print2("?Statement \"%s\" cannot be unified with step %ld.\n",
          g_Statement[k].labelName, s);
        continue;
      }

      assignStatement(k /*statement#*/, s - 1 /*step*/);

      n = nmbrLen(g_ProofInProgress.proof); /* New proof length */
      autoUnify(1);

      /* Automatically interact with user if step not unified */
      /* ???We might want to add a setting to defeat this if user doesn't
         like it */
      /* 8-Apr-05 nm Since ASSIGN LAST is often run from a commmand file, don't
         interact if / NO_UNIFY is specified, so response is predictable */
      if (switchPos("/ NO_UNIFY") == 0) {
        interactiveUnifyStep(s - m + n - 1, 2); /* 2nd arg. means print msg if
                                                 already unified */
      } /* if NO_UNIFY flag not set */

      /******* 8-Apr-05 nm Commented out:
      if (m == n) {
        print2("Step %ld was assigned statement %s.\n",
          s, g_Statement[k].labelName);
      } else {
        if (s != m) {
          printLongLine(cat("Step ", str((double)s),
              " was assigned statement ", g_Statement[k].labelName,
              ".  Steps ", str((double)s), ":",
              str((double)m), " are now ", str((double)(s - m + n)), ":", str((double)n), ".",
              NULL),
              "", " ");
        } else {
          printLongLine(cat("Step ", str((double)s),
              " was assigned statement ", g_Statement[k].labelName,
              ".  Step ", str((double)m), " is now step ", str((double)n), ".",
              NULL),
              "", " ");
        }
      }
      *********/
      /* 8-Apr-05 nm Added: */
      /* 1-Nov-2013 nm No longer needed because of UNDO
      printLongLine(cat("To undo the assignment, DELETE STEP ",
              str((double)(s - m + n)), " and if needed INITIALIZE, UNIFY.",
              NULL),
              "", " ");
      */

      /* 5-Aug-2020 nm */
      /* See if it's in another mathbox; if so, let user know */
      assignMathboxInfo();
      if (k > g_mathboxStmt && g_proveStatement > g_mathboxStmt) {
        if (k < g_mathboxStart[getMathboxNum(g_proveStatement) - 1]) {
          printLongLine(cat("\"", g_Statement[k].labelName,
                "\" is in the mathbox for ",
                g_mathboxUser[getMathboxNum(k) - 1], ".",
                NULL),
              "", " ");
        }
      }

      /* 6/14/98 - Automatically display new unknown steps
         ???Future - add switch to enable/defeat this */
      typeProof(g_proveStatement,
          1 /*pipFlag*/,
          0 /*startStep*/,
          0 /*endStep*/,
          0 /*endIndent*/,
          1 /*essentialFlag*/,
          0 /*renumberFlag*/,
          1 /*unknownFlag*/,
          0 /*notUnifiedFlag*/,
          0 /*reverseFlag*/,
          0 /*noIndentFlag*/,
          0 /*splitColumn*/,
          0 /*skipRepeatedSteps*/, /* 28-Jun-2013 nm */
          0 /*texFlag*/,
          0 /*g_htmlFlag*/);
      /* 6/14/98 end */


      g_proofChangedFlag = 1; /* Flag to push 'undo' stack (future) */
      g_proofChanged = 1; /* Cumulative flag */
      processUndoStack(&g_ProofInProgress, PUS_PUSH, g_fullArgString, 0);
      continue;

    } /* cmdMatches("ASSIGN") */


    if (cmdMatches("REPLACE")) {

      /* s = (long)val(g_fullArg[1]);  obsolete */ /* Step number */

      /* 3-May-2016 nm */
      /* 1 means to override usage locks */
      overrideFlag = ( (switchPos("/ OVERRIDE")) ? 1 : 0)
         || g_globalDiscouragement == 0;

      /* 14-Sep-2012 nm */
      step = getStepNum(g_fullArg[1], g_ProofInProgress.proof,
          0 /* ALL not allowed */);
      if (step == -1) continue;  /* Error; message was provided already */

      /* This limitation is due to the assignKnownSteps call below which
         does not tolerate unknown steps. */
      /******* 10/20/02  Limitation removed
      if (nmbrElementIn(1, g_ProofInProgress.proof, -(long)'?')) {
        print2("?The proof must be complete before you can use REPLACE.\n");
        continue;
      }
      *******/

      /* 14-Sep-2012 nm */
      /* Get the unique statement matching the g_fullArg[2] pattern */
      stmt = getStatementNum(g_fullArg[2],
          1/*startStmt*/,
          g_proveStatement  /*maxStmt*/,
          1/*aAllowed*/,
          1/*pAllowed*/,
          0/*eAllowed*/, /* Not implemented (yet?) */
          0/*fAllowed*/, /* Not implemented (yet?) */
          1/*efOnlyForMaxStmt*/,
          1/*uniqueFlag*/);
      if (stmt == -1) {
        continue; /* Error msg was provided if not unique */
      }

      /*********** 14-Sep-2012 replaced by getStatementNum()
      for (i = 1; i <= g_statements; i++) {
        if (!strcmp(g_fullArg[2], g_Statement[i].labelName)) {
          /@ If a $e or $f, it must be a hypothesis of the statement
             being proved @/
          if (g_Statement[i].type == (char)e_ || g_Statement[i].type == (char)f_){
            if (!nmbrElementIn(1, g_Statement[g_proveStatement].reqHypList, i) &&
                !nmbrElementIn(1, g_Statement[g_proveStatement].optHypList, i))
                continue;
          }
          break;
        }
      }
      if (i > g_statements) {
        printLongLine(cat("?The statement with label \"",
            g_fullArg[2],
            "\" was not found or is not a hypothesis of the statement ",
            "being proved.  ",
            "Use SHOW LABELS for a list of valid labels.", NULL), "", " ");
        continue;
      }
      k = i;

      if (k >= g_proveStatement) {
        print2(
   "?You must specify a statement that occurs earlier the one being proved.\n");
        continue;
      }
      ****************************** end of 14-Sep-2012 deletion *********/

      /* 3-May-2016 nm */
      if (getMarkupFlag(stmt, USAGE_DISCOURAGED)) {
        if (overrideFlag == 0) {
          /* print2("\n"); */ /* Enable for more emphasis */
          print2(
">>> ?Error: Attempt to assign a statement whose usage is discouraged.\n");
          print2(
       ">>> Use REPLACE ... / OVERRIDE if you really want to do this.\n");
          /* print2("\n"); */ /* Enable for more emphasis */
          continue;
        } else {
          /* print2("\n"); */ /* Enable for more emphasis */
          print2(
">>> ?Warning: You are assigning a statement whose usage is discouraged.\n");
          /* print2("\n"); */ /* Enable for more emphasis */
        }
      }

      m = nmbrLen(g_ProofInProgress.proof); /* Original proof length */

      /************** 14-Sep-2012 replaced by getStepNum()
      if (s > m || s < 1) {
        print2("?The step must be in the range from 1 to %ld.\n", m);
        continue;
      }
      ************* end of 14-Sep-2012 deletion **************/

      /* Check to see that the step is a known step */
      /* 22-Aug-2012 nm This check was deleted because it is unnecessary  */
      /*
      if ((g_ProofInProgress.proof)[s - 1] == -(long)'?') {
        print2(
        "?Step %ld is unknown.  You can only replace known steps.\n"
            , s);
        continue;
      }
      */

      /* 10/20/02  Set a flag that proof has unknown steps (for autoUnify()
         call below) */
      if (nmbrElementIn(1, g_ProofInProgress.proof, -(long)'?')) {
        p = 1;
      } else {
        p = 0;
      }

      /* Check to see if the statement selected is allowed */
      if (!checkStmtMatch(stmt, step - 1)) {
        print2("?Statement \"%s\" cannot be unified with step %ld.\n",
          g_Statement[stmt].labelName, step);
        continue;
      }

      /* 16-Sep-2012 nm */
      /* Check dummy variable status of step */
      /* For use in message later */
      dummyVarIsoFlag = checkDummyVarIsolation(step - 1);
            /* 0=no dummy vars, 1=isolated dummy vars, 2=not isolated*/

      /* Do the replacement */
      nmbrTmpPtr = replaceStatement(stmt /*statement#*/,
          step - 1 /*step*/,
          g_proveStatement,
          0,/*scan whole proof to maximize chance of a match*/
          0/*noDistinct*/,
          1/* try to prove $e's */,
          1/*improveDepth*/,
          overrideFlag,   /* 3-May-2016 nm */
          /* Currently REPLACE (not often used) allows other mathboxes
             silently; TODO: we may want to notify user like for ASSIGN */
          1/*mathboxFlag*/ /* 5-Aug-2020 nm */
          );
      if (!nmbrLen(nmbrTmpPtr)) {
        print2(
           "?Hypotheses of statement \"%s\" do not match known proof steps.\n",
            g_Statement[stmt].labelName);
        continue;
      }

      /* Get the subproof at step s */
      q = subproofLen(g_ProofInProgress.proof, step - 1);
      deleteSubProof(step - 1);
      addSubProof(nmbrTmpPtr, step - q);

      /* 10/20/02 Replaced "assignKnownSteps" with code from entry of PROVE
         command so REPLACE can be done in partial proofs */
      /*assignKnownSteps(s - q, nmbrLen(nmbrTmpPtr));*/  /* old code */
      /* Assign known subproofs */
      assignKnownSubProofs();
      /* Initialize remaining steps */
      i = nmbrLen(g_ProofInProgress.proof);
      for (j = 0; j < i; j++) {
        if (!nmbrLen((g_ProofInProgress.source)[j])) {
          initStep(j);
        }
      }
      /* Unify whatever can be unified */
      /* If proof wasn't complete before (p = 1), but is now, print congrats
         for user */
      autoUnify((char)p); /* 0 means no "congrats" message */
      /* end 10/20/02 */

      nmbrLet(&nmbrTmpPtr, NULL_NMBRSTRING); /* Deallocate memory */

      n = nmbrLen(g_ProofInProgress.proof); /* New proof length */
      if (nmbrElementIn(1, g_ProofInProgress.proof, -(long)'?')) {
        /* The proof is not complete, so print step numbers that changed */
        if (m == n) {
          print2("Step %ld was replaced with statement %s.\n",
            step, g_Statement[stmt].labelName);
        } else {
          if (step != m) {
            printLongLine(cat("Step ", str((double)step),
                " was replaced with statement ", g_Statement[stmt].labelName,
                ".  Steps ", str((double)step), ":",
                str((double)m), " are now ", str((double)(step - m + n)), ":",
                str((double)n), ".",
                NULL),
                "", " ");
          } else {
            printLongLine(cat("Step ", str((double)step),
                " was replaced with statement ", g_Statement[stmt].labelName,
                ".  Step ", str((double)m), " is now step ", str((double)n), ".",
                NULL),
                "", " ");
          }
        }
      }
      /*autoUnify(1);*/

      /************ delete 19-Sep-2012 nm - not needed for REPLACE *******
      /@ Automatically interact with user if step not unified @/
      /@ ???We might want to add a setting to defeat this if user doesn't
         like it @/
      if (1 /@ ???Future setting flag @/) {
        interactiveUnifyStep(step - m + n - 1, 2); /@ 2nd arg. means print
                                         msg if already unified @/
      }
      *************** end 19-Sep-2012 deletion ********************/

      g_proofChangedFlag = 1; /* Flag to push 'undo' stack */
      g_proofChanged = 1; /* Cumulative flag */
      processUndoStack(&g_ProofInProgress, PUS_PUSH, g_fullArgString, 0);

      /* 16-Sep-2012 nm */
      /*
      if (dummyVarIsoFlag == 2 && g_proofChangedFlag) {
        printLongLine(cat(
     "Assignments to shared working variables ($nn) are guesses.  If "
     "incorrect, to undo DELETE STEP ",
              str((double)(step - m + n)),
      ", INITIALIZE, UNIFY, then assign them manually with LET ",
      "and try REPLACE again.",
              NULL),
              "", " ");
      }
      */
      /* 25-Feb-2014 nm */
      if (dummyVarIsoFlag == 2 && g_proofChangedFlag) {
        printLongLine(cat(
     "Assignments to shared working variables ($nn) are guesses.  If "
     "incorrect, UNDO then assign them manually with LET ",
      "and try REPLACE again.",
              NULL),
              "", " ");
      }


      /* 14-Sep-2012 nm - Automatically display new unknown steps
         ???Future - add switch to enable/defeat this */
      if (g_proofChangedFlag)
        typeProof(g_proveStatement,
            1 /*pipFlag*/,
            0 /*startStep*/,
            0 /*endStep*/,
            0 /*endIndent*/,
            1 /*essentialFlag*/,
            0 /*renumberFlag*/,
            1 /*unknownFlag*/,
            0 /*notUnifiedFlag*/,
            0 /*reverseFlag*/,
            0 /*noIndentFlag*/,
            0 /*splitColumn*/,
            0 /*skipRepeatedSteps*/, /* 28-Jun-2013 nm */
            0 /*texFlag*/,
            0 /*g_htmlFlag*/);
      /* 14-Sep-2012 end */


      continue;

    } /* REPLACE */


    if (cmdMatches("IMPROVE")) {

      improveDepth = 0; /* Depth */
      i = switchPos("/ DEPTH");
      if (i) improveDepth = (long)val(g_fullArg[i + 1]);
      if (switchPos("/ NO_DISTINCT")) p = 1; else p = 0;
                        /* p = 1 means don't try to use statements with $d's */
      /* 22-Aug-2012 nm Added */
      searchAlg = 1; /* Default */
      if (switchPos("/ 1")) searchAlg = 1;
      if (switchPos("/ 2")) searchAlg = 2;
      if (switchPos("/ 3")) searchAlg = 3;
      /* 4-Sep-2012 nm Added */
      searchUnkSubproofs = 0;
      if (switchPos("/ SUBPROOFS")) searchUnkSubproofs = 1;

      mathboxFlag = (switchPos("/ INCLUDE_MATHBOXES") != 0); /* 5-Aug-2020 */
      /* 5-Aug-2020 nm */
      assignMathboxInfo(); /* In case it hasn't been assigned yet */
      if (g_proveStatement > g_mathboxStmt) {
        /* We're in a mathbox */
        i = getMathboxNum(g_proveStatement);
        if (i <= 0) bug(1130);
        thisMathboxStartStmt = g_mathboxStart[i - 1];
      } else {
        thisMathboxStartStmt = g_mathboxStmt;
      }

      /* 3-May-2016 nm */
      /* 1 means to override usage locks */
      overrideFlag = ( (switchPos("/ OVERRIDE")) ? 1 : 0)
         || g_globalDiscouragement == 0;

      /* 14-Sep-2012 nm */
      s = getStepNum(g_fullArg[1], g_ProofInProgress.proof,
          1 /* 1 = "ALL" is permissible; returns 0 */);
      if (s == -1) continue;  /* Error; message was provided already */

      if (s != 0) {  /* s=0 means ALL */

      /**************** 14-Sep-2012 nm replaced with getStepNum()
      /@ 26-Aug-2006 nm Changed 'IMPROVE STEP <step>' to 'IMPROVE <step>' @/
      let(&str1, g_fullArg[1]); /@ To avoid void pointer problems with g_fullArg @/
      if (toupper((unsigned char)(str1[0])) != 'A') {
        /@ 16-Apr-06 nm - Handle nonpositive step number: 0 = last,
           -1 = penultimate, etc.@/
        offset = 0; /@ 16-Apr-06 @/
        /@ 10/4/99 - Added LAST - this means the last unknown step shown
           with SHOW NEW_PROOF/ESSENTIAL/UNKNOWN @/
        if (toupper((unsigned char)(str1[0])) == 'L'
            || toupper((unsigned char)(str1[0])) == 'F') {
                                        /@ 'IMPROVE LAST' or 'IMPROVE FIRST' @/
          s = 1; /@ Temporary until we figure out which step @/
          offset = 1;          /@ 16-Apr-06 @/
        } else {
          if (toupper((unsigned char)(str1[0])) == 'S') {
            print2(
           "?\"IMPROVE STEP <step>\" is obsolete.  Use \"IMPROVE <step>\".\n");
            continue;
          }
          s = (long)val(g_fullArg[1]); /@ Step number @/
          if (strcmp(g_fullArg[1], str((double)s))) {
            print2(
                "?Expected a number or FIRST or LAST or ALL after IMPROVE.\n");
            continue;
          }
          if (s <= 0) {         /@ 16-Apr-06 @/
            offset = - s + 1;   /@ 16-Apr-06 @/
            s = 1; /@ Temporary until we figure out step @/ /@ 16-Apr-06 @/
          }                     /@ 16-Apr-06 @/
        }
        /@ End of 26-Aug-2006 change @/

      /@ ------- Old code before 26-Aug-2006 -------
      if (cmdMatches("IMPROVE STEP") || cmdMatches("IMPROVE LAST") ||
          cmdMatches("IMPROVE FIRST")) {                     /# 11-Dec-05 nm #/

        /# 16-Apr-06 nm - Handle nonpositive step number: 0 = last,
           -1 = penultimate, etc.#/
        offset = 0; /# 16-Apr-06 #/
        /# 10/4/99 - Added LAST - this means the last unknown step shown
           with SHOW NEW_PROOF/ESSENTIAL/UNKNOWN #/
        if (cmdMatches("IMPROVE LAST") || cmdMatches("IMPROVE FIRST")) {
                               /# "IMPROVE LAST or FIRST" #/ /# 11-Dec-05 nm #/
          s = 1; /# Temporary until we figure out which step #/
          offset = 1;          /# 16-Apr-06 #/
        } else {
          s = val(g_fullArg[2]); /# Step number #/
          if (s <= 0) {         /# 16-Apr-06 #/
            offset = - s + 1;   /# 16-Apr-06 #/
            s = 1; /# Temp. until we figure out which step #/ /# 16-Apr-06 #/
          }                     /# 16-Apr-06 #/
        }
        ------- End of old code ------- @/
        **************** end of 14-Sep-2012 nm ************/

        m = nmbrLen(g_ProofInProgress.proof); /* Original proof length */


        /**************** 14-Sep-2012 nm replaced with getStepNum()
        if (s > m || s < 1) {
          print2("?The step must be in the range from 1 to %ld.\n", m);
          continue;
        }


        /@ 10/4/99 - For IMPROVE FIRST/LAST command, figure out the last
           unknown essential step @/           /@ 11-Dec-05 nm - Added FIRST @/
        /@if (cmdMatches("IMPROVE LAST") || cmdMatches("IMPROVE FIRST")) {@/
                               /@ IMPROVE LAST or FIRST @/ /@ 11-Dec-05 nm @/
        if (offset > 0) {  /@ LAST, FIRST, or step <= 0 @/ /@ 16-Apr-06 @/
          /@ Get the essential step flags @/
          s = 0; /@ Use as flag that step was found @/
          nmbrLet(&essentialFlags, nmbrGetEssential(g_ProofInProgress.proof));
          /@if (cmdMatches("IMPROVE LAST")) {@/
          /@if (!cmdMatches("IMPROVE FIRST")) {@/   /@ 16-Apr-06 @/
          if (toupper((unsigned char)(str1[0])) != 'F') { /@ 26-Aug-2006 @/
            /@ Scan proof backwards until last essential unknown step found @/
            /@ 16-Apr-06 - count back 'offset' unknown steps @/
            j = offset;      /@ 16-Apr-06 @/
            for (i = m; i >= 1; i--) {
              if (essentialFlags[i - 1]
                  && (g_ProofInProgress.proof)[i - 1] == -(long)'?') {
                j--;           /@ 16-Apr-06 @/
                if (j == 0) {  /@ 16-Apr-06 @/
                  /@ Found it @/
                  s = i;
                  break;
                }              /@ 16-Apr-06 @/
              }
            } /@ Next i @/
          } else {
            /@ 11-Dec-05 nm Added IMPROVE FIRST @/
            /@ Scan proof forwards until first essential unknown step found @/
            for (i = 1; i <= m; i++) {
              if (essentialFlags[i - 1]
                  && (g_ProofInProgress.proof)[i - 1] == -(long)'?') {
                /@ Found it @/
                s = i;
                break;
              }
            } /@ Next i @/
          }
          if (s == 0) {
            if (offset == 1) {                                /@ 16-Apr-06 @/
              print2("?There are no unknown essential steps.\n");
            } else {                                          /@ 16-Apr-06 @/
              print2("?There are not at least %ld unknown essential steps.\n",
                offset);                                      /@ 16-Apr-06 @/
            }                                                 /@ 16-Apr-06 @/
            continue;
          }
        } /@ if offset > 0 @/
        **************** end of 14-Sep-2012 nm ************/

        /* Get the subproof at step s */
        q = subproofLen(g_ProofInProgress.proof, s - 1);
        nmbrLet(&nmbrTmp, nmbrSeg(g_ProofInProgress.proof, s - q + 1, s));

        /*???Shouldn't this be just known?*/
        /* Check to see that the subproof has an unknown step. */
        if (!nmbrElementIn(1, nmbrTmp, -(long)'?')) {
          print2(
              "?Step %ld already has a proof and cannot be improved.\n",
              s);
          continue;
        }

        /* 25-Aug-2012 nm */
        /* Check dummy variable status of step */
        dummyVarIsoFlag = checkDummyVarIsolation(s - 1);
              /* 0=no dummy vars, 1=isolated dummy vars, 2=not isolated*/
        if (dummyVarIsoFlag == 2) {
          print2(
  "?Step %ld target has shared dummy variables and cannot be improved.\n", s);
          continue; /* Don't try to improve
                                 dummy variables that aren't isolated */
        }

        /********* Deleted old code 25-Aug-2012 nm
        /@ Check to see that the step has no dummy variables. @/
        j = 0; /@ Break flag @/
        for (i = 0; i < nmbrLen((g_ProofInProgress.target)[s - 1]); i++) {
          if (((nmbrString @)((g_ProofInProgress.target)[s - 1]))[i] > mathTokens) {
            j = 1;
            break;
          }
        }
        if (j) {
          print2(
   "?Step %ld target has dummy variables and cannot be improved.\n", s);
          continue;
        }
        ********/


        if (dummyVarIsoFlag == 0) { /* No dummy vars */ /* 25-Aug-2012 nm */
          /* Only use proveFloating if no dummy vars */
          nmbrTmpPtr = proveFloating((g_ProofInProgress.target)[s - 1],
              g_proveStatement, improveDepth, s - 1, (char)p/*NO_DISTINCT*/,
              overrideFlag,  /* 3-May-2016 nm */
              mathboxFlag /* 5-Aug-2020 nm */
              );
        } else {
          nmbrTmpPtr = NULL_NMBRSTRING; /* Initialize */ /* 25-Aug-2012 nm */
        }
        if (!nmbrLen(nmbrTmpPtr)) {
          /* A proof for the step was not found with proveFloating(). */

          /* 22-Aug-2012 nm Next, try REPLACE algorithm */
          if (searchAlg == 2 || searchAlg == 3) {
            nmbrTmpPtr = proveByReplacement(g_proveStatement,
              s - 1/*prfStep*/, /* 0 means step 1 */
              (char)p/*NO_DISTINCT*/, /* 1 means don't try stmts with $d's */
              dummyVarIsoFlag,
              (char)(searchAlg - 2), /*0=proveFloat for $fs, 1=$e's also */
              improveDepth, /* 4-Sep-2012 */
              overrideFlag,  /* 3-May-2016 nm */
              mathboxFlag /* 5-Aug-2020 nm */
              );
          }
          if (!nmbrLen(nmbrTmpPtr)) {
            print2("A proof for step %ld was not found.\n", s);
            /* REPLACE algorithm also failed */
            continue;
          }
        }

        /* If q=1, subproof must be an unknown step, so don't bother to
           delete it */
        /*???Won't q always be 1 here?*/
        if (q > 1) deleteSubProof(s - 1);
        addSubProof(nmbrTmpPtr, s - q);
        assignKnownSteps(s - q, nmbrLen(nmbrTmpPtr));
        nmbrLet(&nmbrTmpPtr, NULL_NMBRSTRING);

        n = nmbrLen(g_ProofInProgress.proof); /* New proof length */
        if (m == n) {
          print2("A 1-step proof was found for step %ld.\n", s);
        } else {
          if (s != m || q != 1) {
            printLongLine(cat("A ", str((double)(n - m + 1)),
                "-step proof was found for step ", str((double)s),
                ".  Steps ", str((double)s), ":",
                str((double)m), " are now ", str((double)(s - q + 1 - m + n)),
                ":", str((double)n), ".",
                NULL),
                "", " ");
          } else {
            printLongLine(cat("A ", str((double)(n - m + 1)),
                "-step proof was found for step ", str((double)s),
                ".  Step ", str((double)m), " is now step ", str((double)n), ".",
                NULL),
                "", " ");
          }
        }

        autoUnify(1); /* To get 'congrats' message if proof complete */
        g_proofChanged = 1; /* Cumulative flag */
        processUndoStack(&g_ProofInProgress, PUS_PUSH, g_fullArgString, 0);

        /* End if s != 0 i.e. not IMPROVE ALL */   /* 14-Sep-2012 nm */
      } else {
        /* Here, getStepNum() returned 0, meaning ALL */  /* 14-Sep-2012 nm */

        /*if (cmdMatches("IMPROVE ALL")) {*/  /* obsolete */

        if (!nmbrElementIn(1, g_ProofInProgress.proof, -(long)'?')) {
          print2("The proof is already complete.\n");
          continue;
        }

        n = 0; /* Earliest step that changed */

        g_proofChangedFlag = 0;

        for (improveAllIter = 1; improveAllIter <= 4; improveAllIter++) {
                                                           /* 25-Aug-2012 nm */
          if (improveAllIter == 1 && (searchAlg == 2 || searchAlg == 3))
            print2("Pass 1:  Trying to match cut-free statements...\n");
          if (improveAllIter == 2) {
            if (searchAlg == 2) {
              print2("Pass 2:  Trying to match all statements...\n");
            } else {
              print2(
"Pass 2:  Trying to match all statements, with cut-free hypothesis matches...\n"
                  );
            }
          }
          if (improveAllIter == 3 && searchUnkSubproofs)
            print2("Pass 3:  Trying to replace incomplete subproofs...\n");
          if (improveAllIter == 4) {
            if (searchUnkSubproofs) {
              print2("Pass 4:  Repeating pass 1...\n");
            } else {
              print2("Pass 3:  Repeating pass 1...\n");
            }
          }
          /* improveAllIter = 1: run proveFloating only */
          /* improveAllIter = 2: run proveByReplacement on unknown steps */
          /* improveAllIter = 3: run proveByReplacement on steps with
                                   incomplete subproofs */
          /* improveAllIter = 4: if something changed, run everything again */

          if (improveAllIter == 3 && !searchUnkSubproofs) continue;

          m = nmbrLen(g_ProofInProgress.proof); /* Original proof length */

          for (s = m; s > 0; s--) {

            proofStepUnk = ((g_ProofInProgress.proof)[s - 1] == -(long)'?')
                ? 1 : 0;   /* 25-Aug-2012 nm Added for clearer code */

            /* 22-Aug-2012 nm I think this is really too conservative, even
               with the old algorithm, but keep it to imitate the old one */
            if (improveAllIter == 1 || searchAlg == 1) { /* 22-Aug-2012 nm */
              /* If the step is known and unified, don't do it, since nothing
                 would be accomplished. */
              if (!proofStepUnk) {
                if (nmbrEq((g_ProofInProgress.target)[s - 1],
                    (g_ProofInProgress.source)[s - 1])) continue;
              }
            }

            /* Get the subproof at step s */
            q = subproofLen(g_ProofInProgress.proof, s - 1);
            if (proofStepUnk && q != 1) {
              bug(1120); /* 25-Aug-2012 nm Consistency check */
            }
            nmbrLet(&nmbrTmp, nmbrSeg(g_ProofInProgress.proof, s - q + 1, s));

            /* Improve only subproofs with unknown steps */
            if (!nmbrElementIn(1, nmbrTmp, -(long)'?')) continue;

            nmbrLet(&nmbrTmp, NULL_NMBRSTRING); /* No longer needed - dealloc */

            /* 25-Aug-2012 nm */
            /* Check dummy variable status of step */
            dummyVarIsoFlag = checkDummyVarIsolation(s - 1);
                  /* 0=no dummy vars, 1=isolated dummy vars, 2=not isolated*/
            if (dummyVarIsoFlag == 2) continue; /* Don't try to improve
                                     dummy variables that aren't isolated */

            /********* Deleted old code now done by checkDummyVarIsolation()
                       25-Aug-2012 nm
            /@ Check to see that the step has no dummy variables. @/
            j = 0; /@ Break flag @/
            for (i = 0; i < nmbrLen((g_ProofInProgress.target)[s - 1]); i++) {
              if (((nmbrString @)((g_ProofInProgress.target)[s - 1]))[i] >
                  mathTokens) {
                j = 1;
                break;
              }
            }
            if (j) {
              /@ Step has dummy variables and cannot be improved. @/
              continue;
            }
            ********/

            if (dummyVarIsoFlag == 0
                && (improveAllIter == 1
                  || improveAllIter == 4)) {
                /* No dummy vars */ /* 25-Aug-2012 nm */
              /* Only use proveFloating if no dummy vars */
              nmbrTmpPtr = proveFloating((g_ProofInProgress.target)[s - 1],
                  g_proveStatement, improveDepth, s - 1,
                  (char)p/*NO_DISTINCT*/,
                  overrideFlag,  /* 3-May-2016 nm */
                  mathboxFlag /* 5-Aug-2020 nm */
                  );
            } else {
              nmbrTmpPtr = NULL_NMBRSTRING; /* Init */ /* 25-Aug-2012 nm */
            }
            if (!nmbrLen(nmbrTmpPtr)) {
              /* A proof for the step was not found with proveFloating(). */

              /* 22-Aug-2012 nm Next, try REPLACE algorithm */
              if ((searchAlg == 2 || searchAlg == 3)
                  && ((improveAllIter == 2 && proofStepUnk)
                    || (improveAllIter == 3 && !proofStepUnk)
                    /*|| improveAllIter == 4*/)) {
                nmbrTmpPtr = proveByReplacement(g_proveStatement,
                  s - 1/*prfStep*/, /* 0 means step 1 */
                  (char)p/*NO_DISTINCT*/, /* 1 means don't try stmts w/ $d's */
                  dummyVarIsoFlag,
                  (char)(searchAlg - 2),/*searchMethod: 0 or 1*/
                  improveDepth,                        /* 4-Sep-2012 */
                  overrideFlag,   /* 3-May-2016 nm */
                  mathboxFlag /* 5-Aug-2020 nm */
                  );

              }
              if (!nmbrLen(nmbrTmpPtr)) {
                /* REPLACE algorithm also failed */
                continue;
              }
            }

            /* If q=1, subproof must be an unknown step, so don't bother to
               delete it */
            if (q > 1) deleteSubProof(s - 1);
            addSubProof(nmbrTmpPtr, s - q);
            assignKnownSteps(s - q, nmbrLen(nmbrTmpPtr));
            print2("A proof of length %ld was found for step %ld.\n",
                nmbrLen(nmbrTmpPtr), s);
            if (nmbrLen(nmbrTmpPtr) || q != 1) n = s - q + 1;
                                               /* Save earliest step changed */
            nmbrLet(&nmbrTmpPtr, NULL_NMBRSTRING);
            g_proofChangedFlag = 1;
            s = s - q + 1; /* Adjust step position to account for deleted subpr */
          } /* Next step s */

          if (g_proofChangedFlag) {
            autoUnify(0); /* 0 = No 'Congrats' if done */
          }

          if (!g_proofChangedFlag
              && ( (improveAllIter == 2 && !searchUnkSubproofs)
                 || improveAllIter == 3
                 || searchAlg == 1)) {
            print2("No new subproofs were found.\n");
            break; /* out of improveAllIter loop */
          }
          if (g_proofChangedFlag) {
            g_proofChanged = 1; /* Cumulative flag */
          }

          if (!nmbrElementIn(1, g_ProofInProgress.proof, -(long)'?')) {
            break; /* Proof is complete */
          }

          if (searchAlg == 1) break; /* Old algorithm does just 1st pass */

        } /* Next improveAllIter */

        if (g_proofChangedFlag) {
          if (n > 0) {
            /* n is the first step number changed.  It will be 0 if
               the numbering didn't change e.g. a $e was assigned to
               an unknown step. */
            print2("Steps %ld and above have been renumbered.\n", n);
          }
          processUndoStack(&g_ProofInProgress, PUS_PUSH, g_fullArgString, 0);
        }
        if (!nmbrElementIn(1, g_ProofInProgress.proof, -(long)'?')) {
          /* This is a redundant call; its purpose is just to give
             the message if the proof is complete */
          autoUnify(1); /* 1 = 'Congrats' if done */
        }

      } /* End if IMPROVE ALL */

      /* 6/14/98 - Automatically display new unknown steps
         ???Future - add switch to enable/defeat this */
      if (g_proofChangedFlag)
        typeProof(g_proveStatement,
            1 /*pipFlag*/,
            0 /*startStep*/,
            0 /*endStep*/,
            0 /*endIndent*/,
            1 /*essentialFlag*/,
            0 /*renumberFlag*/,
            1 /*unknownFlag*/,
            0 /*notUnifiedFlag*/,
            0 /*reverseFlag*/,
            0 /*noIndentFlag*/,
            0 /*splitColumn*/,
            0 /*skipRepeatedSteps*/, /* 28-Jun-2013 nm */
            0 /*texFlag*/,
            0 /*g_htmlFlag*/);
      /* 6/14/98 end */

      continue;

    } /* cmdMatches("IMPROVE") */


    if (cmdMatches("MINIMIZE_WITH")) {

      /* 16-Aug-2016 nm */
      printTime = 0;
      if (switchPos("/ TIME") != 0) {
        printTime = 1;
      }
      if (printTime == 1) {
        getRunTime(&timeIncr);  /* This call just resets the time */
      }

      /* q = 0; */ /* Line length */  /* 25-Jun-2014 deleted */
      prntStatus = 0; /* Status flag to help determine messages
                         0 = no statement was matched during scan (mainly for
                             error message if user typo in label field)
                         1 = a statement was matched but no shorter proof
                         2 = shorter proof found */
      /* verboseMode = (switchPos("/ BRIEF") == 0); */ /* Non-verbose mode */
      /* 4-Feb-2013 nm VERBOSE is now default */
      verboseMode = (switchPos("/ VERBOSE") != 0); /* Verbose mode */
      /* 30-Jan-06 nm Added single-character-match wildcard argument */
      if (!(instr(1, g_fullArg[1], "*") || instr(1, g_fullArg[1], "?"))) i = 1;
          /* 16-Feb-05 If no wildcard was used, switch to non-verbose mode
             since there is no point to it and an annoying extra blank line
             results */
      mayGrowFlag = (switchPos("/ MAY_GROW") != 0);
                  /* Mode to replace even if it doesn't reduce proof length */
      /* 25-Jun-2014 nm /NO_DISTINCT is obsolete
      noDistinctFlag = (switchPos("/ NO_DISTINCT") != 0);
      */
                                          /* Skip trying statements with $d */
      exceptPos = switchPos("/ EXCEPT"); /* Statement match to skip */
                                                               /* 7-Jan-06 */

      /* 4-Aug-2019 nm */
      allowNewAxiomsMatchPos = switchPos("/ ALLOW_NEW_AXIOMS");
      if (allowNewAxiomsMatchPos != 0) {
        let(&allowNewAxiomsMatchList, g_fullArg[allowNewAxiomsMatchPos + 1]);
      } else {
        let(&allowNewAxiomsMatchList, "");
      }

      /* 20-May-2013 nm */
      noNewAxiomsMatchPos = switchPos("/ NO_NEW_AXIOMS_FROM");
      if (noNewAxiomsMatchPos != 0) {
        let(&noNewAxiomsMatchList, g_fullArg[noNewAxiomsMatchPos + 1]);
      } else {
        let(&noNewAxiomsMatchList, "");
      }

      /* 20-May-2013 nm */
      forbidMatchPos = switchPos("/ FORBID");
      if (forbidMatchPos != 0) {
        let(&forbidMatchList, g_fullArg[forbidMatchPos + 1]);
      } else {
        let(&forbidMatchList, "");
      }

      mathboxFlag = (switchPos("/ INCLUDE_MATHBOXES") != 0); /* 28-Jun-2011 */
      /* 25-Jun-2014 nm /REVERSE is obsolete
      forwFlag = (switchPos("/ REVERSE") != 0); /@ 10-Nov-2011 nm @/
      */
      /****** 5-Aug-2020 nm Replaced w/ assignMathboxInfo() below
      if (g_mathboxStmt == 0) { /@ Look up "mathbox" label if it hasn't been @/
        g_mathboxStmt = lookupLabel("mathbox");
        if (g_mathboxStmt == -1)
          g_mathboxStmt = g_statements + 1;  /@ Default beyond db end if none @/
      }
      *******/

      /* 3-May-2016 nm */
      /* Flag to override any "usage locks" placed in the comment markup */
      overrideFlag = (switchPos("/ OVERRIDE") != 0)
           || g_globalDiscouragement == 0;

      /* 25-Jun-2014 nm */
      /* If a single statement is specified, don't bother to do certain
         actions or print some of the messages */
      /* 18-Jul-2020 nm New code*/
      hasWildCard = 0;
      /* (Note strpbrk warning in mmpars.c) */
      /*if (strpbrk(g_fullArg[1], "*?=~%#@,") != NULL) {*/
      /* Set hasWildCard only when there are potentially > 1 matches */
      if (strpbrk(g_fullArg[1], "*?~%,") != NULL) {
        /* (See matches() function for processing of these)
           "*" 0 or more char match
           "?" 1 char match
           "=" Most recent PROVE command statement  = one statement match
           "~" Statement range
           "%" List of modified statements
           "#" Internal statement number            = one statement match
           "@" Web page statement number            = one statement match
           "," Comma-separated fields */
        hasWildCard = 1;
      }
      /****** 18-Jul-2020 nm Deleted old code:
      hasWildCard = (instr(1, g_fullArg[1], "*")
          || instr(1, g_fullArg[1], "?")
          || instr(1, g_fullArg[1], ",")
          || instr(1, g_fullArg[1], "~") /@ 3-May-2014 nm label~label range @/
          );
      ********/

      g_proofChangedFlag = 0;

      /* Added 14-Aug-2012 nm */
      /* Always scan statements in current mathbox, even if
         "/ INCLUDE_MATHBOXES" is omitted */

      /* 5-Aug-2020 nm */
      assignMathboxInfo(); /* In case it hasn't been assigned yet */
      if (g_proveStatement > g_mathboxStmt) {
        /* We're in a mathbox */
        i = getMathboxNum(g_proveStatement);
        if (i <= 0) bug(1130);
        thisMathboxStartStmt = g_mathboxStart[i - 1];
      } else {
        thisMathboxStartStmt = g_mathboxStmt;
      }
      /****** 5-Aug-2020 deleted old code
      thisMathboxStartStmt = g_mathboxStmt;
                /@ Will become start of current (g_proveStatement's) mathbox @/
      if (g_proveStatement > g_mathboxStmt) {
        /@ We're in a mathbox @/
        for (k = g_proveStatement; k >= g_mathboxStmt; k--) {
          let(&str1, left(g_Statement[k].labelSectionPtr,
              g_Statement[k].labelSectionLen));
          /@ Heuristic to match beginning of mathbox @/
          if (instr(1, str1, "Mathbox for") != 0) {
             /@ Found beginning of current mathbox @/
             thisMathboxStartStmt = k;
             break;
          }
        }
      }
      **************/

      /* 25-Jun-2014 nm */
      copyProofStruct(&saveOrigProof, g_ProofInProgress);

      /* 12-Sep-2016 nm TODO replace this w/ compressedProofSize */
      /* 25-Jun-2014 nm Get the current (original) compressed proof length
         to compare it when a shorter non-compressed proof is found, to see
         if the compressed proof also decreased in size */
      nmbrLet(&nmbrSaveProof, g_ProofInProgress.proof);   /* Redundant? */
      nmbrLet(&nmbrSaveProof, nmbrSquishProof(g_ProofInProgress.proof));
      /* We only care about length; str1 will be discarded */
      let(&str1, compressProof(nmbrSaveProof,
          g_proveStatement, /* statement being proved */
          0 /* Normal (not "fast") compression */
          ));
      origCompressedLength = (long)strlen(str1);
      print2(
"Bytes refer to compressed proof size, steps to uncompressed length.\n");

      /* 25-Jun-2014 nm forwRevPass outer loop added */
      /* Scan forward, then reverse, then pick best result */
      for (forwRevPass = 1; forwRevPass <= 2; forwRevPass++) {

        /* 25-Jun-2014 nm */
        if (forwRevPass == 1) {
          if (hasWildCard) print2("Scanning forward through statements...\n");
          forwFlag = 1;
        } else {
          /* If growth allowed, don't bother with reverse pass */
          if (mayGrowFlag) break;
          /* If nothing was found on forward pass, don't bother with rev pass */
          if (!g_proofChangedFlag) break;
          /* If only one statement was specified, don't bother with rev pass */
          if (!hasWildCard) break;
          print2("Scanning backward through statements...\n");
          forwFlag = 0;
          /* Save proof and length from 1st pass; re-initialize */
          copyProofStruct(&save1stPassProof, g_ProofInProgress);
          forwardLength = nmbrLen(g_ProofInProgress.proof);
          forwardCompressedLength = oldCompressedLength;
          /* Start over from original proof */
          copyProofStruct(&g_ProofInProgress, saveOrigProof);
        }

        /* 20-May-2013 nm */
        /*
        if (forbidMatchList[0]) { /@ User provided a /FORBID list @/
          /@ Save the proof structure in case we have to revert a
             forbidden match. @/
          copyProofStruct(&saveProofForReverting, g_ProofInProgress);
        }
        */
        /* 25-Jun-2014 nm */
        copyProofStruct(&saveProofForReverting, g_ProofInProgress);

        oldCompressedLength = origCompressedLength;

        /* for (k = 1; k < g_proveStatement; k++) { */
        /* 10-Nov-2011 nm */
        /* If forwFlag is 0, scan from g_proveStatement-1 to 1
           If forwFlag is 1, scan from 1 to g_proveStatement-1 */
        for (k = forwFlag ? 1 : (g_proveStatement - 1);
             k * (forwFlag ? 1 : -1) < (forwFlag ? g_proveStatement : 0);
             k = k + (forwFlag ? 1 : -1)) {
          /* 28-Jun-2011 */
          /* Scan mathbox statements only if INCLUDE_MATHBOXES specified */
          /*if (!mathboxFlag && k >= g_mathboxStmt) continue;*/
          /* 14-Aug-2012 nm */
          if (!mathboxFlag && k >= g_mathboxStmt && k < thisMathboxStartStmt) {
            continue;
          }

          if (g_Statement[k].type != (char)p_ && g_Statement[k].type != (char)a_)
            continue;
          /* 30-Jan-06 nm Added single-character-match wildcard argument */
          if (!matchesList(g_Statement[k].labelName, g_fullArg[1], '*', '?'))
            continue;
          /* 25-Jun-2014 nm /NO_DISTINCT is obsolete
          if (noDistinctFlag) {
            /@ Skip the statement if it has a $d requirement.  This option
               prevents illegal minimizations that would violate $d requirements
               since MINIMIZE_WITH does not check for $d violations. @/
            if (nmbrLen(g_Statement[k].reqDisjVarsA)) {
              /@ 30-Jan-06 nm Added single-character-match wildcard argument @/
              if (!(instr(1, g_fullArg[1], "@") || instr(1, g_fullArg[1], "?")))
                print2("?\"%s\" has a $d requirement\n", g_fullArg[1]);
              continue;
            }
          }
          */

          /* 7-Jan-06 nm - Added EXCEPT switch */
          if (exceptPos != 0) {
            /* Skip any match to the EXCEPT argument */
            /* 30-Jan-06 nm Added single-character-match wildcard argument */
            if (matchesList(g_Statement[k].labelName, g_fullArg[exceptPos + 1],
                '*', '?'))
              continue;
          }

          /* 20-May-2013 nm */
          if (forbidMatchList[0]) { /* User provided a /FORBID list */
            /* First, we check to make sure we're not trying a statement
               in the forbidMatchList directly (traceProof() won't find
               this) */
            if (matchesList(g_Statement[k].labelName, forbidMatchList, '*', '?'))
              continue;
          }

          /* 3-May-2016 nm */
          /* Check to see if statement comment specified a usage
             restriction */
          if (!overrideFlag) {
            if (getMarkupFlag(k, USAGE_DISCOURAGED)) {
              continue;
            }
          }

          /* Print individual labels */
          if (prntStatus == 0) prntStatus = 1; /* Matched at least one */
          /* 25-Jun-2014 nm Don't list matched statements anymore
          if (verboseMode) {
            q = q + (long)strlen(g_Statement[k].labelName) + 1;
            if (q > 72) {
              q = (long)strlen(g_Statement[k].labelName) + 1;
              print2("\n");
            }
            print2("%s ",g_Statement[k].labelName);
          }
          */

          m = nmbrLen(g_ProofInProgress.proof); /* Original proof length */
          nmbrLet(&nmbrTmp, g_ProofInProgress.proof);
          minimizeProof(k /* trial statement */,
              g_proveStatement /* statement being proved in MM-PA */,
              (char)mayGrowFlag /* mayGrowFlag */);

          n = nmbrLen(g_ProofInProgress.proof); /* New proof length */
          if (!nmbrEq(nmbrTmp, g_ProofInProgress.proof)) {
            /* The proof got shorter (or it changed if MAY_GROW) */

            /* 20-May-2013 nm Because of the slow speed of traceBack(),
               we only want to check the /FORBID list in the relatively
               rare case where a minimization occurred.  If the FORBID
               list is matched, we then need to revert back to the
               original proof. */
            if (forbidMatchList[0]) { /* User provided a /FORBID list */
              if (g_Statement[k].type == (char)p_) {
                /* We only care about tracing $p statements */
                /* See if the TRACE_BACK list includes a match to the
                   /FORBID argument */
                if (traceProof(k,
                    0, /*essentialFlag*/
                    0, /*axiomFlag*/
                    forbidMatchList,
                    "", /*traceToList*/ /* 18-Jul-2015 nm */
                    1 /* testOnlyFlag */)) {
                  /* Yes, a forbidden statement occurred in traceProof() */
                  /* Revert the proof to before minimization */
                  copyProofStruct(&g_ProofInProgress, saveProofForReverting);
                  /* Skip further printout and flag setting */
                  continue; /* Continue at 'Next k' loop end below */
                }
              }
            }


            /* 22-Nov-2014 nm Because of the slow speed of traceBack(),
               we only want to check the /NO_NEW_AXIOMS_FROM list in the
               relatively rare case where a minimization occurred.  If the
               NO_NEW_AXIOMS_FROM condition applies, we then need to revert
               back to the original proof. */
            /* 4-Aug-2019 nm */
            if (n == n + 0) {  /* By default, no new axioms are permitted */
            /*if (noNewAxiomsMatchList[0]) {*/ /* User provided /NO_NEW_AXIOMS_FROM */
              /* If we haven't called trace yet for the theorem being proved,
                 do it now. */
              if (traceProofFlags[0] == 0) {

                /******** start of 29-Nov-2019 nm ************/
                /* traceProofWork() was written to use the SAVEd proof and
                   not the proof in progress.  In order to use the proof in
                   progress, we temporarily put the proof in progress into the
                   (SAVEd) statement structure to trick traceProofWork() into using
                   the proof in progress instead of the SAVEd proof */
                /* Bad code line between 4-Aug-2019 and 12-Feb-2020: */
                /*nmbrLet(&nmbrSaveProof, nmbrSquishProof(g_ProofInProgress.proof));*/ /* Bad! */
                /* 12-Feb-2020 nm */
                /* Use the version of the proof in progress that existed *before* the
                   MINIMIZE_WITH command was invoked */
                nmbrLet(&nmbrSaveProof, nmbrSquishProof(saveProofForReverting.proof));
                let(&str1, compressProof(nmbrSaveProof,
                    g_proveStatement, /* statement being proved in MM-PA */
                    0 /* Normal (not "fast") compression */
                    ));
                saveZappedProofSectionPtr
                    = g_Statement[g_proveStatement].proofSectionPtr;
                saveZappedProofSectionLen
                    = g_Statement[g_proveStatement].proofSectionLen;
                saveZappedProofSectionChanged
                    = g_Statement[g_proveStatement].proofSectionChanged;
                /* Set flag that this is not the original source */
                g_Statement[g_proveStatement].proofSectionChanged = 1;
                /* str1 has the new compressed trial proof after minimization */
                /* Put space before and after to satisfy "space around token"
                   requirement, to prevent possible error messages, and also
                   add "$." since parseCompressedProof() expects it */
                let(&str1, cat(" ", str1, " $.", NULL));
                /* Don't include the "$." in the length */
                g_Statement[g_proveStatement].proofSectionLen
                    = (long)strlen(str1) - 2;
                /* We don't deallocate previous proofSectionPtr content because
                   we will recover it after the verifyProof() */
                g_Statement[g_proveStatement].proofSectionPtr = str1;
                /******** end of 29-Nov-2019 nm ************/

                traceProofWork(g_proveStatement,
                    1 /*essentialFlag*/,
                    "", /*traceToList*/ /* 18-Jul-2015 nm */
                    &traceProofFlags, /* y/n list of flags */
                    &nmbrTmp /* unproved list - ignored */);
                nmbrLet(&nmbrTmp, NULL_NMBRSTRING); /* Discard */

                /******** start of 29-Nov-2019 nm ************/
                /* Restore the SAVEd proof */
                g_Statement[g_proveStatement].proofSectionPtr
                    = saveZappedProofSectionPtr;
                g_Statement[g_proveStatement].proofSectionLen
                    = saveZappedProofSectionLen;
                g_Statement[g_proveStatement].proofSectionChanged
                    = saveZappedProofSectionChanged; /* 16-Jun-2017 nm */
                /******** end of 29-Nov-2019 nm ************/

              }
              let(&traceTrialFlags, "");
              traceProofWork(k, /* The trial statement */
                  1 /*essentialFlag*/,
                  "", /*traceToList*/ /* 18-Jul-2015 nm */
                  &traceTrialFlags, /* Y/N list of flags */
                  &nmbrTmp /* unproved list - ignored */);
              nmbrLet(&nmbrTmp, NULL_NMBRSTRING); /* Discard */
              j = 1; /* 1 = ok to use trial statement */
              for (i = 1; i < g_proveStatement; i++) {
                if (g_Statement[i].type != (char)a_) continue; /* Not $a */
                if (traceProofFlags[i] == 'Y') continue;
                         /* If the axiom is already used by the proof, we
                            don't care if the trial statement depends on it */
                if (matchesList(g_Statement[i].labelName, allowNewAxiomsMatchList,
                    '*', '?') == 1
                      &&
                    matchesList(g_Statement[i].labelName, noNewAxiomsMatchList,
                    '*', '?') != 1) { /* 4-Aug-2019 nm */
                  /* If the axiom is in the list to allow and not in the list
                     to disallow, we don't care if the trial statement depends
                     on it */
                  continue;
                }
                /*
                if (matchesList(g_Statement[i].labelName, noNewAxiomsMatchList,
                    '@', '?') != 1) {
                  /@ If the axiom isn't in the list to avoid, we don't
                     care if the trial statement depends on it @/
                  continue;
                }
                */
                if (traceTrialFlags[i] == 'Y') {
                  /* The trial statement uses an axiom that the current
                     proof should avoid, so we abort it */
                  j = 0; /* 0 = don't use trial statement */
                  break;
                }
              } /* next i */
              if (j == 0) {
                /* A forbidden axiom is used by the trial proof */
                /* Revert the proof to before minimization */
                copyProofStruct(&g_ProofInProgress, saveProofForReverting);
                /* Skip further printout and flag setting */
                continue; /* Continue at 'Next k' loop end below */
              }
            } /* end if (true) */


            /* 25-Jun-2014 nm Make sure the compressed proof length
               decreased, otherwise revert.  Also, we will use the
               compressed proof for the $d check next */
            if (nmbrLen(g_Statement[k].reqDisjVarsA) || !mayGrowFlag) {
              nmbrLet(&nmbrSaveProof, g_ProofInProgress.proof);
              nmbrLet(&nmbrSaveProof, nmbrSquishProof(g_ProofInProgress.proof));
              let(&str1, compressProof(nmbrSaveProof,
                  g_proveStatement, /* statement being proved in MM-PA */
                  0 /* Normal (not "fast") compression */
                  ));
              newCompressedLength = (long)strlen(str1);
              if (!mayGrowFlag && newCompressedLength > oldCompressedLength) {
                /* The compressed proof length increased, so don't use it.
                   (If it stayed the same, we will use it because the uncompressed
                   length did decrease.) */
                /* Revert the proof to before minimization */
                if (verboseMode) {
                  print2(
 "Reverting \"%s\": Uncompressed steps:  old = %ld, new = %ld\n",
                      g_Statement[k].labelName,
                      m, n );
                  print2(
 "    but compressed size:  old = %ld bytes, new = %ld bytes\n",
                      oldCompressedLength, newCompressedLength);
                }
                copyProofStruct(&g_ProofInProgress, saveProofForReverting);
                /* Skip further printout and flag setting */
                continue; /* Continue at 'Next k' loop end below */
              }
            } /* if (nmbrLen(g_Statement[k].reqDisjVarsA) || !mayGrowFlag) */

            /* 25-Jun-2014 nm */
            /* Make sure there are no $d violations, otherwise revert */
            /* This requires the str1 from above */
            if (nmbrLen(g_Statement[k].reqDisjVarsA)) {
              /* There is currently no way to verify a proof that doesn't
                 read and parse the source directly.  This should be
                 changed in the future to make the program more modular.  But
                 for now, we temporarily zap the source with new compressed
                 proof and see if there are any $d violations by looking at
                 the error message output */
              saveZappedProofSectionPtr
                  = g_Statement[g_proveStatement].proofSectionPtr;
              saveZappedProofSectionLen
                  = g_Statement[g_proveStatement].proofSectionLen;

              /****** Obsolete due to 3-May-2017 update
              /@ (search for "chr(1)" above for explanation) @/
              let(&str1, cat(chr(1), "\n", str1, " $.\n", NULL));
              g_Statement[g_proveStatement].proofSectionPtr = str1 + 1; /@ Compressed
                                                     proof generated above @/
              g_Statement[g_proveStatement].proofSectionLen =
                  newCompressedLength + 4;
              *******/

              /******** start of 16-Jun-2017 nm ************/
              saveZappedProofSectionChanged
                  = g_Statement[g_proveStatement].proofSectionChanged;
              /* Set flag that this is not the original source */
              g_Statement[g_proveStatement].proofSectionChanged = 1;
              /* str1 has the new compressed trial proof after minimization */
              /* Put space before and after to satisfy "space around token"
                 requirement, to prevent possible error messages, and also
                 add "$." since parseCompressedProof() expects it */
              let(&str1, cat(" ", str1, " $.", NULL));
              /* Don't include the "$." in the length */
              g_Statement[g_proveStatement].proofSectionLen = (long)strlen(str1) - 2;
              /* We don't deallocate previous proofSectionPtr content because
                 we will recover it after the verifyProof() */
              g_Statement[g_proveStatement].proofSectionPtr = str1;
              /******** end of 16-Jun-2017 nm ************/

              g_outputToString = 1; /* Suppress error messages */
              /* i = parseProof(g_proveStatement); */
              /* if (i != 0) bug(1121); */
              /* i = verifyProof(g_proveStatement); */
              /* if (i != 0) bug(1122); */
              /* 15-Apr-2015 nm parseProof, verifyProof, cleanWkrProof must be
                 called in sequence to assign the g_WrkProof structure, verify
                 the proof, and deallocate the g_WrkProof structure.  Either none
                 of them or all of them must be called. */
              parseProof(g_proveStatement);
              verifyProof(g_proveStatement); /* Must be called even if error
                                  occurred in parseProof, to init RPN stack
                                  for cleanWrkProof() */
              /* 15-Apr-2015 nm - don't change proof if there is an error
                 (which could be pre-existing). */
              i = (g_WrkProof.errorSeverity > 1);
              /**** Here we look at the screen output sent to a string.
                    This is rather crude, and someday the ability to
                    check proofs and $d violations should be modularized *****/
              j = instr(1, g_printString,
                  "There is a disjoint variable ($d) violation");
              g_outputToString = 0; /* Restore to normal output */
              let(&g_printString, ""); /* Clear out the stored error messages */
              cleanWrkProof(); /* Deallocate verifyProof storage */
              g_Statement[g_proveStatement].proofSectionPtr
                  = saveZappedProofSectionPtr;
              g_Statement[g_proveStatement].proofSectionLen
                  = saveZappedProofSectionLen;
              g_Statement[g_proveStatement].proofSectionChanged
                  = saveZappedProofSectionChanged; /* 16-Jun-2017 nm */
              if (i != 0 || j != 0) {
                /* There was a verify proof error (j!=0) or $d violation (i!=0)
                   so don't used minimized proof */
                /* Revert the proof to before minimization */
                copyProofStruct(&g_ProofInProgress, saveProofForReverting);
                /* Skip further printout and flag setting */
                continue; /* Continue at 'Next k' loop end below */
              }
            } /* if (nmbrLen(g_Statement[k].reqDisjVarsA)) */

            /* 25-Jun-2014 nm - not needed since trials now suppressed */
            /*
            if (verboseMode) {
              print2("\n");
            }
            */

            /*if (nmbrLen(g_Statement[k].reqDisjVarsA) || !mayGrowFlag) {*/

            /* 3-May-2016 nm */
            /* Warn user if a discouraged statement is overridden */
            if (getMarkupFlag(k, USAGE_DISCOURAGED)) {
              if (!overrideFlag) bug(1126);
              /* print2("\n"); */ /* Enable for more emphasis */
              print2(
                  ">>> ?Warning: Overriding discouraged usage of \"%s\".\n",
                  g_Statement[k].labelName);
              /* print2("\n"); */ /* Enable for more emphasis */
            }

            if (!mayGrowFlag) {
              /* Note:  this is the length BEFORE indentation and wrapping,
                 so it is less than SHOW PROOF ... /SIZE */
              if (newCompressedLength < oldCompressedLength) {
                print2(
     "Proof of \"%s\" decreased from %ld to %ld bytes using \"%s\".\n",
                    g_Statement[g_proveStatement].labelName,
                    oldCompressedLength, newCompressedLength,
                    g_Statement[k].labelName);
              } else {
                if (newCompressedLength > oldCompressedLength) bug(1123);
                print2(
     "Proof of \"%s\" stayed at %ld bytes using \"%s\".\n",
                    g_Statement[g_proveStatement].labelName,
                    oldCompressedLength,
                    g_Statement[k].labelName);
                print2(
    "    (Uncompressed steps decreased from %ld to %ld).\n",
                    m, n );
              }
              /* (We don't care about compressed length if MAY_GROW) */
              oldCompressedLength = newCompressedLength;
            }

            if (n < m && (mayGrowFlag || verboseMode)) {
              print2(
      "%sProof of \"%s\" decreased from %ld to %ld steps using \"%s\".\n",
                (mayGrowFlag ? "" : "    "),
                g_Statement[g_proveStatement].labelName,
                m, n, g_Statement[k].labelName);
            }
            /* MAY_GROW possibility */
            if (m < n) print2(
      "Proof of \"%s\" increased from %ld to %ld steps using \"%s\".\n",
                g_Statement[g_proveStatement].labelName,
                m, n, g_Statement[k].labelName);
            /* MAY_GROW possibility */
            if (m == n) print2(
                "Proof of \"%s\" remained at %ld steps using \"%s\".\n",
                g_Statement[g_proveStatement].labelName,
                m, g_Statement[k].labelName);
            /* Distinct variable warning (obsolete) */
            /*
            if (nmbrLen(g_Statement[k].reqDisjVarsA)) {
              printLongLine(cat("Note: \"", g_Statement[k].labelName,
                  "\" has $d constraints.",
                  "  SAVE NEW_PROOF then VERIFY PROOF to check them.",
                  NULL), "", " ");
            }
            */
            /* q = 0; */ /* Line length for label list */ /* 25-Jun-2014 del */

            /* 8-Aug-2020 nm */
            /* See if it's in another mathbox; if so, let user know */
            assignMathboxInfo();
            if (k > g_mathboxStmt && g_proveStatement > g_mathboxStmt) {
              if (k < g_mathboxStart[getMathboxNum(g_proveStatement) - 1]) {
                printLongLine(cat("\"", g_Statement[k].labelName,
                      "\" is in the mathbox for ",
                      g_mathboxUser[getMathboxNum(k) - 1], ".",
                      NULL),
                    "  ", " ");
              }
            }

            prntStatus = 2; /* Found one */
            g_proofChangedFlag = 1;

            /* deleted 25-Jun-2014:
            /@ 20-May-2012 nm @/
            if (forbidMatchList[0]) { /@ User provided a /FORBID list @/
              /@ Save the changed proof in case we have to restore
                 it later @/
              copyProofStruct(&saveProofForReverting, g_ProofInProgress);
            }
            */
            /* 25-Jun-2014 nm */
            /* Save the changed proof in case we have to restore
               it later */
            copyProofStruct(&saveProofForReverting, g_ProofInProgress);

          }

        } /* Next k (statement) */
        /* 25-Jun-2014 nm - not needed since trials now suppressed */
        /*
        if (verboseMode) {
          if (prntStatus) print2("\n");
        }
        */

        if (g_proofChangedFlag && forwRevPass == 2) {
          /* 25-Jun-2014 nm */
          /* Check whether the reverse pass found a better proof than the
             forward pass */
          if (verboseMode) {
            print2(
"Forward vs. backward: %ld vs. %ld bytes; %ld vs. %ld steps\n",
                      forwardCompressedLength,
                      oldCompressedLength,
                      forwardLength,
                      nmbrLen(g_ProofInProgress.proof));
          }
          if (oldCompressedLength < forwardCompressedLength
               || (oldCompressedLength == forwardCompressedLength &&
                   nmbrLen(g_ProofInProgress.proof) < forwardLength)) {
            /* The reverse pass was better */
            print2("The backward scan results were used.\n");
          } else {
            copyProofStruct(&g_ProofInProgress, save1stPassProof);
            print2("The forward scan results were used.\n");
          }
        }

      } /* next forwRevPass */

      if (prntStatus == 1 && !mayGrowFlag)
        print2("No shorter proof was found.\n");
      if (prntStatus == 1 && mayGrowFlag)
        print2("The proof was not changed.\n");
      if (!prntStatus /* && !noDistinctFlag */)
        print2("?No earlier %s$p or $a label matches \"%s\".\n",
            (overrideFlag ? "" : "(allowed) "),  /* 3-May-2016 nm */
            g_fullArg[1]);
      /* 25-Jun-2014 nm /NO_DISTINCT is obsolete
      if (!prntStatus && noDistinctFlag) {
        /@ 30-Jan-06 nm Added single-character-match wildcard argument @/
        if (instr(1, g_fullArg[1], "@") || instr(1, g_fullArg[1], "?"))
          print2("?No earlier $p or $a label (without $d) matches \"%s\".\n",
              g_fullArg[1]);
      }
      */
      /* 28-Jun-2011 nm */
      if (!mathboxFlag && g_proveStatement >= g_mathboxStmt) {
        print2(
  "(Other mathboxes were not checked.  Use / INCLUDE_MATHBOXES to include them.)\n");
      }

      /* 16-Aug-2016 nm */
      if (printTime == 1) {
        getRunTime(&timeIncr);
        print2("MINIMIZE_WITH run time = %7.2f sec for \"%s\"\n", timeIncr,
            g_Statement[g_proveStatement].labelName);
      }

      /* 23-Nov-2019 nm */
      let(&str1, ""); /* Deallocate memory */
      nmbrLet(&nmbrSaveProof, NULL_NMBRSTRING); /* Deallocate memory */

      /* 23-Nov-2019 nm */
      /* Clear these Y/N trace strings unconditionally since new axioms are no
        longer  allowed by default, so they may become set regardless of
        qualifiers */
      let(&traceProofFlags, ""); /* Deallocate memory */
      let(&traceTrialFlags, ""); /* Deallocate memory */

      /* 4-Aug-2019 nm */
      if (allowNewAxiomsMatchList[0]) { /* User provided /NO_NEW_AXIOMS_FROM list */
        let(&allowNewAxiomsMatchList, ""); /* Deallocate memory */
      }

      /* 22-Nov-2014 nm */
      if (noNewAxiomsMatchList[0]) { /* User provided /ALLOW_NEW_AXIOMS list */
        let(&noNewAxiomsMatchList, ""); /* Deallocate memory */
      }

      /* 20-May-2013 nm */
      if (forbidMatchList[0]) { /* User provided a /FORBID list */
        /*deallocProofStruct(&saveProofForReverting);*/ /* Deallocate memory */
        let(&forbidMatchList, ""); /* Deallocate memory */
      }

      /* 25-Jun-2014 nm */
      deallocProofStruct(&saveProofForReverting); /* Deallocate memory */
      deallocProofStruct(&saveOrigProof); /* Deallocate memory */
      deallocProofStruct(&save1stPassProof); /* Deallocate memory */

      if (g_proofChangedFlag) {
        g_proofChanged = 1; /* Cumulative flag */
        processUndoStack(&g_ProofInProgress, PUS_PUSH, g_fullArgString, 0);
      }
      continue;

    } /* End if MINIMIZE_WITH */


    /* 11-Sep-2016 nm Added EXPAND command */
    if (cmdMatches("EXPAND")) {

      g_proofChangedFlag = 0;
      nmbrLet(&nmbrSaveProof, g_ProofInProgress.proof);
      s = compressedProofSize(nmbrSaveProof, g_proveStatement);

      for (i = g_proveStatement - 1; i >= 1; i--) {
        if (g_Statement[i].type != (char)p_) continue; /* Not a $p */
        /* 30-Jan-06 nm Added single-character-match wildcard argument */
        if (!matchesList(g_Statement[i].labelName, g_fullArg[1], '*', '?')) {
          continue;
        }
        sourceStatement = i;

        nmbrTmp = expandProof(nmbrSaveProof,
            sourceStatement /*, g_proveStatement*/);

        if (!nmbrEq(nmbrTmp, nmbrSaveProof)) {
          g_proofChangedFlag = 1;
          n = compressedProofSize(nmbrTmp, g_proveStatement);
          printLongLine(cat("Proof of \"",
            g_Statement[g_proveStatement].labelName, "\" ",
            (s == n ? cat("stayed at ", str((double)s), NULL)
                : cat((s < n ? "increased from " : " decreased from "),
                    str((double)s), " to ", str((double)n), NULL)),
            " bytes after expanding \"",
            g_Statement[sourceStatement].labelName, "\".", NULL), " ", " ");
          s = n;
          nmbrLet(&nmbrSaveProof, nmbrTmp);
        }
      }

      if (g_proofChangedFlag) {
        g_proofChanged = 1; /* Cumulative flag */
        /* Clear the existing proof structure */
        deallocProofStruct(&g_ProofInProgress);
        /* Then rebuild proof structure from new proof nmbrTmp */
        initProofStruct(&g_ProofInProgress, nmbrTmp, g_proveStatement);
        /* Save the new proof structure on the undo stack */
        processUndoStack(&g_ProofInProgress, PUS_PUSH, g_fullArgString, 0);
      } else {
        print2("No expansion occurred.  The proof was not changed.\n");
      }
      nmbrLet(&nmbrSaveProof, NULL_NMBRSTRING);
      nmbrLet(&nmbrTmp, NULL_NMBRSTRING);
      continue;
    } /* EXPAND */


    if (cmdMatches("DELETE STEP") || (cmdMatches("DELETE ALL"))) {

      if (cmdMatches("DELETE STEP")) {
        s = (long)val(g_fullArg[2]); /* Step number */
      } else {
        s = nmbrLen(g_ProofInProgress.proof);
      }
      if ((g_ProofInProgress.proof)[s - 1] == -(long)'?') {
        print2("?Step %ld is unknown and cannot be deleted.\n", s);
        continue;
      }
      m = nmbrLen(g_ProofInProgress.proof); /* Original proof length */
      if (s > m || s < 1) {
        print2("?The step must be in the range from 1 to %ld.\n", m);
        continue;
      }

      deleteSubProof(s - 1);
      n = nmbrLen(g_ProofInProgress.proof); /* New proof length */
      if (m == n) {
        print2("Step %ld was deleted.\n", s);
      } else {
        if (n > 1) {
          printLongLine(cat("A ", str((double)(m - n + 1)),
              "-step subproof at step ", str((double)s),
              " was deleted.  Steps ", str((double)s), ":",
              str((double)m), " are now ", str((double)(s - m + n)), ":",
              str((double)n), ".",
              NULL),
              "", " ");
        } else {
          print2("The entire proof was deleted.\n");
        }
      }

      /* 6/14/98 - Automatically display new unknown steps
         ???Future - add switch to enable/defeat this */
      typeProof(g_proveStatement,
          1 /*pipFlag*/,
          0 /*startStep*/,
          0 /*endStep*/,
          0 /*endIndent*/,
          1 /*essentialFlag*/,
          0 /*renumberFlag*/,
          1 /*unknownFlag*/,
          0 /*notUnifiedFlag*/,
          0 /*reverseFlag*/,
          0 /*noIndentFlag*/,
          0 /*splitColumn*/,
          0 /*skipRepeatedSteps*/, /* 28-Jun-2013 nm */
          0 /*texFlag*/,
          0 /*g_htmlFlag*/);
      /* 6/14/98 end */

      g_proofChanged = 1; /* Cumulative flag */
      processUndoStack(&g_ProofInProgress, PUS_PUSH, g_fullArgString, 0);

      continue;

    }

    if (cmdMatches("DELETE FLOATING_HYPOTHESES")) {

      /* Get the essential step flags */
      nmbrLet(&nmbrTmp, nmbrGetEssential(g_ProofInProgress.proof));

      m = nmbrLen(g_ProofInProgress.proof); /* Original proof length */

      n = 0; /* Earliest step that changed */
      g_proofChangedFlag = 0;

      for (s = m; s > 0; s--) {

        /* Skip essential steps and unknown steps */
        if (nmbrTmp[s - 1] == 1) continue; /* Not floating */
        if ((g_ProofInProgress.proof)[s - 1] == -(long)'?') continue; /* Unknown */

        /* Get the subproof length at step s */
        q = subproofLen(g_ProofInProgress.proof, s - 1);

        deleteSubProof(s - 1);

        n = s - q + 1; /* Save earliest step changed */
        g_proofChangedFlag = 1;
        s = s - q + 1; /* Adjust step position to account for deleted subpr */
      } /* Next step s */

      if (g_proofChangedFlag) {
        print2("All floating-hypothesis steps were deleted.\n");

        if (n) {
          print2("Steps %ld and above have been renumbered.\n", n);
        }

        /* 6/14/98 - Automatically display new unknown steps
           ???Future - add switch to enable/defeat this */
        typeProof(g_proveStatement,
            1 /*pipFlag*/,
            0 /*startStep*/,
            0 /*endStep*/,
            0 /*endIndent*/,
            1 /*essentialFlag*/,
            0 /*renumberFlag*/,
            1 /*unknownFlag*/,
            0 /*notUnifiedFlag*/,
            0 /*reverseFlag*/,
            0 /*noIndentFlag*/,
            0 /*splitColumn*/,
            0 /*skipRepeatedSteps*/, /* 28-Jun-2013 nm */
            0 /*texFlag*/,
            0 /*g_htmlFlag*/);
        /* 6/14/98 end */

        g_proofChanged = 1; /* Cumulative flag */
        processUndoStack(&g_ProofInProgress, PUS_PUSH, g_fullArgString, 0);
      } else {
        print2("?There are no floating-hypothesis steps to delete.\n");
      }

      continue;

    } /* End if DELETE FLOATING_HYPOTHESES */

    if (cmdMatches("INITIALIZE")) {

      if (cmdMatches("INITIALIZE ALL")) {
        i = nmbrLen(g_ProofInProgress.proof);

        /* Reset the dummy variable counter (all will be refreshed) */
        g_pipDummyVars = 0;

        /* Initialize all steps */
        for (j = 0; j < i; j++) {
          initStep(j);
        }

        /* Assign known subproofs */
        assignKnownSubProofs();

        print2("All steps have been initialized.\n");
        g_proofChanged = 1; /* Cumulative flag */
        processUndoStack(&g_ProofInProgress, PUS_PUSH, g_fullArgString, 0);
        continue;
      }

        /* Added 16-Apr-06 nm */
      if (cmdMatches("INITIALIZE USER")) {
        i = nmbrLen(g_ProofInProgress.proof);
        /* Delete all LET STEP assignments */
        for (j = 0; j < i; j++) {
          nmbrLet((nmbrString **)(&((g_ProofInProgress.user)[j])),
              NULL_NMBRSTRING);
        }
        print2(
      "All LET STEP user assignments have been initialized (i.e. deleted).\n");
        g_proofChanged = 1; /* Cumulative flag */
        processUndoStack(&g_ProofInProgress, PUS_PUSH, g_fullArgString, 0);
        continue;
      }
      /* End 16-Apr-06 */

      /* cmdMatches("INITIALIZE STEP") */
      s = (long)val(g_fullArg[2]); /* Step number */
      if (s > nmbrLen(g_ProofInProgress.proof) || s < 1) {
        print2("?The step must be in the range from 1 to %ld.\n",
            nmbrLen(g_ProofInProgress.proof));
        continue;
      }

      initStep(s - 1);

      /* Also delete LET STEPs, per HELP INITIALIZE */          /* 16-Apr-06 */
      nmbrLet((nmbrString **)(&((g_ProofInProgress.user)[s - 1])),  /* 16-Apr-06 */
              NULL_NMBRSTRING);                                 /* 16-Apr-06 */

      print2(
          "Step %ld and its hypotheses have been initialized.\n",
          s);

      g_proofChanged = 1; /* Cumulative flag */
      processUndoStack(&g_ProofInProgress, PUS_PUSH, g_fullArgString, 0);
      continue;

    }


    if (cmdMatches("SEARCH")) {
      if (switchPos("/ ALL")) {
        m = 1;  /* Include $e, $f statements */
      } else {
        m = 0;  /* Show $a, $p only */
      }

      /* 14-Apr-2008 nm added */
      if (switchPos("/ JOIN")) {
        joinFlag = 1;  /* Join $e's to $a,$p for matching */
      } else {
        joinFlag = 0;  /* Search $a,$p by themselves */
      }

      if (switchPos("/ COMMENTS")) {
        n = 1;  /* Search comments */
      } else {
        n = 0;  /* Search statement math symbols */
      }

      let(&str1, g_fullArg[2]); /* String to match */

      if (n) { /* COMMENTS switch */
        /* Trim leading, trailing spaces; reduce white space to space;
           convert to upper case */
        let(&str1, edit(str1, 8 + 16 + 128 + 32));
      } else { /* No COMMENTS switch */
        /* Trim leading, trailing spaces; reduce white space to space */
        let(&str1, edit(str1, 8 + 16 + 128));

        /* Change all spaces to double spaces */
        q = (long)strlen(str1);
        let(&str3, space(q + q));
        s = 0;
        for (p = 0; p < q; p++) {
          str3[p + s] = str1[p];
          if (str1[p] == ' ') {
            s++;
            str3[p + s] = str1[p];
          }
        }
        let(&str1, left(str3, q + s));

        /* 30-Jan-06 nm Added single-character-match wildcard argument "$?"
           (or "?" for convenience).  Use ASCII 3 for the exactly-1-char
           wildcard character.  This is a single-character
           match, not a single-token match:  we need "??" to match "ph". */
        while (1) {
          p = instr(1, str1, "$?");
          if (!p) break;
          let(&str1, cat(left(str1, p - 1), chr(3), right(str1, p + 2), NULL));
        }
        /* Allow just "?" for convenience. */
        while (1) {
          p = instr(1, str1, "?");
          if (!p) break;
          let(&str1, cat(left(str1, p - 1), chr(3), right(str1, p + 1), NULL));
        }
        /* End of 30-Jan-06 addition */

        /* Change wildcard to ASCII 2 (to be different from printable chars) */
        /* 1/3/02 (Why are we matching with and without space? I'm not sure.)*/
        /* 30-Jan-06 nm Answer:  We need the double-spacing, and the removal
           of space in the "with spaces" case, so that "ph $ ph" will match
           "ph  ph" (0-token case) - "ph  $  ph" won't match this in the
           (character-based, not token-based) matches().  The "with spaces"
           case is for matching whole tokens, whereas the "without spaces"
           case is for matching part of a token. */
        while (1) {
          p = instr(1, str1, " $* ");
          if (!p) break;
          /* This removes the space before and after the $* */
          let(&str1, cat(left(str1, p - 1), chr(2), right(str1, p + 4), NULL));
        }
        while (1) {
          p = instr(1, str1, "$*");
          if (!p) break;
          /* This simply replaces $* with chr(2) */
          let(&str1, cat(left(str1, p - 1), chr(2), right(str1, p + 2), NULL));
        }
        /* 1/3/02  Also allow a plain $ as a wildcard, for convenience. */
        while (1) {
          p = instr(1, str1, " $ ");
          if (!p) break;
          /* 30-Jan-06 nm Bug fix - changed "2" to "3" below in order to
             properly match 0 tokens */
          let(&str1, cat(left(str1, p - 1), chr(2), right(str1, p + 3), NULL));
        }
        while (1) {
          /* Note: the "$" shortcut must be done last to avoid picking up
             "$*" and "$?". */
          p = instr(1, str1, "$");
          if (!p) break;
          let(&str1, cat(left(str1, p - 1), chr(2), right(str1, p + 1), NULL));
        }

        /* Add wildcards to beginning and end to match middle of any string */
        let(&str1, cat(chr(2), " ", str1, " ", chr(2), NULL));
      } /* End no COMMENTS switch */

      for (i = 1; i <= g_statements; i++) {
        if (!g_Statement[i].labelName[0]) continue; /* No label */
        if (!m && g_Statement[i].type != (char)p_ &&
            g_Statement[i].type != (char)a_) {
          continue; /* No /ALL switch */
        }
        /* 30-Jan-06 nm Added single-character-match wildcard argument */
        if (!matchesList(g_Statement[i].labelName, g_fullArg[1], '*', '?'))
          continue;
        if (n) { /* COMMENTS switch */
          let(&str2, "");
          str2 = getDescription(i); /* str2 must be deallocated here */
          /* Strip linefeeds and reduce spaces; cvt to uppercase */
          j = instr(1, edit(str2, 4 + 8 + 16 + 128 + 32), str1);
          if (!j) { /* No match */
            let(&str2, "");
            continue;
          }
          /* Strip linefeeds and reduce spaces */
          let(&str2, edit(str2, 4 + 8 + 16 + 128));
          j = j + ((long)strlen(str1) / 2); /* Center of match location */
          p = g_screenWidth - 7 - (long)strlen(str((double)i)) - (long)strlen(g_Statement[i].labelName);
                        /* Longest comment portion that will fit in one line */
          q = (long)strlen(str2); /* Length of comment */
          if (q <= p) { /* Use entire comment */
            let(&str3, str2);
          } else {
            if (q - j <= p / 2) { /* Use right part of comment */
              let(&str3, cat("...", right(str2, q - p + 4), NULL));
            } else {
              if (j <= p / 2) { /* Use left part of comment */
                let(&str3, cat(left(str2, p - 3), "...", NULL));
              } else { /* Use middle part of comment */
                let(&str3, cat("...", mid(str2, j - p / 2, p - 6), "...",
                    NULL));
              }
            }
          }
          print2("%s\n", cat(str((double)i), " ", g_Statement[i].labelName, " $",
              chr(g_Statement[i].type), " \"", str3, "\"", NULL));
          let(&str2, "");
        } else { /* No COMMENTS switch */
          let(&str2,nmbrCvtMToVString(g_Statement[i].mathString));

          /* 14-Apr-2008 nm JOIN flag */
          tmpFlag = 0; /* Flag that $p or $a is already in string */
          if (joinFlag && (g_Statement[i].type == (char)p_ ||
              g_Statement[i].type == (char)a_)) {
            /* If $a or $p, prepend $e's to string to match */
            k = nmbrLen(g_Statement[i].reqHypList);
            for (j = k - 1; j >= 0; j--) {
              p = g_Statement[i].reqHypList[j];
              if (g_Statement[p].type == (char)e_) {
                let(&str2, cat("$e ",
                    nmbrCvtMToVString(g_Statement[p].mathString),
                    tmpFlag ? "" : cat(" $", chr(g_Statement[i].type), NULL),
                    " ", str2, NULL));
                tmpFlag = 1; /* Flag that a $p or $a was added */
              }
            }
          }

          /* Change all spaces to double spaces */
          q = (long)strlen(str2);
          let(&str3, space(q + q));
          s = 0;
          for (p = 0; p < q; p++) {
            str3[p + s] = str2[p];
            if (str2[p] == ' ') {
              s++;
              str3[p + s] = str2[p];
            }
          }
          let(&str2, left(str3, q + s));

          let(&str2, cat(" ", str2, " ", NULL));
          /* 30-Jan-06 nm Added single-character-match wildcard argument */
          /* We should use matches() and not matchesList() here, because
             commas can be legal token characters in math symbols */
          if (!matches(str2, str1, 2/* ascii 2 0-or-more-token match char*/,
              3/* ascii 3 single-token-match char*/))
            continue;
          let(&str2, edit(str2, 8 + 16 + 128)); /* Trim leading, trailing
              spaces; reduce white space to space */
          printLongLine(cat(str((double)i)," ",
              g_Statement[i].labelName,
              tmpFlag ? "" : cat(" $", chr(g_Statement[i].type), NULL),
              " ", str2,
              NULL), "    ", " ");
        } /* End no COMMENTS switch */
      } /* Next i */
      continue;
    }


    if (cmdMatches("SET ECHO")) {
      if (cmdMatches("SET ECHO ON")) {
        g_commandEcho = 1;
        /* 15-Jun-2009 nm Added "!" (see 15-Jun-2009 note above) */
        print2("!SET ECHO ON\n");
        print2("Command line echoing is now turned on.\n");
      } else {
        g_commandEcho = 0;
        print2("Command line echoing is now turned off.\n");
      }
      continue;
    }

    if (cmdMatches("SET MEMORY_STATUS")) {
      if (cmdMatches("SET MEMORY_STATUS ON")) {
        print2("Memory status display has been turned on.\n");
        print2("This command is intended for debugging purposes only.\n");
        g_memoryStatus = 1;
      } else {
        g_memoryStatus = 0;
        print2("Memory status display has been turned off.\n");
      }
      continue;
    }


    if (cmdMatches("SET JEREMY_HENTY_FILTER")) {
      if (cmdMatches("SET JEREMY_HENTY_FILTER ON")) {
        print2("The unification equivalence filter has been turned on.\n");
        print2("This command is intended for debugging purposes only.\n");
        g_hentyFilter = 1;
      } else {
        print2("This command is intended for debugging purposes only.\n");
        print2("The unification equivalence filter has been turned off.\n");
        g_hentyFilter = 0;
      }
      continue;
    }


    if (cmdMatches("SET EMPTY_SUBSTITUTION")) {
      if (cmdMatches("SET EMPTY_SUBSTITUTION ON")) {
        g_minSubstLen = 0;
        print2("Substitutions with empty symbol sequences is now allowed.\n");
        continue;
      }
      if (cmdMatches("SET EMPTY_SUBSTITUTION OFF")) {
        g_minSubstLen = 1;
        printLongLine(cat("The ability to substitute empty expressions",
            " for variables  has been turned off.  Note that this may",
            " make the Proof Assistant too restrictive in some cases.",
            NULL),
            "", " ");
        continue;
      }
    }


    if (cmdMatches("SET SEARCH_LIMIT")) {
      s = (long)val(g_fullArg[2]); /* Timeout value */
      print2("IMPROVE search limit has been changed from %ld to %ld\n",
          g_userMaxProveFloat, s);
      g_userMaxProveFloat = s;
      continue;
    }

    if (cmdMatches("SET WIDTH")) { /* 18-Nov-85 nm Was SCREEN_WIDTH */
      s = (long)val(g_fullArg[2]); /* Screen width value */

      /************ 19-Jun-2020 nm printBuffer is now dynamically allocated
      if (s >= PRINTBUFFERSIZE - 1) {
        print2(
"?Maximum screen width is %ld.  Recompile with larger PRINTBUFFERSIZE in\n",
            (long)(PRINTBUFFERSIZE - 2));
        print2("mminou.h if you need more.\n");
        continue;
      }
      *************/

      /* 26-Sep-2017 nm */
      /* TODO: figure out why s=2 crashes program! */
      if (s < 3) s = 3; /* Less than 3 may cause a segmentation fault */
      i = g_screenWidth;
      g_screenWidth = s; /* 26-Sep-2017 nm - print with new screen width */
      print2("Screen width has been changed from %ld to %ld.\n",
          i, s);
      continue;
    }


    if (cmdMatches("SET HEIGHT")) {  /* 18-Nov-05 nm Added */
      s = (long)val(g_fullArg[2]); /* Screen height value */
      if (s < 2) s = 2;  /* Less than 2 makes no sense */
      i = g_screenHeight;
      g_screenHeight = s - 1;
      print2("Screen height has been changed from %ld to %ld.\n",
          i + 1, s);
      /* g_screenHeight is one less than the physical screen to account for the
         prompt line after pausing. */
      continue;
    }


    /* 10-Jul-2016 nm */
    if (cmdMatches("SET DISCOURAGEMENT")) {
      if (!strcmp(g_fullArg[2], "ON")) {
        g_globalDiscouragement = 1;
        print2("\"(...is discouraged.)\" markup tags are now honored.\n");
      } else if (!strcmp(g_fullArg[2], "OFF")) {
        print2(
    "\"(...is discouraged.)\" markup tags are no longer honored.\n");
        /* print2("\n"); */ /* Enable for more emphasis */
        print2(
">>> ?Warning: This setting is intended for advanced users only.  Please turn\n");
        print2(
">>> it back ON if you are not intimately familiar with this database.\n");
        /* print2("\n"); */ /* Enable for more emphasis */
        g_globalDiscouragement = 0;
      } else {
        bug(1129);
      }
      continue;
    }


    /* 14-May-2017 nm */
    if (cmdMatches("SET CONTRIBUTOR")) {
      print2("\"Contributed by...\" name was changed from \"%s\" to \"%s\"\n",
          g_contributorName, g_fullArg[2]);
      let(&g_contributorName, g_fullArg[2]);
      continue;
    }


    /* 31-Dec-2017 nm */
    if (cmdMatches("SET ROOT_DIRECTORY")) {
      let(&str1, g_rootDirectory); /* Save previous one */
      let(&g_rootDirectory, edit(g_fullArg[2], 2/*discard spaces,tabs*/));
      if (g_rootDirectory[0] != 0) {  /* Not an empty directory path */
        /* Add trailing "/" to g_rootDirectory if missing */
        if (instr(1, g_rootDirectory, "\\") != 0
            || instr(1, g_input_fn, "\\") != 0
            || instr(1, g_output_fn, "\\") != 0 ) {
          /* Using Windows-style path (not really supported, but at least
             make full path consistent) */
          if (g_rootDirectory[strlen(g_rootDirectory) - 1] != '\\') {
            let(&g_rootDirectory, cat(g_rootDirectory, "\\", NULL));
          }
        } else {
          if (g_rootDirectory[strlen(g_rootDirectory) - 1] != '/') {
            let(&g_rootDirectory, cat(g_rootDirectory, "/", NULL));
          }
        }
      }
      if (strcmp(str1, g_rootDirectory)){
        print2("Root directory was changed from \"%s\" to \"%s\"\n",
            str1, g_rootDirectory);
      }
      let(&str1, "");
      continue;
    }


    /* 1-Nov-2013 nm Added UNDO */
    if (cmdMatches("SET UNDO")) {
      s = (long)val(g_fullArg[2]); /* Maximum UNDOs */
      if (s < 0) s = 0;  /* Less than 0 UNDOs makes no sense */
      /* Reset the stack size if it changed */
      if (processUndoStack(NULL, PUS_GET_SIZE, "", 0) != s) {
        print2(
            "The maximum number of UNDOs was changed from %ld to %ld\n",
            processUndoStack(NULL, PUS_GET_SIZE, "", 0), s);
        processUndoStack(NULL, PUS_NEW_SIZE, "", s);
        if (g_PFASmode == 1) {
          /* If we're in the Proof Assistant, assign the first stack
             entry with the current proof (the stack was erased) */
          processUndoStack(&g_ProofInProgress, PUS_PUSH, "", 0);
        }
      } else {
        print2("The maximum number of UNDOs was not changed.\n");
      }
      continue;
    }


    if (cmdMatches("SET UNIFICATION_TIMEOUT")) {
      s = (long)val(g_fullArg[2]); /* Timeout value */
      print2("Unification timeout has been changed from %ld to %ld\n",
          g_userMaxUnifTrials,s);
      g_userMaxUnifTrials = s;
      continue;
    }


    if (cmdMatches("OPEN LOG")) {
        /* Open a log file */
        let(&g_logFileName, g_fullArg[2]);
        g_logFilePtr = fSafeOpen(g_logFileName, "w", 0/*noVersioningFlag*/);
        if (!g_logFilePtr) continue; /* Couldn't open it (err msg was provided) */
        g_logFileOpenFlag = 1;
        print2("The log file \"%s\" was opened %s %s.\n",g_logFileName,
            date(),time_());
        continue;
    }

    if (cmdMatches("CLOSE LOG")) {
        /* Close the log file */
        if (!g_logFileOpenFlag) {
          print2("?Sorry, there is no log file currently open.\n");
        } else {
          print2("The log file \"%s\" was closed %s %s.\n",g_logFileName,
              date(),time_());
          fclose(g_logFilePtr);
          g_logFileOpenFlag = 0;
        }
        let(&g_logFileName,"");
        continue;
    }

    if (cmdMatches("OPEN TEX")) {
    /* 2-Oct-2017 nm OPEN HTML is very obsolete, no need to warn anymore
    if (cmdMatches("OPEN TEX") || cmdMatches("OPEN HTML") ) {
      if (cmdMatches("OPEN HTML")) {
        print2("?OPEN HTML is obsolete - use SHOW STATEMENT * / HTML\n");
        continue;
      }
    */

      /* 17-Nov-2015 TODO: clean up mixed LaTeX/HTML attempts (check
         g_texFileOpenFlag when switching to HTML & close LaTeX file) */

      if (g_texDefsRead) {
        /* Current limitation - can only read .def once */
        /* 2-Oct-2017 nm OPEN HTML is obsolete */
        /*if (cmdMatches("OPEN HTML") != g_htmlFlag) {*/
        if (g_htmlFlag) {
          /* Actually it isn't clear to me this is still the case, but
             to be safe I left it in */
          print2("?You cannot use both LaTeX and HTML in the same session.\n");
          print2(
              "?You must EXIT and restart Metamath to switch to the other.\n");
          continue;
        }
      }
      /* 2-Oct-2017 nm OPEN HTML is obsolete */
      /*g_htmlFlag = cmdMatches("OPEN HTML");*/

      /* Open a TeX file */
      let(&g_texFileName,g_fullArg[2]);
      if (switchPos("/ NO_HEADER")) {
        texHeaderFlag = 0;
      } else {
        texHeaderFlag = 1;
      }
      /* 14-Sep-2010 nm Added OLD_TEX */
      if (switchPos("/ OLD_TEX")) {
        g_oldTexFlag = 1;
      } else {
        g_oldTexFlag = 0;
      }
      g_texFilePtr = fSafeOpen(g_texFileName, "w", 0/*noVersioningFlag*/);
      if (!g_texFilePtr) continue; /* Couldn't open it (err msg was provided) */
      g_texFileOpenFlag = 1;
      /* 2-Oct-2017 nm OPEN HTML is obsolete */
      print2("Created %s output file \"%s\".\n",
          g_htmlFlag ? "HTML" : "LaTeX", g_texFileName);
      printTexHeader(texHeaderFlag);
      g_oldTexFlag = 0;
      continue;
    }

    /* 2-Oct-2017 nm CLOSE HTML is obsolete */
    /******
    if (cmdMatches("CLOSE TEX") || cmdMatches("CLOSE HTML")) {
      if (cmdMatches("CLOSE HTML")) {
        print2("?CLOSE HTML is obsolete - use SHOW STATEMENT @ / HTML\n");
        continue;
      }
      /@ Close the TeX file @/
      if (!g_texFileOpenFlag) {
        print2("?Sorry, there is no %s file currently open.\n",
            g_htmlFlag ? "HTML" : "LaTeX");
      } else {
        print2("The %s output file \"%s\" has been closed.\n",
            g_htmlFlag ? "HTML" : "LaTeX", g_texFileName);
        printTexTrailer(texHeaderFlag);
        fclose(g_texFilePtr);
        g_texFileOpenFlag = 0;
      }
      let(&g_texFileName,"");
      continue;
    }
    *****/
    if (cmdMatches("CLOSE TEX")) {
      /* Close the TeX file */
      if (!g_texFileOpenFlag) {
        print2("?Sorry, there is no LaTeX file currently open.\n");
      } else {
        print2("The LaTeX output file \"%s\" has been closed.\n",
            g_texFileName);
        printTexTrailer(texHeaderFlag);
        fclose(g_texFilePtr);
        g_texFileOpenFlag = 0;
      }
      let(&g_texFileName,"");
      continue;
    }

    /* Similar to Unix 'more' */
    if (cmdMatches("MORE")) {
      list1_fp = fSafeOpen(g_fullArg[1], "r", 0/*noVersioningFlag*/);
      if (!list1_fp) continue; /* Couldn't open it (error msg was provided) */
      while (1) {
        if (!linput(list1_fp, NULL, &str1)) break; /* End of file */
        /* Print a line on the screen */
        if (!print2("%s\n", str1)) break; /* User typed Q */
      }
      fclose(list1_fp);
      continue;
    } /* end MORE */


    if (cmdMatches("FILE SEARCH")) {
      /* Search the contents of a file and type on the screen */

      type_fp = fSafeOpen(g_fullArg[2], "r", 0/*noVersioningFlag*/);
      if (!type_fp) continue; /* Couldn't open it (error msg was provided) */
      fromLine = 0;
      toLine = 0;
      searchWindow = 0;
      i = switchPos("/ FROM_LINE");
      if (i) fromLine = (long)val(g_fullArg[i + 1]);
      i = switchPos("/ TO_LINE");
      if (i) toLine = (long)val(g_fullArg[i + 1]);
      i = switchPos("/ WINDOW");
      if (i) searchWindow = (long)val(g_fullArg[i + 1]);
      /*??? Implement SEARCH /WINDOW */
      if (i) print2("Sorry, WINDOW has not be implemented yet.\n");

      let(&str2, g_fullArg[3]); /* Search string */
      let(&str2, edit(str2, 32)); /* Convert to upper case */

      tmpFlag = 0;

      /* Search window buffer */
      pntrLet(&pntrTmp, pntrSpace(searchWindow));

      j = 0; /* Line # */
      m = 0; /* # matches */
      while (linput(type_fp, NULL, &str1)) {
        j++;
        if (j > toLine && toLine != 0) break;
        if (j >= fromLine || fromLine == 0) {
          let(&str3, edit(str1, 32)); /* Convert to upper case */
          if (instr(1, str3, str2)) { /* Match occurred */
            if (!tmpFlag) {
              tmpFlag = 1;
              print2(
                    "The line number in the file is shown before each line.\n");
            }
            m++;
            if (!print2("%ld:  %s\n", j, left(str1,
                MAX_LEN - (long)strlen(str((double)j)) - 3))) break;
          }
        }
        for (k = 1; k < searchWindow; k++) {
          let((vstring *)(&pntrTmp[k - 1]), pntrTmp[k]);
        }
        if (searchWindow > 0)
            let((vstring *)(&pntrTmp[searchWindow - 1]), str1);
      }
      if (!tmpFlag) {
        print2("There were no matches.\n");
      } else {
        if (m == 1) {
          print2("There was %ld matching line in the file %s.\n", m,
              g_fullArg[2]);
        } else {
          print2("There were %ld matching lines in the file %s.\n", m,
              g_fullArg[2]);
        }
      }

      fclose(type_fp);

      /* Deallocate search window buffer */
      for (i = 0; i < searchWindow; i++) {
        let((vstring *)(&pntrTmp[i]), "");
      }
      pntrLet(&pntrTmp, NULL_PNTRSTRING);


      continue;
    }


    if (cmdMatches("SET UNIVERSE") || cmdMatches("ADD UNIVERSE") ||
        cmdMatches("DELETE UNIVERSE")) {

      /*continue;*/ /* ???Not implemented */
    } /* end if xxx UNIVERSE */



    if (cmdMatches("SET DEBUG FLAG")) {
      print2("Notice:  The DEBUG mode is intended for development use only.\n");
      print2("The printout will not be meaningful to the user.\n");
      i = (long)val(g_fullArg[3]);
      if (i == 4) db4 = 1;  /* Not used */
      if (i == 5) db5 = 1;  /* mmpars.c statistics; mmunif.c overview */
      if (i == 6) db6 = 1;  /* mmunif.c details */
      if (i == 7) db7 = 1;  /* mmunif.c more details; mmveri.c */
      if (i == 8) db8 = 1;  /* mmpfas.c unification calls */
      if (i == 9) db9 = 1;  /* memory */ /* use SET MEMORY_STATUS ON instead */
      continue;
    }
    if (cmdMatches("SET DEBUG OFF")) {
      db4 = 0;
      db5 = 0;
      db6 = 0;
      db7 = 0;
      db8 = 0;
      db9 = 0;
      print2("The DEBUG mode has been turned off.\n");
      continue;
    }

    if (cmdMatches("ERASE")) {
      if (g_sourceChanged) {
        print2("Warning:  You have not saved changes to the source.\n");
        str1 = cmdInput1("Do you want to ERASE anyway (Y, N) <N>? ");
        if (str1[0] != 'y' && str1[0] != 'Y') {
          print2("Use WRITE SOURCE to save the changes.\n");
          continue;
        }
        g_sourceChanged = 0;
      }
      eraseSource();
      g_sourceHasBeenRead = 0; /* Global variable */ /* 31-Dec-2017 nm */
      g_showStatement = 0;
      g_proveStatement = 0;
      print2("Metamath has been reset to the starting state.\n");
      continue;
    }

    if (cmdMatches("VERIFY PROOF")) {
      if (switchPos("/ SYNTAX_ONLY")) {
        verifyProofs(g_fullArg[2],0); /* Parse only */
      } else {
        verifyProofs(g_fullArg[2],1); /* Parse and verify */
      }
      continue;
    }

    /* 7-Nov-2015 nm New */  /* 17-Nov-2015 nm Updated */
    /* 25-Jun-2020 nm Added UNDERSCORE_SKIP */
    /* 17-Jul-2020 nm Added MATHBOX_SKIP */
    if (cmdMatches("VERIFY MARKUP")) {
      i = (switchPos("/ DATE_SKIP") != 0) ? 1 : 0;
      j = (switchPos("/ TOP_DATE_SKIP") != 0) ? 1 : 0;
      k = (switchPos("/ FILE_SKIP") != 0) ? 1 : 0;
      l = (switchPos("/ UNDERSCORE_SKIP") != 0) ? 1 : 0;
      m = (switchPos("/ MATHBOX_SKIP") != 0) ? 1 : 0;
      n = (switchPos("/ VERBOSE") != 0) ? 1 : 0;
      if (i == 1 && j == 1) {
        printf(
            "?Only one of / DATE_SKIP and / TOP_DATE_SKIP may be specified.\n");
        continue;
      }
      verifyMarkup(g_fullArg[2],
          (flag)i, /* 1 = skip checking date consistency */
          (flag)j, /* 1 = skip checking top date only */
          (flag)k, /* 1 = skip checking external files GIF, mmset.html,... */
          (flag)l, /* 1 = skip checking labels for underscores */
          (flag)m, /* 1 = skip checking mathbox cross-references */
          (flag)n); /* 1 = verbose mode */  /* 26-Dec-2016 nm */
      continue;
    }

    /* 10-Dec-2018 nm Added */
    if (cmdMatches("MARKUP")) {
      g_htmlFlag = 1;
      g_altHtmlFlag = (switchPos("/ ALT_HTML") != 0);
      if ((switchPos("/ HTML") != 0) == (switchPos("/ ALT_HTML") != 0)) {
        print2("?Please specify exactly one of / HTML and / ALT_HTML.\n");
        continue;
      }
      i = 0;
      i = ((switchPos("/ SYMBOLS") != 0) ? PROCESS_SYMBOLS : 0)
          + ((switchPos("/ LABELS") != 0) ? PROCESS_LABELS : 0)
          + ((switchPos("/ NUMBER_AFTER_LABEL") != 0) ? ADD_COLORED_LABEL_NUMBER : 0)
          + ((switchPos("/ BIB_REFS") != 0) ? PROCESS_BIBREFS : 0)
          + ((switchPos("/ UNDERSCORES") != 0) ? PROCESS_UNDERSCORES : 0);
      processMarkup(g_fullArg[1], /* Input file */
          g_fullArg[2],  /* Output file */
          (switchPos("/ CSS") != 0),
          i); /* Action bits */
      continue;
    }

    print2("?This command has not been implemented.\n");
    continue;

  }
} /* command */


