/*****************************************************************************/
/*        Copyright (C) 2020  NORMAN MEGILL  nm at alum.mit.edu              */
/*            License terms:  GNU General Public License                     */
/*****************************************************************************/
/*34567890123456 (79-character line to adjust editor window) 2345678901234567*/

/* mmcmds.c - assorted user commands */

#include <string.h>
#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
#include <ctype.h>
#include <stdarg.h>
#include <time.h>  /* 28-May-04 nm For clock() */
#include "mmvstr.h"
#include "mmdata.h"
#include "mmcmdl.h" /* For g_texFileName */
#include "mmcmds.h"
#include "mminou.h"
#include "mmpars.h"
#include "mmveri.h"
#include "mmwtex.h" /* For g_htmlVarColor,... */
#include "mmpfas.h"
#include "mmunif.h" /* 26-Sep-2010 nm For g_bracketMatchInit, g_minSubstLen */
                    /* 1-Oct-2017 nm ...and g_firstConst */

/* 12-Nov-2018 nm */
/* Local prototypes */
vstring bigAdd(vstring bignum1, vstring bignum2);
vstring bigSub(vstring bignum1, vstring bignum2);

/* vstring mainFileName = ""; */ /* 28-Dec-05 nm Obsolete */
flag g_printHelp = 0;

/* For HTML output */
vstring g_printStringForReferencedBy = "";

/* For MIDI */
flag g_midiFlag = 0;
vstring g_midiParam = "";

/* Type (i.e. print) a statement */
void typeStatement(long showStmt,
  flag briefFlag,
  flag commentOnlyFlag,
  flag texFlag,
  flag htmlFlg)
{
  /* From HELP SHOW STATEMENT: Optional qualifiers:
    / TEX - This qualifier will write the statement information to the
        LaTeX file previously opened with OPEN TEX.
            [Note:  texFlag=1 and htmlFlg=0 with this qualifier.]

    / HTML - This qualifier will write the statement information to the
        HTML file previously opened with OPEN HTML.
            [Note:  texFlag=1 and htmlFlg=1 with this qualifier.]

    / COMMENT_ONLY - This qualifier will show only the comment that
        immediatley precedes the statement.  This is useful when you are
        using Metamath to preprocess LaTeX source you have created (see
        HELP TEX)

    / BRIEF - This qualifier shows the statement and its $e hypotheses
        only.
  */
  long i, j, k, m, n;
  vstring str1 = "", str2 = "", str3 = "";
  nmbrString *nmbrTmpPtr1; /* Pointer only; not allocated directly */
  nmbrString *nmbrTmpPtr2; /* Pointer only; not allocated directly */
  nmbrString *nmbrDDList = NULL_NMBRSTRING;
  flag q1, q2;
  flag type;
  flag subType;
  vstring htmlDistinctVars = ""; /* 12/23/01 */
  char htmlDistinctVarsCommaFlag = 0; /* 12/23/01 */
  vstring str4 = ""; /* 10/10/02 */
  vstring str5 = ""; /* 19-Sep-2012 nm */
  long distVarGrps = 0;  /* 11-Aug-2006 nm */

  /* For syntax breakdown of definitions in HTML page */
  long zapStatement1stToken;
  static long wffToken = -1; /* array index of the hard-coded token "wff" -
      static so we only have to look it up once - set to -2 if not found */

  subType = 0; /* Assign to prevent compiler warnings - not theor. necessary */

  if (!showStmt) bug(225); /* Must be 1 or greater */

  if (!commentOnlyFlag && !briefFlag) {
    assignStmtFileAndLineNum(showStmt); /* 9-Jan-2018 nm */
    let(&str1, cat("Statement ", str((double)showStmt),
        " is located on line ", str((double)(g_Statement[showStmt].lineNum)),
        " of the file ", NULL));
    if (!texFlag) {
      assignMathboxInfo(); /* In case it hasn't been assigned yet */
          /* We need to call assignMathboxInfo() to do the initial assignment,
             in order to prevent "let(&" from executing during the
             getMathboxUser() call below, which would corrupt the "cat()"
             temporary stack (since "let(&" empties the stack). */
      printLongLine(cat(str1,
        "\"", g_Statement[showStmt].fileName,
        "\".",
        /* 8-Feb-2007 nm Added HTML page info to SHOW STATEMENT ... /FULL */
        (g_Statement[showStmt].pinkNumber == 0) ?   /* !=0 means $a or $p */
           "" :
           cat("  Its statement number for HTML pages is ",
               str((double)(g_Statement[showStmt].pinkNumber)), ".", NULL),
        /* 5-Aug-2020 nm Added mathbox user to SHOW STATEMENT ... /FULL */
        ((getMathboxUser(showStmt))[0] == 0) ?
           "" :
           cat("  It is in the mathbox for ", getMathboxUser(showStmt), ".",
               NULL),
        NULL), "", " ");
    } else {
      if (!htmlFlg) let(&g_printString, "");
      g_outputToString = 1; /* Flag for print2 to add to g_printString */
                     /* Note that printTexLongMathString resets it */
      if (!(htmlFlg && texFlag)) {
        /*
        printLongLine(cat(str1, "{\\tt ",
            asciiToTt(g_Statement[showStmt].fileName),
            "}.", NULL), "", " ");
        */
      } else {
        /* For categorizing html pages, we use the source file convention
           that syntax statements don't start with "|-" and that axioms
           have labels starting with "ax-".  It is up to the database
           creator to follow this standard, which is not enforced. */
#define SYNTAX 1
#define DEFINITION 2
#define AXIOM 3
#define THEOREM 4
        if (g_Statement[showStmt].type == (char)p_) {
          subType = THEOREM;
        } else {
          /* Must be a_ due to filter in main() */
          if (g_Statement[showStmt].type != (char)a_) bug(228);
          if (strcmp("|-", g_MathToken[
              (g_Statement[showStmt].mathString)[0]].tokenName)) {
            subType = SYNTAX;
          } else {
            if (!strcmp("ax-", left(g_Statement[showStmt].labelName, 3))) {
              subType = AXIOM;
            } else {
              subType = DEFINITION;
            }
          }
        }
        switch (subType) {
          case SYNTAX:
            let(&str1, "Syntax Definition"); break;
          case DEFINITION:
            let(&str1, "Definition"); break;
          case AXIOM:
            let(&str1, "Axiom"); break;
          case THEOREM:
            let(&str1, "Theorem"); break;
          default:
            bug(229);
        }

        /* Print a small pink statement number after the statement */
        let(&str2, "");
        str2 = pinkHTML(showStmt);
        printLongLine(cat("<CENTER><B><FONT SIZE=\"+1\">", str1,
            " <FONT COLOR=", GREEN_TITLE_COLOR,
            ">", g_Statement[showStmt].labelName,
            "</FONT></FONT></B>", str2, "</CENTER>", NULL), "", "\"");
      } /* (htmlFlg && texFlag) */
      g_outputToString = 0;
    } /* texFlag */
  }

  if (!briefFlag || commentOnlyFlag) {
    let(&str1, "");
    str1 = getDescription(showStmt);
    if (!str1[0]    /* No comment */

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

        || (str1[0] == '[' && str1[strlen(str1) - 1] == ']')
        /* 7-Sep-04 Allow both "$([<date>])$" and "$( [<date>] )$" */
        || (strlen(str1) > 1 &&
            str1[1] == '[' && str1[strlen(str1) - 2] == ']') /* Make sure
            getDescription() didn't pick up date stamp from previous proof */

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

        ) {
      print2("?Warning: Statement \"%s\" has no comment\n",
          g_Statement[showStmt].labelName);
      /* 14-Sep-2010 nm We must print a blank comment to have \begin{lemma} */
      if (texFlag && !htmlFlg && !g_oldTexFlag) {
        let(&str1, "TO DO: PUT DESCRIPTION HERE");
      }
    }
    if (str1[0]) {
      if (!texFlag) {
        printLongLine(cat("\"", str1, "\"", NULL), "", " ");
      } else {
        /* 10/10/02 This is now done in mmwtex.c printTexComment */
        /* Although it will affect the 2nd (and only other) call to
           printTexComment below, that call is obsolete and there should
           be no side effect. */
        /*******
        if (htmlFlg && texFlag) {
          let(&str1, cat("<CENTER><B>Description: </B>", str1,
              "</CENTER><BR>", NULL));
        }
        *******/
        if (!htmlFlg) {  /* LaTeX */
          if (!g_oldTexFlag) {
            /* 14-Sep-2010 */

            /* 1-May-2017 nm */
            /* Distinguish axiom, definition, theorem */
            /* Note: changes here must be mirrored in the \end{...} below */
            if (g_Statement[showStmt].type == a_) {
              if (!strcmp(left(g_Statement[showStmt].labelName, 3), "ax-")) {
                let(&str3, "axiom");
              } else {
                let(&str3, "definition");
              }
            } else {
              let(&str3, "theorem");
            }
            let(&str1, cat("\\begin{", str3, "}\\label{",
                left(str3, 3), ":",
                g_Statement[showStmt].labelName, "} ", str1, NULL));
            /* old code before 1-May-2017:
            let(&str1, cat("\\begin{lemma}\\label{lem:",
                g_Statement[showStmt].labelName, "} ", str1, NULL));
            */

          } else {
            /* 6-Dec-03 Add separation space between theorems */
            let(&str1, cat("\n\\vspace{1ex} %2\n\n", str1, NULL));
          }
        }
        /* printTexComment(str1, 1); */
        /* 17-Nov-2015 nm Add 3rd & 4th arguments */
        printTexComment(str1,              /* Sends result to g_texFilePtr */
            1, /* 1 = htmlCenterFlag */
            PROCESS_EVERYTHING, /* actionBits */ /* 13-Dec-2018 nm */
            0  /* 1 = noFileCheck */);
      }
    }
  }
  if (commentOnlyFlag && !briefFlag) goto returnPoint;

  if ((briefFlag && !texFlag) ||
       (htmlFlg && texFlag) /* HTML page 12/23/01 */) {
    /* In BRIEF mode screen output, show $d's */
    /* This section was added 8/31/99 */

    /* 12/23/01 - added algorithm to HTML pages also; the string to print out
       is stored in htmlDistinctVars for later printing */

    /* 12/23/01 */
    if (htmlFlg && texFlag) {
      let(&htmlDistinctVars, "");
      htmlDistinctVarsCommaFlag = 0;
    }

    /* Note added 22-Aug-04:  This algorithm is used to re-merge $d pairs
       into groups of 3 or more when possible, for a more compact display.
       The algorithm does not merge groups optimally, but it should be
       adequate.  For example, in set.mm (e.g. old r19.23aivv):
         $d x ps $.  $d y ps $.  $d y A $.  $d x y $.
       produces in SHOW STATEMENT (note redundant 3rd $d):
         $d ps x y $.  $d y A $.  $d x y $.
       However, in set.mm the equivalent (and better anyway):
         $d x y ps $.  $d y A $.
       produces the same thing when remerged in SHOW STATEMENT. */
    let(&str1, "");
    nmbrTmpPtr1 = g_Statement[showStmt].reqDisjVarsA;
    nmbrTmpPtr2 = g_Statement[showStmt].reqDisjVarsB;
    i = nmbrLen(nmbrTmpPtr1);
    if (i /* Number of mandatory $d pairs */) {
      nmbrLet(&nmbrDDList, NULL_NMBRSTRING);
      for (k = 0; k < i; k++) {
        /* Is one of the variables in the current list? */
        if (!nmbrElementIn(1, nmbrDDList, nmbrTmpPtr1[k]) &&
            !nmbrElementIn(1, nmbrDDList, nmbrTmpPtr2[k])) {
          /* No, so close out the current list */
          if (!(htmlFlg && texFlag)) { /* 12/23/01 */
            if (k == 0) let(&str1, "$d");
            else let(&str1, cat(str1, " $.  $d", NULL));
          } else {

            /* 12/23/01 */
            let(&htmlDistinctVars, cat(htmlDistinctVars, " &nbsp; ",
                NULL));
            htmlDistinctVarsCommaFlag = 0;
            distVarGrps++;  /* 11-Aug-2006 nm */

          }
          nmbrLet(&nmbrDDList, NULL_NMBRSTRING);
        }
        /* Are both variables required to be distinct from all others
           in current list? */
        for (n = 0; n < nmbrLen(nmbrDDList); n++) {
          if (nmbrDDList[n] != nmbrTmpPtr1[k] &&
              nmbrDDList[n] != nmbrTmpPtr2[k]) {
            q1 = 0; q2 = 0;
            for (m = 0; m < i; m++) {
              if ((nmbrTmpPtr1[m] == nmbrDDList[n] &&
                      nmbrTmpPtr2[m] == nmbrTmpPtr1[k]) ||
                  (nmbrTmpPtr2[m] == nmbrDDList[n] &&
                      nmbrTmpPtr1[m] == nmbrTmpPtr1[k])) {
                q1 = 1; /* 1st var is required to be distinct */
              }
              if ((nmbrTmpPtr1[m] == nmbrDDList[n] &&
                      nmbrTmpPtr2[m] == nmbrTmpPtr2[k]) ||
                  (nmbrTmpPtr2[m] == nmbrDDList[n] &&
                      nmbrTmpPtr1[m] == nmbrTmpPtr2[k])) {
                q2 = 1;  /* 2nd var is required to be distinct */
              }
              if (q1 && q2) break; /* Found both */
            }  /* Next m */
            if (!q1 || !q2) {
              /* One of the variables is not required to be distinct from
                all others in the current list, so close out current list */
              if (!(htmlFlg && texFlag)) {
                if (k == 0) let(&str1, "$d");
                else let(&str1, cat(str1, " $.  $d", NULL));
              } else {

                /* 12/23/01 */
                let(&htmlDistinctVars, cat(htmlDistinctVars, " &nbsp; ",
                    NULL));
                htmlDistinctVarsCommaFlag = 0;
                distVarGrps++;  /* 11-Aug-2006 nm */

              }
              nmbrLet(&nmbrDDList, NULL_NMBRSTRING);
              break; /* Out of n loop */
            }
          } /* If $d var in current list is not same as one we're adding */
        } /* Next n */
        /* If the variable is not already in current list, add it */
        if (!nmbrElementIn(1, nmbrDDList, nmbrTmpPtr1[k])) {
          if (!(htmlFlg && texFlag)) {
            let(&str1, cat(str1, " ", g_MathToken[nmbrTmpPtr1[k]].tokenName,
                NULL));
          } else {

            /* 12/23/01 */
            if (htmlDistinctVarsCommaFlag) {
              let(&htmlDistinctVars, cat(htmlDistinctVars, ",", NULL));
            }
            htmlDistinctVarsCommaFlag = 1;
            let(&str2, "");
            str2 = tokenToTex(g_MathToken[nmbrTmpPtr1[k]].tokenName, showStmt);
                 /* tokenToTex allocates str2; we must deallocate it */
            let(&htmlDistinctVars, cat(htmlDistinctVars, str2, NULL));

          }
          nmbrLet(&nmbrDDList, nmbrAddElement(nmbrDDList, nmbrTmpPtr1[k]));
        }
        if (!nmbrElementIn(1, nmbrDDList, nmbrTmpPtr2[k])) {
          if (!(htmlFlg && texFlag)) {
            let(&str1, cat(str1, " ", g_MathToken[nmbrTmpPtr2[k]].tokenName,
                NULL));
          } else {

            /* 12/23/01 */
            if (htmlDistinctVarsCommaFlag) {
              let(&htmlDistinctVars, cat(htmlDistinctVars, ",", NULL));
            }
            htmlDistinctVarsCommaFlag = 1;
            let(&str2, "");
            str2 = tokenToTex(g_MathToken[nmbrTmpPtr2[k]].tokenName, showStmt);
                 /* tokenToTex allocates str2; we must deallocate it */
            let(&htmlDistinctVars, cat(htmlDistinctVars, str2, NULL));

          }
          nmbrLet(&nmbrDDList, nmbrAddElement(nmbrDDList, nmbrTmpPtr2[k]));
        }
      } /* Next k */
      /* Close out entire list */
      if (!(htmlFlg && texFlag)) {
        let(&str1, cat(str1, " $.", NULL));
        printLongLine(str1, "  ", " ");
      } else {

        /* 12/23/01 */
        /* (do nothing) */
        /*let(&htmlDistinctVars, cat(htmlDistinctVars, "<BR>", NULL));*/

      }
    } /* if i(#$d's) > 0 */
  }

  if (briefFlag || texFlag /*(texFlag && htmlFlg)*/) { /* 6-Dec-03 */
    /* For BRIEF mode, print $e hypotheses (only) before statement */
    /* Also do it for HTML output */
    /* 6-Dec-03  For the LaTeX output, now print hypotheses before statement */
    j = nmbrLen(g_Statement[showStmt].reqHypList);
    k = 0;
    for (i = 0; i < j; i++) {
      /* Count the number of essential hypotheses */
      if (g_Statement[g_Statement[showStmt].reqHypList[i]].type
        == (char)e_) k++;

      /* Added 5/26/03 */
      /* For syntax definitions, also include $f hypotheses so user can more
         easily match them in syntax breakdowns of axioms and definitions */
      if (subType == SYNTAX && (texFlag && htmlFlg)) {
        if (g_Statement[g_Statement[showStmt].reqHypList[i]].type
          == (char)f_) k++;
      }

    }
    if (k) {
      if (texFlag) {
        /* Note that printTexLongMath resets it to 0 */
        g_outputToString = 1;
      }
      if (texFlag && htmlFlg) {
        print2("<CENTER><TABLE BORDER CELLSPACING=0 BGCOLOR=%s\n",
            MINT_BACKGROUND_COLOR);
        /* For bobby.cast.org approval */
        print2("SUMMARY=\"%s\">\n", (k == 1) ? "Hypothesis" : "Hypotheses");
        print2("<CAPTION><B>%s</B></CAPTION>\n",
            (k == 1) ? "Hypothesis" : "Hypotheses");
        print2("<TR><TH>Ref\n");
        print2("</TH><TH>Expression</TH></TR>\n");
      }
      for (i = 0; i < j; i++) {
        k = g_Statement[showStmt].reqHypList[i];
        if (g_Statement[k].type != (char)e_

            /* Added 5/26/03 */
            /* For syntax definitions, include $f hypotheses so user can more
               easily match them in syntax breakdowns of axioms & definitions */
            && !(subType == SYNTAX && (texFlag && htmlFlg)
                && g_Statement[k].type == (char)f_)

            ) continue;

        if (!texFlag) {
          let(&str2, cat(str((double)k), " ", NULL));
        } else {
          let(&str2, "  ");
        }
        let(&str2, cat(str2, g_Statement[k].labelName,
            " $", chr(g_Statement[k].type), " ", NULL));
        if (!texFlag) {
          printLongLine(cat(str2,
              nmbrCvtMToVString(g_Statement[k].mathString), " $.", NULL),
              "      "," ");
        } else { /* if texFlag */
          /* texFlag was (misleadingly) included below to facilitate search
             for "htmlFlg && texFlag". */
          if (!(htmlFlg && texFlag)) {
            if (!g_oldTexFlag) {  /* 14-Sep-2010 nm */
              /* Do nothing */
            } else {
              let(&str3, space((long)strlen(str2)));
              printTexLongMath(g_Statement[k].mathString,
                  str2, str3, 0, 0);
            }
          } else {
            g_outputToString = 1;
            print2("<TR ALIGN=LEFT><TD>%s</TD><TD>\n",
                g_Statement[k].labelName);
            /* Print hypothesis */
            printTexLongMath(g_Statement[k].mathString, "", "", 0, 0);
          }
        }
      } /* next i */
      if (texFlag && htmlFlg) {
        g_outputToString = 1;
        print2("</TABLE></CENTER>\n");
      }
    } /* if k (#essential hyp) */
  }

  let(&str1, "");
  type = g_Statement[showStmt].type;
  if (type == p_) let(&str1, " $= ...");
  if (!texFlag)
    let(&str2, cat(str((double)showStmt), " ", NULL));
  else
    let(&str2, "  ");
  let(&str2, cat(str2, g_Statement[showStmt].labelName,
      " $",chr(type), " ", NULL));
  if (!texFlag) {
    printLongLine(cat(str2,
        nmbrCvtMToVString(g_Statement[showStmt].mathString),
        str1, " $.", NULL), "      ", " ");
  } else {
    if (!(htmlFlg && texFlag)) {  /* really !htmlFlg & texFlag */
      if (!g_oldTexFlag) {
        /* 14-Sep-2010 nm new LaTeX code: */
        g_outputToString = 1;
        print2("\\begin{align}\n");
        let(&str3, "");
        /* Get HTML hypotheses => assertion */
        str3 = getTexOrHtmlHypAndAssertion(showStmt); /* In mmwtex.c */
        printLongLine(cat(str3,
              /* No space before \label to make it easier to find last
                 parenthesis in a post-processing script */
              "\\label{eq:",
              g_Statement[showStmt].labelName,
              "}",

              /* 1-May-2017 nm */
              /* Add "\tag{..}" to use .mm labels instead of equation numbers */
              /* (Suggested by Ari Ferrera) */
              "\\tag{",
              g_Statement[showStmt].labelName,
              "}",

              NULL), "    ", " ");
        /* print2("    \\label{eq:%s}\n",g_Statement[showStmt].labelName); */
        print2("\\end{align}\n");


        /* 1-May-2017 nm */
        /* Distinguish axiom, definition, theorem for LaTeX */
        /* Note: changes here must be mirrored in the \begin{...} above */
        if (g_Statement[showStmt].type == a_) {
          if (!strcmp(left(g_Statement[showStmt].labelName, 3), "ax-")) {
            let(&str3, "axiom");
          } else {
            let(&str3, "definition");
          }
        } else {
          let(&str3, "theorem");
        }
        print2("%s\n", cat("\\end{", str3, "}", NULL));
        /* old code before 1-May-2017:
        print2("\\end{lemma}\n");
        */

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

      } else { /* old TeX code */
        let(&str3, space((long)strlen(str2))); /* 3rd argument of printTexLongMath
            cannot be temp allocated */
        printTexLongMath(g_Statement[showStmt].mathString,
            str2, str3, 0, 0);
      }
    } else { /* (htmlFlg && texFlag) */
      g_outputToString = 1;
      print2("<CENTER><TABLE BORDER CELLSPACING=0 BGCOLOR=%s\n",
          MINT_BACKGROUND_COLOR);
      /* For bobby.cast.org approval */
      print2("SUMMARY=\"Assertion\">\n");
      print2("<CAPTION><B>Assertion</B></CAPTION>\n");
      print2("<TR><TH>Ref\n");
      print2("</TH><TH>Expression</TH></TR>\n");
      printLongLine(cat(
       "<TR ALIGN=LEFT><TD><FONT COLOR=",
          GREEN_TITLE_COLOR, "><B>", g_Statement[showStmt].labelName,
          "</B></FONT></TD><TD>", NULL), "      ", " ");
      printTexLongMath(g_Statement[showStmt].mathString, "", "", 0, 0);
      g_outputToString = 1;
      print2("</TABLE></CENTER>\n");
    }
  }

  if (briefFlag) goto returnPoint;

  /* 6-Dec-03 In the LaTeX output, the hypotheses used to be printed after the
     statement.  Now they are printed before (see above 6-Dec-03 comments),
     so some code below is commented out. */
  switch (type) {
    case a_:
    case p_:
      /* 6-Dec-03  This is not really needed but keeps output consistent
         with previous version.  It puts a blank line before the HTML
         "distinct variable" list. */
      if (texFlag && htmlFlg) { /* 6-Dec-03 */
        g_outputToString = 1;
        print2("\n");
        g_outputToString = 0;
      }

      /*if (!(htmlFlg && texFlag)) {*/
      if (!texFlag) {  /* 6-Dec-03 fix */
        print2("Its mandatory hypotheses in RPN order are:\n");
      }
      /*if (texFlag) g_outputToString = 0;*/ /* 6-Dec-03 */
      j = nmbrLen(g_Statement[showStmt].reqHypList);
      for (i = 0; i < j; i++) {
        k = g_Statement[showStmt].reqHypList[i];
        if (g_Statement[k].type != (char)e_ && (!htmlFlg && texFlag))
          continue; /* 9/2/99 Don't put $f's in LaTeX output */
        let(&str2, cat("  ",g_Statement[k].labelName,
            " $", chr(g_Statement[k].type), " ", NULL));
        if (!texFlag) {
          printLongLine(cat(str2,
              nmbrCvtMToVString(g_Statement[k].mathString), " $.", NULL),
              "      "," ");
        } else {
          if (!(htmlFlg && texFlag)) {  /* LaTeX */
            /*let(&str3, space((long)strlen(str2)));*/ /* 6-Dec-03 */
            /* This clears out g_printString */
            /*printTexLongMath(g_Statement[k].mathString,
                str2, str3, 0, 0);*/ /* 6-Dec-03 */
          }
        }
      }
      /* 6-Dec-03  This is not really needed but keeps output consistent
         with previous version.  It puts a blank line before the HTML
         "distinct variable" list. */
      if (texFlag && htmlFlg) { /* 6-Dec-03 */
        g_outputToString = 1;
        print2("\n");
        g_outputToString = 0;
      }
      /*if (j == 0 && !(htmlFlg && texFlag)) print2("  (None)\n");*/
      if (j == 0 && !texFlag) print2("  (None)\n"); /* 6-Dec-03 fix */
      let(&str1, "");
      nmbrTmpPtr1 = g_Statement[showStmt].reqDisjVarsA;
      nmbrTmpPtr2 = g_Statement[showStmt].reqDisjVarsB;
      i = nmbrLen(nmbrTmpPtr1);
      if (i) {
        for (k = 0; k < i; k++) {
          if (!texFlag) {
            let(&str1, cat(str1, ", <",
                g_MathToken[nmbrTmpPtr1[k]].tokenName, ",",
                g_MathToken[nmbrTmpPtr2[k]].tokenName, ">", NULL));
          } else {
            if (htmlFlg && texFlag) {
              let(&str2, "");
              str2 = tokenToTex(g_MathToken[nmbrTmpPtr1[k]].tokenName, showStmt);
                   /* tokenToTex allocates str2; we must deallocate it */
              let(&str1, cat(str1, " &nbsp; ", str2, NULL));
              let(&str2, "");
              str2 = tokenToTex(g_MathToken[nmbrTmpPtr2[k]].tokenName, showStmt);
              let(&str1, cat(str1, ",", str2, NULL));
            }
          }
        }
        if (!texFlag)
          printLongLine(cat(
              "Its mandatory disjoint variable pairs are:  ",
              right(str1,3),NULL),"  "," ");
      }
      if (type == p_ &&
          nmbrLen(g_Statement[showStmt].optHypList)
          && !texFlag) {
        printLongLine(cat(
           "Its optional hypotheses are:  ",
            nmbrCvtRToVString(
            g_Statement[showStmt].optHypList,
                /* 25-Jan-2016 nm */
                0, /*explicitTargets*/
                0 /*statemNum, used only if explicitTargets*/), NULL),
            "      "," ");
      }
      nmbrTmpPtr1 = g_Statement[showStmt].optDisjVarsA;
      nmbrTmpPtr2 = g_Statement[showStmt].optDisjVarsB;
      i = nmbrLen(nmbrTmpPtr1);
      if (i && type == p_) {
        if (!texFlag) {
          let(&str1, "");
        } else {
          if (htmlFlg && texFlag) {
            /*  12/1/01 don't output dummy variables
            let(&str1, cat(str1,
            " &nbsp; (Dummy variables for use in proof:) ", NULL));
            */
          }
        }
        for (k = 0; k < i; k++) {
          if (!texFlag) {
            let(&str1, cat(str1, ", <",
                g_MathToken[nmbrTmpPtr1[k]].tokenName, ",",
                g_MathToken[nmbrTmpPtr2[k]].tokenName, ">", NULL));
          } /* if !texFlag */
        } /* next k */
        if (!texFlag) {
          printLongLine(cat(
              "Its optional disjoint variable pairs are:  ",
              right(str1,3),NULL),"  "," ");
        }
      } /* if (i && type == p_) */

      /* Before 12/23/01 **********
           Future: once stable, take out redundant code producing str1
      if (texFlag && htmlFlg && str1[0]) {
        g_outputToString = 1;
        printLongLine(cat("<CENTER>Substitutions into these variable",
            " pairs may not have variables in common: ",
            str1, "</CENTER>", NULL), "", " ");
        g_outputToString = 0;
      }
      ***********/


      /* 12/23/01 */
      if (texFlag && htmlFlg) { /* It's a web page */

        if (htmlDistinctVars[0] != 0) {
          g_outputToString = 1;
          printLongLine(cat(
              "<CENTER>",
              "<A HREF=\"",

              /* 26-Aug-2017 nm */
              /* g_htmlHome is set by htmlhome in $t comment */
              (instr(1, g_htmlHome, "mmset.html") > 0) ?
                  "mmset.html" :
                  /* The following link will work in the NF and other
                     "Proof Explorers" */
                  "../mpeuni/mmset.html",  /* 19-Aug-2017, 26-Sep-2017 nm */

              "#distinct\">Distinct variable</A> group",
              /* 11-Aug-2006 nm Determine whether "group" or "groups". */
              distVarGrps > 1 ? "s" : "",  /* 11-Aug-2006 */
              ": ",
              /* 14-Jan-2016 nm Put a span around the variable list to localize
                 the use of the special math font for ALT_HTML */
              (g_altHtmlFlag ? cat("<SPAN ", g_htmlFont, ">", NULL) : ""),
                                                 /* 14-Jan-2016 nm */
              htmlDistinctVars,
              (g_altHtmlFlag ? "</SPAN>" : ""),
              "</CENTER>",
              NULL), "", "\"");
          g_outputToString = 0;
        }

        /* 26-Aug-2017 nm Moved this down into proof section */
        /*
        /@ 13-Aug-2017 nm @/
        let(&str3, "");
        if (type == p_) {
          str3 = htmlDummyVars(showStmt);
          if (str3[0] != 0) {
            g_outputToString = 1;
            /@ We don't need <BR> if code is surrounded by <CENTER>...</CENTER>
            if (htmlDistinctVars[0] != 0) print2("<BR>\n");
            @/
            /@ Print the list of dummy variables @/
            printLongLine(str3, "", "\"");
            g_outputToString = 0;
          }
        }
        /@ (end of 13-Aug-2017) @/
        */

        /* 4-Jan-2014 nm */
        let(&str2, "");
        str2 = htmlAllowedSubst(showStmt);
        if (str2[0] != 0) {
          g_outputToString = 1;
          /* We don't need <BR> if code is surrounded by <CENTER>...</CENTER>
          if (htmlDistinctVars[0] != 0 || str3[0] != 0) print2("<BR>\n");
          */
          /* Print the list of allowed free variables */
          printLongLine(str2, "", "\"");
          g_outputToString = 0;
        }

      } /* if (texFlag && htmlFlg) */

      if (texFlag) {
        g_outputToString = 1;
        if (htmlFlg && texFlag) print2("<HR NOSHADE SIZE=1>\n");
        g_outputToString = 0; /* Restore normal output */
        /* will be done automatically at closing
        fprintf(g_texFilePtr, "%s", g_printString);
        let(&g_printString, "");
        */
        break; /* case a_ or p_ */
      }
      let(&str1, nmbrCvtMToVString(
          g_Statement[showStmt].reqVarList));
      if (!strlen(str1)) let(&str1, "(None)");
      printLongLine(cat(
          "The statement and its hypotheses require the variables:  ",
          str1, NULL), "      ", " ");
      if (type == p_ &&
          nmbrLen(g_Statement[showStmt].optVarList)) {
        printLongLine(cat(
            "These additional variables are allowed in its proof:  "
            ,nmbrCvtMToVString(
            g_Statement[showStmt].optVarList),NULL),"      ",
            " ");
        /*??? Add variables required by proof */
      }
      /* Note:  g_Statement[].reqVarList is only stored for $a and $p
         statements, not for $e or $f. */
      let(&str1, nmbrCvtMToVString(
          g_Statement[showStmt].reqVarList));
      if (!strlen(str1)) let(&str1, "(None)");
      printLongLine(cat("The variables it contains are:  ",
          str1, NULL),
          "      ", " ");
      break; /* case a_ or p_ */
    default:
      break;
  } /* End switch(type) */
  if (texFlag) {
    g_outputToString = 0;
    /* will be done automatically at closing
    fprintf(g_texFilePtr, "%s", g_printString);
    let(&g_printString, "");
    */
  }

  /* Start of finding definition for syntax statement */
  if (htmlFlg && texFlag) {

    /* For syntax declarations, find the first definition that follows
       it.  It is up to the user to arrange the database so that a
       meaningful definition is picked. */
    if (subType == SYNTAX) {
      for (i = showStmt + 1; i <= g_statements; i++) {
        if (g_Statement[i].type == (char)a_) {
          if (!strcmp("|-", g_MathToken[
              (g_Statement[i].mathString)[0]].tokenName)) {
            /* It's a definition or axiom */
            /* See if each constant token in the syntax statement
               exists in the definition; if not don't use the definition */
            j = 1;
            /* We start with k=1 for 2nd token (1st is wff, class, etc.) */
            for (k = 1; k < g_Statement[showStmt].mathStringLen; k++) {
              if (g_MathToken[(g_Statement[showStmt].mathString)[k]].
                  tokenType == (char)con_) {
                if (!nmbrElementIn(1, g_Statement[i].mathString,
                    (g_Statement[showStmt].mathString)[k])) {
                  /* The definition being considered doesn't have one of
                     the constant symbols in the syntax statement, so
                     reject it */
                  j = 0;
                  break; /* Out of k loop */
                }
              }
            } /* Next k */
            if (j) {
              /* Successful - use this definition or axiom as the reference */
              g_outputToString = 1;
              let(&str1, left(g_Statement[i].labelName, 3));
              let(&str2, "");
              str2 = pinkHTML(i);
              if (!strcmp(str1, "ax-")) {
                printLongLine(cat(
                    "<CENTER>This syntax is primitive.",
                    "  The first axiom using it is <A HREF=\"",
                    g_Statement[i].labelName, ".html\">",
                    g_Statement[i].labelName,
                    "</A>", str2, ".</CENTER><HR NOSHADE SIZE=1>",
                    NULL), "", "\"");
              } else {
                printLongLine(cat(
                    "<CENTER>See definition <A HREF=\"",
                    g_Statement[i].labelName, ".html\">",
                    g_Statement[i].labelName, "</A>", str2,
                    " for more information.</CENTER><HR NOSHADE SIZE=1>",
                    NULL), "", "\"");
              }

              /* 10/10/02 Moved here from mmwtex.c */
              /*print2("<FONT SIZE=-1 FACE=sans-serif>Colors of variables:\n");*/
              printLongLine(cat(
                  "<CENTER><TABLE CELLSPACING=7><TR><TD ALIGN=LEFT><FONT SIZE=-1>",
                  "<B>Colors of variables:</B> ",
                  g_htmlVarColor, "</FONT></TD></TR>",
                  NULL), "", "\"");
              g_outputToString = 0;
              break; /* Out of i loop */
            }
          }
        }
      } /* Next i */
    } /* if (subType == SYNTAX) */


    /* For definitions, we pretend that the definition is a "wff" (hard-coded
       here; the .mm database provided by the user must use this convention).
       We use the proof assistant tools to prove that the statement is
       a wff, then we print the wff construction proof to the HTML file. */
    if (subType == DEFINITION || subType == AXIOM) {

      /* Look up the token "wff" if we haven't found it before */
      if (wffToken == -1) { /* First time */
        wffToken = -2; /* In case it's not found because the user's source
            used a convention different for "wff" for wffs */
        for (i = 0; i < g_mathTokens; i++) {
          if (!strcmp("wff", g_MathToken[i].tokenName)) {
            wffToken = i;
            break;
          }
        }
      }

      if (wffToken >= 0) {
        /* Temporarily zap statement type from $a to $p */
        if (g_Statement[showStmt].type != (char)a_) bug(231);
        g_Statement[showStmt].type = (char)p_;
        /* Temporarily zap statement with "wff" token in 1st position
           so parseProof will not give errors (in typeProof() call) */
        zapStatement1stToken = (g_Statement[showStmt].mathString)[0];
        (g_Statement[showStmt].mathString)[0] = wffToken;
        if (strcmp("|-", g_MathToken[zapStatement1stToken].tokenName)) bug(230);

        nmbrTmpPtr1 = NULL_NMBRSTRING;
        nmbrLet(&nmbrTmpPtr1, g_Statement[showStmt].mathString);

        /* Find proof of formula or simple theorem (no new vars in $e's) */
        /* maxEDepth is the maximum depth at which statements with $e
           hypotheses are
           considered.  A value of 0 means none are considered. */
        nmbrTmpPtr2 = proveFloating(nmbrTmpPtr1 /*mString*/,
            showStmt /*statemNum*/, 0 /*maxEDepth*/,
            0, /*step:  0 = step 1 */ /*For messages*/
            0,  /*not noDistinct*/
            /* 3-May-2016 nm */
            2, /* override discouraged-usage statements silently */
            1 /* Always allow other mathboxes */ /* 5-Aug-2020 nm */
            );

        if (nmbrLen(nmbrTmpPtr2)) {
          /* A proof for the step was found. */
          /* Get packed form of proof for shorter display */
          nmbrLet(&nmbrTmpPtr2, nmbrSquishProof(nmbrTmpPtr2));
          /* Temporarily zap proof into statement structure */
          /* (The bug check makes sure there is no proof attached to the
              definition - this would be impossible) */
          if (strcmp(g_Statement[showStmt].proofSectionPtr, "")) bug(231);
          if (g_Statement[showStmt].proofSectionLen != 0) bug(232);
          let(&str1, nmbrCvtRToVString(nmbrTmpPtr2,
                /* 25-Jan-2016 nm */
                0, /*explicitTargets*/
                0 /*statemNum, used only if explicitTargets*/));
          /* Temporarily zap proof into the $a statement */
          g_Statement[showStmt].proofSectionPtr = str1;
          g_Statement[showStmt].proofSectionLen = (long)strlen(str1) - 1;

          /* Display the HTML proof of syntax breakdown */
          typeProof(showStmt,
              0 /*pipFlag*/,
              0 /*startStep*/,
              0 /*endStep*/,
              0 /*endIndent*/,
              0 /*essentialFlag*/, /* <- also used as def flag in typeProof */
              1 /*renumberFlag*/,
              0 /*unknownFlag*/,
              0 /*notUnifiedFlag*/,
              0 /*reverseFlag*/,
              1 /*noIndentFlag*/,
              0 /*splitColumn*/,
              0 /*skipRepeatedSteps*/,  /* 28-Jun-2013 nm */
              1 /*texFlag*/,  /* Means either latex or html */
              1 /*htmlFlg*/);

          /* Restore the zapped statement structure */
          g_Statement[showStmt].proofSectionPtr = "";
          g_Statement[showStmt].proofSectionLen = 0;

          /* Deallocate storage */
          let(&str1, "");
          nmbrLet(&nmbrTmpPtr2, NULL_NMBRSTRING);

        } else { /* if (nmbrLen(nmbrTmpPtr2)) else */
          /* 5-Aug-2011 nm */
          /* Proof was not found - probable syntax error */
          if (g_outputToString != 0) bug(246);
          printLongLine(cat(
              "?Warning: Unable to generate syntax breakdown for \"",
              g_Statement[showStmt].labelName,
              "\".", NULL), "    ", " ");
        }


        /* Restore the zapped statement structure */
        g_Statement[showStmt].type = (char)a_;
        (g_Statement[showStmt].mathString)[0] = zapStatement1stToken;

        /* Deallocate storage */
        nmbrLet(&nmbrTmpPtr1, NULL_NMBRSTRING);

      } /* if (wffToken >= 0) */

    } /* if (subType == DEFINITION) */


  } /* if (htmlFlg && texFlag) */
  /* End of finding definition for syntax statement */


  /* 10/6/99 - Start of creating used-by list for html page */
  if (htmlFlg && texFlag) {
    /* 10/25/02 Clear out any previous g_printString accumulation
       for g_printStringForReferencedBy case below */
    fprintf(g_texFilePtr, "%s", g_printString);
    let(&g_printString, "");
    /* Start outputting to g_printString */
    if (g_outputToString != 0) bug(242);
    g_outputToString = 1;
    if (subType != SYNTAX) { /* Only do this for
        definitions, axioms, and theorems, not syntax statements */
      let(&str1, "");
      g_outputToString = 0; /* Switch output to console in case
            traceUsage reports an error */ /* 8-Dec-2018 nm */
      str1 = traceUsage(showStmt,
          0, /* recursiveFlag */
          0 /* cutoffStmt */);
      g_outputToString = 1; /* Restore output to string */ /* 8-Dec-2018 nm */
      /* if (str1[0]) { */ /* Used by at least one */
      /* 18-Jul-2015 nm */

      /* 30-Oct-2018 nm Commented out, and block unindented: */
      /*if (str1[0] == 'Y') {*/ /* Used by at least one */

      /* 30-Oct-2018 nm */
      /* We now output this all the time, using "(None)" if none, per
         request of Benoit Jubin */

      /* str1[i] will be 'Y' if used by showStmt */
      /* Convert usage list str1 to html links */
      switch (subType) {
        case AXIOM:  let(&str3, "axiom"); break;
        case DEFINITION: let(&str3, "definition"); break;
        case THEOREM: let(&str3, "theorem"); break;
        default: bug(233);
      }
      /******* pre 10/10/02
      let(&str2, cat("<FONT SIZE=-1 FACE=sans-serif>This ", str3,
          " is referenced by: ", NULL));
      *******/
      /* 10/10/02 */
      let(&str2, cat("<TR><TD ALIGN=LEFT><FONT SIZE=-1><B>This ", str3,
          " is referenced by:</B>", NULL));

      if (str1[0] == 'Y') { /* Used by at least one */
        /********* 18-Jul-2015 Deleted code *********************/
        /*
        /@ Convert str1 to trailing space after each label @/
        let(&str1, cat(right(str1, 2), " ", NULL));
        let(&str5, ""); /@ Buffer for very long strings @/ /@ 19-Sep-2012 nm @/
        i = 0;
        while (1) {
          j = i + 1;
          i = instr(j, str1, " ");
          if (!i) break;
          /@ Extract the label @/
          let(&str3, seg(str1, j, i - 1));
          /@ Find the statement number @/
          m = lookupLabel(str3);
          if (m < 0) {
            /@ The lookup should never fail @/
            bug(240);
            continue;
          }
          /@ It should be a $p @/
          if (g_Statement[m].type != p_) bug(241);
          /@ Get the pink number @/
          let(&str4, "");
          str4 = pinkHTML(m);
          /@ Assemble the href @/
          let(&str2, cat(str2, " &nbsp;<A HREF=\"",
              str3, ".html\">",
              str3, "</A>", str4, NULL));
          /@ 8-Aug-2008 nm If line is very long, print it out and reset
             it to speed up program (SHOW STATEMENT syl/HTML is very slow) @/
          /@ 8-Aug-2008 nm This doesn't solve problem, because the bottleneck
             is printing g_printStringForReferencedBy below.  This whole
             code section needs to be redesigned to solve the speed problem. @/
          /@
          if (strlen(str2) > 500) {
            printLongLine(str2, "", "\"");
            let(&str2, "");
          }
          @/
          /@ 19-Sep-2012 nm Try again to fix SHOW STATEMENT syl/HTML speed
             without a major rewrite @/
          /@
          Unfortunately, makes little difference.  Using lcc:
          orig    real    1m6.676s
          500     real    1m2.285s
          3000    real    1m2.181s
          3000    real    1m1.663s
          3000    real    1m1.785s
          5000    real    1m0.678s
          5000    real    1m0.169s
          5000    real    1m1.951s
          5000    real    1m2.307s
          5000    real    1m1.717s
          7000    real    1m2.048s
          7000    real    1m2.012s
          7000    real    1m1.817s
          10000   real    1m2.779s
          10000   real    1m1.830s
          20000   real    1m1.431s
          50000   real    1m1.325s
          100000  real    1m3.172s
          100000  real    1m4.657s
          (Added 17-Jul-2015: The 1 minute is probably due to the old
              traceUsage algorithm that built a huge string of labels; should
              be faster now.)
          @/
          /@ Accumulate large cat buffer when small cats exceed certain size @/
          if (strlen(str2) > 5000) {
            let(&str5, cat(str5, str2, NULL));
            let(&str2, "");
          }
          /@ End 19-Sep-2012 @/
        }
        /@ let(&str2, cat(str2, "</FONT></TD></TR>", NULL)); @/ /@ old @/
         /@ 19-Sep-2012 nm Include buffer in output string@/
        let(&str2, cat(str5, str2, "</FONT></TD></TR>", NULL));
        printLongLine(str2, "", "\"");
        */
        /**************** 18-Jul-2015 End of deleted code *********/


        let(&str5, ""); /* Buffer for very long strings */ /* 19-Sep-2012 nm */
        /* Scan all future statements in str1 Y/N list */
        for (m = showStmt + 1; m <= g_statements; m++) {
          /* Scan the used-by map */
          if (str1[m] != 'Y') continue;
          /* Get the label */
          let(&str3, g_Statement[m].labelName);
          /* It should be a $p */
          if (g_Statement[m].type != p_) bug(241);
          /* Get the pink number */
          let(&str4, "");
          str4 = pinkHTML(m);
          /* Assemble the href */
          let(&str2, cat(str2, " &nbsp;<A HREF=\"",
              str3, ".html\">",
              /*str3, "</A>", str4, NULL));*/
              str3, "</A>\n", str4, NULL));  /* 18-Jul-2015 nm */
          /* 8-Aug-2008 nm If line is very long, print it out and reset
             it to speed up program (SHOW STATEMENT syl/HTML is very slow) */
          /* 8-Aug-2008 nm This doesn't solve problem, because the bottleneck
             is printing g_printStringForReferencedBy below.  This whole
             code section needs to be redesigned to solve the speed problem. */
          /* 19-Sep-2012 nm Try again to fix SHOW STATEMENT syl/HTML speed
             without a major rewrite.  Unfortunately, made little difference. */
          /* 18-Jul-2015: Part of slowdown was due to the old
              traceUsage algorithm that built a huge string of labels.  Improved
             from 313 sec to 280 sec for 'sh st syl/a'; still a problem. */
          /* Accumulate large cat buffer when small cats exceed certain size */
          if (strlen(str2) > 5000) {
            let(&str5, cat(str5, str2, NULL));
            let(&str2, "");
          }
          /* End 19-Sep-2012 */
        } /* next m (statement number) */


      /* 30-Oct-2018 nm Added "else" clause to print "(None)" if no refs */
      } else {
        /* There is no usage of this statement; print "(None)" */
        let(&str5, "");
        let(&str2, cat(str2, " (None)", NULL));

      } /* if (str1[0] == 'Y') */
      /* let(&str2, cat(str2, "</FONT></TD></TR>", NULL)); */ /* old */
       /* 19-Sep-2012 nm Include buffer in output string*/
      let(&str2, cat(str5, str2, "</FONT></TD></TR>", NULL));
      /*printLongLine(str2, "", "\"");*/ /* 18-Jul-2015 nm Deleted */
      if (g_printString[0]) {
        bug(256);  /* 18-Jul-2015 nm */
      }
      let(&g_printString, str2); /* 18-Jul-2015 nm */
    } /* if (subType != SYNTAX) */
    if (subType == THEOREM) {
      /* 10/25/02 The "referenced by" does not show up after the proof
         because we moved the typeProof() to below.  Therefore, we save
         g_printString into a temporary global holding variable to print
         at the proper place inside of typeProof().  Ugly but necessary
         with present design. */
      /* In the case of THEOREM, we save and reset the g_printString.  In the
         case of != THEOREM (i.e. AXIOM and DEFINITION), g_printString will
         be printed and cleared below. */
      let(&g_printStringForReferencedBy, g_printString);
      let(&g_printString, "");
    }

    /* Printing of the trailer in mmwtex.c will close out string later */
    g_outputToString = 0;
  } /* if (htmlFlg && texFlag) */
  /* 10/6/99 - End of used-by list for html page */


  /* 10/25/02  Moved this to after the block above, so referenced statements
     show up first for convenience */
  if (htmlFlg && texFlag) {
    /*** Output the html proof for $p statements ***/
    /* Note that we also output the axiom and definition usage
       lists inside this function */
    if (g_Statement[showStmt].type == (char)p_) {
      typeProof(showStmt,
          0 /*pipFlag*/,
          0 /*startStep*/,
          0 /*endStep*/,
          0 /*endIndent*/,
          1 /*essentialFlag*/,
          1 /*renumberFlag*/,
          0 /*unknownFlag*/,
          0 /*notUnifiedFlag*/,
          0 /*reverseFlag*/,
          1 /*noIndentFlag*/,
          0 /*splitColumn*/,
          0 /*skipRepeatedSteps*/,  /* 28-Jun-2013 nm */
          1 /*texFlag*/,  /* Means either latex or html */
          1 /*htmlFlg*/);
    } /* if (g_Statement[showStmt].type == (char)p_) */
  } /* if (htmlFlg && texFlag) */
  /* End of html proof for $p statements */

  /* typeProof should have cleared this out */
  if (g_printStringForReferencedBy[0]) bug(243);

 returnPoint:
  /* Deallocate strings */
  nmbrLet(&nmbrDDList, NULL_NMBRSTRING);
  let(&str1, "");
  let(&str2, "");
  let(&str3, "");
  let(&str4, "");
  let(&str5, "");
  let(&htmlDistinctVars, "");
} /* typeStatement */



/* 13-Aug-2017 nm */
/* Get the HTML string of dummy variables used by a proof for the
   theorem's web page.  It should be called only if we're in
   HTML output mode i.e.  SHOW STATEMENT .../HTML or /ALT_HTML */
/* This is HARD-CODED FOR SET.MM and will not produce meaningful
   output for other databases (so far none) with $d's */
/* Caller must deallocate returned string */
vstring htmlDummyVars(long showStmt)
{
  nmbrString *optDVA; /* Pointer only; not allocated directly */
  nmbrString *optDVB; /* Pointer only; not allocated directly */
  long numDVs;
  nmbrString *optHyp; /* Pointer only; not allocated directly */
  long numOptHyps;
  vstring str1 = "";
  long k, l, n, hypStmt;

  /* Variables used while collecting a statement's dummy variables in $d's */
  long dummyVarCount; /* # of (different) dummy vars found in $d statements */
  vstring dummyVarUsed = ""; /* 'Y'/'N' indicators that we found that var */
  vstring htmlDummyVarList = ""; /* Output HTML string */
  long dummyVar; /* Current variable in a $d; test if it's a dummy variable */

  /* This function should be called only for web page generation */
  /*if (!(g_htmlFlag && texFlag)) bug(261);*/  /* texFlag is not global */
  if (!g_htmlFlag) bug(261);

  if (g_Statement[showStmt].type != p_) bug(262);
  if (strcmp("|-", g_MathToken[
            (g_Statement[showStmt].mathString)[0]].tokenName)) {
    /* Don't process syntax statements */
    goto RETURN_POINT;
  }

  optDVA = g_Statement[showStmt].optDisjVarsA;
  optDVB = g_Statement[showStmt].optDisjVarsB;
  numDVs = nmbrLen(optDVA);
  optHyp = g_Statement[showStmt].optHypList;
  numOptHyps = nmbrLen(optHyp);

  if (numDVs == 0) {  /* Don't create a hint list if no $d's */
    /*let(&htmlDummyVarList, "(no restrictions)");*/
    goto RETURN_POINT;
  }

  dummyVarCount = 0;
  if (numDVs != 0) {

    /* Update g_WrkProof.proofString with current proof so we can
       search it later to see if it uses the dummy variable */
    parseProof(showStmt); /* Prints message if severe error */

    /* Create an array of Y/N indicators that variable is occurs in a
       $d statement as a dummy variable */
    let(&dummyVarUsed, string(g_mathTokens, 'N'));
    for (k = 0; k < numDVs; k++) {
      for (l = 1; l <= 2; l++) {
        if (l == 1) {
          dummyVar = optDVA[k];
        } else {
          dummyVar = optDVB[k];
        }
        /* At this point, dummyVar is just a var in the $d; we
           must still check that it is in the optHypList */
        /* See if potential dummyVar is in optHypList */
        if (dummyVarUsed[dummyVar] == 'N') {
          for (n = 0; n < numOptHyps; n++) {
            /* Check whether dummyVar matches the 2nd token of an
               optional hypothesis list entry e.g. "x" in "set x" */
            hypStmt = g_Statement[showStmt].optHypList[n];
            if (g_Statement[hypStmt].mathString[1] == dummyVar) {
              /* dummyVar is a dummy variable */

              /* See if it is used by the proof */
              /* g_WrkProof.proofString was updated by parseProof(showStmt)
                 above */
              if (nmbrElementIn(1, g_WrkProof.proofString, hypStmt) == 0) {
                break; /* It's not used by the proof; stop hyp scan */
              }

              dummyVarUsed[dummyVar] = 'Y';
              dummyVarCount++;
              /* tokenToTex allocates str1; must deallocate it first */
              let(&str1, "");
              /* Convert token to htmldef/althtmldef string */
              str1 = tokenToTex(g_MathToken[dummyVar].tokenName,
                  showStmt);
              let(&htmlDummyVarList, cat(htmlDummyVarList, " ", str1, NULL));
              break; /* Found a match, so stop further checking */
            }
          } /* next n, 0 to numOptHyps-1*/
        } /* if dummy var not used (yet) */
      } /* next l */
    } /* next k */
  } /* if (numDVs != 0) */

  if (dummyVarCount > 0) {
    let(&htmlDummyVarList, cat(
        "<CENTER>",
         "<A HREF=\"",

         /* 26-Aug-2017 nm */
         /* g_htmlHome is set by htmlhome in $t comment */
         (instr(1, g_htmlHome, "mmset.html") > 0) ?
             "mmset.html" :
             /* The following link will work in the NF and other
                "Proof Explorers" */
             "../mpeuni/mmset.html",  /* 19-Aug-2017, 26-Sep-2017 nm */

        "#dvnote1\">Dummy variable",
        /* Determine whether singular or plural */
        dummyVarCount > 1 ? "s" : "",
        "</A> ",  /* 14-Aug-2017 nm */
        /* 14-Jan-2016 nm Put a span around the variable list to localize
           the use of the special math font for ALT_HTML */
        (g_altHtmlFlag ? cat("<SPAN ", g_htmlFont, ">", NULL) : ""),
                                           /* 14-Jan-2016 nm */
        htmlDummyVarList,
        (g_altHtmlFlag ? "</SPAN>" : ""),
        /*
        dummyVarCount > 1 ? " are assumed to be mutually distinct and"
            : " is assumed to be",
        */
        dummyVarCount > 1 ? " are mutually distinct and"
            : " is",
        " distinct from all other variables.",
        "</CENTER>",
        NULL));
  } /* htmlDummyVars */


 RETURN_POINT:
  /* Deallocate strings */
  let(&dummyVarUsed, "");
  let(&str1, "");

  return htmlDummyVarList;
} /* htmlDummyVars */



/* 4-Jan-2014 nm */
/* Get the HTML string of "allowed substitutions" list for an axiom
   or theorem's web page.  It should be called only if we're in
   HTML output mode i.e.  SHOW STATEMENT .../HTML or /ALT_HTML */
/* This is HARD-CODED FOR SET.MM and will not produce meaningful
   output for other databases (so far none) with $d's */
/* Caller must deallocate returned string */
vstring htmlAllowedSubst(long showStmt)
{
  nmbrString *reqHyp; /* Pointer only; not allocated directly */
  long numReqHyps;
  nmbrString *reqDVA; /* Pointer only; not allocated directly */
  nmbrString *reqDVB; /* Pointer only; not allocated directly */
  long numDVs;
  nmbrString *setVar = NULL_NMBRSTRING; /* set (individual) variables */
  char *strptr;
  vstring str1 = "";
  long setVars;
  long wffOrClassVar;
  vstring setVarDVFlag = "";
  flag found, first;
  long i, j, k;
  vstring htmlAllowedList = "";
  long countInfo = 0;

  reqDVA = g_Statement[showStmt].reqDisjVarsA;
  reqDVB = g_Statement[showStmt].reqDisjVarsB;
  numDVs = nmbrLen(reqDVA);

  reqHyp = g_Statement[showStmt].reqHypList;
  numReqHyps = nmbrLen(reqHyp);

  /* This function should be called only for web page generation */
  /*if (!(g_htmlFlag && texFlag)) bug(250);*/  /* texFlag is not global */
  if (!g_htmlFlag) bug(250);

  if (g_Statement[showStmt].mathStringLen < 1) bug(254);
  if (strcmp("|-", g_MathToken[
            (g_Statement[showStmt].mathString)[0]].tokenName)) {
    /* Don't process syntax statements */
    goto RETURN_POINT;
  }

  if (numDVs == 0) {  /* Don't create a hint list if no $d's */
    /*let(&htmlAllowedList, "(no restrictions)");*/
    goto RETURN_POINT;
  }

  /* Collect list of all set variables in the theorem */
  /* First, count the number of set variables */
  setVars = 0;
  for (i = 0; i < numReqHyps; i++) {
    /* Scan "setvar" variables */
    if (g_Statement[reqHyp[i]].type == (char)e_) continue;
    if (g_Statement[reqHyp[i]].type != (char)f_) bug(251);
    if (g_Statement[reqHyp[i]].mathStringLen != 2)
      bug(252); /* $f must have 2 tokens */
    strptr = g_MathToken[
              (g_Statement[reqHyp[i]].mathString)[0]].tokenName;
    /* THE FOLLOWING IS SPECIFIC TO set.mm */
    if (strcmp("setvar", strptr)) continue;
                                  /* Not a set variable */
    setVars++;
  }
  /* Next, create a list of them in setVar[] */
  j = 0;
  nmbrLet(&setVar, nmbrSpace(setVars));
  for (i = 0; i < numReqHyps; i++) {
    /* Scan "setvar" variables */
    if (g_Statement[reqHyp[i]].type == (char)e_) continue;
    strptr = g_MathToken[
              (g_Statement[reqHyp[i]].mathString)[0]].tokenName;
    if (strcmp("setvar", strptr)) continue;
                                  /* Not a set variable */
    setVar[j] = (g_Statement[reqHyp[i]].mathString)[1];
    j++;
  }
  if (j != setVars) bug(253);

  /* Scan "wff" and "class" variables for attached $d's */
  for (i = 0; i < numReqHyps; i++) {
    /* Look for a "wff" and "class" variable */
    if (g_Statement[reqHyp[i]].type == (char)e_) continue;
    strptr = g_MathToken[
              (g_Statement[reqHyp[i]].mathString)[0]].tokenName;
    if (strcmp("wff", strptr) && strcmp("class", strptr)) continue;
                                  /* Not a wff or class variable */
    wffOrClassVar = (g_Statement[reqHyp[i]].mathString)[1];
    let(&setVarDVFlag, string(setVars, 'N')); /* No $d yet */
    /* Scan for attached $d's */
    for (j = 0; j < numDVs; j++) {
      found = 0;
      if (wffOrClassVar == reqDVA[j]) {
        for (k = 0; k < setVars; k++) {
          if (setVar[k] == reqDVB[j]) {
            setVarDVFlag[k] = 'Y';
            found = 1;
            break;
          }
        }
      }
      if (found) continue;
      /* Repeat with swapped $d arguments */
      if (wffOrClassVar == reqDVB[j]) {
        for (k = 0; k < setVars; k++) {
          if (setVar[k] == reqDVA[j]) {
            setVarDVFlag[k] = 'Y';
            break;
          }
        }
      }
    } /* next $d */

    /* Collect set vars that don't have $d's with this wff or class var */
    /* First, if there aren't any, then omit this wff or class var */
    found = 0;
    for (j = 0; j < setVars; j++) {
      if (setVarDVFlag[j] == 'N') {
        found = 1;
        break;
      }
    }
    if (found == 0) continue; /* All set vars have $d with this wff or class */

    let(&str1, "");
    str1 = tokenToTex(g_MathToken[wffOrClassVar].tokenName, showStmt);
         /* tokenToTex allocates str1; we must deallocate it eventually */
    countInfo++;
    let(&htmlAllowedList, cat(htmlAllowedList, " &nbsp; ",
        str1, "(", NULL));
    first = 1;
    for (j = 0; j < setVars; j++) {
      if (setVarDVFlag[j] == 'N') {
        let(&str1, "");
        str1 = tokenToTex(g_MathToken[setVar[j]].tokenName, showStmt);
        let(&htmlAllowedList, cat(htmlAllowedList,
            (first == 0) ? "," : "", str1, NULL));
        if (first == 0) countInfo++;
        first = 0;
      }
    }
    let(&htmlAllowedList, cat(htmlAllowedList, ")", NULL));

  } /* next i (wff or class var) */

 RETURN_POINT:

  if (htmlAllowedList[0] != 0) {
    let(&htmlAllowedList, cat("<CENTER>",
        "<A HREF=\"",

        /* 26-Aug-2017 nm */
        /* g_htmlHome is set by htmlhome in $t comment */
        (instr(1, g_htmlHome, "mmset.html") > 0) ?
            "mmset.html" :
            /* The following link will work in the NF and other
               "Proof Explorers" */
            "../mpeuni/mmset.html",  /* 19-Aug-2017, 26-Sep-2017 nm */

        "#allowedsubst\">Allowed substitution</A> hint",
        ((countInfo != 1) ? "s" : ""), ": ",
        (g_altHtmlFlag ? cat("<SPAN ", g_htmlFont, ">", NULL) : ""),
                                           /* 14-Jan-2016 nm */
        htmlAllowedList,
        (g_altHtmlFlag ? "</SPAN>" : ""),    /* 14-Jan-2016 nm */
        "</CENTER>", NULL));
  }

  /* Deallocate strings */
  nmbrLet(&setVar, NULL_NMBRSTRING);
  let(&str1, "");
  let(&setVarDVFlag, "");

  return htmlAllowedList;
} /* htmlAllowedSubst */



/* Displays a proof (or part of a proof, depending on arguments). */
/* Note that parseProof() and verifyProof() are assumed to have been called,
   so that the g_WrkProof structure elements are assigned for the current
   statement. */
/* 8/28/00 - this is also used for the MIDI output, since we conveniently
   have the necessary proof information here.  The function outputMidi()
   is called from within. */
void typeProof(long statemNum,
  flag pipFlag, /* Means use g_ProofInProgress; statemNum must be proveStatement*/
  long startStep, long endStep,
  long endIndent,
  flag essentialFlag, /* <- also used as definition/axiom flag for HTML
      syntax breakdown when called from typeStatement() */
  flag renumberFlag,
  flag unknownFlag,
  flag notUnifiedFlag,
  flag reverseFlag,
  flag noIndentFlag, /* Means Lemmon-style proof */
  long splitColumn, /* START_COLUMN */
  flag skipRepeatedSteps, /* NO_REPEATED_STEPS */  /* 28-Jun-2013 nm */
  flag texFlag,
  flag htmlFlg /* htmlFlg added 6/27/99 */
  /* flag g_midiFlag - global to avoid changing many calls to typeProof() */
  )
{
  /* From HELP SHOW PROOF: Optional qualifiers:
    / ESSENTIAL - the proof tree is trimmed of all $f hypotheses before
        being displayed.
    / FROM_STEP <step> - the display starts at the specified step.  If
        this qualifier is omitted, the display starts at the first step.
    / TO_STEP <step> - the display ends at the specified step.  If this
        qualifier is omitted, the display ends at the last step.
    / TREE_DEPTH <number> - Only steps at less than the specified proof
        tree depth are displayed.  Useful for obtaining an overview of
        the proof.
    / REVERSE - the steps are displayed in reverse order.
    / RENUMBER - when used with / ESSENTIAL, the steps are renumbered
        to correspond only to the essential steps.
    / TEX - the proof is converted to LaTeX and stored in the file opened
        with OPEN TEX.
    / HTML - the proof is converted to HTML and stored in the file opened
        with OPEN HTML.
    / LEMMON - The proof is displayed in a non-indented format known
        as Lemmon style, with explicit previous step number references.
        If this qualifier is omitted, steps are indented in a tree format.
    / START_COLUMN <number> - Overrides the default column at which
        the formula display starts in a Lemmon style display.  May be
        used only in conjuction with / LEMMON.
    / NO_REPEATED_STEPS - When a proof step is identical to an earlier
        step, it will not be repeated.  Instead, a reference to it will be
        changed to a reference to the earlier step.  In particular,
        SHOW PROOF <label> / LEMMON / RENUMBER / NO_REPEATED_STEPS
        will have the same proof step numbering as the web page proof
        generated by SHOW STATEMENT  <label> / HTML, rather than
        the proof step numbering of the indented format
        SHOW PROOF <label> / RENUMBER.  This qualifier affects only
        displays also using the / LEMMON qualifier.
    / NORMAL - The proof is displayed in normal format suitable for
        inclusion in a source file.  May not be used with any other
        qualifier.
    / COMPRESSED - The proof is displayed in compressed format
        suitable for inclusion in a source file.  May not be used with
        any other qualifier.
    / STATEMENT_SUMMARY - Summarizes all statements (like a brief SHOW
        STATEMENT) used by the proof.  May not be used with any other
        qualifier except / ESSENTIAL.
    / DETAILED_STEP <step> - Shows the details of what is happening at
        a specific proof step.  May not be used with any other qualifier.
    / MIDI - 8/28/00 - puts out a midi sound file instead of a proof
        - determined by the global variable g_midiFlag, not by a parameter to
        typeProof()
  */
  long i, j, plen, step, stmt, lens, lent, maxStepNum;
  vstring tmpStr = "";
  vstring tmpStr1 = "";
  vstring locLabDecl = "";
  vstring tgtLabel = "";
  vstring srcLabel = "";
  vstring startPrefix = "";
  vstring tgtPrefix = "";
  vstring srcPrefix = "";
  vstring userPrefix = "";
  vstring contPrefix = "";
  vstring statementUsedFlags = "";
  vstring startStringWithNum = ""; /* 22-Apr-2015 nm */
  vstring startStringWithoutNum = ""; /* 22-Apr-2015 nm */
  nmbrString *proof = NULL_NMBRSTRING;
  nmbrString *localLabels = NULL_NMBRSTRING;
  nmbrString *localLabelNames = NULL_NMBRSTRING;
  nmbrString *indentationLevel = NULL_NMBRSTRING;
  nmbrString *targetHyps = NULL_NMBRSTRING;
  nmbrString *essentialFlags = NULL_NMBRSTRING;
  nmbrString *stepRenumber = NULL_NMBRSTRING;
  nmbrString *notUnifiedFlags = NULL_NMBRSTRING;
  nmbrString *unprovedList = NULL_NMBRSTRING; /* For traceProofWork() */
  nmbrString *relativeStepNums = NULL_NMBRSTRING; /* For unknownFlag */
  long maxLabelLen = 0;
  long maxStepNumLen = 1;
  long maxStepNumOffsetLen = 0; /* 22-Apr-2015 nm */
  char type;
  flag stepPrintFlag;
  long fromStep, toStep, byStep;
  vstring hypStr = "";
  nmbrString *hypPtr;
  long hyp, hypStep;

  /* For statement syntax breakdown (see section below added 2/5/02 for better
     syntax hints), we declare the following 3 variables. */
  static long wffToken = -1; /* array index of the hard-coded token "wff" -
      static so we only have to look it up once - set to -2 if not found */
  nmbrString *nmbrTmpPtr1; /* Pointer only; not allocated directly */
  nmbrString *nmbrTmpPtr2; /* Pointer only; not allocated directly */

  /* 31-Jan-2010 nm */
  /* skipRepeatedSteps = 0; */ /* 28-Jun-2010 nm Now set by parameter */
  if (htmlFlg && texFlag) skipRepeatedSteps = 1; /* Keep old behavior */
  /* Comment out the following line if you want to revert to the old
     Lemmon-style behavior with local label references for reused compressed
     proof steps is desired.  The only reason for doing this is to obtain
     the same steps and step numbers as the indented proof, rather than
     those on the HTML pages. */
  /* if (noIndentFlag) skipRepeatedSteps = 1; */
           /* 28-Jun-2013 nm Now set by parameter */

  if (htmlFlg && texFlag) {




    g_outputToString = 1; /* Flag for print2 to add to g_printString */
    if (essentialFlag) {

      /* 26-Aug-2017 nm */
      /* See if there are dummy variables.  If so, print them below
         "Proof of Theorem", which means we have to make the "Proof of
         Theorem" line separate and not the table caption, so that the
         "Distinct variables..." line does not become part of the table. */
      let(&tmpStr, "");
      tmpStr = htmlDummyVars(statemNum);
      if (tmpStr[0] != 0) {
        print2("<CENTER><B>Proof of Theorem <FONT\n");
        printLongLine(cat("   COLOR=", GREEN_TITLE_COLOR, ">",
            asciiToTt(g_Statement[statemNum].labelName),
            "</FONT></B></CENTER>", NULL), "", "\"");
        /* Print the list of dummy variables */
        printLongLine(tmpStr, "", "\"");
        let(&tmpStr, "");
        print2("<CENTER><TABLE BORDER CELLSPACING=0 BGCOLOR=%s\n",
            MINT_BACKGROUND_COLOR);
        print2("SUMMARY=\"Proof of theorem\">\n");
      } else {
      /* End of 26-Aug-2017 */

        /* For bobby.cast.org approval */
        print2("<CENTER><TABLE BORDER CELLSPACING=0 BGCOLOR=%s\n",
            MINT_BACKGROUND_COLOR);
        print2("SUMMARY=\"Proof of theorem\">\n");
        print2("<CAPTION><B>Proof of Theorem <FONT\n");
        printLongLine(cat("   COLOR=", GREEN_TITLE_COLOR, ">",
            asciiToTt(g_Statement[statemNum].labelName),
            "</FONT></B></CAPTION>", NULL), "", "\"");
      }
    } else {
      /* This is a syntax breakdown "proof" of a definition called
         from typeStatement */
      print2("<CENTER><TABLE BORDER CELLSPACING=0 BGCOLOR=%s\n",
          MINT_BACKGROUND_COLOR);
      if (!strcmp("ax-", left(g_Statement[g_showStatement].labelName, 3))) {
        /* For bobby.cast.org approval */
        print2("SUMMARY=\"Detailed syntax breakdown of axiom\">\n");
        print2("<CAPTION><B>Detailed syntax breakdown of Axiom <FONT\n");
      } else {
        /* For bobby.cast.org approval */
        print2("SUMMARY=\"Detailed syntax breakdown of definition\">\n");
        print2("<CAPTION><B>Detailed syntax breakdown of Definition <FONT\n");
      }
      printLongLine(cat("   COLOR=", GREEN_TITLE_COLOR, ">",
          asciiToTt(g_Statement[statemNum].labelName),
          "</FONT></B></CAPTION>", NULL), "", "\"");
    }
    print2(
        "<TR><TH>Step</TH><TH>Hyp</TH><TH>Ref\n");
    print2("</TH><TH>Expression</TH></TR>\n");
    g_outputToString = 0;
    /* printTexLongMath in typeProof will do this
    fprintf(g_texFilePtr, "%s", g_printString);
    let(&g_printString, "");
    */
  }

  if (!pipFlag) {
    parseProof(g_showStatement);
    if (g_WrkProof.errorSeverity > 1) {
      /* 2-Nov-2014 nm Prevent population of g_printString outside of web
         page generation to fix bug 1114 (reported by Sefan O'Rear). */
      if (htmlFlg && texFlag) {
        /* Print warning and close out proof table */
        g_outputToString = 1;
        print2(
      "<TD COLSPAN=4><B><FONT COLOR=RED>WARNING: Proof has a severe error.\n");
        print2("</FONT></B></TD></TR>\n");
        g_outputToString = 0;
        /* 18-Nov-2012 nm Fix bug 243 */
        /* Clear out g_printStringForReferencedBy to prevent bug 243 above */
        let(&g_printStringForReferencedBy, "");
      }
      return; /* verifyProof() could crash */
    }
    verifyProof(g_showStatement);
  }

  if (!pipFlag) {
    nmbrLet(&proof, g_WrkProof.proofString); /* The proof */
    if (g_midiFlag) { /* 8/28/00 */
      /* Get the uncompressed version of the proof */
      nmbrLet(&proof, nmbrUnsquishProof(proof));
    }
  } else {
    nmbrLet(&proof, g_ProofInProgress.proof); /* The proof */
  }
  plen = nmbrLen(proof);

  /* 6/27/99 - to reduce the number of steps displayed in an html proof,
     we will use a local label to reference the 2nd or later reference to a
     hypothesis, so the hypothesis won't have to be shown multiple times
     in the proof.
     31-Jan-2010 - do this for all Lemmon-style proofs */
  if (htmlFlg && texFlag && !noIndentFlag /* Lemmon */) {
    /* Only Lemmon-style proofs are implemented for html */
    bug(218);
  }
  /*if (htmlFlg && texFlag) {*/   /* pre-31-Jan-2010 */
  /* 31-Jan-2010 nm Do this for all Lemmon-style proofs */
  if (skipRepeatedSteps) {
    for (step = 0; step < plen; step++) {
      stmt = proof[step];
      if (stmt < 0) continue;  /* Unknown or label ref */
      type = g_Statement[stmt].type;
      if (type == f_ || type == e_  /* It's a hypothesis */
          || g_Statement[stmt].numReqHyp == 0) { /* A statement w/ no hyp */
        for (i = 0; i < step; i++) {
          if (stmt == proof[i]) {
            /* The hypothesis at 'step' matches an earlier hypothesis at i,
               so we will backreference 'step' to i with a local label */
            proof[step] = -1000 - i;
            break;
          }
        } /* next i */
      }
    } /* next step */
  }


  /* Collect local labels */
  for (step = 0; step < plen; step++) {
    stmt = proof[step];
    if (stmt <= -1000) {
      stmt = -1000 - stmt;
      if (!nmbrElementIn(1, localLabels, stmt)) {
        nmbrLet(&localLabels, nmbrAddElement(localLabels, stmt));
      }
    }
  }

  /* localLabelNames[] hold an integer which, when converted to string,
    is the local label name. */
  nmbrLet(&localLabelNames, nmbrSpace(plen));

  /* Get the indentation level */
  nmbrLet(&indentationLevel, nmbrGetIndentation(proof, 0));

  /* Get the target hypotheses */
  nmbrLet(&targetHyps, nmbrGetTargetHyp(proof, statemNum));

  /* Get the essential step flags, if required */
  if (essentialFlag || g_midiFlag) {
    nmbrLet(&essentialFlags, nmbrGetEssential(proof));
  } else {
    nmbrLet(&essentialFlags, NULL_NMBRSTRING);
  }

  /* 8/28/00 We now have enough information for the MIDI output, so
     do it */
  if (g_midiFlag) {
    outputMidi(plen, indentationLevel,
        essentialFlags, g_midiParam, g_Statement[statemNum].labelName);
    goto typeProof_return;
  }

  /* Get the step renumbering */
  nmbrLet(&stepRenumber, nmbrSpace(plen)); /* This initializes all step
      renumbering to step 0.  Later, we will use (for html) the fact that
      a step renumbered to 0 is a step to be skipped (6/27/99). */
  i = 0;
  maxStepNum = 0;
  for (step = 0; step < plen; step++) {
    stepPrintFlag = 1; /* Note: stepPrintFlag is reused below with a
        slightly different meaning (i.e. it will be printed after
        a filter such as notUnified is applied) */
    if (renumberFlag && essentialFlag) {
      if (!essentialFlags[step]) stepPrintFlag = 0;
    }
    /* if (htmlFlg && texFlag && proof[step] < 0) stepPrintFlag = 0; */
    /* 31-Jan-2010 nm changed to: */
    if (skipRepeatedSteps && proof[step] < 0) stepPrintFlag = 0;
    /* For standard numbering, stepPrintFlag will be always be 1 here */
    if (stepPrintFlag) {
      i++;
      stepRenumber[step] = i; /* Numbering for step to be printed */
      maxStepNum = i; /* To compute maxStepNumLen below */
    }
  }

  /* 22-Apr-2015 nm */
  /* Get the relative offset (0, -1, -2,...) for unknown steps */
  if (unknownFlag) {
    /* 21-Aug-2017 nm There could be unknown steps outside of MM-PA
       So remove this bugcheck, which seems spurious.  I can't see that
       getRelStepNums() cares whether we are in MM-PA. */
    /*
    if (!pipFlag) {
      bug(255);
    }
    */
    relativeStepNums = getRelStepNums(g_ProofInProgress.proof);
  }

  /* Get steps not unified (pipFlag only) */
  if (notUnifiedFlag) {
    if (!pipFlag) bug(205);
    nmbrLet(&notUnifiedFlags, nmbrSpace(plen));
    for (step = 0; step < plen; step++) {
      notUnifiedFlags[step] = 0;
      if (nmbrLen(g_ProofInProgress.source[step])) {
        if (!nmbrEq(g_ProofInProgress.target[step],
            g_ProofInProgress.source[step])) notUnifiedFlags[step] = 1;
      }
      if (nmbrLen(g_ProofInProgress.user[step])) {
        if (!nmbrEq(g_ProofInProgress.target[step],
            g_ProofInProgress.user[step])) notUnifiedFlags[step] = 1;
      }
    }
  }

  /* Get the printed character length of the largest step number */
  i = maxStepNum;
  while (i >= 10) {
    i = i/10; /* The number is printed in base 10 */
    maxStepNumLen++;
  }
  /* 22-Apr-2015 nm */
  /* Add extra space for negative offset numbers e.g. "3:-1" */
  if (unknownFlag) {
    maxStepNumOffsetLen = 3; /* :, -, # */
    j = 0;
    for (i = 0; i < plen; i++) {
      j = relativeStepNums[i];
      if (j <= 0) break; /* Found first unknown step (largest offset) */
    }
    while (j <= -10) {
      j = j/10; /* The number is printed in base 10 */
      maxStepNumOffsetLen++;
    }
  }



  /* Get local labels and maximum label length */
  /* lent = target length, lens = source length */
  for (step = 0; step < plen; step++) {
    lent = (long)strlen(g_Statement[targetHyps[step]].labelName);
    stmt = proof[step];
    if (stmt < 0) {
      if (stmt <= -1000) {
        stmt = -1000 - stmt;
        /* stmt is now the step number a local label refers to */
        lens = (long)strlen(str((double)(localLabelNames[stmt])));
        let(&tmpStr1, ""); /* Clear temp alloc stack for str function */
      } else {
        if (stmt != -(long)'?') bug (219); /* the only other possibility */
        lens = 1; /* '?' (unknown step) */
      }
    } else {
      if (nmbrElementIn(1, localLabels, step)) {

        /* 6/27/99 The new philosophy is to number all local labels with the
           actual step number referenced, for better readability.  This means
           that if a *.mm label is a pure number, there may be ambiguity in
           the proof display, but this is felt to be too rare to be a serious
           drawback. */
        localLabelNames[step] = stepRenumber[step];

      }
      lens = (long)strlen(g_Statement[stmt].labelName);
    }
    /* Find longest label assignment, excluding local label declaration */
    if (maxLabelLen < lent + 1 + lens) {
      maxLabelLen = lent + 1 + lens; /* Target, =, source */
    }
  } /* Next step */

  /* Print the steps */
  if (reverseFlag
      && !g_midiFlag /* 8/28/00 */
      ) {
    fromStep = plen - 1;
    toStep = -1;
    byStep = -1;
  } else {
    fromStep = 0;
    toStep = plen;
    byStep = 1;
  }
  for (step = fromStep; step != toStep; step = step + byStep) {

    /* Filters to decide whether to print the step */
    stepPrintFlag = 1;
    if (startStep > 0) { /* The user's FROM_STEP */
      if (step + 1 < startStep) stepPrintFlag = 0;
    }
    if (endStep > 0) { /* The user's TO_STEP */
      if (step + 1 > endStep) stepPrintFlag = 0;
    }
    if (endIndent > 0) { /* The user's INDENTATION_DEPTH */
      if (indentationLevel[step] + 1 > endIndent) stepPrintFlag = 0;
    }
    if (essentialFlag) {
      if (!essentialFlags[step]) stepPrintFlag = 0;
    }
    if (notUnifiedFlag) {
      if (!notUnifiedFlags[step]) stepPrintFlag = 0;
    }
    if (unknownFlag) {
      if (proof[step] != -(long)'?') stepPrintFlag = 0;
    }

    /* 6/27/99 Skip steps that are local label references for html */
    /* if (htmlFlg && texFlag) { */
    /* 31-Jan-2010 nm changed to: */
    if (skipRepeatedSteps) {
      if (stepRenumber[step] == 0) stepPrintFlag = 0;
    }

    /* 8/28/00 For MIDI files, ignore all qualifiers and process all steps */
    if (g_midiFlag) stepPrintFlag = 1;

    if (!stepPrintFlag) continue;

    if (noIndentFlag) {
      let(&tgtLabel, "");
    } else {
      let(&tgtLabel, g_Statement[targetHyps[step]].labelName);
    }
    let(&locLabDecl, ""); /* Local label declaration */
    stmt = proof[step];
    if (stmt < 0) {
      if (stmt <= -1000) {
        stmt = -1000 - stmt;
        /* if (htmlFlg && texFlag) bug(220); */
        /* 31-Jan-2010 nm Changed to: */
        if (skipRepeatedSteps) bug(220); /* If html, a step referencing a
            local label will never be printed since it will be skipped above */
        /* stmt is now the step number a local label refers to */
        if (noIndentFlag) {
          let(&srcLabel, cat("@", str((double)(localLabelNames[stmt])), NULL));
        } else {
          let(&srcLabel, cat("=", str((double)(localLabelNames[stmt])), NULL));
        }
        type = g_Statement[proof[stmt]].type;
      } else {
        if (stmt != -(long)'?') bug(206);
        if (noIndentFlag) {
          let(&srcLabel, chr(-stmt)); /* '?' */
        } else {
          let(&srcLabel, cat("=", chr(-stmt), NULL)); /* '?' */
        }
        type = '?';
      }
    } else {
      if (nmbrElementIn(1, localLabels, step)) {
        /* This statement declares a local label */
        if (noIndentFlag) {
          /* if (!(htmlFlg && texFlag)) { */
          /* 31-Jan-2010 nm Changed to: */
          if (!(skipRepeatedSteps)) { /* No local label declaration is
              shown for html */
            let(&locLabDecl, cat("@", str((double)(localLabelNames[step])), ":", NULL));
          }
        } else {
          let(&locLabDecl, cat(str((double)(localLabelNames[step])), ":", NULL));
        }
      }

      if (noIndentFlag) {
        let(&srcLabel, g_Statement[stmt].labelName);

        /* For non-indented mode, add step numbers of hypotheses after label */
        let(&hypStr, "");
        hypStep = step - 1;
        hypPtr = g_Statement[stmt].reqHypList;
        for (hyp = g_Statement[stmt].numReqHyp - 1; hyp >=0; hyp--) {
          if (!essentialFlag || g_Statement[hypPtr[hyp]].type == (char)e_) {
            i = stepRenumber[hypStep];
            if (i == 0) {
              /* if (!(htmlFlg && texFlag)) bug(221); */
              /* 31-Jan-2010 nm Changed to: */
              if (!(skipRepeatedSteps)) bug(221);
              if (proof[hypStep] != -(long)'?') {
                if (proof[hypStep] > -1000) bug(222);
                if (localLabelNames[-1000 - proof[hypStep]] == 0) bug(223);
                if (localLabelNames[-1000 - proof[hypStep]] !=
                    stepRenumber[-1000 - proof[hypStep]]) bug(224);
                /* Get the step number the hypothesis refers to */
                i = stepRenumber[-1000 - proof[hypStep]];
              } else {
                /* The hypothesis refers to an unknown step - use i as flag */
                i = -(long)'?';
              }
            }
            if (!hypStr[0]) {
              if (i != -(long)'?') {
                let(&hypStr, str((double)i));
              } else {
                let(&hypStr, "?");
              }
            } else {
              /* Put comma between more than one hypothesis reference */
              if (i != -(long)'?') {
                let(&hypStr, cat(str((double)i), ",", hypStr, NULL));
              } else {
                let(&hypStr, cat("?", ",", hypStr, NULL));
              }
            }
          }
          if (hyp < g_Statement[stmt].numReqHyp) {
            /* Move down to previous hypothesis */
            hypStep = hypStep - subproofLen(proof, hypStep);
          }
        } /* Next hyp */

        if (hypStr[0]) {
          /* Add hypothesis list after label */
          let(&srcLabel, cat(hypStr, " ", srcLabel, NULL));
        }

      } else {
        let(&srcLabel, cat("=", g_Statement[stmt].labelName, NULL));
      }
      type = g_Statement[stmt].type;
    }


#define PF_INDENT_INC 2
    /* Print the proof line */
    if (stepPrintFlag) {

      if (noIndentFlag) {
        let(&startPrefix, cat(
            space(maxStepNumLen - (long)strlen(str((double)(stepRenumber[step])))),
            str((double)(stepRenumber[step])),
            " ",
            srcLabel,
            space(splitColumn - (long)strlen(srcLabel) - (long)strlen(locLabDecl) - 1
                - maxStepNumLen - 1),
            " ", locLabDecl,
            NULL));
        if (pipFlag) {
          let(&tgtPrefix, startPrefix);
          let(&srcPrefix, cat(
              space(maxStepNumLen - (long)strlen(str((double)(stepRenumber[step])))),
              space((long)strlen(str((double)(stepRenumber[step])))),
              " ",
              space(splitColumn - 1
                  - maxStepNumLen),
              NULL));
          let(&userPrefix, cat(
              space(maxStepNumLen - (long)strlen(str((double)(stepRenumber[step])))),
              space((long)strlen(str((double)(stepRenumber[step])))),
              " ",
              "(User)",
              space(splitColumn - (long)strlen("(User)") - 1
                  - maxStepNumLen),
              NULL));
        }
        let(&contPrefix, space((long)strlen(startPrefix) + 4));
      } else {  /* not noIndentFlag */

        /* 22-Apr-2015 nm */
        /* Compute prefix with and without step number.  For 'show new_proof
           /unknown', unknownFlag is set, and we add the negative offset. */
        let(&tmpStr, "");
        if (unknownFlag) {
          if (relativeStepNums[step] < 0) {
            let(&tmpStr, cat(" ", str((double)(relativeStepNums[step])), NULL));
          }
          let(&tmpStr, cat(tmpStr, space(maxStepNumOffsetLen
              - (long)(strlen(tmpStr))), NULL));
        }

        let(&startStringWithNum, cat(
            space(maxStepNumLen - (long)strlen(str((double)(stepRenumber[step])))),
            str((double)(stepRenumber[step])),
            tmpStr,
            " ", NULL));
        let(&startStringWithoutNum, space(maxStepNumLen + 1));


        let(&startPrefix, cat(
            startStringWithNum,
            space(indentationLevel[step] * PF_INDENT_INC
                - (long)strlen(locLabDecl)),
            locLabDecl,
            tgtLabel,
            srcLabel,
            space(maxLabelLen - (long)strlen(tgtLabel)
                - (long)strlen(srcLabel)),
            NULL));
        if (pipFlag) {
          let(&tgtPrefix, cat(
              startStringWithNum,
              space(indentationLevel[step] * PF_INDENT_INC - (long)strlen(locLabDecl)),
              locLabDecl,
              tgtLabel,
              space((long)strlen(srcLabel)),
              space(maxLabelLen - (long)strlen(tgtLabel) - (long)strlen(srcLabel)),
              NULL));
          let(&srcPrefix, cat(
              startStringWithoutNum,
              space(indentationLevel[step] * PF_INDENT_INC - (long)strlen(locLabDecl)),
              space((long)strlen(locLabDecl)),
              space((long)strlen(tgtLabel)),
              srcLabel,
              space(maxLabelLen - (long)strlen(tgtLabel) - (long)strlen(srcLabel)),
              NULL));
          let(&userPrefix, cat(
              startStringWithoutNum,
              space(indentationLevel[step] * PF_INDENT_INC - (long)strlen(locLabDecl)),
              space((long)strlen(locLabDecl)),
              space((long)strlen(tgtLabel)),
              "=(User)",
              space(maxLabelLen - (long)strlen(tgtLabel) - (long)strlen("=(User)")),
              NULL));
        }
        /*
        let(&contPrefix, space(maxStepNumLen + 1 + indentationLevel[step] *
            PF_INDENT_INC
            + maxLabelLen + 4));
        */
        let(&contPrefix, ""); /* Continuation lines use whole screen width */
      }

      if (!pipFlag) {

        if (!texFlag) {
          if (!g_midiFlag) { /* 8/28/00 */
            printLongLine(cat(startPrefix," $", chr(type), " ",
                nmbrCvtMToVString(g_WrkProof.mathStringPtrs[step]),
                NULL),
                contPrefix,
                chr(1));
                /* chr(1) is right-justify flag for printLongLine */
          }
        } else {  /* TeX or HTML */
          printTexLongMath(g_WrkProof.mathStringPtrs[step],
              cat(startPrefix, " $", chr(type), " ", NULL),
              contPrefix, stmt, indentationLevel[step]);
        }

      } else { /* pipFlag */
        if (texFlag) {
          /* nm 3-Feb-04  Added this bug check - it doesn't make sense to
             do this and it hasn't been tested anyway */
          print2("?Unsupported:  HTML or LaTeX proof for NEW_PROOF.\n");
          bug(244);
        }

        if (!nmbrEq(g_ProofInProgress.target[step], g_ProofInProgress.source[step])
            && nmbrLen(g_ProofInProgress.source[step])) {

          if (!texFlag) {
            printLongLine(cat(tgtPrefix, " $", chr(type), " ",
                nmbrCvtMToVString(g_ProofInProgress.target[step]),
                NULL),
                contPrefix,
                chr(1)); /* chr(1) is right-justify flag for printLongLine */
            printLongLine(cat(srcPrefix,"  = ",
                nmbrCvtMToVString(g_ProofInProgress.source[step]),
                NULL),
                contPrefix,
                chr(1)); /* chr(1) is right-justify flag for printLongLine */
          } else { /* TeX or HTML */
            printTexLongMath(g_ProofInProgress.target[step],
                cat(tgtPrefix, " $", chr(type), " ", NULL),
                contPrefix, 0, 0);
            printTexLongMath(g_ProofInProgress.source[step],
                cat(srcPrefix, "  = ", NULL),
                contPrefix, 0, 0);
          }
        } else {
          if (!texFlag) {
            printLongLine(cat(startPrefix, " $", chr(type), " ",
                nmbrCvtMToVString(g_ProofInProgress.target[step]),
                NULL),
                contPrefix,
                chr(1)); /* chr(1) is right-justify flag for printLongLine */
          } else {  /* TeX or HTML */
            printTexLongMath(g_ProofInProgress.target[step],
                cat(startPrefix, " $", chr(type), " ", NULL),
                contPrefix, 0, 0);
          }

        }
        if (nmbrLen(g_ProofInProgress.user[step])) {

          if (!texFlag) {
            printLongLine(cat(userPrefix, "  = ",
                nmbrCvtMToVString(g_ProofInProgress.user[step]),
                NULL),
                contPrefix,
                chr(1)); /* chr(1) is right-justify flag for printLongLine */
          } else {
            printTexLongMath(g_ProofInProgress.user[step],
                cat(userPrefix, "  = ", NULL),
                contPrefix, 0, 0);
          }

        }
      }
    }


  } /* Next step */

  if (!pipFlag) {
    cleanWrkProof(); /* Deallocate verifyProof storage */
  }

  if (htmlFlg && texFlag) {
    g_outputToString = 1;
    print2("</TABLE></CENTER>\n");

    /* 10/10/02 Moved here from mmwtex.c */
    /*print2("<FONT SIZE=-1 FACE=sans-serif>Colors of variables:\n");*/
    printLongLine(cat(
        "<CENTER><TABLE CELLSPACING=5><TR><TD ALIGN=LEFT><FONT SIZE=-1>",
        "<B>Colors of variables:</B> ",
        g_htmlVarColor, "</FONT></TD></TR>",
        NULL), "", "\"");

    if (essentialFlag) {  /* Means this is not a syntax breakdown of a
        definition which is called from typeStatement() */

      /* Create list of syntax statements used */
      let(&statementUsedFlags, string(g_statements + 1, 'N')); /* Init. to 'no' */
      for (step = 0; step < plen; step++) {
        stmt = proof[step];
        /* Convention: collect all $a's that don't begin with "|-" */
        if (stmt > 0) {
          if (g_Statement[stmt].type == a_) {
            if (strcmp("|-", g_MathToken[
                (g_Statement[stmt].mathString)[0]].tokenName)) {
              statementUsedFlags[stmt] = 'Y'; /* Flag to use it */
            }
          }
        }
      }

      /******************************************************************/
      /* Start of section added 2/5/02 - for a more complete syntax hints
         list in the HTML pages, parse the wffs comprising the hypotheses
         and the statement, and add their syntax to the hints list. */

      /* Look up the token "wff" (hard-coded) if we haven't found it before */
      if (wffToken == -1) { /* First time */
        wffToken = -2; /* In case it's not found because the user's source
            used a convention different for "wff" for wffs */
        for (i = 0; i < g_mathTokens; i++) {
          if (!strcmp("wff", g_MathToken[i].tokenName)) {
            wffToken = i;
            break;
          }
        }
      }

      if (wffToken >= 0) {

        /* Scan the statement being proved and its essential hypotheses,
           and find a proof for each of them expressed as a wff */
        for (i = -1; i < g_Statement[statemNum].numReqHyp; i++) {
          /* i = -1 is the statement itself; i >= 0 is hypotheses i */
          if (i == -1) {
            /* If it's not a $p we shouldn't be here */
            if (g_Statement[statemNum].type != (char)p_) bug(245);
            nmbrTmpPtr1 = NULL_NMBRSTRING;
            nmbrLet(&nmbrTmpPtr1, g_Statement[statemNum].mathString);
          } else {
            /* Ignore $f */
            if (g_Statement[g_Statement[statemNum].reqHypList[i]].type
                == (char)f_) continue;
            /* Must therefore be a $e */
            if (g_Statement[g_Statement[statemNum].reqHypList[i]].type
                != (char)e_) bug(234);
            nmbrTmpPtr1 = NULL_NMBRSTRING;
            nmbrLet(&nmbrTmpPtr1,
                g_Statement[g_Statement[statemNum].reqHypList[i]].mathString);
          }
          if (strcmp("|-", g_MathToken[nmbrTmpPtr1[0]].tokenName)) {
            /* 1-Oct-05 nm Since non-standard logics may not have this,
               just break out of this section gracefully */
            nmbrTmpPtr2 = NULL_NMBRSTRING; /* To be known after break */
            break;
            /* bug(235); */  /* 1-Oct-05 nm No longer a bug */
          }
          /* Turn "|-" assertion into a "wff" assertion */
          nmbrTmpPtr1[0] = wffToken;

          /* Find proof of formula or simple theorem (no new vars in $e's) */
          /* maxEDepth is the maximum depth at which statements with $e
             hypotheses are
             considered.  A value of 0 means none are considered. */
          nmbrTmpPtr2 = proveFloating(nmbrTmpPtr1 /*mString*/,
              statemNum /*statemNum*/, 0 /*maxEDepth*/,
              0, /* step; 0 = step 1 */ /*For messages*/
              0,  /*not noDistinct*/
              /* 3-May-2016 nm */
              2, /* override discouraged-usage statements silently */
              1 /* Always allow other mathboxes */ /* 5-Aug-2020 nm */
              );
          if (!nmbrLen(nmbrTmpPtr2)) {
            /* 1-Oct-05 nm Since a proof may not be found for non-standard
               logics, just break out of this section gracefully */
            break;
            /* bug(236); */ /* Didn't find syntax proof */
          }

          /* Add to list of syntax statements used */
          for (step = 0; step < nmbrLen(nmbrTmpPtr2); step++) {
            stmt = nmbrTmpPtr2[step];
            /* Convention: collect all $a's that don't begin with "|-" */
            if (stmt > 0) {
              if (statementUsedFlags[stmt] == 'N') { /* For slight speedup */
                if (g_Statement[stmt].type == a_) {
                  if (strcmp("|-", g_MathToken[
                      (g_Statement[stmt].mathString)[0]].tokenName)) {
                    statementUsedFlags[stmt] = 'Y'; /* Flag to use it */
                  } else {
                    /* In a syntax proof there should be no |- */
                    /* (In the future, we may want to break instead of
                       calling it a bug, if it's a problem for non-standard
                       logics.) */
                    bug(237);
                  }
                }
              }
            } else {
              /* proveFloating never returns a compressed proof */
              bug(238);
            }
          }

          /* Deallocate memory */
          nmbrLet(&nmbrTmpPtr2, NULL_NMBRSTRING);
          nmbrLet(&nmbrTmpPtr1, NULL_NMBRSTRING);
        } /* next i */
        /* 1-Oct-05 nm Deallocate memory in case we broke out above */
        nmbrLet(&nmbrTmpPtr2, NULL_NMBRSTRING);
        nmbrLet(&nmbrTmpPtr1, NULL_NMBRSTRING);
      } /* if (wffToken >= 0) */
      /* End of section added 2/5/02 */
      /******************************************************************/

      let(&tmpStr, "");
      for (stmt = 1; stmt <= g_statements; stmt++) {
        if (statementUsedFlags[stmt] == 'Y') {
          if (!tmpStr[0]) {
            let(&tmpStr,
               /* 10/10/02 */
               /*"<FONT SIZE=-1><FONT FACE=sans-serif>Syntax hints:</FONT> ");*/
               "<TR><TD ALIGN=LEFT><FONT SIZE=-1><B>Syntax hints:</B> ");
          }

          /* 10/6/99 - Get the main symbol in the syntax */
          /* This section can be deleted if not wanted - it is custom
             for set.mm and might not work with other .mm's */
          let(&tmpStr1, "");
          for (i = 1 /* Skip |- */; i < g_Statement[stmt].mathStringLen; i++) {
            if (g_MathToken[(g_Statement[stmt].mathString)[i]].tokenType ==
                (char)con_) {
              /* Skip parentheses, commas, etc. */
              if (strcmp(g_MathToken[(g_Statement[stmt].mathString)[i]
                      ].tokenName, "(")
                  && strcmp(g_MathToken[(g_Statement[stmt].mathString)[i]
                      ].tokenName, ",")
                  && strcmp(g_MathToken[(g_Statement[stmt].mathString)[i]
                      ].tokenName, ")")
                  && strcmp(g_MathToken[(g_Statement[stmt].mathString)[i]
                      ].tokenName, ":")
                  /* 14-Oct-2019 nm Use |-> rather than e. for cmpt, cmpt2 */
                  && !(!strcmp(g_MathToken[(g_Statement[stmt].mathString)[i]
                      ].tokenName, "e.")
                      && (!strcmp(g_Statement[stmt].labelName, "cmpt")
                          || !strcmp(g_Statement[stmt].labelName, "cmpt2")))
                  ) {
                tmpStr1 =
                    tokenToTex(g_MathToken[(g_Statement[stmt].mathString)[i]
                    ].tokenName, stmt);
                /* 14-Jan-2016 nm */
                let(&tmpStr1, cat(
                    (g_altHtmlFlag ? cat("<SPAN ", g_htmlFont, ">", NULL) : ""),
                                           /* 14-Jan-2016 nm */
                    tmpStr1,
                    (g_altHtmlFlag ? "</SPAN>" : ""),
                    NULL));
                break;
              }
            }
          } /* Next i */
          /* Special cases hard-coded for set.mm */
          if (!strcmp(g_Statement[stmt].labelName, "wbr")) /* binary relation */
            let(&tmpStr1, "<i> class class class </i>");
          if (!strcmp(g_Statement[stmt].labelName, "cv"))
            let(&tmpStr1, "[set variable]");
          /* 10/10/02 Let's don't do cv - confusing to reader */
          if (!strcmp(g_Statement[stmt].labelName, "cv"))
            continue;
          if (!strcmp(g_Statement[stmt].labelName, "co")) /* operation */
            let(&tmpStr1, "(<i>class class class</i>)");
          let(&tmpStr, cat(tmpStr, " &nbsp;", tmpStr1, NULL));
          /* End of 10/6/99 section - Get the main symbol in the syntax */

          let(&tmpStr1, "");
          tmpStr1 = pinkHTML(stmt);
          /******* 10/10/02
          let(&tmpStr, cat(tmpStr, "<FONT FACE=sans-serif><A HREF=\"",
              g_Statement[stmt].labelName, ".html\">",
              g_Statement[stmt].labelName, "</A></FONT> &nbsp; ", NULL));
          *******/
          let(&tmpStr, cat(tmpStr, "<A HREF=\"",
              g_Statement[stmt].labelName, ".html\">",
              g_Statement[stmt].labelName, "</A>", tmpStr1, NULL));


        }
      }
      if (tmpStr[0]) {
        let(&tmpStr, cat(tmpStr,
            "</FONT></TD></TR>", NULL));
        printLongLine(tmpStr, "", "\"");
      }
      /* End of syntax hints list */


      /* 10/25/02 Output "referenced by" list here */
      /* 30-Oct-2018 nm Moved this block to after axioms/defs lists */
      /*
      if (g_printStringForReferencedBy[0]) {
        /@ printLongLine takes 130 sec for 'sh st syl/a' @/
        /@printLongLine(g_printStringForReferencedBy, "", "\"");@/
        /@ 18-Jul-2015 nm Speedup for 'sh st syl/a' @/
        if (g_outputToString != 1) bug(257);
        let(&g_printString, cat(g_printString, g_printStringForReferencedBy, NULL));
        let(&g_printStringForReferencedBy, "");
      }
      */


      /* Get list of axioms and definitions assumed by proof */
      let(&statementUsedFlags, "");
      traceProofWork(statemNum,
          1, /*essentialFlag*/
          "", /*traceToList*/ /* 18-Jul-2015 nm */
          &statementUsedFlags, /*&statementUsedFlags*/
          &unprovedList /* &unprovedList */);
      if ((signed)(strlen(statementUsedFlags)) != g_statements + 1) bug(227);

      /* First get axioms */
      let(&tmpStr, "");
      for (stmt = 1; stmt <= g_statements; stmt++) {
        if (statementUsedFlags[stmt] == 'Y' && g_Statement[stmt].type == a_) {
          let(&tmpStr1, left(g_Statement[stmt].labelName, 3));
          if (!strcmp(tmpStr1, "ax-")) {
            if (!tmpStr[0]) {
              let(&tmpStr,
 /******* 10/10/02
 "<FONT SIZE=-1 FACE=sans-serif>The theorem was proved from these axioms: ");
 *******/
 "<TR><TD ALIGN=LEFT><FONT SIZE=-1><B>This theorem was proved from axioms:</B>");
            }
            let(&tmpStr1, "");
            tmpStr1 = pinkHTML(stmt);
            let(&tmpStr, cat(tmpStr, " &nbsp;<A HREF=\"",
                g_Statement[stmt].labelName, ".html\">",
                g_Statement[stmt].labelName, "</A>", tmpStr1, NULL));
          }
        }
      } /* next stmt */
      if (tmpStr[0]) {
        let(&tmpStr, cat(tmpStr, "</FONT></TD></TR>", NULL));
        printLongLine(tmpStr, "", "\"");
      }

      /* 10/10/02 Then get definitions */
      let(&tmpStr, "");
      for (stmt = 1; stmt <= g_statements; stmt++) {
        if (statementUsedFlags[stmt] == 'Y' && g_Statement[stmt].type == a_) {
          let(&tmpStr1, left(g_Statement[stmt].labelName, 3));
          if (!strcmp(tmpStr1, "df-")) {
            if (!tmpStr[0]) {
              let(&tmpStr,
 "<TR><TD ALIGN=LEFT><FONT SIZE=-1><B>This theorem depends on definitions:</B>");
            }
            let(&tmpStr1, "");
            tmpStr1 = pinkHTML(stmt);
            let(&tmpStr, cat(tmpStr, " &nbsp;<A HREF=\"",
                g_Statement[stmt].labelName, ".html\">",
                g_Statement[stmt].labelName, "</A>", tmpStr1, NULL));
          }
        }
      } /* next stmt */
      if (tmpStr[0]) {
        let(&tmpStr, cat(tmpStr, "</FONT></TD></TR>", NULL));
        printLongLine(tmpStr, "", "\"");
      }

      /* Print any unproved statements */
      if (nmbrLen(unprovedList)) {
        if (nmbrLen(unprovedList) == 1 &&
            !strcmp(g_Statement[unprovedList[0]].labelName,
            g_Statement[statemNum].labelName)) {
          /* When the unproved list consists only of the statement that
             was traced, it means the statement traced has no
             proof (or it has a proof, but is incomplete and all earlier
             ones do have complete proofs). */
          printLongLine(cat(
"<TR><TD ALIGN=left >&nbsp;<B><FONT COLOR=\"#FF6600\">",
"WARNING: This theorem has an incomplete proof.</FONT></B><BR></TD></TR>",
              NULL), "", "\"");

        } else {
          printLongLine(cat(
"<TR><TD ALIGN=left >&nbsp;</TD><TD><B><FONT COLOR=\"#FF6600\">",
"WARNING: This proof depends on the following unproved theorem(s): ",
              NULL), "", "\"");
          let(&tmpStr, "");
          for (i = 0; i < nmbrLen(unprovedList); i++) {
            let(&tmpStr, cat(tmpStr, " <A HREF=\"",
                g_Statement[unprovedList[i]].labelName, ".html\">",
                g_Statement[unprovedList[i]].labelName, "</A>",
                NULL));
          }
          printLongLine(cat(tmpStr, "</B></FONT></TD></TR>", NULL), "", "\"");
        }
      }

      /* End of axiom list */

      /* 30-Oct-2018 nm Moved down from above to put referenced by list last */
      if (g_printStringForReferencedBy[0]) {
        /* printLongLine takes 130 sec for 'sh st syl/a' */
        /*printLongLine(g_printStringForReferencedBy, "", "\"");*/
        /* 18-Jul-2015 nm Speedup for 'sh st syl/a' */
        if (g_outputToString != 1) bug(257);
        /* 30-Oct-2018 nm Deleted line: */
        /*let(&g_printString, cat(g_printString, g_printStringForReferencedBy, NULL));*/
        /* 30-Oct-2018 nm Added line: */
        printLongLine(g_printStringForReferencedBy, "", "\"");
        let(&g_printStringForReferencedBy, "");

      /* 30-Oct-2018 nm */
      } else {
        /* Since we now always print ref-by list even if "(None)",
           g_printStringForReferencedBy should never be empty */
        bug(263);
      }

    }  /* if essentialFlag */


    /* Printing of the trailer in mmwtex.c will close out string later */
    g_outputToString = 0;
  }

 typeProof_return:
  let(&tmpStr, "");
  let(&tmpStr1, "");
  let(&statementUsedFlags, "");
  let(&locLabDecl, "");
  let(&tgtLabel, "");
  let(&srcLabel, "");
  let(&startPrefix, "");
  let(&tgtPrefix, "");
  let(&srcPrefix, "");
  let(&userPrefix, "");
  let(&contPrefix, "");
  let(&hypStr, "");
  let(&startStringWithNum, ""); /* 22-Apr-2015 nm */
  let(&startStringWithoutNum, ""); /* 22-Apr-2015 nm */
  nmbrLet(&unprovedList, NULL_NMBRSTRING);
  nmbrLet(&localLabels, NULL_NMBRSTRING);
  nmbrLet(&localLabelNames, NULL_NMBRSTRING);
  nmbrLet(&proof, NULL_NMBRSTRING);
  nmbrLet(&targetHyps, NULL_NMBRSTRING);
  nmbrLet(&indentationLevel, NULL_NMBRSTRING);
  nmbrLet(&essentialFlags, NULL_NMBRSTRING);
  nmbrLet(&stepRenumber, NULL_NMBRSTRING);
  nmbrLet(&notUnifiedFlags, NULL_NMBRSTRING);
  nmbrLet(&relativeStepNums, NULL_NMBRSTRING); /* 22-Apr-2015 nm */
} /* typeProof() */

/* Show details of one proof step */
/* Note:  detailStep is the actual step number (starting with 1), not
   the actual step - 1. */
void showDetailStep(long statemNum, long detailStep) {

  long i, j, plen, step, stmt, sourceStmt, targetStmt;
  vstring tmpStr = "";
  vstring tmpStr1 = "";
  nmbrString *proof = NULL_NMBRSTRING;
  nmbrString *localLabels = NULL_NMBRSTRING;
  nmbrString *localLabelNames = NULL_NMBRSTRING;
  nmbrString *targetHyps = NULL_NMBRSTRING;
  long nextLocLabNum = 1; /* Next number to be used for a local label */
  void *voidPtr; /* bsearch result */
  char type;

  /* Error check */
  i = parseProof(statemNum);
  if (i) {
    printLongLine("?The proof is incomplete or has an error", "", " ");
    return;
  }
  plen = nmbrLen(g_WrkProof.proofString);
  if (plen < detailStep || detailStep < 1) {
    printLongLine(cat("?The step number should be from 1 to ",
        str((double)plen), NULL), "", " ");
    return;
  }

  /* Structure getStep is declared in mmveri.h. */
  getStep.stepNum = detailStep; /* Non-zero is flag for verifyProof */
  parseProof(statemNum); /* ???Do we need to do this again? */
  verifyProof(statemNum);


  nmbrLet(&proof, g_WrkProof.proofString); /* The proof */
  plen = nmbrLen(proof);

  /* Collect local labels */
  for (step = 0; step < plen; step++) {
    stmt = proof[step];
    if (stmt <= -1000) {
      stmt = -1000 - stmt;
      if (!nmbrElementIn(1, localLabels, stmt)) {
        nmbrLet(&localLabels, nmbrAddElement(localLabels, stmt));
      }
    }
  }

  /* localLabelNames[] hold an integer which, when converted to string,
    is the local label name. */
  nmbrLet(&localLabelNames, nmbrSpace(plen));

  /* Get the target hypotheses */
  nmbrLet(&targetHyps, nmbrGetTargetHyp(proof, statemNum));

  /* Get local labels */
  for (step = 0; step < plen; step++) {
    stmt = proof[step];
    if (stmt >= 0) {
      if (nmbrElementIn(1, localLabels, step)) {
        /* This statement declares a local label */
        /* First, get a name for the local label, using the next integer that
           does not match any integer used for a statement label. */
        let(&tmpStr1, str((double)nextLocLabNum));
        while (1) {
          voidPtr = (void *)bsearch(tmpStr,
              g_allLabelKeyBase, (size_t)g_numAllLabelKeys,
              sizeof(long), labelSrchCmp);
          if (!voidPtr) break; /* It does not conflict */
          nextLocLabNum++; /* Try the next one */
          let(&tmpStr1, str((double)nextLocLabNum));
        }
        localLabelNames[step] = nextLocLabNum;
        nextLocLabNum++; /* Prepare for next local label */
      }
    }
  } /* Next step */

  /* Print the step */
  let(&tmpStr, g_Statement[targetHyps[detailStep - 1]].labelName);
  let(&tmpStr1, ""); /* Local label declaration */
  stmt = proof[detailStep - 1];
  if (stmt < 0) {
    if (stmt <= -1000) {
      stmt = -1000 - stmt;
      /* stmt is now the step number a local label refers to */
      let(&tmpStr, cat(tmpStr,"=", str((double)(localLabelNames[stmt])), NULL));
      type = g_Statement[proof[stmt]].type;
    } else {
      if (stmt != -(long)'?') bug(207);
      let(&tmpStr, cat(tmpStr,"=",chr(-stmt), NULL)); /* '?' */
      type = '?';
    }
  } else {
    if (nmbrElementIn(1, localLabels, detailStep - 1)) {
      /* This statement declares a local label */
      let(&tmpStr1, cat(str((double)(localLabelNames[detailStep - 1])), ":",
          NULL));
    }
    let(&tmpStr, cat(tmpStr, "=", g_Statement[stmt].labelName, NULL));
    type = g_Statement[stmt].type;
  }

  /* Print the proof line */
  printLongLine(cat("Proof step ",
      str((double)detailStep),
      ":  ",
      tmpStr1,
      tmpStr,
      " $",
      chr(type),
      " ",
      nmbrCvtMToVString(g_WrkProof.mathStringPtrs[detailStep - 1]),
      NULL),
      "  ",
      " ");

  /* Print details about the step */
  let(&tmpStr, cat("This step assigns ", NULL));
  let(&tmpStr1, "");
  stmt = proof[detailStep - 1];
  sourceStmt = stmt;
  if (stmt < 0) {
    if (stmt <= -1000) {
      stmt = -1000 - stmt;
      /* stmt is now the step number a local label refers to */
      let(&tmpStr, cat(tmpStr, "step ", str((double)stmt),
          " (via local label reference \"",
          str((double)(localLabelNames[stmt])), "\") to ", NULL));
    } else {
      if (stmt != -(long)'?') bug(208);
      let(&tmpStr, cat(tmpStr, "an unknown statement to ", NULL));
    }
  } else {
    let(&tmpStr, cat(tmpStr, "source \"", g_Statement[stmt].labelName,
        "\" ($", chr(g_Statement[stmt].type), ") to ", NULL));
    if (nmbrElementIn(1, localLabels, detailStep - 1)) {
      /* This statement declares a local label */
      let(&tmpStr1, cat("  This step also declares the local label ",
          str((double)(localLabelNames[detailStep - 1])),
          ", which is used later on.",
          NULL));
    }
  }
  targetStmt = targetHyps[detailStep - 1];
  if (detailStep == plen) {
    let(&tmpStr, cat(tmpStr, "the final assertion being proved.", NULL));
  } else {
    let(&tmpStr, cat(tmpStr, "target \"", g_Statement[targetStmt].labelName,
    "\" ($", chr(g_Statement[targetStmt].type), ").", NULL));
  }

  let(&tmpStr, cat(tmpStr, tmpStr1, NULL));

  if (sourceStmt >= 0) {
    if (g_Statement[sourceStmt].type == a_
        || g_Statement[sourceStmt].type == p_) {
      j = nmbrLen(g_Statement[sourceStmt].reqHypList);
      if (j != nmbrLen(getStep.sourceHyps)) bug(209);
      if (!j) {
        let(&tmpStr, cat(tmpStr,
            "  The source assertion requires no hypotheses.", NULL));
      } else {
        if (j == 1) {
          let(&tmpStr, cat(tmpStr,
              "  The source assertion requires the hypothesis ", NULL));
        } else {
          let(&tmpStr, cat(tmpStr,
              "  The source assertion requires the hypotheses ", NULL));
        }
        for (i = 0; i < j; i++) {
          let(&tmpStr, cat(tmpStr, "\"",
              g_Statement[g_Statement[sourceStmt].reqHypList[i]].labelName,
              "\" ($",
              chr(g_Statement[g_Statement[sourceStmt].reqHypList[i]].type),
              ", step ", str((double)(getStep.sourceHyps[i] + 1)), ")", NULL));
          if (i == 0 && j == 2) {
            let(&tmpStr, cat(tmpStr, " and ", NULL));
          }
          if (i < j - 2 && j > 2) {
            let(&tmpStr, cat(tmpStr, ", ", NULL));
          }
          if (i == j - 2 && j > 2) {
            let(&tmpStr, cat(tmpStr, ", and ", NULL));
          }
        }
        let(&tmpStr, cat(tmpStr, ".", NULL));
      }
    }
  }

  if (detailStep < plen) {
    let(&tmpStr, cat(tmpStr,
         "  The parent assertion of the target hypothesis is \"",
        g_Statement[getStep.targetParentStmt].labelName, "\" ($",
        chr(g_Statement[getStep.targetParentStmt].type),", step ",
        str((double)(getStep.targetParentStep)), ").", NULL));
  } else {
    let(&tmpStr, cat(tmpStr,
        "  The target has no parent because it is the assertion being proved.",
        NULL));
  }

  printLongLine(tmpStr, "", " ");

  if (sourceStmt >= 0) {
    if (g_Statement[sourceStmt].type == a_
        || g_Statement[sourceStmt].type == p_) {
      print2("The source assertion before substitution was:\n");
      printLongLine(cat("    ", g_Statement[sourceStmt].labelName, " $",
          chr(g_Statement[sourceStmt].type), " ", nmbrCvtMToVString(
          g_Statement[sourceStmt].mathString), NULL),
          "        ", " ");
      j = nmbrLen(getStep.sourceSubstsNmbr);
      if (j == 1) {
        printLongLine(cat(
            "The following substitution was made to the source assertion:",
            NULL),""," ");
      } else {
        printLongLine(cat(
            "The following substitutions were made to the source assertion:",
            NULL),""," ");
      }
      if (!j) {
        print2("    (None)\n");
      } else {
        print2("    Variable  Substituted with\n");
        for (i = 0; i < j; i++) {
          printLongLine(cat("     ",
              g_MathToken[getStep.sourceSubstsNmbr[i]].tokenName," ",
              space(9 - (long)strlen(
                g_MathToken[getStep.sourceSubstsNmbr[i]].tokenName)),
              nmbrCvtMToVString(getStep.sourceSubstsPntr[i]), NULL),
              "                ", " ");
        }
      }
    }
  }

  if (detailStep < plen) {
    print2("The target hypothesis before substitution was:\n");
    printLongLine(cat("    ", g_Statement[targetStmt].labelName, " $",
        chr(g_Statement[targetStmt].type), " ", nmbrCvtMToVString(
        g_Statement[targetStmt].mathString), NULL),
        "        ", " ");
    j = nmbrLen(getStep.targetSubstsNmbr);
    if (j == 1) {
      printLongLine(cat(
          "The following substitution was made to the target hypothesis:",
          NULL),""," ");
    } else {
      printLongLine(cat(
          "The following substitutions were made to the target hypothesis:",
          NULL),""," ");
    }
    if (!j) {
      print2("    (None)\n");
    } else {
      print2("    Variable  Substituted with\n");
      for (i = 0; i < j; i++) {
        printLongLine(cat("     ",
            g_MathToken[getStep.targetSubstsNmbr[i]].tokenName, " ",
            space(9 - (long)strlen(
              g_MathToken[getStep.targetSubstsNmbr[i]].tokenName)),
            nmbrCvtMToVString(getStep.targetSubstsPntr[i]), NULL),
            "                ", " ");
      }
    }
  }

  cleanWrkProof();
  getStep.stepNum = 0; /* Zero is flag for verifyProof to ignore getStep info */

  /* Deallocate getStep contents */
  j = pntrLen(getStep.sourceSubstsPntr);
  for (i = 0; i < j; i++) {
    nmbrLet((nmbrString **)(&getStep.sourceSubstsPntr[i]),
        NULL_NMBRSTRING);
  }
  j = pntrLen(getStep.targetSubstsPntr);
  for (i = 0; i < j; i++) {
    nmbrLet((nmbrString **)(&getStep.targetSubstsPntr[i]),
        NULL_NMBRSTRING);
  }
  nmbrLet(&getStep.sourceHyps, NULL_NMBRSTRING);
  pntrLet(&getStep.sourceSubstsPntr, NULL_PNTRSTRING);
  nmbrLet(&getStep.sourceSubstsNmbr, NULL_NMBRSTRING);
  pntrLet(&getStep.targetSubstsPntr, NULL_PNTRSTRING);
  nmbrLet(&getStep.targetSubstsNmbr, NULL_NMBRSTRING);

  /* Deallocate other strings */
  let(&tmpStr, "");
  let(&tmpStr1, "");
  nmbrLet(&localLabels, NULL_NMBRSTRING);
  nmbrLet(&localLabelNames, NULL_NMBRSTRING);
  nmbrLet(&proof, NULL_NMBRSTRING);
  nmbrLet(&targetHyps, NULL_NMBRSTRING);

} /* showDetailStep */

/* Summary of statements in proof for SHOW PROOF / STATEMENT_SUMMARY */
void proofStmtSumm(long statemNum, flag essentialFlag, flag texFlag) {

  long i, j, k, pos, stmt, plen, slen, step;
  char type;
  vstring statementUsedFlags = ""; /* 'Y'/'N' flag that statement is used */
  vstring str1 = "";
  vstring str2 = "";
  vstring str3 = "";
  nmbrString *statementList = NULL_NMBRSTRING;
  nmbrString *proof = NULL_NMBRSTRING;
  nmbrString *essentialFlags = NULL_NMBRSTRING;

  /* 10/10/02 This section is never called in HTML mode anymore.  The code is
     left in though just in case we somehow get here and the user continues
     through the bug. */
  if (texFlag && g_htmlFlag) bug(239);

  if (!texFlag) {
    print2("Summary of statements used in the proof of \"%s\":\n",
        g_Statement[statemNum].labelName);
  } else {
    g_outputToString = 1; /* Flag for print2 to add to g_printString */
    if (!g_htmlFlag) {
      print2("\n");
      print2("\\vspace{1ex} %%3\n");
      printLongLine(cat("Summary of statements used in the proof of ",
          "{\\tt ",
          asciiToTt(g_Statement[statemNum].labelName),
          "}:", NULL), "", " ");
    } else {
      printLongLine(cat("Summary of statements used in the proof of ",
          "<B>",
          asciiToTt(g_Statement[statemNum].labelName),
          "</B>:", NULL), "", "\"");
    }
    g_outputToString = 0;
    fprintf(g_texFilePtr, "%s", g_printString);
    let(&g_printString, "");
  }

  if (g_Statement[statemNum].type != p_) {
    print2("  This is not a provable ($p) statement.\n");
    return;
  }

  /* 20-Oct-2013 nm Don't use bad proofs (incomplete proofs are ok) */
  if (parseProof(statemNum) > 1) {
    /* The proof has an error, so use the empty proof */
    nmbrLet(&proof, nmbrAddElement(NULL_NMBRSTRING, -(long)'?'));
  } else {
    nmbrLet(&proof, g_WrkProof.proofString);
  }

  plen = nmbrLen(proof);
  /* Get the essential step flags, if required */
  if (essentialFlag) {
    nmbrLet(&essentialFlags, nmbrGetEssential(proof));
  }

  for (step = 0; step < plen; step++) {
    if (essentialFlag) {
      if (!essentialFlags[step]) continue;     /* Ignore floating hypotheses */
    }
    stmt = proof[step];
    if (stmt < 0) {
      continue; /* Ignore '?' and local labels */
    }
    if (1) { /* Limit list to $a and $p only */
      if (g_Statement[stmt].type != a_ && g_Statement[stmt].type != p_) {
        continue;
      }
    }
    /* Add this statement to the statement list if not already in it */
    if (!nmbrElementIn(1, statementList, stmt)) {
      nmbrLet(&statementList, nmbrAddElement(statementList, stmt));
    }
  } /* Next step */

  /* Prepare the output */
  /* First, fill in the statementUsedFlags char array.  This allows us to sort
     the output by statement number without calling a sort routine. */
  slen = nmbrLen(statementList);
  let(&statementUsedFlags, string(g_statements + 1, 'N')); /* Init. to 'no' */
  for (pos = 0; pos < slen; pos++) {
    stmt = statementList[pos];
    if (stmt > statemNum || stmt < 1) bug(210);
    statementUsedFlags[stmt] = 'Y';
  }
  /* Next, build the output string */
  for (stmt = 1; stmt < statemNum; stmt++) {
    if (statementUsedFlags[stmt] == 'Y') {
      assignStmtFileAndLineNum(stmt); /* 9-Jan-2018 nm */
      let(&str1, cat(" is located on line ",
          str((double)(g_Statement[stmt].lineNum)),
          " of the file ", NULL));
      if (!texFlag) {
        print2("\n");
        printLongLine(cat("Statement ", g_Statement[stmt].labelName, str1,
          "\"", g_Statement[stmt].fileName,
          "\".",NULL), "", " ");
      } else {
        g_outputToString = 1; /* Flag for print2 to add to g_printString */
        if (!g_htmlFlag) {
          print2("\n");
          print2("\n");
          print2("\\vspace{1ex} %%4\n");
          printLongLine(cat("Statement {\\tt ",
              asciiToTt(g_Statement[stmt].labelName), "} ",
              str1, "{\\tt ",
              asciiToTt(g_Statement[stmt].fileName),
              "}.", NULL), "", " ");
          print2("\n");
        } else {
          printLongLine(cat("Statement <B>",
              asciiToTt(g_Statement[stmt].labelName), "</B> ",
              str1, " <B>",
              asciiToTt(g_Statement[stmt].fileName),
              "</B> ", NULL), "", "\"");
        }
        g_outputToString = 0;
        fprintf(g_texFilePtr, "%s", g_printString);
        let(&g_printString, "");
      }

      /* type = g_Statement[stmt].type; */ /* 18-Sep-2013 Not used */
      let(&str1, "");
      str1 = getDescription(stmt);
      if (str1[0]) {
        if (!texFlag) {
          printLongLine(cat("\"", str1, "\"", NULL), "", " ");
        } else {
          /* printTexComment(str1, 1); */
          /* 17-Nov-2015 nm Added 3rd & 4th arguments */
          printTexComment(str1,              /* Sends result to g_texFilePtr */
              1, /* 1 = htmlCenterFlag */
              PROCESS_EVERYTHING, /* actionBits */ /* 13-Dec-2018 nm */
              0 /* 1 = noFileCheck */);
        }
      }

      /* print2("Its mandatory hypotheses in RPN order are:\n"); */
      j = nmbrLen(g_Statement[stmt].reqHypList);
      for (i = 0; i < j; i++) {
        k = g_Statement[stmt].reqHypList[i];
        if (!essentialFlag || g_Statement[k].type != f_) {
          let(&str2, cat("  ",g_Statement[k].labelName,
              " $", chr(g_Statement[k].type), " ", NULL));
          if (!texFlag) {
            printLongLine(cat(str2,
                nmbrCvtMToVString(g_Statement[k].mathString), " $.", NULL),
                "      "," ");
          } else {
            let(&str3, space((long)strlen(str2)));
            printTexLongMath(g_Statement[k].mathString,
                str2, str3, 0, 0);
          }
        }
      }

      let(&str1, "");
      type = g_Statement[stmt].type;
      if (type == p_) let(&str1, " $= ...");
      let(&str2, cat("  ", g_Statement[stmt].labelName,
          " $",chr(type), " ", NULL));
      if (!texFlag) {
        printLongLine(cat(str2,
            nmbrCvtMToVString(g_Statement[stmt].mathString),
            str1, " $.", NULL), "      ", " ");
      } else {
        let(&str3, space((long)strlen(str2)));
        printTexLongMath(g_Statement[stmt].mathString,
            str2, str3, 0, 0);
      }

    } /* End if (statementUsedFlag[stmt] == 'Y') */
  } /* Next stmt */

  let(&statementUsedFlags, ""); /* 'Y'/'N' flag that statement is used */
  let(&str1, "");
  let(&str2, "");
  let(&str3, "");
  nmbrLet(&statementList, NULL_NMBRSTRING);
  nmbrLet(&proof, NULL_NMBRSTRING);
  nmbrLet(&essentialFlags, NULL_NMBRSTRING);

} /* proofStmtSumm */


/* Traces back the statements used by a proof, recursively. */
/* Returns 1 if at least one label is printed (or would be printed in
   case testOnlyFlag=1); otherwise, returns 0 */
/* matchList suppresses all output except labels matching matchList */
/* testOnlyFlag prevents any printout; it is used to determine whether
   there is an unwanted axiom for MINIMIZE_WITH /FORBID. */
/* void traceProof(long statemNum, */ /* before 20-May-2013 */
flag traceProof(long statemNum, /* 20-May-2013 nm */
  flag essentialFlag,
  flag axiomFlag,
  vstring matchList, /* 19-May-2013 nm */
  vstring traceToList, /* 18-Jul-2015 nm */
  flag testOnlyFlag /* 20-May-2013 nm */)
{

  long stmt, pos;
  vstring statementUsedFlags = ""; /* y/n flags that statement is used */
  vstring outputString = "";
  nmbrString *unprovedList = NULL_NMBRSTRING;
  flag foundFlag = 0;

  /* Make sure we're calling this with $p statements only */
  if (g_Statement[statemNum].type != (char)p_) bug(249);

  if (!testOnlyFlag) {  /* 20-May-2013 nm */
    if (axiomFlag) {
      print2(
  "Statement \"%s\" assumes the following axioms ($a statements):\n",
          g_Statement[statemNum].labelName);
    } else  if (traceToList[0] == 0) {
      print2(
  "The proof of statement \"%s\" uses the following earlier statements:\n",
          g_Statement[statemNum].labelName);
    } else {
      print2(
  "The proof of statement \"%s\" traces back to \"%s\" via:\n",
          g_Statement[statemNum].labelName, traceToList);
    }
  }

  traceProofWork(statemNum,
      essentialFlag,
      traceToList, /* /TO argument of SHOW TRACE_BACK */ /* 18-Jul-2015 nm */
      &statementUsedFlags,
      &unprovedList);
  if ((signed)(strlen(statementUsedFlags)) != g_statements + 1) bug(226);

  /* Build the output string */
  let(&outputString, "");
  for (stmt = 1; stmt < statemNum; stmt++) {
    if (statementUsedFlags[stmt] == 'Y') {

      /* 19-May-2013 nm - Added MATCH qualifier */
      if (matchList[0]) {  /* There is a list to match */
        /* Don't include unmatched labels */
        if (!matchesList(g_Statement[stmt].labelName, matchList, '*', '?'))
          continue;
      }

      /* 20-May-2013 nm  Skip rest of scan in testOnlyFlag mode */
      foundFlag = 1; /* At least one label would be printed */
      if (testOnlyFlag) {
        goto TRACE_RETURN;
      }
      if (axiomFlag) {
        if (g_Statement[stmt].type == a_) {
          let(&outputString, cat(outputString, " ", g_Statement[stmt].labelName,
              NULL));
        }
      } else {
        let(&outputString, cat(outputString, " ", g_Statement[stmt].labelName,
            NULL));
        switch (g_Statement[stmt].type) {
          case a_: let(&outputString, cat(outputString, "($a)", NULL)); break;
          case e_: let(&outputString, cat(outputString, "($e)", NULL)); break;
          case f_: let(&outputString, cat(outputString, "($f)", NULL)); break;
        }
      }
    } /* End if (statementUsedFlag[stmt] == 'Y') */
  } /* Next stmt */

  /* 20-May-2013 nm  Skip printing in testOnlyFlag mode */
  if (testOnlyFlag) {
    goto TRACE_RETURN;
  }

  if (outputString[0]) {
    let(&outputString, cat(" ", outputString, NULL));
  } else {
    let(&outputString, "  (None)");
  }

  /* Print the output */
  printLongLine(outputString, "  ", " ");

  /* Print any unproved statements */
  if (nmbrLen(unprovedList)) {
    print2("Warning: The following traced statement(s) were not proved:\n");
    let(&outputString, "");
    for (pos = 0; pos < nmbrLen(unprovedList); pos++) {
      let(&outputString, cat(outputString, " ", g_Statement[unprovedList[
          pos]].labelName, NULL));
    }
    let(&outputString, cat("  ", outputString, NULL));
    printLongLine(outputString, "  ", " ");
  }

 TRACE_RETURN:
  /* Deallocate */
  let(&outputString, "");
  let(&statementUsedFlags, "");
  nmbrLet(&unprovedList, NULL_NMBRSTRING);
  return foundFlag;
} /* traceProof */

/* Traces back the statements used by a proof, recursively.  Returns
   a nmbrString with a list of statements and unproved statements */
void traceProofWork(long statemNum,
  flag essentialFlag,
  vstring traceToList, /* /TO argument of SHOW TRACE_BACK */ /* 18-Jul-2015 nm */
  vstring *statementUsedFlagsP, /* 'Y'/'N' flag that statement is used */
  nmbrString **unprovedListP)
{

  long pos, stmt, plen, slen, step;
  nmbrString *statementList = NULL_NMBRSTRING;
  nmbrString *proof = NULL_NMBRSTRING;
  nmbrString *essentialFlags = NULL_NMBRSTRING;
  vstring traceToFilter = ""; /* 18-Jul-2015 nm */
  vstring str1 = ""; /* 18-Jul-2015 nm */
  long j; /* 18-Jul-2015 nm */

  /* 18-Jul-2015 nm */
  /* Preprocess the "SHOW TRACE_BACK ... / TO" traceToList list if any */
  if (traceToList[0] != 0) {
    let(&traceToFilter, string(g_statements + 1, 'N')); /* Init. to 'no' */
    /* Wildcard match scan */
    for (stmt = 1; stmt <= g_statements; stmt++) {
      if (g_Statement[stmt].type != (char)a_
          && g_Statement[stmt].type != (char)p_)
        continue; /* Not a $a or $p statement; skip it */
      /* Wildcard matching */
      if (!matchesList(g_Statement[stmt].labelName, traceToList, '*', '?'))
        continue;
      let(&str1, "");
      str1 = traceUsage(stmt /*g_showStatement*/,
          1, /*recursiveFlag*/
          statemNum /* cutoffStmt */);
      traceToFilter[stmt] = 'Y'; /* Include the statement we're showing
                                    usage of */
      if (str1[0] == 'Y') {  /* There is some usage */
        for (j = stmt + 1; j <= g_statements; j++) {
          /* OR in the usage to the filter */
          if (str1[j] == 'Y') traceToFilter[j] = 'Y';
        }
      }
    } /* Next i (statement number) */
  } /* if (traceToList[0] != 0) */

  nmbrLet(&statementList, nmbrSpace(g_statements));
  statementList[0] = statemNum;
  slen = 1;
  nmbrLet(&(*unprovedListP), NULL_NMBRSTRING); /* List of unproved statements */
  let(&(*statementUsedFlagsP), string(g_statements + 1, 'N')); /* Init. to 'no' */
  (*statementUsedFlagsP)[statemNum] = 'Y';  /* nm 22-Nov-2014 */
  for (pos = 0; pos < slen; pos++) {
    if (g_Statement[statementList[pos]].type != p_) {
      continue; /* Not a $p */
    }

    /* 20-Oct-2013 nm Don't use bad proofs (incomplete proofs are ok) */
    if (parseProof(statementList[pos]) > 1) {
      /* The proof has an error, so use the empty proof */
      nmbrLet(&proof, nmbrAddElement(NULL_NMBRSTRING, -(long)'?'));
    } else {
      nmbrLet(&proof, g_WrkProof.proofString);
    }

    plen = nmbrLen(proof);
    /* Get the essential step flags, if required */
    if (essentialFlag) {
      nmbrLet(&essentialFlags, nmbrGetEssential(proof));
    }
    for (step = 0; step < plen; step++) {
      if (essentialFlag) {
        if (!essentialFlags[step]) continue;  /* Ignore floating hypotheses */
      }
      stmt = proof[step];
      if (stmt < 0) {
        if (stmt > -1000) {
          /* '?' */
          if (!nmbrElementIn(1, *unprovedListP, statementList[pos])) {
            nmbrLet(&(*unprovedListP), nmbrAddElement(*unprovedListP,
                statementList[pos]));  /* Add to list of unproved statements */
          }
        }
        continue; /* Ignore '?' and local labels */
      }
      if (1) { /* Limit list to $a and $p only */
        if (g_Statement[stmt].type != a_ && g_Statement[stmt].type != p_) {
          continue;
        }
      }
      /* Add this statement to the statement list if not already in it */
      if ((*statementUsedFlagsP)[stmt] == 'N') {
        /*(*statementUsedFlagsP)[stmt] = 'Y';*/  /* 18-Jul-2015 deleted */

        /* 18-Jul-2015 nm */
        if (traceToList[0] == 0) {
          statementList[slen] = stmt;
          slen++;
          (*statementUsedFlagsP)[stmt] = 'Y';
        } else { /* TRACE_BACK / TO */
          if (traceToFilter[stmt] == 'Y') {
            statementList[slen] = stmt;
            slen++;
            (*statementUsedFlagsP)[stmt] = 'Y';
          }
        }
      }
    } /* Next step */
  } /* Next pos */

  /* Deallocate */
  nmbrLet(&essentialFlags, NULL_NMBRSTRING);
  nmbrLet(&proof, NULL_NMBRSTRING);
  nmbrLet(&statementList, NULL_NMBRSTRING);
  let(&str1, "");
  let(&str1, "");
  return;

} /* traceProofWork */

nmbrString *stmtFoundList = NULL_NMBRSTRING;
long indentShift = 0;

/* Traces back the statements used by a proof, recursively, with tree display.*/
void traceProofTree(long statemNum,
  flag essentialFlag, long endIndent)
{
  if (g_Statement[statemNum].type != p_) {
    print2("Statement %s is not a $p statement.\n",
        g_Statement[statemNum].labelName);
    return;
  }

  printLongLine(cat("The proof tree traceback for statement \"",
      g_Statement[statemNum].labelName,
      "\" follows.  The statements used by each proof are indented one level in,",
      " below the statement being proved.  Hypotheses are not included.",
      NULL),
      "", " ");
  print2("\n");

  nmbrLet(&stmtFoundList, NULL_NMBRSTRING);
  indentShift = 0;
  traceProofTreeRec(statemNum, essentialFlag, endIndent, 0);
  nmbrLet(&stmtFoundList, NULL_NMBRSTRING);
} /* traceProofTree */


void traceProofTreeRec(long statemNum,
  flag essentialFlag, long endIndent, long recursDepth)
{
  long i, pos, stmt, plen, slen, step;
  vstring outputStr = "";
  nmbrString *localFoundList = NULL_NMBRSTRING;
  nmbrString *localPrintedList = NULL_NMBRSTRING;
  flag unprovedFlag = 0;
  nmbrString *proof = NULL_NMBRSTRING;
  nmbrString *essentialFlags = NULL_NMBRSTRING;


  let(&outputStr, "");
  outputStr = getDescription(statemNum); /* Get statement comment */
  let(&outputStr, edit(outputStr, 8 + 16 + 128)); /* Trim and reduce spaces */
  slen = len(outputStr);
  for (i = 0; i < slen; i++) {
    /* Change newlines to spaces in comment */
    if (outputStr[i] == '\n') {
      outputStr[i] = ' ';
    }
  }

#define INDENT_INCR 3
#define MAX_LINE_LEN 79

  if ((recursDepth * INDENT_INCR - indentShift) >
      (g_screenWidth - MAX_LINE_LEN) + 50) {
    indentShift = indentShift + 40 + (g_screenWidth - MAX_LINE_LEN);
    print2("****** Shifting indentation.  Total shift is now %ld.\n",
      (long)indentShift);
  }
  if ((recursDepth * INDENT_INCR - indentShift) < 1 && indentShift != 0) {
    indentShift = indentShift - 40 - (g_screenWidth - MAX_LINE_LEN);
    print2("****** Shifting indentation.  Total shift is now %ld.\n",
      (long)indentShift);
  }

  let(&outputStr, cat(space(recursDepth * INDENT_INCR - indentShift),
      g_Statement[statemNum].labelName, " $", chr(g_Statement[statemNum].type),
      "  \"", edit(outputStr, 8 + 128), "\"", NULL));

  if (len(outputStr) > MAX_LINE_LEN + (g_screenWidth - MAX_LINE_LEN)) {
    let(&outputStr, cat(left(outputStr,
        MAX_LINE_LEN + (g_screenWidth - MAX_LINE_LEN) - 3), "...", NULL));
  }

  if (g_Statement[statemNum].type == p_ || g_Statement[statemNum].type == a_) {
    /* Only print assertions to reduce output bulk */
    print2("%s\n", outputStr);
  }

  if (g_Statement[statemNum].type != p_) {
    let(&outputStr, "");
    return;
  }

  if (endIndent) {
    /* An indentation level limit is set */
    if (endIndent < recursDepth + 2) {
      let(&outputStr, "");
      return;
    }
  }

  /* 20-Oct-2013 nm Don't use bad proofs (incomplete proofs are ok) */
  if (parseProof(statemNum) > 1) {
    /* The proof has an error, so use the empty proof */
    nmbrLet(&proof, nmbrAddElement(NULL_NMBRSTRING, -(long)'?'));
  } else {
    nmbrLet(&proof, g_WrkProof.proofString);
  }

  plen = nmbrLen(proof);
  /* Get the essential step flags, if required */
  if (essentialFlag) {
    nmbrLet(&essentialFlags, nmbrGetEssential(proof));
  }
  nmbrLet(&localFoundList, NULL_NMBRSTRING);
  nmbrLet(&localPrintedList, NULL_NMBRSTRING);
  for (step = 0; step < plen; step++) {
    if (essentialFlag) {
      if (!essentialFlags[step]) continue;
                                                /* Ignore floating hypotheses */
    }
    stmt = proof[step];
    if (stmt < 0) {
      if (stmt > -1000) {
        /* '?' */
        unprovedFlag = 1;
      }
      continue; /* Ignore '?' and local labels */
    }
    if (!nmbrElementIn(1, localFoundList, stmt)) {
      nmbrLet(&localFoundList, nmbrAddElement(localFoundList, stmt));
    }
    if (!nmbrElementIn(1, stmtFoundList, stmt)) {
      traceProofTreeRec(stmt, essentialFlag, endIndent, recursDepth + 1);
      nmbrLet(&localPrintedList, nmbrAddElement(localPrintedList, stmt));
      nmbrLet(&stmtFoundList, nmbrAddElement(stmtFoundList, stmt));
    }
  } /* Next step */

  /* See if there are any old statements printed previously */
  slen = nmbrLen(localFoundList);
  let(&outputStr, "");
  for (pos = 0; pos < slen; pos++) {
    stmt = localFoundList[pos];
    if (!nmbrElementIn(1, localPrintedList, stmt)) {
      /* Don't include $f, $e in output */
      if (g_Statement[stmt].type == p_ || g_Statement[stmt].type == a_) {
        let(&outputStr, cat(outputStr, " ",
            g_Statement[stmt].labelName, NULL));
      }
    }
  }

  if (len(outputStr)) {
    printLongLine(cat(space(INDENT_INCR * (recursDepth + 1) - 1 - indentShift),
      outputStr, " (shown above)", NULL),
      space(INDENT_INCR * (recursDepth + 2) - indentShift), " ");
  }

  if (unprovedFlag) {
    printLongLine(cat(space(INDENT_INCR * (recursDepth + 1) - indentShift),
      "*** Statement ", g_Statement[statemNum].labelName, " has not been proved."
      , NULL),
      space(INDENT_INCR * (recursDepth + 2)), " ");
  }

  let(&outputStr, "");
  nmbrLet(&localFoundList, NULL_NMBRSTRING);
  nmbrLet(&localPrintedList, NULL_NMBRSTRING);
  nmbrLet(&proof, NULL_NMBRSTRING);
  nmbrLet(&essentialFlags, NULL_NMBRSTRING);

} /* traceProofTreeRec */


/* Called by SHOW TRACE_BACK <label> / COUNT_STEPS */
/* Counts the number of steps a completely exploded proof would require */
/* (Recursive) */
/* 0 is returned if some assertions have incomplete proofs. */
double countSteps(long statemNum, flag essentialFlag)
{
  static double *stmtCount;
  static double *stmtNodeCount;
  static long *stmtDist;
  static long *stmtMaxPath;
  static double *stmtAveDist;
  static long *stmtProofLen; /* The actual number of steps in stmt's proof */
  static long *stmtUsage; /* The number of times the statement is used */
  static long level = 0;
  static flag unprovedFlag;

  long stmt, plen, step, i, j, k;
  long essentialplen;
  nmbrString *proof = NULL_NMBRSTRING;
  double stepCount; /* The total steps if fully expanded */

  /* 12-Nov-2018 nm */
  static vstring *stmtBigCount; /* Unlimited precision stmtCount */
  vstring stepBigCount = ""; /* Unlimited precision stepCount */
  vstring tmpBig1 = "";

  double stepNodeCount;
  double stepDistSum;
  nmbrString *essentialFlags = NULL_NMBRSTRING;
  vstring tmpStr = "";
  long actualSteps, actualSubTheorems;
  long actualSteps2, actualSubTheorems2;

  /* Initialization to avoid compiler warning (should not be theoretically
     necessary) */
  essentialplen = 0;

  /* If this is the top level of recursion, initialize things */
  if (!level) {
    stmtCount = malloc((sizeof(double) * ((size_t)g_statements + 1)));
    stmtBigCount = malloc((sizeof(vstring) * ((size_t)g_statements + 1)));
                                                           /* 12-Nov-2018 nm */
    stmtNodeCount = malloc(sizeof(double) * ((size_t)g_statements + 1));
    stmtDist = malloc(sizeof(long) * ((size_t)g_statements + 1));
    stmtMaxPath = malloc(sizeof(long) * ((size_t)g_statements + 1));
    stmtAveDist = malloc(sizeof(double) * ((size_t)g_statements + 1));
    stmtProofLen = malloc(sizeof(long) * ((size_t)g_statements + 1));
    stmtUsage = malloc(sizeof(long) * ((size_t)g_statements + 1));
    if (!stmtCount || !stmtNodeCount || !stmtDist || !stmtMaxPath ||
        !stmtAveDist || !stmtProofLen || !stmtUsage) {
      print2("?Memory overflow.  Step count will be wrong.\n");
      if (stmtCount) free(stmtCount);
      if (stmtBigCount) free(stmtBigCount); /* 12-Nov-2018 nm */
      if (stmtNodeCount) free(stmtNodeCount);
      if (stmtDist) free(stmtDist);
      if (stmtMaxPath) free(stmtMaxPath);
      if (stmtAveDist) free(stmtAveDist);
      if (stmtProofLen) free(stmtProofLen);
      if (stmtUsage) free(stmtUsage);
      return (0);
    }
    for (stmt = 1; stmt < g_statements + 1; stmt++) {
      stmtCount[stmt] = 0;
      stmtBigCount[stmt] = ""; /* 12-Nov-2018 nm */
      stmtUsage[stmt] = 0;
      stmtDist[stmt] = 0; /* 18-Sep-2013 Initialize */
    }
    unprovedFlag = 0; /* Flag that some proof wasn't complete */
  }
  level++;
  stepCount = 0;
  let(&stepBigCount, "0"); /* "" and "0" both mean 0 */ /* 12-Nov-2018 nm */
  stepNodeCount = 0;
  stepDistSum = 0;
  stmtDist[statemNum] = -2; /* Forces at least one assignment */

  if (g_Statement[statemNum].type != (char)p_) {
    /* $a, $e, or $f */
    stepCount = 1;
    let(&stepBigCount, "1"); /* 12-Nov-2018 nm */
    stepNodeCount = 0;
    stmtDist[statemNum] = 0;
    goto returnPoint;
  }

  /* 20-Oct-2013 nm Don't use bad proofs (incomplete proofs are ok) */
  if (parseProof(statemNum) > 1) {
    /* The proof has an error, so use the empty proof */
    nmbrLet(&proof, nmbrAddElement(NULL_NMBRSTRING, -(long)'?'));
  } else {
    /*nmbrLet(&proof, nmbrUnsquishProof(g_WrkProof.proofString));*/ /* The proof */
    /* 13-Nov-2018 nm - Use proof as it is saved (so user can choose
       compressed or not) */
    nmbrLet(&proof, g_WrkProof.proofString); /* The proof */
  }

  plen = nmbrLen(proof);
  /* Get the essential step flags, if required */
  if (essentialFlag) {
    nmbrLet(&essentialFlags, nmbrGetEssential(proof));
  }
  essentialplen = 0;
  for (step = 0; step < plen; step++) {
  /* 12-May-04 nm Use the following loop instead to get an alternate maximum
     path */
    if (essentialFlag) {
      if (!essentialFlags[step]) continue;     /* Ignore floating hypotheses */
    }
    essentialplen++;
    stmt = proof[step];
    if (stmt < 0) {
      if (stmt <= -1000) {
        /* Pre-13-Nov-2010: */
        /*bug(215);*/ /* The proof was expanded; there should be no local labels */
        /* 13-Nov-2018 nm */
        /* User can choose to count compressed or normal steps by saving
           the proof that way */
        /* A local label does not add a proof step in the web page proof */
        continue;
      } else {
        /* '?' */
        unprovedFlag = 1;
        stepCount = stepCount + 1;

        /* 12-Nov-2018 nm */
        let(&tmpBig1, "");
        tmpBig1 = bigAdd(stepBigCount, "1");
        let(&stepBigCount, tmpBig1);

        stepNodeCount = stepNodeCount + 1;
        stepDistSum = stepDistSum + 1;
      }
    } else {
      if (stmtCount[stmt] == 0) {
        /* It has not been computed yet - call this function recursively */
        stepCount = stepCount + countSteps(stmt, essentialFlag);
      } else {
        /* It has already been computed */
        stepCount = stepCount + stmtCount[stmt];
      }

      /* 12-Nov-2018 nm */
      /* In either case, stmtBigCount[stmt] will be populated now */
      let(&tmpBig1, "");
      tmpBig1 = bigAdd(stepBigCount, stmtBigCount[stmt]);
      let(&stepBigCount, tmpBig1);

      if (g_Statement[stmt].type == (char)p_) {
        /*stepCount--;*/ /* -1 to account for the replacement of this step */
        for (j = 0; j < g_Statement[stmt].numReqHyp; j++) {
          k = g_Statement[stmt].reqHypList[j];
          if (!essentialFlag || g_Statement[k].type == (char)e_) {
            stepCount--;

            /* 12-Nov-2018 nm */
            /* In either case, stmtBigCount[stmt] will be populated now */
            let(&tmpBig1, "");
            tmpBig1 = bigSub(stepBigCount, "1");
            let(&stepBigCount, tmpBig1);

          }
        }
      }
      stmtUsage[stmt]++;
      if (stmtDist[statemNum] < stmtDist[stmt] + 1) {
        stmtDist[statemNum] = stmtDist[stmt] + 1;
        stmtMaxPath[statemNum] = stmt;
      }
      stepNodeCount = stepNodeCount + stmtNodeCount[stmt];
      stepDistSum = stepDistSum + stmtAveDist[stmt] + 1;
    }

  } /* Next step */

 returnPoint:

  /* Assign step count to statement list */
  stmtCount[statemNum] = stepCount;

  /* 12-Nov-2018 nm */
  if ((stmtBigCount[statemNum])[0] != 0) bug(264);
  let(&stmtBigCount[statemNum], stepBigCount);

  stmtNodeCount[statemNum] = stepNodeCount + 1;
  stmtAveDist[statemNum] = (double)stepDistSum / (double)essentialplen;
  stmtProofLen[statemNum] = essentialplen;

  nmbrLet(&proof, NULL_NMBRSTRING);
  nmbrLet(&essentialFlags, NULL_NMBRSTRING);

  level--;
  /* If this is the top level of recursion, deallocate */
  if (!level) {
    if (unprovedFlag) stepCount = 0; /* Don't mislead user */

    /* Compute the total actual steps, total actual subtheorems */
    actualSteps = stmtProofLen[statemNum];
    actualSubTheorems = 0;
    actualSteps2 = actualSteps; /* Steps w/ single-use subtheorems eliminated */
    actualSubTheorems2 = 0; /* Multiple-use subtheorems only */
    for (i = 1; i < statemNum; i++) {
      if (g_Statement[i].type == (char)p_ && stmtCount[i] != 0) {
        actualSteps = actualSteps + stmtProofLen[i];
        actualSubTheorems++;
        if (stmtUsage[i] > 1) {
          actualSubTheorems2++;
          actualSteps2 = actualSteps2 + stmtProofLen[i];
        } else {
          actualSteps2 = actualSteps2 + stmtProofLen[i] - 1;
          for (j = 0; j < g_Statement[i].numReqHyp; j++) {
            /* Subtract out hypotheses if subtheorem eliminated */
            k = g_Statement[i].reqHypList[j];
            if (!essentialFlag || g_Statement[k].type == (char)e_) {
              actualSteps2--;
            }
          }
        }
      }
    }

    j = statemNum;
    for (i = stmtDist[statemNum]; i >= 0; i--) {
      if (stmtDist[j] != i) bug(214);
      let(&tmpStr, cat(tmpStr, " <- ", g_Statement[j].labelName,
          NULL));
      j = stmtMaxPath[j];
    }
    printLongLine(cat(
       "The statement's actual proof has ",
           str((double)(stmtProofLen[statemNum])), " steps.  ",
       "Backtracking, a total of ", str((double)actualSubTheorems),
           " different subtheorems are used.  ",
       "The statement and subtheorems have a total of ",
           str((double)actualSteps), " actual steps.  ",
       "If subtheorems used only once were eliminated,",
           " there would be a total of ",
           str((double)actualSubTheorems2), " subtheorems, and ",
       "the statement and subtheorems would have a total of ",
           str((double)actualSteps2), " steps.  ",
       "The proof would have ",

       /* This is the old stepCount; can be enabled to troubleshoot
          the new unlimited precision algorithm */
       /* 27-May-05 nm stepCount is inaccurate for over 16 or so digits due
          to roundoff errors. */
       /*stepCount > 1000000000 ? ">1000000000" : str((double)stepCount),*/

       /* 12-Nov-2018 nm */
       stepBigCount,
       strlen(stepBigCount) < 6 ? ""
           : cat(" =~ ",
                 left(
                    str((double)
                        ((5.0 + val(left(stepBigCount, 3))) / 100.0)),
                    3),
                 " x 10^",
                 str((double)strlen(stepBigCount) - 1), NULL),


       " steps if fully expanded back to axiom references.  ",
       /*
       "The proof tree has ", str((double)stmtNodeCount[statemNum])," nodes.  ",
       "A random backtrack path has an average path length of ",
       str((double)(stmtAveDist[statemNum])),
       ".  ",
       */
       "The maximum path length is ",
       str((double)(stmtDist[statemNum])),
       ".  A longest path is:  ", right(tmpStr, 5), " .", NULL),
       "", " ");
    let(&tmpStr, "");

    free(stmtCount);
    free(stmtNodeCount);
    free(stmtDist);
    free(stmtMaxPath);
    free(stmtAveDist);
    free(stmtProofLen);
    free(stmtUsage);

    /* 12-Nov-2018 nm */
    /* Deallocate the big number strings */
    for (stmt = 1; stmt < g_statements + 1; stmt++) {
      let(&stmtBigCount[stmt], "");
    }
    free(stmtBigCount);

  }

  /* Deallocate local strings */ /* 12-Nov-2018 nm */
  let(&tmpBig1, "");
  let(&stepBigCount, "");

  return(stepCount);

} /* countSteps */


/* 12-Nov-2018 nm */
/* Add two arbitrary precision nonnegative integers represented
   as strings of digits e.g. bigAdd("1234", "55") returns "1289".
   Zero can be represented by "", "0", "00", etc. */
/* This is a slow, unsophisticated algorithm intended for use by
   countSteps() i.e. SHOW TRACE_BACK .../COUNT_STEPS */
/* The caller must deallocate the returned string, and the string arguments
   must not be volatile i.e. will not be freed by unrelated 'let()' */
vstring bigAdd(vstring bignum1, vstring bignum2) {
  long len1, len2, maxlen, p, p1, p2, p3;
  char d1, d2, carry, dsum;
  vstring bignum3 = "";
  len1 = (long)strlen(bignum1);
  len2 = (long)strlen(bignum2);
  maxlen = (len1 < len2 ? len2 : len1);
  let(&bignum3, space(maxlen + 1)); /* +1 to allow for final carry */
  carry = 0;
  for (p = 1; p <= maxlen; p++) {
    p1 = len1 - p;
    p2 = len2 - p;
    d1 = (char)(p1 >= 0 ? bignum1[p1] - '0' : 0);
    d2 = (char)(p2 >= 0 ? bignum2[p2] - '0' : 0);
    dsum = (char)(d1 + d2 + carry);
    if (dsum > 9) {
      dsum = (char)(dsum - 10);
      carry = 1;
    } else {
      carry = 0;
    }
    p3 = maxlen + 1 - p;
    bignum3[p3] = (char)(dsum + '0');
  }
  bignum3[0] = (char)(carry + '0');
  while (bignum3[0] == '0') {
    /* Supress leading 0s */
    let(&bignum3, right(bignum3, 2));
  }
  return bignum3;
}


/* 12-Nov-2018 nm */
/* Subtract a nonnegative number (2nd arg) from a larger nonnegative number
   (1st arg).  If the 1st arg is smaller than the 2nd, results are
   not meaningful; there is no error checking for this.  */
/* This is a slow, unsophisticated algorithm intended for use by
   countSteps() i.e. SHOW TRACE_BACK .../COUNT_STEPS */
/* The caller must deallocate the returned string */
/* The arguments must be strings that will not be freed by unrelated 'let()' */
vstring bigSub(vstring bignum1, vstring bignum2) {
  long len1, len3, p;
  vstring bignum3 = "";
  vstring bignum1cmpl = "";
  len1 = (long)strlen(bignum1);
  let(&bignum1cmpl, space(len1));
  for (p = 0; p <= len1 - 1; p++) {
    /* Take 9s complement of 1st arg */
    bignum1cmpl[p] = (char)(9 - (bignum1[p] - '0') + '0');
  }
  bignum3 = bigAdd(bignum1cmpl, bignum2);
  len3 = (long)strlen(bignum3);
  if (len3 < len1) {
    /* We need to pad 0s before taking 9s complement */
    let(&bignum3, cat(string(len1 - len3, '0'), bignum3, NULL));
    len3 = len1;
  }
  for (p = 0; p <= len3 - 1; p++) {
    /* Take 9s complement of result */
    bignum3[p] = (char)(9 - (bignum3[p] - '0') + '0');
  }
  while (bignum3[0] == '0') {
    /* Supress leading 0s */
    let(&bignum3, right(bignum3, 2));
  }
  let(&bignum1cmpl, ""); /* Deallocate */
  return bignum3;
}


/**** 12-Nov-2018 nm In case multiplication is ever needed, this
  code will do it but is commented out because currently there
  is no need for it.
****************************************************************************
/@ Multiply an arbitrary precision nonnegative integers by a positive
   digit e.g. bigMulDigit("123", 9) returns "1107".
   Zero can be represented by "", "0", "00", etc. @/
/@ This is a slow, unsophisticated algorithm intended for use by
   SHOW TRACE_BACK .../COUNT_STEPS @/
/@ The caller must deallocate the returned string @/
/@ The arguments must be strings that will not be freed by unrelated 'let()' @/
vstring bigMulDigit(vstring bignum1, long digit) {
  long len1, p, p1, p3;
  char d1, carry, dprod;
  vstring bignum3 = "";
  len1 = (long)strlen(bignum1);
  let(&bignum3, space(len1 + 1)); /@ +1 to allow for final carry @/
  carry = 0;
  for (p = 1; p <= len1; p++) {
    p1 = len1 - p;
    d1 = (char)(bignum1[p1] - '0');
    dprod = (char)((d1 @ digit) + carry);
    if (dprod > 9) {
      carry = dprod / 10;
      dprod = dprod % 10;
    } else {
      carry = 0;
    }
    p3 = len1 + 1 - p;
    bignum3[p3] = (char)(dprod + '0');
  }
  bignum3[0] = (char)(carry + '0');
  while (bignum3[0] == '0') {
    /@ Supress leading 0s @/
    let(&bignum3, right(bignum3, 2));
  }
  return bignum3;
}

/@ Multiply two arbitrary precision nonnegative integers represented
   as strings of digits e.g. bigMul("1234", "55") returns "67870".
   Zero can be represented by "", "0", "00", etc. @/
/@ This is a slow, unsophisticated algorithm intended for use by
   SHOW TRACE_BACK .../COUNT_STEPS @/
/@ The caller must deallocate the returned string @/
/@ The arguments must be strings that will not be freed by 'let()' @/
vstring bigMul(vstring bignum1, vstring bignum2) {
  long len2, p, p2;
  char d2;
  vstring bignum3 = "";
  vstring bigdprod = "";
  vstring bigpprod = "";
  len2 = (long)strlen(bignum2);
  for (p = 1; p <= len2; p++) {
    p2 = len2 - p;
    d2 = (char)(bignum2[p2] - '0');
    if (d2 > 0) {
      let(&bigdprod, "");
      bigdprod = bigMulDigit(bignum1, d2);
      if (p > 1) {
        /@ Shift the digit product by adding trailing 0s @/
        let(&bigdprod, cat(bigdprod, string(p - 1, '0'), NULL));
      }
      let(&bigpprod, "");
      bigpprod = bigAdd(bignum3, bigdprod); /@ Accumulate partial product @/
      let(&bignum3, bigpprod);
    }
  } /@ next p @/
  let(&bigdprod, "");
  let(&bigpprod, "");
  return bignum3;
}
**** end commented out section added 12-Nov-2018 ***/


/* Traces what statements require the use of a given statement */
/* The output string must be deallocated by the user. */
/* 18-Jul-2015 nm changed the meaning of the returned string as follows: */
/* The return string [0] will be 'Y' or 'N' depending on whether there are any
   statements that use statemNum.  Return string [i] will be 'Y' or 'N'
   depending on whether g_Statement[i] uses statemNum.  All i will be populated
   with 'Y'/'N' even if not $a or $p (always 'N' for non-$a,$p). */
/* 18-Jul-2015 added optional 'cutoffStmt' parameter:  if nonzero, then
   statements above cutoffStmt will not be scanned (for speedup) */
vstring traceUsage(long statemNum,
  flag recursiveFlag,
  long cutoffStmt /* for speedup */) {

  long lastPos, stmt, slen, pos;
  flag tmpFlag;
  vstring statementUsedFlags = ""; /* 'Y'/'N' flag that statement is used */
  /* vstring outputString = ""; */ /* 18-Jun-2015 nm Deleted */
  nmbrString *statementList = NULL_NMBRSTRING;
  nmbrString *proof = NULL_NMBRSTRING;

  /* For speed-up code */
  char *fbPtr;
  char *fbPtr2;
  char zapSave;
  flag notEFRec; /* Not ($e or $f or recursive) */

  if (g_Statement[statemNum].type == e_ || g_Statement[statemNum].type == f_
      || recursiveFlag) {
    notEFRec = 0;
  } else {
    notEFRec = 1;
  }

  nmbrLet(&statementList, nmbrAddElement(statementList, statemNum));
  lastPos = 1;

  /* 18-Jul-2015 nm */
  /* For speedup (in traceProofWork), scan only up to cutoffStmt if it
     is specified, otherwise scan all statements. */
  if (cutoffStmt == 0) cutoffStmt = g_statements;

  /*for (stmt = statemNum + 1; stmt <= g_statements; stmt++) {*/ /* Scan all stmts*/
  for (stmt = statemNum + 1; stmt <= cutoffStmt; stmt++) { /* Scan stmts*/
    if (g_Statement[stmt].type != p_) continue; /* Ignore if not $p */

    /* Speed up:  Do a character search for the statement label in the proof,
       before parsing the proof.  Skip this if the label refers to a $e or $f
       because these might not have their labels explicit in a compressed
       proof.  Also, bypass speed up in case of recursive search. */
    if (notEFRec) {
      fbPtr = g_Statement[stmt].proofSectionPtr; /* Start of proof */
      if (fbPtr[0] == 0) { /* The proof was never assigned */
        continue; /* Don't bother */
      }
      fbPtr = fbPtr + whiteSpaceLen(fbPtr); /* Get past white space */
      if (fbPtr[0] == '(') { /* "(" is flag for compressed proof */
        fbPtr2 = fbPtr;
        while (fbPtr2[0] != ')') {
          fbPtr2++;
          if (fbPtr2[0] == 0) bug(217); /* Didn't find closing ')' */
        }
      } else {
        /* A non-compressed proof; use whole proof */
        fbPtr2 = g_Statement[stmt].proofSectionPtr +
            g_Statement[stmt].proofSectionLen;
      }
      zapSave = fbPtr2[0];
      fbPtr2[0] = 0; /* Zap source for character string termination */
      if (!instr(1, fbPtr, g_Statement[statemNum].labelName)) {
        fbPtr2[0] = zapSave; /* Restore source buffer */
        /* There is no string match for label in proof; don't bother to
           parse. */
        continue;
      } else {
        /* The label was found in the ASCII source.  Proceed with parse. */
        fbPtr2[0] = zapSave; /* Restore source buffer */
      }
    } /* (End of speed-up code) */

    /* 20-Oct-2013 nm Don't use bad proofs (incomplete proofs are ok) */
    if (parseProof(stmt) > 1) {
      /* The proof has an error, so use the empty proof */
      nmbrLet(&proof, nmbrAddElement(NULL_NMBRSTRING, -(long)'?'));
    } else {
      nmbrLet(&proof, g_WrkProof.proofString);
    }

    tmpFlag = 0;
    for (pos = 0; pos < lastPos; pos++) {
      if (nmbrElementIn(1, proof, statementList[pos])) {
        tmpFlag = 1;
        break;
      }
    }
    if (!tmpFlag) continue;
    /* The traced statement is used in this proof */
    /* Add this statement to the statement list */
    nmbrLet(&statementList, nmbrAddElement(statementList, stmt));
    if (recursiveFlag) lastPos++;
  } /* Next stmt */

  slen = nmbrLen(statementList);

  /* Prepare the output */
  /* First, fill in the statementUsedFlags char array.  This allows us to sort
     the output by statement number without calling a sort routine. */
  let(&statementUsedFlags, string(g_statements + 1, 'N')); /* Init. to 'no' */
  if (slen > 1) statementUsedFlags[0] = 'Y';  /* Used by at least one */
                                              /* 18-Jul-2015 nm */
  for (pos = 1; pos < slen; pos++) { /* Start with 1 (ignore traced statement)*/
    stmt = statementList[pos];
    if (stmt <= statemNum || g_Statement[stmt].type != p_ || stmt > g_statements)
        bug(212);
    statementUsedFlags[stmt] = 'Y';
  }
  return (statementUsedFlags); /* 18-Jul-2015 nm */

  /********** 18-Jul-2015 nm Deleted code - this is now done by caller ******/
  /*
  /@ Next, build the output string @/
  let(&outputString, "");
  for (stmt = 1; stmt <= g_statements; stmt++) {
    if (statementUsedFlags[stmt] == 'Y') {
      let(&outputString, cat(outputString, " ", g_Statement[stmt].labelName,
          NULL));
      /@ For temporary unofficial use by NDM to help build command files: @/
      /@
      print2("prove %s$min %s/a$sa n/compr$q\n",
          g_Statement[stmt].labelName,g_Statement[statemNum].labelName);
      @/
    } /@ End if (statementUsedFlag[stmt] == 'Y') @/
  } /@ Next stmt @/

  /@ Deallocate @/
  let(&statementUsedFlags, "");
  nmbrLet(&statementList, NULL_NMBRSTRING);
  nmbrLet(&proof, NULL_NMBRSTRING);

  return (outputString);
  */
  /*************** 18-Jul-2015 End of deleted code **************/

} /* traceUsage */



/* This implements the READ command (although the / VERIFY qualifier is
   processed separately in metamath.c). */
void readInput(void)
{
  vstring fullInput_fn = "";

  let(&fullInput_fn, cat(g_rootDirectory, g_input_fn, NULL)); /* 31-Dec-2017 nm */

  /*g_includeCalls = 0;*/ /* Initialized by readSourceAndIncludes() */

  /* 31-Dec-2017 nm */
  g_sourcePtr = readSourceAndIncludes(g_input_fn, &g_sourceLen);
  if (g_sourcePtr == NULL) {
    print2(
"?Source was not read due to error(s).  Please correct and try again.\n");
    goto RETURN_POINT;
  }

  g_sourcePtr = readRawSource(/*g_input_fn,*/  /* 2-Feb-2018 nm */
      g_sourcePtr, &g_sourceLen);
  parseKeywords();
  parseLabels();
  parseMathDecl();
  parseStatements();
  g_sourceHasBeenRead = 1;

 RETURN_POINT:
  let(&fullInput_fn, "");

} /* readInput */

/* This function implements the WRITE SOURCE command. */
/* Note that the labelSection, mathSection, and proofSection do not
   contain keywords ($a, $p,...; $=; $.).  The keywords are added
   by outputStatement. */
void writeSource(/* flag cleanFlag, 3-May-2017 */ /* 1 = "/ CLEAN" qualifier was chosen */
                flag reformatFlag /* 1 = "/ FORMAT", 2 = "/REWRAP" */,
                /* 31-Dec-2017 nm */
                flag splitFlag,  /* /SPLIT - write out separate $[ $] includes */
                flag noVersioningFlag, /* /NO_VERSIONING - no ~1 backup */
                flag keepSplitsFlag, /* /KEEP_INCLUDES - don't delete included
                                      files when /SPIT is not specified */
                vstring extractLabelList /* "" means /EXTRACT wasn't specified */
                )
{

  /* Temporary variables and strings */
  long i;
  /* long p; */ /* deleted 3-May-2017 nm */
  vstring buffer = "";
  vstring fullOutput_fn = "";
  /* long skippedCount = 0; */ /* deleted 3-May-2017 nm */
  FILE *fp;

  let(&fullOutput_fn, cat(g_rootDirectory, g_output_fn, NULL));

  if (splitFlag == 0 /* If 1, it will have message from writeSplitSource() */
      && extractLabelList[0] == 0) {  /* If non-zero, it will have messages
                     from writeExtractedSource() */
    print2("Writing \"%s\"...\n", fullOutput_fn);
  }

  /* 24-Aug-2020 nm */
  if (extractLabelList[0] != 0) {
    writeExtractedSource(
       extractLabelList, /* EXTRACT label list argument provided by user */
       fullOutput_fn,
       noVersioningFlag
       );
    /* All the writing was done by writeExtractedSource() so just return */
    goto RETURN_POINT;
  }

  if (reformatFlag > 0) {
    /* 31-Dec-2017 nm */
    /* Now the outputSource function just reformats and puts the
       source back into the g_Statement[] array.  So we don't need
       to do it when we're not reformatting/wrapping */
    /* TODO: turn this into a REWRAP command */
    /* Process statements */
    for (i = 1; i <= g_statements + 1; i++) {

      /******** deleted 3-May-2017 nm
      /@ Added 24-Oct-03 nm @/
      if (cleanFlag && g_Statement[i].type == (char)p_) {
        /@ Clean out any proof-in-progress (that user has flagged with a ? in its
           date comment field) @/
        /@ Get the comment section after the statement @/
        let(&str1, space(g_Statement[i + 1].labelSectionLen));
        memcpy(str1, g_Statement[i + 1].labelSectionPtr,
            (size_t)(g_Statement[i + 1].labelSectionLen));
        /@ Make sure it's a date comment @/
        let(&str1, edit(str1, 2 + 4)); /@ Discard whitespace + control chrs @/
        p = instr(1, str1, "]$)"); /@ Get end of date comment @/
        let(&str1, left(str1, p)); /@ Discard stuff after date comment @/
        if (instr(1, str1, "$([") == 0) {
          printLongLine(cat(
              "?Warning: The proof for $p statement \"", g_Statement[i].labelName,
              "\" does not have a date comment after it and will not be",
              " removed by the CLEAN qualifier.", NULL), " ", " ");
        } else {
          /@ See if the date comment has a "?" in it @/
          if (instr(1, str1, "?")) {
            skippedCount++;
            let(&str2, cat(str2, " ", g_Statement[i].labelName, NULL));
            /@ Write at least the date from the _previous_ statement's
               post-comment section so that WRITE RECENT can pick it up. @/
            let(&str1, space(g_Statement[i].labelSectionLen));
            memcpy(str1, g_Statement[i].labelSectionPtr,
                (size_t)(g_Statement[i].labelSectionLen));
            /@ nm 19-Jan-04 Don't discard w.s. because string will be returned to
               the source file @/
            /@let(&str1, edit(str1, 2 + 4));@/ /@ Discard whitespace + ctrl chrs @/
            p = instr(1, str1, "]$)"); /@ Get end of date comment @/
            if (p == 0) {
              /@ In the future, "] $)" may flag date end to conform with
                 space-around-keyword Metamath spec recommendation @/
              /@ 7-Sep-04 The future is now @/
              p = instr(1, str1, "] $)"); /@ Get end of date comment @/
              if (p != 0) p++; /@ Match what it would be for old standard @/
            }
            if (p != 0) {  /@ The previous statement has a date comment @/
              let(&str1, left(str1, p + 2)); /@ Discard stuff after date cmnt @/
              if (!instr(1, str1, "?"))
                /@ Don't bother to print $([?]$) of previous statement because
                   the previous statement was skipped @/
                fprintf(g_output_fp, "%s\n", str1);
                                               /@ Output just the date comment @/
            }

            /@ Skip the normal output of this statement @/
            continue;
          } /@ if (instr(1, str1, "?")) @/
        } /@ (else clause) if (instr(1, str1, "$([") == 0) @/
      } /@ if cleanFlag @/
      ********** end of 3-May-2017 deletion */

      let(&buffer,""); /* Deallocate vstring */

      /* 31-Dec-2017 nm This now only affects g_Statement[] array.  It does
         not write any files. */
      buffer = outputStatement(i, /* cleanFlag, 3-May-2017 */ reformatFlag);

      /* fprintf(g_output_fp, "%s", str1); */ /* 31-Dec-2017 nm deleted */
    } /* next i */
  } /* if (reformatFlag > 0) */

  /* 31-Dec-2017 nm */
  /* Get put the g_Statement[] array into one linear buffer */
  let(&buffer, "");
  buffer = writeSourceToBuffer();
  if (splitFlag == 1) { /* Write includes as separate files */

    /* Make sure we aren't overwriting one of the include files */
    for (i = 1; i <= g_includeCalls; i++) {  /* Start at 1 to skip main file */
      if (g_IncludeCall[i].pushOrPop == 0 /* Don't include pop back to main file */
          && !strcmp(g_output_fn, g_IncludeCall[i].included_fn)) {
        print2(
"?The output was not written because the main output file name is\n");
        print2(
"  the same as an included file.  Use a different name.\n");
        goto RETURN_POINT;
      }
    }

    /* Note that writeSplitSource requires a file name witout path since
       it is called recursively for file inclusions where path is added */
    writeSplitSource(&buffer, g_output_fn, noVersioningFlag, keepSplitsFlag);
  } else {  /* Write a non-split version */
    fp = fSafeOpen(fullOutput_fn, "w", noVersioningFlag);
    if (fp == NULL) {
      print2("?Error trying to write \"%s\".\n", fp);
    } else {
      fprintf(fp, "%s", buffer); /* Write the non-split output file */
      fclose(fp);
      if (keepSplitsFlag == 0) {
        deleteSplits(&buffer, noVersioningFlag); /* Delete any old includes */
      }
    }
  }

  print2("%ld source statement(s) were written.\n", g_statements);


  /******** deleted 3-May-2017 nm
  /@ Added 24-Oct-03 nm @/
  if (cleanFlag) {
    if (skippedCount == 0) {
      print2("No statements were deleted by the CLEAN qualifier.\n");
    } else {
      printLongLine(cat("The following ", str((double)skippedCount), " statement",
          (skippedCount == 1) ? " was" : "s were",
          " deleted by the CLEAN qualifier:", NULL), "", " ");
      printLongLine(cat(" ", str2, NULL), "  ", " ");
      printLongLine(cat(
          "You should ERASE, READ the new output file, and run VERIFY PROOF",
          " to ensure that no proof dependencies were broken.", NULL),
          "", " ");
    }
  }
  ********** end of 3-May-2017 deletion */

 RETURN_POINT:
  let(&buffer,""); /* Deallocate vstring */
  let(&fullOutput_fn,""); /* Deallocate vstring */
  return;
  /* let(&str2,""); */ /* Deallocate vstring */  /* deleted 3-May-2017 nm */
} /* writeSource */


/* 24-Aug-2020 nm */
/* Get info for WRITE SOURCE ... / EXTRACT */
void writeExtractedSource(
    vstring extractLabelList, /* EXTRACT argument provided by user */
    vstring fullOutput_fn,
    flag noVersioningFlag)
{
  vstring statementUsedFlags = ""; /* Y/N flags that statement is used */
  long stmt, stmtj, scpStmt, strtScpStmt, endScpStmt, j, p1, p2, p3, p4;
  vstring extractNeeded = "";
  nmbrString *unprovedList = NULL_NMBRSTRING; /* Needed for traceProofWork()
                                                 but not used */
  nmbrString *mstring = NULL_NMBRSTRING; /* Temporary holder for math string */
  long maxStmt; /* The largest statement number (excluding $t) */
  long hyp, hyps, mtkn, mtkns, dv, dvs;
  long dollarTStmt; /* $t statement */
  vstring dollarTCmt = ""; /* $t comment */
  char zapChar; /* For finding $t statement */
  char *tmpPtr; /* For finding $t statement */
  vstring hugeHdrNeeded = ""; /* N/M/Y that output needs the huge header */
  vstring bigHdrNeeded = "";                              /* big */
  vstring smallHdrNeeded = "";                            /* small */
  vstring tinyHdrNeeded = "";                             /* tiny */
  char hdrNeeded;
  /* The following 8 are needed for getSectionHeadings() */
  vstring hugeHdr = "";
  vstring bigHdr = "";
  vstring smallHdr = "";
  vstring tinyHdr = "";
  vstring hugeHdrComment = "";
  vstring bigHdrComment = "";
  vstring smallHdrComment = "";
  vstring tinyHdrComment = "";

  vstring mathTokenDeclared = "";
  vstring undeclaredC = "";
  vstring undeclaredV = "";
  long extractedStmts;
  vstring hdrSuffix = "";
  FILE *fp;
  vstring buf = "";


  /* Note that extractNeeded is 1-based to match 1-based
     indices of the g_Statement array.  We also may need labelSection of entry
     g_statements + 1 for text after last statement, which explains the +2. */
  let(&extractNeeded, string(g_statements + 2, 'N'));

  /* First, do the trace_backs for the statements in user's / EXTRACT argument */
  /* By scanning backwards, we get a speedup by not having to trace
     a statement twice, especially if we did "/ EXTRACT *" */
  print2("Tracing back through proofs for $a and $p statements needed...\n");
  if (!strcmp(extractLabelList, "*")) {
    /**/
    print2(
       "   This may take up to 10 minutes.  (For audio alert when done,\n");
    print2("   type ahead \"b\" then \"<enter>\".)\n");
    /**/
    /*
    print2(
       "   This may take up to 10 minutes.  (\n");
    */
  }

  for (stmt = g_statements; stmt >= 1; stmt--) {
    if (extractNeeded[stmt] == 'Y') {
      /* We've already traced this back, so skip it */
      continue;
    }
    /* Wildcard matching */
    if (!matchesList(g_Statement[stmt].labelName, extractLabelList, '*', '?'))
      continue;
    if (g_Statement[stmt].type == (char)a_) {
      extractNeeded[stmt] = 'Y'; /* Add in axioms but don't trace */
      continue;
    }
    if (g_Statement[stmt].type != (char)p_)
      continue; /* Not a $p statement; skip it */
    traceProofWork(stmt,
        0, /*essentialFlag,*/
        "", /*traceToList,*/ /* /TO argument of SHOW TRACE_BACK */
        &statementUsedFlags,
        &unprovedList);
    if ((signed)(strlen(statementUsedFlags)) != g_statements + 1) bug(268);
    /* OR in all the statements found by the trace */
    for (stmtj = 1; stmtj <= stmt; stmtj++) {
      if (statementUsedFlags[stmtj] == 'Y')
        extractNeeded[stmtj] = 'Y';
    }
  } /* next stmt */

  /* Next, we add in all necessary  ${ $} scoping statements */
  print2("Determining which ${ and $} scoping statements are needed...\n");
  for (stmt = 1; stmt <= g_statements; stmt++) {
    if (extractNeeded[stmt] == 'Y'
        /* All flagged statements so far will be $a or $p */
        /* Skip the forward $}'s that this loop populates (is it
           necessary? */
        && (g_Statement[stmt].type == a_ || g_Statement[stmt].type == p_)) {
      scpStmt = stmt;
      while (g_Statement[scpStmt].beginScopeStatementNum != 0) {
        /* We're still in an inner scope */
        strtScpStmt = g_Statement[scpStmt].beginScopeStatementNum;
        if (g_Statement[strtScpStmt].type != lb_) bug(269);
        if (extractNeeded[strtScpStmt] == 'Y')
          /* We've already processed this ${ */
          break;
        endScpStmt = g_Statement[strtScpStmt].endScopeStatementNum;
        if (g_Statement[endScpStmt].type != rb_) bug(270);
        extractNeeded[strtScpStmt] = 'Y';
        extractNeeded[endScpStmt] = 'Y';
        scpStmt = strtScpStmt;
      }
    } /* if extraction needed */
  } /* next stmt */

  /* Next, we add in hypotheses and variable declarations for all $a's and $p's */
  print2("Adding in $e and $f hypotheses and $d provisos...\n");
  for (stmt = 1; stmt <= g_statements; stmt++) {
    if (extractNeeded[stmt] == 'Y'
        /* All flagged statements so far will be $a or $p */
        /* Skip the ${'s and $}'s that earlier loop populates (is it
           necessary? */
        && (g_Statement[stmt].type == a_ || g_Statement[stmt].type == p_)) {
      hyps = g_Statement[stmt].numReqHyp;
      for (hyp = 0; hyp < hyps; hyp++) {
        extractNeeded[g_Statement[stmt].reqHypList[hyp]] = 'Y';
      }
      hyps = nmbrLen(g_Statement[stmt].optHypList);
      for (hyp = 0; hyp < hyps; hyp++) {
        extractNeeded[g_Statement[stmt].optHypList[hyp]] = 'Y';
      }
      mtkns = nmbrLen(g_Statement[stmt].reqVarList);
      for (mtkn = 0; mtkn < mtkns; mtkn++) {
        /* Flag the $v statement for a required variable */
        /* (This may be redundant because of next stmt loop below) */
        extractNeeded[g_MathToken[
            (g_Statement[stmt].reqVarList)[mtkn]].statement] = 'Y';
      }
      mtkns = nmbrLen(g_Statement[stmt].optVarList);
      for (mtkn = 0; mtkn < mtkns; mtkn++) {
        /* Flag the $v statement for an optional variable */
        extractNeeded[g_MathToken[
            (g_Statement[stmt].optVarList)[mtkn]].statement] = 'Y';
      }
      dvs = nmbrLen(g_Statement[stmt].reqDisjVarsStmt);
      for (dv = 0; dv < dvs; dv++) {
        /* Flag the $d statement */
        extractNeeded[(g_Statement[stmt].reqDisjVarsStmt)[dv]] = 'Y';
      }
      dvs = nmbrLen(g_Statement[stmt].optDisjVarsStmt);
      for (dv = 0; dv < dvs; dv++) {
        /* Flag the $d statement */
        extractNeeded[(g_Statement[stmt].optDisjVarsStmt)[dv]] = 'Y';
      }
    } /* if extraction needed */
  } /* next stmt */

  /* Next, add in the $c, $v required by all statements */
  print2("Determining which $c and $v statements are needed...\n");
  for (stmt = 1; stmt <= g_statements; stmt++) {
    if (extractNeeded[stmt] == 'Y'
        /* All $a, $p, $f, $e */
        && (g_Statement[stmt].type == a_
             || g_Statement[stmt].type == p_
             || g_Statement[stmt].type == e_
             || g_Statement[stmt].type == f_
           )) {
      nmbrLet(&mstring, g_Statement[stmt].mathString);
      mtkns = g_Statement[stmt].mathStringLen;
      for (mtkn = 0; mtkn < mtkns; mtkn++) {
        /* Flag the $c or $v statement for the token */
        extractNeeded[g_MathToken[mstring[mtkn]].statement] = 'Y';
      }
    } /* if extract needed */
  } /* next stmt */

  /* Get largest statement number (excluding $t comment) */
  maxStmt = 0;
  for (stmt = g_statements; stmt >= 1; stmt--) {
    if (extractNeeded[stmt] == 'Y') {
      maxStmt = stmt;
      break;
    }
  }

  /* Find the $t statement */  /* (Should this be done globally somewhere?) */
  print2("Locating the $t statement if any...\n");
  dollarTStmt = 0;
  let(&dollarTCmt, "");
  /* Note that g_Statement[g_statements + 1] is a special (empty) statement whose
     labelSection holds any comment after the last statement.  It is possible
     that the $t statement could be there. */
  for (stmt = 1; stmt <= g_statements + 1; stmt++) {
    /* We do low-level zapping in the xxx.mm input file buffer for speed */
    tmpPtr = g_Statement[stmt].labelSectionPtr;
    j = g_Statement[stmt].labelSectionLen;
    zapChar = tmpPtr[j]; /* Save the original character */
    tmpPtr[j] = 0; /* Create an end-of-string */
    p1 = instr(1, tmpPtr, "$t");
    if (p1 != 0) { /* Found the $t */
      dollarTStmt = stmt;
      /* Get the full $t comment */
      p2 = instr(p1, tmpPtr, "$)");
      let(&dollarTCmt, left(tmpPtr, p2 + 1));
      /* We need the above because rinstr doesn't have starting arg */
      p1 = rinstr(dollarTCmt, "$(");
      /* Search backwards for non-space or beginning of string */
      p1--;
      while (p1 != 0) {
        if (dollarTCmt[p1 - 1] != ' ') break;
        p1--;
      }
      let(&dollarTCmt, cat("\n", seg(dollarTCmt, p1 + 1, p2 + 1), NULL));
    }
    tmpPtr[j] = zapChar; /* Restore the xxx.mm input file buffer */
    if (dollarTStmt != 0) {
      break; /* Found the $t, so no reason to continue */
    }
  }


  /* Get header information about which headers to use */
  print2("Analyzing scopes of section headings...\n");
  let(&hugeHdrNeeded, string(g_statements + 2, 'N'));
  let(&bigHdrNeeded, string(g_statements + 2, 'N'));
  let(&smallHdrNeeded, string(g_statements + 2, 'N'));
  let(&tinyHdrNeeded, string(g_statements + 2, 'N'));
  /* Scan the database to determine which headers exist */
  for (stmt = 1; stmt <= maxStmt; stmt++) {
    getSectionHeadings(stmt, &hugeHdr, &bigHdr, &smallHdr,
        &tinyHdr,
        &hugeHdrComment, &bigHdrComment, &smallHdrComment,
        &tinyHdrComment,
        1, /* fineResolution */
        1 /* fullComment */);
    if (hugeHdr[0] != 0) hugeHdrNeeded[stmt] = '?'; /* Don't know yet */
    if (bigHdr[0] != 0) bigHdrNeeded[stmt] = '?';
    if (smallHdr[0] != 0) smallHdrNeeded[stmt] = '?';
    if (tinyHdr[0] != 0) tinyHdrNeeded[stmt] = '?';
  } /* next stmt */

  /* For each tiny header, scan until next tiny, small, big, or huge header
     is found (which means end of the tiny header's scope).  Set '?' to
     'Y' if a used stmt is found along the way (meaning the header should
     be in the output file) or to 'N' otherwise.  Then do the same starting
     from small, then big, then huge header. */
  for (stmt = 1; stmt <= maxStmt; stmt++) {
    /***** deleted
    if (g_Statement[stmt].type != a_ && g_Statement[stmt].type != p_)
      continue;
    *****/
    /* We do ALL statements, not just $a, $p, so that headers will go to
       the right place in the output file.  We called getSectionHeadings()
       with fineResolution=1 above so that "header area" will be 1 statement
       rather than the multiple-statement content between successive $a/$p
       statements. */
    if (tinyHdrNeeded[stmt] == '?') {
      hdrNeeded = 0;
      for (stmtj = stmt; stmtj <= maxStmt; stmtj++) {
        if (hugeHdrNeeded[stmtj] != 'N' || bigHdrNeeded[stmtj] != 'N'
            || smallHdrNeeded[stmtj] != 'N' || tinyHdrNeeded[stmtj] != 'N') {
          /* The scope of header at stmt has ended with no used statement
             found, so header is not needed; abort the scan */
          if (stmtj > stmt) break; /* Ignore starting point of scan since we
              are looking for a later header to end the scope */
        }
        if (extractNeeded[stmtj] == 'Y') {
          hdrNeeded = 1;
          /* We now know the header is needed, so abort the scan */
          break;
        }
      }
      if (hdrNeeded == 1) {
        tinyHdrNeeded[stmt] = 'Y';
      } else {
        tinyHdrNeeded[stmt] = 'N';
      }
    } /* if tinyHdrNeeded[stmt] == '?' */
    if (smallHdrNeeded[stmt] == '?') {
      hdrNeeded = 0;
      for (stmtj = stmt; stmtj <= maxStmt; stmtj++) {
        if (hugeHdrNeeded[stmtj] != 'N' || bigHdrNeeded[stmtj] != 'N'
            || smallHdrNeeded[stmtj] != 'N') {
          /* The scope of header at stmt has ended with no used statement
             found, so header is not needed; abort the scan */
          if (stmtj > stmt) break; /* Ignore starting point of scan since we
              are looking for a later header to end the scope */
        }
        if (extractNeeded[stmtj] == 'Y') {
          hdrNeeded = 1;
          /* We now know the header is needed, so abort the scan */
          break;
        }
      }
      if (hdrNeeded == 1) {
        smallHdrNeeded[stmt] = 'Y';
      } else {
        smallHdrNeeded[stmt] = 'N';
      }
    } /* if smallHdrNeeded[stmt] == '?' */
    if (bigHdrNeeded[stmt] == '?') {
      hdrNeeded = 0;
      /*for (stmtj = stmt + 1; stmtj <= maxStmt; stmtj++) {*/
      for (stmtj = stmt; stmtj <= maxStmt; stmtj++) {
        if (hugeHdrNeeded[stmtj] != 'N' || bigHdrNeeded[stmtj] != 'N') {
          /* The scope of header at stmt has ended with no used statement
             found, so header is not needed; abort the scan */
          if (stmtj > stmt) break;
        }
        if (extractNeeded[stmtj] == 'Y') {
          hdrNeeded = 1;
          /* We now know the header is needed, so abort the scan */
          break;
          /*if (stmtj > stmt) break;*/ /* Ignore starting point of scan since we
              are looking for a later header to end the scope */
        }
      }
      if (hdrNeeded == 1) {
        bigHdrNeeded[stmt] = 'Y';
      } else {
        bigHdrNeeded[stmt] = 'N';
      }
    } /* if bigHdrNeeded[stmt] == '?' */
    if (hugeHdrNeeded[stmt] == '?') {
      hdrNeeded = 0;
      for (stmtj = stmt; stmtj <= maxStmt; stmtj++) {
        if (hugeHdrNeeded[stmtj] != 'N') {
          /* The scope of header at stmt has ended with no used statement
             found, so header is not needed; abort the scan */
          if (stmtj > stmt) break; /* Ignore starting point of scan since we
              are looking for a later header to end the scope */
        }
        if (extractNeeded[stmtj] == 'Y') {
          hdrNeeded = 1;
          /* We now know the header is needed, so abort the scan */
          break;
        }
      }
      if (hdrNeeded == 1) {
        hugeHdrNeeded[stmt] = 'Y';
      } else {
        hugeHdrNeeded[stmt] = 'N';
      }
    } /* if hugeHdrNeeded[stmt] == '?' */
  } /* next stmt */

  /* Collect all $c and $v tokens that are not declared in the
     extract, so they can be included in the output .mm to satisfy
     the htmldefs and for use in ` math ` comment markup */
  print2("Building $c and $v statements for unused math tokens...\n");
  let(&mathTokenDeclared, string(g_mathTokens, 'N'));
  for (stmt = 1; stmt <= g_statements; stmt++) {
    if (extractNeeded[stmt] == 'Y'
       && (g_Statement[stmt].type == c_
           || g_Statement[stmt].type == v_)) {
      mtkns = g_Statement[stmt].mathStringLen;
      for (mtkn = 0; mtkn < mtkns; mtkn++) {
        /* Flag the math token as being declared */
        mathTokenDeclared[(g_Statement[stmt].mathString)[mtkn]] = 'Y';
      }
    }
  }
  /* Build $c and $v statements for undeclared math tokens.  They are used to
     make the database consistent with the htmldef's in the $t comment. */
  let(&undeclaredC, "");
  let(&undeclaredV, "");
  for (mtkn = 0; mtkn < g_mathTokens; mtkn++) {
    if (mathTokenDeclared[mtkn] == 'N') {
      if (g_MathToken[mtkn].tokenType == con_) {
        let(&undeclaredC, cat(undeclaredC, " ", g_MathToken[mtkn].tokenName,
            NULL));
      } else {
        if (g_MathToken[mtkn].tokenType != var_) bug(271);
        /* Before adding it to the unused var list, make sure it isn't declared
           in another scope */
        p1 = 0;
        for (j = 0; j < g_mathTokens; j++) {
          if (j == mtkn) continue;
          if (!strcmp(g_MathToken[mtkn].tokenName, g_MathToken[j].tokenName)) {
            /* See if it the $v was already declared in another scope */
            if (mathTokenDeclared[j] == 'Y') {
              p1 = 1;
              break;
            }
          }
        }
        if (p1 == 0) {
          let(&undeclaredV, cat(undeclaredV, " ", g_MathToken[mtkn].tokenName,
            NULL));
        }
        /* 4-Sep-2020 nm */
        /* Tag the variable as declared for use in later j loops above, so it
           won't be added to the undeclaredV twice */
        mathTokenDeclared[mtkn] = 'Y';
      }
    }
  } /* next mtkn */

/*
/@D@/for(stmt=1;stmt<=2;stmt++)
/@D@/printf("s=%ld t=%c cs=%ld bs=%ld es=%ld en=%c %c%c%c%c\n",
/@D@/  stmt,g_Statement[stmt].type,(long)g_Statement[stmt].scope,
/@D@/ g_Statement[stmt].beginScopeStatementNum,
/@D@/ g_Statement[stmt].endScopeStatementNum,extractNeeded[stmt],
/@D@/ hugeHdrNeeded[stmt],bigHdrNeeded[stmt],
/@D@/ smallHdrNeeded[stmt],tinyHdrNeeded[stmt]);
*/

  /* Write the output file */
  /* (We don't call the standard output functions because there's too
     much customization needed) */
  print2("Creating the final output file \"%s\"...\n", fullOutput_fn);

  fp = fSafeOpen(fullOutput_fn, "w", noVersioningFlag);
  if (fp == NULL) {
    print2("?Error trying to write \"%s\".\n", fp);
    goto EXTRACT_RETURN;
  }

  /* Get the first line of the .mm file that normally has version and date */
  /* (The memcpy to buf is not really necessary, just a safety
     measure in case the input file has garbage with no "\n".) */
  let(&buf, space(g_Statement[1].labelSectionLen));
  memcpy(buf, g_Statement[1].labelSectionPtr,
      (size_t)(g_Statement[1].labelSectionLen));
  let(&buf, left(buf, instr(1, buf, "\n") - 1)); /* This will not include the \n */
  let(&buf, right(edit(buf, 8/*leading space*/), 3)); /* Take off $( */
  j = (long)strlen(" Extracted from: ");
  if (!strcmp(left(buf, j), " Extracted from: ")) {
    /* Prevent "Extracted from:" accumulation if sent through /EXTRACT again */
    let(&buf, right(buf, j + 1));
  }
  fprintf(fp, "$( Extracted from: %s", buf);
  if (instr(1, buf, "$)") == 0) fprintf(fp, " $)");
  fprintf(fp,
      "\n$( Created %s %s using \"READ '%s'\" then\n",
      date(), time_(), g_input_fn);
  fprintf(fp,
      "   \"WRITE SOURCE '%s' /EXTRACT %s\" $)",
      fullOutput_fn, extractLabelList);

  extractedStmts = 0; /* How many statements were extracted */
  for (stmt = 1; stmt <= g_statements + 1; stmt++) {
    /* If the header is part of the labelSection of an extracted stmt,
       we don't want to add a newline in order for extractions to be
       stable (i.e. more lines aren't added when we extract from an
       extraction). */
    if (extractNeeded[stmt] == 'Y') {
      let(&hdrSuffix, "");
    } else {
      let(&hdrSuffix, "\n");
    }
    /* Output headers if needed */
    if (hugeHdrNeeded[stmt] == 'Y'
        || bigHdrNeeded[stmt] == 'Y'
        || smallHdrNeeded[stmt] == 'Y'
        || tinyHdrNeeded[stmt] == 'Y') {
      getSectionHeadings(stmt, &hugeHdr, &bigHdr, &smallHdr, &tinyHdr,
          &hugeHdrComment, &bigHdrComment, &smallHdrComment,
          &tinyHdrComment,
          1, /*fineResolution*/
          1 /*fullComment*/ /* 12-Sep-2020 nm */
          );
      let(&buf, "");
      if (hugeHdrNeeded[stmt] == 'Y') {
        fixUndefinedLabels(extractNeeded, &hugeHdrComment);
        /**** 12-Sep-2020 nm Deleted
        let(&buf, "");
        buf = buildHeader(hugeHdr, hugeHdrComment, HUGE_DECORATION);
        fprintf(fp, "%s", buf);
        ****/
        /* 12-Sep-2020 nm */
        fprintf(fp, "%s", cat(hugeHdr, hugeHdrComment, hdrSuffix, NULL));
      }
      if (bigHdrNeeded[stmt] == 'Y') {
        fixUndefinedLabels(extractNeeded, &bigHdrComment);
        /**** 12-Sep-2020 nm Deleted
        let(&buf, "");
        buf = buildHeader(bigHdr, bigHdrComment, HUGE_DECORATION);
        fprintf(fp, "%s", buf);
        ****/
        /* 12-Sep-2020 nm */
        fprintf(fp, "%s", cat(bigHdr, bigHdrComment, hdrSuffix, NULL));
      }
      if (smallHdrNeeded[stmt] == 'Y') {
        fixUndefinedLabels(extractNeeded, &smallHdrComment);
        /**** 12-Sep-2020 nm Deleted
        let(&buf, "");
        buf = buildHeader(smallHdr, smallHdrComment, SMALL_DECORATION);
        fprintf(fp, "%s", buf);
        ****/
        /* 12-Sep-2020 nm */
        fprintf(fp, "%s", cat(smallHdr, smallHdrComment, hdrSuffix, NULL));
      }
      if (tinyHdrNeeded[stmt] == 'Y') {
        fixUndefinedLabels(extractNeeded, &tinyHdrComment);
        /**** 12-Sep-2020 nm Deleted
        let(&buf, "");
        buf = buildHeader(tinyHdr, tinyHdrComment, TINY_DECORATION);
        fprintf(fp, "%s", buf);
        ****/
        /* 12-Sep-2020 nm */
        fprintf(fp, "%s", cat(tinyHdr, tinyHdrComment, hdrSuffix, NULL));
      }
    } /* if header(s) needed */

    /* Output $t statement if needed */
    if (dollarTStmt == stmt) {
      fprintf(fp, "\n%s", dollarTCmt);
    }

    /* Output statement if needed */
    if (extractNeeded[stmt] == 'Y') {
      let(&buf, "");
      buf = getDescriptionAndLabel(stmt);

      fixUndefinedLabels(extractNeeded, &buf);

      fprintf(fp, "%s", buf);
      if (stmt == g_statements + 1) bug(272); /* Text below last statement
           isn't (currently) used - do we need it? */
      if (stmt != g_statements + 1) {
        extractedStmts++; /* For final message */
        fprintf(fp, "$%c", g_Statement[stmt].type);
        if (g_Statement[stmt].type != lb_ && g_Statement[stmt].type != rb_) {
          /* $v $c $d $e $f $a $p */
          let(&buf, space(g_Statement[stmt].mathSectionLen));
          memcpy(buf, g_Statement[stmt].mathSectionPtr,
              (size_t)(g_Statement[stmt].mathSectionLen));
          fprintf(fp, "%s", buf);
          if (g_Statement[stmt].type != p_) {
            fprintf(fp, "$.");
/*D*//*fprintf(fp, "#%ld#",stmt);*/
          } else {
            /* $p */
            fprintf(fp, "$=");
            let(&buf, space(g_Statement[stmt].proofSectionLen));
            memcpy(buf, g_Statement[stmt].proofSectionPtr,
                (size_t)(g_Statement[stmt].proofSectionLen));
            fprintf(fp, "%s$.", buf);
          }
        } /* if not ${ $} */
        if (extractNeeded[stmt + 1] == 'N') {
/*D*//*printf("added \\n stmt=%ld type=%c,%c\n",stmt+1,g_Statement[stmt].type,g_Statement[stmt+1].type);*/
          /* Put a newline following end of statement since the next
             statement's label section will be suppressed */
          fprintf(fp, "\n");
        }
      } /* if (stmt != statements + 1) */

    } /* if (extractNeeded[stmt] == 'Y') */
  } /* next stmt */

  /* Add in unused $c, $v at the end to satisfy htmldefs */
  if (g_outputToString == 1) bug(273); /* Should be turned off here */
  if (g_printString[0] != 0) bug(274);
  g_outputToString = 1;
  if (undeclaredC[0] != 0) {
    print2("\n");
    print2(  /* 4-Sep-2020 nm Can't use literal "$t"; change to $ t. */
"  $( Unused constants to satisfy the htmldef's in the $ t comment. $)\n");
    printLongLine(cat("  $c", undeclaredC, " $.", NULL), "    ", " ");
  }
  if (undeclaredV[0] != 0) {
    print2("\n");
    print2(  /* 4-Sep-2020 nm Can't use literal "$t"; change to $ t. */
"  $( Unused variables to satisfy the htmldef's in the $ t comment. $)\n");
    printLongLine(cat("  $v", undeclaredV, " $.", NULL), "    ", " ");
  }
  g_outputToString = 0;
  if (g_printString[0] != 0) {
    fprintf(fp, "%s", g_printString);
    let(&g_printString, "");
  }

  /* Write the non-split output file */
  fclose(fp);
  /*print2("%ld source statement(s) were extracted.\n", extractedStmts);*/
  /* 5-Sep-2020 nm */
  j = 0; p1 = 0; p2 = 0; p3 = 0; p4 = 0;
  for (stmt = 1; stmt <= g_statements; stmt++) {
    if (extractNeeded[stmt] == 'Y') {
      j++;
      if (g_Statement[stmt].type == a_) {
        p1++;
        if (!strcmp("ax-", left(g_Statement[stmt].labelName, 3))) p3++;
        if (!strcmp("df-", left(g_Statement[stmt].labelName, 3))) p4++;
        let(&buf, ""); /* Deallocate stack created by left() */
      }
      if (g_Statement[stmt].type == p_) p2++;
    }
  }
  print2(
"Extracted %ld statements incl. %ld $a (%ld \"ax-\", %ld \"df-\"), %ld $p.\n",
      j, p1, p3, p4, p2);

 EXTRACT_RETURN:
  /* Deallocate */
  let(&extractNeeded, "");
  let(&statementUsedFlags, "");
  nmbrLet(&unprovedList, NULL_NMBRSTRING);
  nmbrLet(&mstring, NULL_NMBRSTRING);
  let(&dollarTCmt, "");
  let(&hugeHdrNeeded, "");
  let(&bigHdrNeeded, "");
  let(&smallHdrNeeded, "");
  let(&tinyHdrNeeded, "");
  let(&hugeHdr, "");   /* Deallocate memory */
  let(&bigHdr, "");   /* Deallocate memory */
  let(&smallHdr, ""); /* Deallocate memory */
  let(&tinyHdr, ""); /* Deallocate memory */
  let(&hugeHdrComment, "");   /* Deallocate memory */
  let(&bigHdrComment, "");   /* Deallocate memory */
  let(&smallHdrComment, ""); /* Deallocate memory */
  let(&tinyHdrComment, ""); /* Deallocate memory */
  let(&mathTokenDeclared, "");
  let(&undeclaredC, "");
  let(&undeclaredV, "");
  let(&buf, "");
  return;
} /* getExtractionInfo */


/* 24-Aug-2020 nm */
/* Some labels in comments may not exist in statements extracted
   with WRITE SOURCE ... / EXTRACT.  This function changes them
   to external links to us.metamath.org. */
void fixUndefinedLabels(vstring extractNeeded/*'Y'/'N' list*/,
    vstring *buf/*header comment*/) {
  long p1, p2, p3;
  vstring label = "";
  vstring newLabelWithTilde = "";
  vstring restOfComment = "";
  int mathMode; /* char gives Wconversion gcc warning */
#define ASCII_4 4

  /* 12-Sep-2020 nm */
  /* Change ~ in math symbols to nonprintable ASCII 4 to prevent
     interpretation as label indicator */
  p1 = (long)strlen(*buf);
  mathMode = 0;
  for (p2 = 0; p2 < p1; p2++) {
    if ((*buf)[p2] == '`') {
      mathMode = 1 - mathMode;
      continue;
    }
    if ((*buf)[p2] == '~' && mathMode == 1) {
      (*buf)[p2] = ASCII_4;
    }
  }

  p1 = 0;
  let(&(*buf), cat(*buf, " \n", NULL)); /* Ensure white space after last label */
  while (1) {
    p1 = instr(p1 + 1, *buf, "~");
    if (p1 == 0) break;
    if (p1 - 2 >= 0) { /* Prevent out-of-bounds access */
      if ((*buf)[p1 - 2] == '~') {
        continue; /* it is a ~~ escape; don't process */
      }
    }
    while (1) {
      /* Get beyond any whitespace between ~ and label */
      if ((*buf)[p1 + 1] == 0) break; /* end of string */
      if ((*buf)[p1 + 1] != ' ' && (*buf)[p1 + 1] != '\n') {
        /* Found a non-space character, assume it is start of label */
        break;
      }
      p1++; /* Move past the whitespace */
    }
    if ((*buf)[p1 + 1] == 0) break; /* Ignore stray ~ at end of comment */
    p2 = instr(p1 + 2, *buf, " ");
    p3 = instr(p1 + 2, *buf, "\n");
    if (p3 < p2) p2 = p3;  /* p2 is end of label */
    let(&label, seg(*buf, p1 + 2, p2 - 1));
    let(&restOfComment, right(*buf, p2));
    /*** 4-Sep-2020 nm This is taken care of by lookupLabel
    if (instr(1, label, "//" /@ http[s]:// @/) continue; /@ Ignore special case @/
    if (!strcmp(left(label, 2)), "mm") continue; /@ Ignore special case @/
    ***/
    p3 = lookupLabel(label);
    if (p3 == -1) continue; /* Not a statement label (e.g. ~ http://...) */
    if (extractNeeded[p3] == 'Y') continue; /* Label link won't be broken */
    /* Change label to external link */
    let(&newLabelWithTilde, cat(label,
        "\n ~ http://us.metamath.org/mpeuni/",
        label, ".html", NULL));
    let(&(*buf), cat(left(*buf, p1 - 1), newLabelWithTilde, NULL));
    /* Adjust pointer to go past the modified label */
    p1 = p1 + (long)strlen(newLabelWithTilde)
          - ((long)strlen(label) + 2/*for "~ "*/);
    /* 4-Sep-2020 nm */
    /* Put newline if not at end of line - assumes no trailing spaces in .mm! */
    /* We do this to prevent too-long lines.  But if we're already at end
       of line, we don't want to create a paragraph, so we don't add newline. */
    if (restOfComment[0] == '\n') {
      let(&(*buf), cat(*buf, restOfComment, NULL));
    } else {
      let(&(*buf), cat(*buf, "\n", restOfComment, NULL));
    }
  }
  let(&(*buf), left(*buf, (long)strlen(*buf) - 2));
       /* Take off the '\n' we added at beginning of this function */

  /* 12-Sep-2020 nm */
  /* Restore ASCII 4 to ~ */
  p1 = (long)strlen(*buf);
  for (p2 = 0; p2 < p1; p2++) {
    if ((*buf)[p2] == ASCII_4) (*buf)[p2] = '~';
  }

  let(&label, ""); /* Deallocate */
  let(&newLabelWithTilde, ""); /* Deallocate */
  let(&restOfComment, ""); /* Deallocate */
  return;
} /* fixUndefinedLabels */


void writeDict(void)
{
  print2("This function has not been implemented yet.\n");
  return;
} /* writeDict */

/* Free up all memory space and initialize all variables */
void eraseSource(void)    /* ERASE command */
{
  long i;
  vstring tmpStr = "";

  /* 24-Jun-2014 nm */
  /* Deallocate g_WrkProof structure if g_wrkProofMaxSize != 0 */
  /* Assigned in parseProof() in mmpars.c */
  if (g_wrkProofMaxSize) { /* It has been allocated */
    free(g_WrkProof.tokenSrcPtrNmbr);
    free(g_WrkProof.tokenSrcPtrPntr);
    free(g_WrkProof.stepSrcPtrNmbr);
    free(g_WrkProof.stepSrcPtrPntr);
    free(g_WrkProof.localLabelFlag);
    free(g_WrkProof.hypAndLocLabel);
    free(g_WrkProof.localLabelPool);
    poolFree(g_WrkProof.proofString);
    free(g_WrkProof.mathStringPtrs);
    free(g_WrkProof.RPNStack);
    free(g_WrkProof.compressedPfLabelMap);
    g_wrkProofMaxSize = 0;
  }

  if (g_statements == 0) {
    /* Already called */
    memFreePoolPurge(0);
    return;
  }

  for (i = 0; i <= g_includeCalls; i++) {
    let(&g_IncludeCall[i].source_fn, "");
    let(&g_IncludeCall[i].included_fn, "");
    let(&g_IncludeCall[i].current_includeSource, "");
  }
  g_includeCalls = -1;

  /* Deallocate the g_Statement[] array */
  for (i = 1; i <= g_statements + 1; i++) { /* g_statements + 1 is a dummy statement
                                          to hold source after last statement */

    /* 28-Aug-2013 am - g_Statement[stmt].reqVarList allocated in
       parseStatements() was not always freed by eraseSource().  If reqVars==0
       (in parseStatements()) then g_Statement[i].reqVarList[0]==-1 (in
       eraseSource()) and so eraseSource() thought that
       g_Statement[stmt].reqVarList was not allocated and should not be freed.

       There were similar problems for reqHypList, reqDisjVarsA, reqDisjVarsB,
       reqDisjVarsStmt, optDisjVarsA, optDisjVarsB, optDisjVarsStmt.

       These were fixed by comparing to NULL_NMBRSTRING instead of -1.

       I think other files (mathString, proofString, optHypList, optVarList)
       should be also fixed, but I could not find simple example for memory
       leak and leave it as it was.  */
    if (g_Statement[i].labelName[0]) free(g_Statement[i].labelName);
    /*if (g_Statement[i].mathString[0] != -1)*/
    if (g_Statement[i].mathString != NULL_NMBRSTRING) /* 14-May-2014 nm */
        poolFree(g_Statement[i].mathString);
    /*if (g_Statement[i].proofString[0] != -1)*/
    if (g_Statement[i].proofString != NULL_NMBRSTRING) /* 14-May-2014 nm */
        poolFree(g_Statement[i].proofString);
    if (g_Statement[i].reqHypList != NULL_NMBRSTRING)
        poolFree(g_Statement[i].reqHypList);
    /*if (g_Statement[i].optHypList[0] != -1)*/
    if (g_Statement[i].optHypList != NULL_NMBRSTRING) /* 14-May-2014 nm */
        poolFree(g_Statement[i].optHypList);
    if (g_Statement[i].reqVarList != NULL_NMBRSTRING)
        poolFree(g_Statement[i].reqVarList);
    /*if (g_Statement[i].optVarList[0] != -1)*/
    if (g_Statement[i].optVarList != NULL_NMBRSTRING) /* 14-May-2014 nm */
        poolFree(g_Statement[i].optVarList);
    if (g_Statement[i].reqDisjVarsA != NULL_NMBRSTRING)
        poolFree(g_Statement[i].reqDisjVarsA);
    if (g_Statement[i].reqDisjVarsB != NULL_NMBRSTRING)
        poolFree(g_Statement[i].reqDisjVarsB);
    if (g_Statement[i].reqDisjVarsStmt != NULL_NMBRSTRING)
        poolFree(g_Statement[i].reqDisjVarsStmt);
    if (g_Statement[i].optDisjVarsA != NULL_NMBRSTRING)
        poolFree(g_Statement[i].optDisjVarsA);
    if (g_Statement[i].optDisjVarsB != NULL_NMBRSTRING)
        poolFree(g_Statement[i].optDisjVarsB);
    if (g_Statement[i].optDisjVarsStmt != NULL_NMBRSTRING)
        poolFree(g_Statement[i].optDisjVarsStmt);

    /**** 3-May-17 deleted
    /@ See if the proof was "saved" @/
    if (g_Statement[i].proofSectionLen) {
      if (g_Statement[i].proofSectionPtr[-1] == 1) {
        /@ Deallocate proof if not original source @/
        /@ (ASCII 1 is the flag for this) @/
        tmpStr = g_Statement[i].proofSectionPtr - 1;
        let(&tmpStr, "");
      }
    }
    ********/

    /* 3-May-2017 nm */
    if (g_Statement[i].labelSectionChanged == 1) {
      /* Deallocate text before label if not original source */
      let(&(g_Statement[i].labelSectionPtr), "");
    }
    if (g_Statement[i].mathSectionChanged == 1) {
      /* Deallocate math symbol text if not original source */
      let(&(g_Statement[i].mathSectionPtr), "");
    }
    if (g_Statement[i].proofSectionChanged == 1) {
      /* Deallocate proof if not original source */
      let(&(g_Statement[i].proofSectionPtr), "");
    }

  } /* Next i (statement) */

  /* 28-Aug-2013 am - g_MathToken[g_mathTokens].tokenName is assigned in
     parseMathDecl() by let().  eraseSource() should free every g_MathToken and
     there are (g_mathTokens + g_dummyVars) tokens. */
  for (i = 0; i <= g_mathTokens + g_dummyVars; i++) {
    let(&(g_MathToken[i].tokenName), "");
  }

  memFreePoolPurge(0);
  /*g_statements = 0;*/ /* Must be done below */
  g_errorCount = 0;

  free(g_Statement);
  free(g_IncludeCall);  /* Will be init'ed in initBigArrays */
  free(g_MathToken);
  g_dummyVars = 0; /* For Proof Assistant */
  free(g_sourcePtr);
  free(g_labelKey);
  free(g_mathKey); /* 4-May-2017 Ari Ferrera */
  free(g_allLabelKeyBase);

  /* Deallocate the g_WrkProof structure */
  /*???*/

  /* Deallocate the texdef/htmldef storage */ /* Added 27-Oct-2012 nm */
  eraseTexDefs(); /* 17-Nov-2015 nm */

  g_extHtmlStmt = 0; /* May be used by a non-zero test; init to be safe */

  /* 5-Aug-2020 nm */
  /* Initialize and deallocate mathbox information */
  g_mathboxStmt = 0; /* Used by a non-zero test in mmwtex.c to see if assigned */
  nmbrLet(&g_mathboxStart, NULL_NMBRSTRING);
  nmbrLet(&g_mathboxEnd, NULL_NMBRSTRING);
  for (i = 1; i <= g_mathboxes; i++) {
    let((vstring *)(&g_mathboxUser[i - 1]), "");
  }
  pntrLet(&g_mathboxUser, NULL_PNTRSTRING);
  g_mathboxes = 0;

  /* Allocate big arrays */
  initBigArrays();

  /* 2-Oct-2017 nm Future possibilty: add 'reset' parameter to unify() to clear
     the 5 variables below */
  g_bracketMatchInit = 0; /* Clear to force mmunif.c to scan $a's again */
  g_minSubstLen = 1; /* Initialize to the default SET EMPTY_SUBSTITUTION OFF */
  /* 1-Oct-2017 nm Fix 'erase' bug found by Benoit Jubin */
  /* Clear g_firstConst to trigger clearing of g_lastConst and
     g_oneConst in mmunif.c */
  nmbrLet(&g_firstConst, NULL_NMBRSTRING);
  /* 2-Oct-2017 nm Clear these directly so they will be truly deallocated
     for valgrind */
  nmbrLet(&g_lastConst, NULL_NMBRSTRING);
  nmbrLet(&g_oneConst, NULL_NMBRSTRING);

  /* 3-May-2016 nm */
  getMarkupFlag(0, RESET); /* Erase the cached markup flag storage */

  /* 2-May-2017 nm */
  /* Erase the contributor markup cache */
  /* 3-May-2017 nm */
  let(&tmpStr, "");
  tmpStr = getContrib(0 /*stmt is ignored*/, GC_RESET);
  /****** old 3-May-2017
  /@ The string arguments are not assigned in RESET mode. @/
  getContrib(0, &tmpStr, &tmpStr,
      &tmpStr, &tmpStr, &tmpStr, &tmpStr,
      &tmpStr,
      0/@printErrorsFlag@/,
      RESET/@mode: 0 == RESET = reset, 1 = normal @/);
  *********/

  /* 2-May-2017 nm */
  /* getContrib uses g_statements (global var), so don't do this earlier */
  g_statements = 0; /* getContrib uses g_statements for loop limit */

} /* eraseSource */


/* If verify = 0, parse the proofs only for gross error checking.
   If verify = 1, do the full verification. */
void verifyProofs(vstring labelMatch, flag verifyFlag) {
  vstring emptyProofList = "";
  long i, k;
  long lineLen = 0;
  vstring header = "";
  flag errorFound;
#ifdef CLOCKS_PER_SEC
  clock_t clockStart;  /* 28-May-04 nm */
#endif

#ifdef __WATCOMC__
  vstring tmpStr="";
#endif

#ifdef VAXC
  vstring tmpStr="";
#endif

#ifdef CLOCKS_PER_SEC
  clockStart = clock();  /* 28-May-04 nm Retrieve start time */
#endif
  if (!strcmp("*", labelMatch) && verifyFlag) {
    /* Use status bar */
    let(&header, "0 10%  20%  30%  40%  50%  60%  70%  80%  90% 100%");
#ifdef XXX /*__WATCOMC__*/
    /* The vsprintf function discards text after "%" in 3rd argument string. */
    /* This is a workaround. */
    for (i = 1; i <= (long)strlen(header); i++) {
      let(&tmpStr, mid(header, i, 1));
      if (tmpStr[0] == '%') let(&tmpStr, "%%");
      print2("%s", tmpStr);
    }
    print2("\n");
#else
#ifdef XXX /*VAXC*/
    /* The vsprintf function discards text after "%" in 3rd argument string. */
    /* This is a workaround. */
    for (i = 1; i <= (long)strlen(header); i++) {
      let(&tmpStr, mid(header, i, 1));
      if (tmpStr[0] == '%') let(&tmpStr, "%%");
      print2("%s", tmpStr);
    }
    print2("\n");
#else
    print2("%s\n", header);
    let(&header, "");
#endif
#endif
  }

  errorFound = 0;
  for (i = 1; i <= g_statements; i++) {
    if (!strcmp("*", labelMatch) && verifyFlag) {
      while (lineLen < (50 * i) / g_statements) {
        print2(".");
        lineLen++;
      }
    }

    if (g_Statement[i].type != p_) continue;
    /* 30-Jan-06 nm Added single-character-match argument */
    if (!matchesList(g_Statement[i].labelName, labelMatch, '*', '?')) continue;
    if (strcmp("*",labelMatch) && verifyFlag) {
      /* If not *, print individual labels */
      lineLen = lineLen + (long)strlen(g_Statement[i].labelName) + 1;
      if (lineLen > 72) {
        lineLen = (long)strlen(g_Statement[i].labelName) + 1;
        print2("\n");
      }
      print2("%s ",g_Statement[i].labelName);
    }

    k = parseProof(i);
    if (k >= 2) errorFound = 1;
    if (k < 2) { /* $p with no error */
      if (verifyFlag) {
        if (verifyProof(i) >= 2) errorFound = 1;
        cleanWrkProof(); /* Deallocate verifyProof storage */
      }
    }
    if (k == 1) {
      let(&emptyProofList, cat(emptyProofList, ", ", g_Statement[i].labelName,
          NULL));
    }
  }
  if (verifyFlag) {
    print2("\n");
  }

  if (emptyProofList[0]) {
    printLongLine(cat(
        "Warning: The following $p statement(s) were not proved:  ",
        right(emptyProofList,3), NULL)," ","  ");
  }
  if (!emptyProofList[0] && !errorFound && !strcmp("*", labelMatch)) {
    if (verifyFlag) {
#ifdef CLOCKS_PER_SEC    /* 28-May-04 nm */
      print2("All proofs in the database were verified in %1.2f s.\n",
           (double)((1.0 * (double)(clock() - clockStart)) / CLOCKS_PER_SEC));
#else
      print2("All proofs in the database were verified.\n");
#endif

    } else {
      print2("All proofs in the database passed the syntax-only check.\n");
    }
  }
  let(&emptyProofList, ""); /* Deallocate */

} /* verifyProofs */


/* 7-Nov-2015 nm Added this function for date consistency */
/* 17-Nov-2015 nm Added htmldefs, bib, markup checking */
/* 13-Dec-2016 nm Added checks for undesireable labels (mm*,
   Microsoft conflicts) */
/* 24-Mar-2019 nm Added topDateSkip */
/* 25-Jun-2020 nm Added underscoreSkip */
/* 17-Jul-2020 nm Added mathboxSkip */
void verifyMarkup(vstring labelMatch,
    flag dateSkip, /* 1 = don't check date consistency */
    flag topDateSkip, /* 1 = don't check top date but check others */
    flag fileSkip, /* 1 = don't check external files (gifs, mmset.html,...) */
    flag underscoreSkip, /* 1 = don't check labels for "_" characters) */
    flag mathboxSkip, /* 1 = don't check mathbox cross-references) */
    flag verboseMode) /* 1 = more details */ {   /* 26-Dec-2016 nm */
  flag f;
  flag saveHtmlFlag, saveAltHtmlFlag;
  flag errFound = 0;
  long stmtNum, p1, p2, p3;
  long flen, lnum, lstart; /* For line length check */ /* 26-Dec-2016 nm */
  /***** deleted 3-May-2017 nm
  vstring contributor = ""; vstring contribDate = "";
  vstring reviser = ""; vstring reviseDate = "";
  vstring shortener = ""; vstring shortenDate = "";
  **********/

  /* 13-Dec-2016 nm */
  vstring mmVersionDate = ""; /* Version date at top of .mm file */
  vstring mostRecentDate = ""; /* For entire .mm file */
  long mostRecentStmt = 0; /* For error message */
  /* 18-Dec-2016 nm For getSectionHeadings() call */
  vstring hugeHdr = ""; /* 21-Jun-2014 nm */
  vstring bigHdr = "";
  vstring smallHdr = "";
  vstring tinyHdr = ""; /* 21-Aug-2017 nm */
  vstring hugeHdrComment = ""; /* 8-May-2015 nm */
  vstring bigHdrComment = ""; /* 8-May-2015 nm */
  vstring smallHdrComment = ""; /* 8-May-2015 nm */
  vstring tinyHdrComment = ""; /* 21-Aug-2017 nm */

  vstring descr = "";
  vstring str1 = ""; vstring str2 = "";
  /* 17-Jul-2020 nm */ /* For mathbox check */
  long mbox, pmbox, stmt, pstmt, plen, step;
  /**** 5-Aug-2020 nm These are now globals
  long mathboxes;
  nmbrString *mathboxStart = NULL_NMBRSTRING;
  nmbrString *mathboxEnd = NULL_NMBRSTRING;
  pntrString *mathboxUser = NULL_PNTRSTRING;
  ****/
  nmbrString *proof = NULL_NMBRSTRING;
  vstring dupCheck = "";

  saveHtmlFlag = g_htmlFlag;  saveAltHtmlFlag = g_altHtmlFlag;

  print2("Checking statement label conventions...\n");

  /* Check that labels can create acceptable file names for web pages */

  /* 13-Dec-2016 nm Moved this check from metamath.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 (stmtNum = 1; stmtNum <= g_statements; stmtNum++) {
    if (!matchesList(g_Statement[stmtNum].labelName, labelMatch, '*', '?')) {
      continue;
    }

    /* 25-Jun-2020 nm */
    /* Check labels for "_" characters */
    /* See discussion in https://github.com/metamath/set.mm/pull/1691 */
    if (underscoreSkip == 0
        && instr(1, g_Statement[stmtNum].labelName, "_") != 0) {
      assignStmtFileAndLineNum(stmtNum);
      printLongLine(cat("?Warning: In statement \"",
          g_Statement[stmtNum].labelName, "\" at line ",
          str((double)(g_Statement[stmtNum].lineNum)),
          " in file \"", g_Statement[stmtNum].fileName,
          "\".  Underscores in labels are not recommended per our conventions.  ",
          "Use the / UNDERSCORE_SKIP ",
          "qualifier to skip this check.",
          NULL),
          "    ", " ");
      /* g_errorCount++; */
      errFound = 1;
    }

    /* Only $a and $p can produce web pages, so check only them */
    if (g_Statement[stmtNum].type != a_ && g_Statement[stmtNum].type != p_) {
      continue;
    }
    let(&str2, cat(",", edit(g_Statement[stmtNum].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");*/ /* 25-Jun-2020 nm Deleted */
      assignStmtFileAndLineNum(stmtNum); /* 9-Jan-2018 nm */
      printLongLine(cat("?Warning: In statement \"",
          g_Statement[stmtNum].labelName, "\" at line ",
          str((double)(g_Statement[stmtNum].lineNum)),
          " in file \"", g_Statement[stmtNum].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++; */
      errFound = 1;
    }

    /* Check that $a assertions start with "ax-" or "df-" */
    if (g_Statement[stmtNum].type == (char)a_) {
      if (!strcmp("|-", g_MathToken[
          (g_Statement[stmtNum].mathString)[0]].tokenName)) {
        let(&str1, left(g_Statement[stmtNum].labelName, 3));
        if (strcmp("ax-", str1) && strcmp("df-", str1)) {
          assignStmtFileAndLineNum(stmtNum); /* 9-Jan-2018 nm */
          printLongLine(cat("?Warning: In the $a statement \"",
              g_Statement[stmtNum].labelName, "\" at line ",
              str((double)(g_Statement[stmtNum].lineNum)),
              " in file \"", g_Statement[stmtNum].fileName,
              "\", the label does not start with \"ax-\" or \"df-\"",
              " per our convention for axiomatic assertions (\"$a |- ...\").",
              NULL), "    ", " ");
          errFound = 1;
        }
      }
    }


  } /* next stmtNum */
  /* 10/21/02 end */  /* 13-Dec-2016 nm end */


  /* 5-Jul-2020 nm */
  /* Check for math tokens containing "@" */
  /* 18-Jul-2020 nm */
  /* Check for math tokens containing "?" */
  /* Note:  g_MathToken[] is 0-based, not 1-based */
  for (p1 = 0; p1 < g_mathTokens; p1++) {
    if (strchr(g_MathToken[p1].tokenName, '@') != NULL) {
      stmtNum = g_MathToken[p1].statement;
      assignStmtFileAndLineNum(stmtNum);
      printLongLine(cat("?Warning: The token \"",
          g_MathToken[p1].tokenName,
          "\" declared at line ",
          str((double)(g_Statement[stmtNum].lineNum)),
          " in file \"", g_Statement[stmtNum].fileName,
          "\" has an \"@\" character, which is discouraged because ",
          "\"@\" is traditionally used to replace \"$\" in commented-out ",
          "database source code.",
          NULL),
          "    ", " ");
      /* g_errorCount++; */
      errFound = 1;
    }
    if (strchr(g_MathToken[p1].tokenName, '?') != NULL) {
      stmtNum = g_MathToken[p1].statement;
      assignStmtFileAndLineNum(stmtNum);
      printLongLine(cat("?Warning: The token \"",
          g_MathToken[p1].tokenName,
          "\" declared at line ",
          str((double)(g_Statement[stmtNum].lineNum)),
          " in file \"", g_Statement[stmtNum].fileName,
          "\" has a \"?\" character, which is discouraged because ",
          "\"?\" is sometimes used as a math token search wildcard.",
          NULL),
          "    ", " ");
      /* g_errorCount++; */
      errFound = 1;
    }
  }


  /* 20-Dec-2016 nm */
  /* Check $a ax-* vs. $p ax* */
  /*print2("Checking ax-XXX axioms vs. axXXX theorems...\n");*/
      /* (Don't print status - this runs very fast) */
  for (stmtNum = 1; stmtNum <= g_statements; stmtNum++) {
    if (g_Statement[stmtNum].type != a_) {
      continue;
    }
    if (!matchesList(g_Statement[stmtNum].labelName, labelMatch, '*', '?')) {
      continue;
    }

    let(&str1, ""); /* Prevent string stack buildup/overflow in left() below */
    /* Look for "ax-*" axioms */
    if (strcmp("ax-", left(g_Statement[stmtNum].labelName, 3))) {
      continue;
    }

    let(&str1, g_Statement[stmtNum].labelName);
    /* Convert ax-XXX to axXXX */
    let(&str2, cat(left(str1, 2), right(str1, 4), NULL));

    p1 = lookupLabel(str2);
    if (p1 == -1) continue;  /* There is no corresponding axXXX */

    /* 26-Dec-2016 nm */
    if (verboseMode == 1) {
      print2("Comparing \"%s\" to \"%s\"...\n", str2, str1);
    }


    /* Compare statements */
    if (nmbrEq(g_Statement[stmtNum].mathString,
        g_Statement[p1].mathString) != 1) {
      printLongLine(cat("?Warning: The assertions for statements \"",
          g_Statement[stmtNum].labelName, "\" and \"",
          g_Statement[p1].labelName, "\" are different.",
          NULL), "  ", " ");
      errFound = 1;
      continue;
    }

    /* Compare number of mandatory hypotheses */
    if (g_Statement[stmtNum].numReqHyp != g_Statement[p1].numReqHyp) {
      printLongLine(cat("?Warning: Statement \"",
          g_Statement[stmtNum].labelName, "\" has ",
          str((double)(g_Statement[stmtNum].numReqHyp)),
          " mandatory hypotheses but \"",
          g_Statement[p1].labelName, "\" has ",
          str((double)(g_Statement[p1].numReqHyp)), ".",
          NULL), "  ", " ");
      errFound = 1;
      continue;
    }

    /* Compare mandatory distinct variables */
    if (nmbrEq(g_Statement[stmtNum].reqDisjVarsA,
        g_Statement[p1].reqDisjVarsA) != 1) {
      printLongLine(cat(
          "?Warning: The mandatory distinct variable pairs for statements \"",
          g_Statement[stmtNum].labelName, "\" and \"",
          g_Statement[p1].labelName,
          "\" are different or have a different order.",
          NULL), "  ", " ");
      errFound = 1;
      continue;
    } else if (nmbrEq(g_Statement[stmtNum].reqDisjVarsB,
        g_Statement[p1].reqDisjVarsB) != 1) {
      printLongLine(cat(
          "?Warning: The mandatory distinct variable pairs for statements \"",
          g_Statement[stmtNum].labelName, "\" and \"",
          g_Statement[p1].labelName,
          "\" are different or have a different order.",
          NULL), "  ", " ");
      errFound = 1;
      continue;
    }

    /* Compare mandatory hypotheses */
    for (p2 = 0; p2 < g_Statement[stmtNum].numReqHyp; p2++) {
      if (nmbrEq(g_Statement[(g_Statement[stmtNum].reqHypList)[p2]].mathString,
          g_Statement[(g_Statement[p1].reqHypList)[p2]].mathString) != 1) {
        printLongLine(cat("?Warning: The mandatory hypotheses of statements \"",
            g_Statement[stmtNum].labelName, "\" and \"",
            g_Statement[p1].labelName, "\" are different.",
            NULL), "  ", " ");
        errFound = 1;
        break;
      }
    } /* next p2 */

    for (p2 = 0; p2 < nmbrLen(g_Statement[stmtNum].reqDisjVarsA); p2++) {
      if (nmbrEq(g_Statement[stmtNum].reqDisjVarsA,
          g_Statement[p1].reqDisjVarsA) != 1) {
        printLongLine(cat("?Warning: The mandatory hypotheses of statements \"",
            g_Statement[stmtNum].labelName, "\" and \"",
            g_Statement[p1].labelName, "\" are different.",
            NULL), "  ", " ");
        errFound = 1;
        break;
      }
    } /* next p2 */
  } /* next stmtNum */


  /* 26-Dec-2016 nm */
  /* Check line lengths */
  /*print2("Checking line lengths...\n");*/
      /* (Don't print status - this runs very fast) */
  let(&str1, ""); /* Prepare to use as pointer */
  let(&str2, ""); /* Prepare to use as pointer */
  if (g_statements >= /*1*/0
          /* 6-Aug-2019 nm - no reason to skip empty .mm */
      /*&& g_includeCalls == 0*/) { /* No $[...$] */ /* TODO - handle $[...$] */
          /* 6-Aug-2019 nm - g_includeCalls is alway nonzero now - but check
             anyway; line numbers may be off if there are >1 files. */
    str1 = g_Statement[1].labelSectionPtr; /* Start of input file */
    str2 = g_Statement[g_statements + 1].labelSectionPtr
       + g_Statement[g_statements + 1].labelSectionLen; /* End of input file */
        /* g_statements + 1 is dummy statement to hold text after last statement */
    if (str2[0] != 0) bug(258); /* Should be end of (giant) string */
    if (str2[-1] != '\n') bug(259); /* End of last line */
    flen = str2 - str1;  /* Length of input file */
    str2 = ""; /* Restore pointer for use as vstring */
    lnum = 0; /* Line number */
    lstart = 0; /* Line start */
    for (p1 = 0; p1 < flen; p1++) {
      if (str1[p1] == 0) {
        /* print2("pl=%ld flen=%ld\n", p1, flen); */
        bug(260); /* File shouldn't have nulls */
      }
      if (str1[p1] == '\n') {
        /* End of a line found */
        lnum++;

        /* 25-Jun-2020 nm */
        if (p1 > 0) { /* Not 1st character in file */
          if (str1[p1 - 1] == ' ') {
            printLongLine(cat("?Warning: Line number ",
                str((double)lnum),
                " ends with a space character, which is discouraged.",
                NULL), "    ", " ");
            errFound = 1;
          }
        }

        if (p1 - lstart > g_screenWidth) { /* Normally 79; see mminou.c */
          /* Put line in str2 for error message */
          let(&str2, space(p1 - lstart));
          memcpy(str2, str1 + lstart,
            (size_t)(p1 - lstart));
          printLongLine(cat("?Warning: Line number ",
              str((double)lnum),
              " has ",
              str((double)(p1 - lstart)),
              " characters (should have ",
              str((double)g_screenWidth),
              " or less):",
              NULL), "    ", " ");
          print2("    %s...\n", left(str2, g_screenWidth - 7));
          errFound = 1;
          let(&str2, ""); /* Deallocate string memory */
        }
        lstart = p1 + 1;
      }

      /* 3-May-2017 nm */
      /* Check for tabs */
      if (str1[p1] == '\t') {
        printLongLine(cat("?Warning: Line number ",
            str((double)lnum + 1),
            " contains a tab, which is discouraged.",
            NULL), "    ", " ");
        errFound = 1;
      }

    } /* next p1 */
    str1 = ""; /* Restore pointer for use as vstring */
  } /* end of line length check - if (g_statements >= 1... */


  /* Check $t comment content */

  eraseTexDefs(); /* Force a reread regardless of previous mode */
  /* Check latexdef statements */
  print2("Checking latexdef, htmldef, althtmldef...\n");
  g_htmlFlag = 0; /* 1 = HTML, not TeX */
  g_altHtmlFlag = 0; /* 1 = Unicode, not GIFs (when g_htmlFlag = 1) */
  f = readTexDefs(1/*errorsOnly*/,
          fileSkip /* 1 = no GIF file existence check */  );
  if (f != 0) errFound = 1;
  if (f != 2) {   /* We continue if no severe errors (warnings are ok) */
    /*print2("Checking htmldefs...\n");*/
    /* Check htmldef statements (reread since we've switched modes) */
    g_htmlFlag = 1; /* 1 = HTML, not TeX */
    g_altHtmlFlag = 0; /* 1 = Unicode, not GIFs (when g_htmlFlag = 1) */
    f = readTexDefs(1/*errorsOnly*/,
            fileSkip /* 1 = no GIF file existence check */  );
  }
  if (f != 0) errFound = 1;
  if (f != 2) {  /* We continue if no severe errors (warnings are ok) */
    /*print2("Checking althtmldefs...\n");*/
    /* Check althtmldef statements (reread since we've switched modes) */
    g_htmlFlag = 1; /* 1 = HTML, not TeX */
    g_altHtmlFlag = 1; /* 1 = Unicode, not GIFs (when g_htmlFlag = 1) */
    f = readTexDefs(1/*errorsOnly*/,
            fileSkip /* 1 = no GIF file existence check */  );
  }
  if (f != 0) errFound = 1;


  /* Check date consistency and comment markup in all statements */
  print2("Checking statement comments...\n");
  let(&mostRecentDate, ""); /* 13-Dec-2016 nm */
  for (stmtNum = 1; stmtNum <= g_statements; stmtNum++) {
    if (g_Statement[stmtNum].type != a_ && g_Statement[stmtNum].type != p_) {
      continue;
    }
    if (!matchesList(g_Statement[stmtNum].labelName, labelMatch, '*', '?')) {
      continue;
    }

    /* 3-May-2017 nm */
    /* Check the contributor */
    let(&str1, "");
    str1 = getContrib(stmtNum, CONTRIBUTOR);
    if (!strcmp(str1, DEFAULT_CONTRIBUTOR)) {
      printLongLine(cat(
          "?Warning: Contributor \"", DEFAULT_CONTRIBUTOR,  /* 14-May-2017 nm */
          "\" should be updated in statement \"",
          g_Statement[stmtNum].labelName, "\".", NULL), "    ", " ");
      errFound = 1;
    }
    /* 15-May-2017 nm */
    let(&str1, "");
    str1 = getContrib(stmtNum, REVISER);
    if (!strcmp(str1, DEFAULT_CONTRIBUTOR)) {
      printLongLine(cat(
          "?Warning: Reviser \"", DEFAULT_CONTRIBUTOR,  /* 14-May-2017 nm */
          "\" should be updated in statement \"",
          g_Statement[stmtNum].labelName, "\".", NULL), "    ", " ");
      errFound = 1;
    }

    if (dateSkip == 0) {

      /* Check date consistency of the statement */
      /* Use the error-checking feature of getContrib() extractor */
      /* 3-May-2017 nm */
      let(&str1, "");
      str1 = getContrib(stmtNum, GC_ERROR_CHECK_PRINT); /* Returns P or F */
      if (str1[0] == 'F') errFound = 1;
      let(&str1, "");
      str1 = getContrib(stmtNum, MOST_RECENT_DATE);

      /***** deleted 3-May-2017
      f = getContrib(stmtNum, &contributor, &contribDate,
          &reviser, &reviseDate, &shortener, &shortenDate,
          &str1 /@ most recent of the 3 dates @/, /@ 13-Dec-2016 nm @/
          1/@printErrorsFlag@/,
          1/@mode: 0 == RESET = reset, 1 = normal @/ /@ 2-May-2017 nm @/);
      if (f == 1) errFound = 1;
      *********/

      /* Save most recent date in file - used to check Version date below */
      if (compareDates(mostRecentDate, str1) == -1) {
        let(&mostRecentDate, str1);
        mostRecentStmt = stmtNum;
      }

    }

    let(&descr, "");
    descr = getDescription(stmtNum);

    /* 17-Nov-2015 */
    /* Check comment markup of the statement */
    g_showStatement /* global */ = stmtNum; /* For printTexComment */
    g_texFilePtr /* global */  = NULL; /* Not used, but set to something */
    /* Use the errors-only (no output) feature of printTexComment() */
    f = printTexComment(descr,
        0, /* 1 = htmlCenterFlag (irrelevant for this call) */
        PROCESS_EVERYTHING + ERRORS_ONLY, /* actionBits */ /* 13-Dec-2018 nm */
        fileSkip /* 1 = noFileCheck */);
    if (f == 1) errFound = 1;

    /* 20-Jun-2020 nm */
    /* Check that $a has no "(Proof modification is discouraged.)" */
    if (g_Statement[stmtNum].type == a_) {
      if (getMarkupFlag(stmtNum, PROOF_DISCOURAGED) == 1) {
        printLongLine(cat(
            "?Warning: Statement \"", g_Statement[stmtNum].labelName,
            "\" is a $a but has a \"(Proof modification is discouraged.)\" tag.",
            NULL), "    ", " ");
        errFound = 1;
      }
    }
    /* Check that *OLD and *ALT have both discouragements */
    /* See discussion at
       https://groups.google.com/d/msg/metamath/NhPM9XNNh1E/otl0uskKBgAJ */
    p1 = (long)strlen(g_Statement[stmtNum].labelName);
    let(&str1, right(g_Statement[stmtNum].labelName, p1 - 2)); /* Last 3 chars. */
    if (!strcmp(str1, "OLD") || !strcmp(str1, "ALT")) {
      if (getMarkupFlag(stmtNum, PROOF_DISCOURAGED) != 1
          && g_Statement[stmtNum].type == p_ /* Ignore $a's */
          ) {
        printLongLine(cat(
            "?Warning: Statement \"", g_Statement[stmtNum].labelName,
            "\" has suffix \"", str1,
            "\" but has no \"(Proof modification is discouraged.)\" tag.",
            NULL), "    ", " ");
        errFound = 1;
      }
      if (getMarkupFlag(stmtNum, USAGE_DISCOURAGED) != 1) {
        printLongLine(cat(
            "?Warning: Statement \"", g_Statement[stmtNum].labelName,
            "\" has suffix \"", str1,
            "\" but has no \"(New usage is discouraged.)\" tag.",
            NULL), "    ", " ");
        errFound = 1;
      }
    }
  } /* next stmtNum */

  /* 13-Dec-2016 nm */
  /* Check that the version date of the .mm file is g.e. all statement dates */
  /* This code expects a version date at the top of the file such as:
         $( set.mm - Version of 13-Dec-2016 $)
     If we later want a different format, this code should be modified. */
  if (dateSkip == 0  && topDateSkip == 0) {
    /* Get the top of the .mm file */
    let(&str1, space(g_Statement[1].labelSectionLen));
    memcpy(str1, g_Statement[1].labelSectionPtr,
      (size_t)(g_Statement[1].labelSectionLen));
    /* Find the version date */
    p1 = instr(1, str1, "Version of ");
    if (p1 == 0) {
      printLongLine(cat(
          "?Warning: There is no \"Version of \" comment at the top of the",
          " file \"", g_input_fn, "\".", NULL), "    ", " ");
      errFound = 1;
    } else {
      p2 = instr(p1 + 11, str1, " ");
      let(&str2, seg(str1, p1 + 11, p2 - 1)); /* The date */
      f = parseDate(str2, &p1, &p2, &p3);
      if (f == 1) {
        printLongLine(cat(
            "?Warning: The Version date \"", str2, "\" at the top of file \"",
            g_input_fn, "\" is not a valid date.", NULL), "    ", " ");
        errFound = 1;
      } else {
        if (compareDates(mostRecentDate, str2) == 1) {
          printLongLine(cat(
              "?Warning: The \"Version of\" date ", str2,
              " at the top of file \"",
              g_input_fn,
              "\" is less recent than the date ", mostRecentDate,
              " in the description of statement \"",
              g_Statement[mostRecentStmt].labelName, "\".", NULL), "    ", " ");
          errFound = 1;
        }
      }
    }
  } /* if (dateSkip == 0) */


  /* 18-Dec-2016 nm */
  print2("Checking section header comments...\n");
  for (stmtNum = 1; stmtNum <= g_statements; stmtNum++) {
    if (g_Statement[stmtNum].type != a_ && g_Statement[stmtNum].type != p_) {
      continue;
    }
    if (!matchesList(g_Statement[stmtNum].labelName, labelMatch, '*', '?')) {
      continue;
    }

    let(&hugeHdr, "");
    let(&bigHdr, "");
    let(&smallHdr, "");
    let(&tinyHdr, ""); /* 21-Aug-2017 nm */
    let(&hugeHdrComment, "");
    let(&bigHdrComment, "");
    let(&smallHdrComment, "");
    let(&tinyHdrComment, ""); /* 21-Aug-2017 nm */
    f = getSectionHeadings(stmtNum, &hugeHdr, &bigHdr, &smallHdr,
        &tinyHdr, /* 21-Aug-2017 nm */
        /* 5-May-2015 nm */
        &hugeHdrComment, &bigHdrComment, &smallHdrComment,
        &tinyHdrComment,
        0, /* fineResolution */
        0 /* fullComment */);
    if (f != 0) errFound = 1;  /* 6-Aug-2019 nm */

    g_showStatement /* global */ = stmtNum; /* For printTexComment() */
    g_texFilePtr /* global */  = NULL; /* Not used, but set to something */

    f = 0;
    if (hugeHdrComment[0] != 0)
      f = (char)(f + printTexComment(hugeHdrComment,
          0, /* 1 = htmlCenterFlag (irrelevant for this call) */
          PROCESS_EVERYTHING + ERRORS_ONLY, /* actionBits */ /* 13-Dec-2018 nm */
          fileSkip /* 1 = noFileCheck */));
    if (bigHdrComment[0] != 0)
      f = (char)(f + printTexComment(bigHdrComment,
          0, /* 1 = htmlCenterFlag (irrelevant for this call) */
          PROCESS_EVERYTHING + ERRORS_ONLY, /* actionBits */ /* 13-Dec-2018 nm */
          fileSkip /* 1 = noFileCheck */));
    if (smallHdrComment[0] != 0)
      f = (char)(f + printTexComment(smallHdrComment,
          0, /* 1 = htmlCenterFlag (irrelevant for this call) */
          PROCESS_EVERYTHING + ERRORS_ONLY, /* actionBits */ /* 13-Dec-2018 nm */
          fileSkip /* 1 = noFileCheck */));

    /* Added 21-Aug-2017 nm */
    if (tinyHdrComment[0] != 0)
      f = (char)(f + printTexComment(tinyHdrComment,
          0, /* 1 = htmlCenterFlag (irrelevant for this call) */
          PROCESS_EVERYTHING + ERRORS_ONLY, /* actionBits */ /* 13-Dec-2018 nm */
          fileSkip /* 1 = noFileCheck */));
    /* (End of 21-Aug-2017 addition) */

    if (f != 0) printf(
"    (The warning above refers to a header above the referenced statement.)\n");
    if (f != 0) errFound = 1;
  } /* next stmtNum */


  /* Use the errors-only (no output) feature of writeBibliography() */
  print2("Checking bibliographic references...\n");
  f = writeBibliography("mmbiblio.html",
          labelMatch,
          1,  /* 1 = no output, just warning msgs if any */
          fileSkip); /* 1 = ignore missing external files (gifs, bib, etc.) */
  if (f != 0) errFound = 1;

  /* 17-Jul-2020 nm */
  /* Check mathboxes for cross-references */
  if (mathboxSkip == 0) {
    print2("Checking mathbox independence...\n");
    /* 5-Aug-2020 nm Deleted; these are now globals */
    /*mathboxes = getMathboxLoc(&mathboxStart, &mathboxEnd, &mathboxUser);*/
    assignMathboxInfo();  /* Populate global mathbox variables */
    /* Scan proofs in mathboxes to see if earlier mathbox is referenced */
    for (pmbox = 2; pmbox <= g_mathboxes; pmbox++) {
      /* Note g_mathboxStart, etc. are 0-based */
      for (pstmt = g_mathboxStart[pmbox - 1]; pstmt <= g_mathboxEnd[pmbox - 1];
          pstmt++) {
        if (g_Statement[pstmt].type != (char)p_)
          continue; /* Not a $p statement; skip it */
        /* Don't use bad proofs (incomplete proofs are ok) */
        if (parseProof(pstmt) > 1) {
          /* The proof has an error, so use the empty proof */
          nmbrLet(&proof, nmbrAddElement(NULL_NMBRSTRING, -(long)'?'));
        } else {
          nmbrLet(&proof, g_WrkProof.proofString);
        }
        plen = nmbrLen(proof);
        /* Get the essential step flags, if required */
        /*
        if (essentialFlag) {
          nmbrLet(&essentialFlags, nmbrGetEssential(proof));
        }
        */
        for (step = 0; step < plen; step++) {
          /*
          if (essentialFlag) {
            if (!essentialFlags[step]) continue;  /@ Ignore floating hypotheses @/
          }
          */
          stmt = proof[step];
          if (stmt < 0) continue; /* Local step or '?' step */
          if (stmt == 0) bug(266);
          if (stmt > g_mathboxStmt && stmt < g_mathboxStart[pmbox - 1]) {
            /* A statement in another mathbox is referenced */

            /* Eliminate duplicate error messages: */
            let(&str1, cat(str((double)pstmt), "-", str((double)stmt), NULL));
            if (lookup(str1, dupCheck) != 0) {
              continue;
            } else {
              let(&dupCheck, cat(dupCheck,
                  (dupCheck[0] == 0) ? "" : ",", str1, NULL)); /* Add to list */
            }

            mbox = getMathboxNum(stmt);
            if (mbox == 0) bug(267);
            if (verboseMode == 0) {
              printLongLine(cat("?Warning: The proof of \"",
                  g_Statement[pstmt].labelName,
                  "\" in the mathbox for ", (vstring *)(g_mathboxUser[pmbox - 1]),
                  " references \"", g_Statement[stmt].labelName,
                  "\" in the mathbox for ", (vstring *)(g_mathboxUser[mbox - 1]),
                  ".",
                  NULL),
                  "    ", " ");
            } else {
              /* Verbose output experiment */
              assignStmtFileAndLineNum(stmt);
              assignStmtFileAndLineNum(pstmt);
              printLongLine(cat("?Warning: The proof of statement \"",
                  g_Statement[pstmt].labelName,
                  "\" in the mathbox for \"", (vstring *)(g_mathboxUser[pmbox - 1]),
                  "\" at line ", str((double)(g_Statement[pstmt].lineNum)),
                  " in file \"", g_Statement[pstmt].fileName,
                  "\" references statement \"", g_Statement[stmt].labelName,
                  "\" in the mathbox for \"", (vstring *)(g_mathboxUser[mbox - 1]),
                  "\" at line ", str((double)(g_Statement[stmt].lineNum)),
                  " in file \"", g_Statement[stmt].fileName,
                  "\".  ",
                  "(Use the / MATHBOX_SKIP qualifier to skip this check.)",
                  NULL),
                  "    ", " ");
            }
            /* g_errorCount++; */
            errFound = 1;
          } /* if stmt in another mathbox */
        } /* next step */
      } /* next pstmt */
    } /* next pmbox */
    /* Deallocate */
    let(&dupCheck, "");
    /**** 5-Aug-2020 nm Deleted
    nmbrLet(&mathboxStart, NULL_NMBRSTRING);
    nmbrLet(&mathboxEnd, NULL_NMBRSTRING);
    for (mbox = 1; mbox <= mathboxes; mbox++) {
      let((vstring *)(&mathboxUser[mbox - 1]), "");
    }
    pntrLet(&mathboxUser, NULL_PNTRSTRING);
    ****/
    nmbrLet(&proof, NULL_NMBRSTRING);
  }

  if (errFound == 0) {
    print2("No errors were found.\n");
  }

  g_htmlFlag = saveHtmlFlag;  g_altHtmlFlag = saveAltHtmlFlag;
  /* Force reread to get current mode defaults for future user commands */
  eraseTexDefs();

  /* Deallocate string memory */
  /**** deleted 3-May-2017 because now we use only mostRecentDate
  let(&contributor, "");
  let(&contribDate, "");
  let(&reviser, "");
  let(&reviseDate, "");
  let(&shortener, "");
  let(&shortenDate, "");
  ******/
  let(&mostRecentDate, "");
  let(&mmVersionDate, "");
  let(&descr, "");
  let(&str1, "");
  let(&str2, "");

  /* Added 21-Aug-2017 nm */
  /* (It seems that I forgot to deallocate in earlier versions,
     although it usually wouldn't have caused leakage since the last
     statement in the .mm file normally won't have a header.  But
     we do it here to be safe.) */
  let(&hugeHdr, "");
  let(&bigHdr, "");
  let(&smallHdr, "");
  let(&tinyHdr, "");
  let(&hugeHdrComment, "");
  let(&bigHdrComment, "");
  let(&smallHdrComment, "");
  let(&tinyHdrComment, "");
  /* (End of 21-Aug-2017 addition) */

  return;

} /* verifyMarkup */



/* 10-Dec-2018 nm Added */
/* Function to process markup in an arbitrary non-Metamath HTML file, treating
   the file as a giant comment. */
void processMarkup(vstring inputFileName, vstring outputFileName,
    flag processCss, long actionBits) {
  FILE *outputFilePtr;
  vstring inputFileContent = "";
  long size;
  long p;

  /* Check that globals aren't in a weird state */
  if (g_outputToString == 1 || g_printString[0] != 0) {
    bug(265);
  }

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

  print2("Reading \"%s\"...\n", inputFileName);

  let(&inputFileContent, "");
  inputFileContent = readFileToString(inputFileName, 1/*verbose*/, &size);
  if (inputFileContent == NULL) {
    /* Couldn't open the file; error msg provided by readFileToString */
    inputFileContent = ""; /* Restore to normal vstring to prevent seg fault */
    goto PROCESS_MARKUP_RETURN;
  }

  print2("Creating \"%s\"...\n", outputFileName);

  /* Insert CSS from .mm file before "</HEAD>" if it isn't already there */
  if (processCss != 0 && instr(1, inputFileContent, g_htmlCSS) == 0) {
    p = instr(1, edit(inputFileContent, 32/*uppercase*/), "</HEAD>");
    if (p != 0) {
      let(&inputFileContent, cat(left(inputFileContent, p - 1),
          g_htmlCSS, "\n", right(inputFileContent, p), NULL));
    }
  }

  /* fSafeOpen() renames existing files with ~1,~2,etc.  This way
     existing user files will not be accidentally destroyed. */
  outputFilePtr = fSafeOpen(outputFileName, "w", 0/*noVersioningFlag*/);
  if (outputFilePtr == NULL) {
    /* (Error msg already provided by fSafeOpen) */
    /* print2("?Couldn't open \"%s\"\n", outputFileName); */
    goto PROCESS_MARKUP_RETURN;
  }

  g_outputToString = 0;
  let(&g_printString, "");
  g_showStatement = 0; /* For printTexComment */
  g_texFilePtr = outputFilePtr; /* For printTexComment */
  printTexComment(  /* Sends result to g_texFilePtr */
      inputFileContent,
      0, /* 1 = htmlCenterFlag */
      actionBits, /* bit-mapped list of actions */
      0 /* 1 = noFileCheck */);
  fclose(g_texFilePtr);
  g_texFilePtr = NULL;

 PROCESS_MARKUP_RETURN:
  /* Deallocate */
  let(&inputFileContent, "");
  let(&g_printString, "");
  return;
}


/* 3-May-2016 nm */
/* List "discouraged" statements with "(Proof modification is discouraged."
   and "(New usage is discourged.)" comment markup tags. */
/* This function is primarily intended for use with the "travis" system
   to identify versioning differences on GitHub. */
void showDiscouraged(void) {   /* was: showRestricted */
  long stmt, s, usageCount;
  long lowStmt = 0, highStmt = 0; /* For a slight speedup */
  flag notQuitPrint = 1; /* Goes to 0 if user typed 'q' at scroll prompt */
  vstring str1 = "";
  for (stmt = 1; stmt <= g_statements; stmt++) {

    /* Since this command is slow, quit immediately if user typed 'q'
       at scrolling prompt */
    if (notQuitPrint == 0) break;

    if (g_Statement[stmt].type != p_ && g_Statement[stmt].type != a_) continue;
    if (getMarkupFlag(stmt, PROOF_DISCOURAGED) == 1
        && g_Statement[stmt].type == p_ /* Ignore $a's */
        ) {
      /* Restricted proof */
      /* Get number of steps */
      parseProof(stmt);
      notQuitPrint = print2(
"SHOW DISCOURAGED:  Proof modification of \"%s\" is discouraged (%ld steps).\n",
          g_Statement[stmt].labelName,
          nmbrLen(g_WrkProof.proofString));
    } /* if discouraged proof */
    if (getMarkupFlag(stmt, USAGE_DISCOURAGED) == 1) {
      /* Discouraged usage */
      usageCount = 0;
      let(&str1, "");
      str1 = traceUsage(stmt,
          0, /* recursiveFlag */
          0 /* cutoffStmt */);
      if (str1[0] == 'Y') { /* Used by at least one */
        /* str1[i] will be 'Y' if used by stmt */
        lowStmt = g_statements;
        highStmt = 0;
        /* Scan all future statements in str1 Y/N list */
        for (s = stmt + 1; s <= g_statements; s++) {
          /* Scan the used-by map */
          if (str1[s] != 'Y') continue;
          usageCount++;
          if (lowStmt > s) lowStmt = s;
          if (highStmt < s) highStmt = s;
        } /* Next s */
      } /* if (str1[0] == 'Y') */
      notQuitPrint = print2(
"SHOW DISCOURAGED:  New usage of \"%s\" is discouraged (%ld uses).\n",
          g_Statement[stmt].labelName,
          usageCount);
      if (str1[0] == 'Y') { /* Used by at least one */
        /* str1[i] will be 'Y' if used by stmt */
        /* Scan all future statements in str1 Y/N list */
        for (s = lowStmt; s <= highStmt; s++) {
          /* Scan the used-by map */
          if (str1[s] != 'Y') continue;
          notQuitPrint = print2(
              "SHOW DISCOURAGED:  \"%s\" is used by \"%s\".\n",
              g_Statement[stmt].labelName,
              g_Statement[s].labelName);
        } /* Next s */
      } /* if (str1[0] == 'Y') */
    } /* if discouraged usage */
  } /* next stmt */
  let(&str1, ""); /* Deallocate */
} /* showDiscouraged */

/* Added 14-Sep-2012 nm */
/* Take a relative step FIRST, LAST, +nn, -nn (relative to the unknown
   essential steps) or ALL, and return the actual step for use by ASSIGN,
   IMPROVE, REPLACE, LET (or 0 in case of ALL, used by IMPROVE).  In case
   stepStr is an unsigned integer nn, it is assumed to already be an actual
   step and is returned as is.  If format is illegal, -1 is returned.  */
long getStepNum(vstring relStep, /* User's argument */
   nmbrString *pfInProgress, /* g_ProofInProgress.proof */
   flag allFlag /* 1 = "ALL" is permissible */)
{
  long pfLen, i, j, relStepVal, actualStepVal;
  flag negFlag = 0;
  nmbrString *essentialFlags = NULL_NMBRSTRING;
  vstring relStepCaps = "";

  let(&relStepCaps, edit(relStep, 32/*upper case*/));
  pfLen = nmbrLen(pfInProgress); /* Proof length */
  relStepVal = (long)(val(relStepCaps)); /* val() tolerates ill-formed numbers */

  if (relStepVal >= 0 && !strcmp(relStepCaps, str((double)relStepVal))) {
    /* User's argument is an unsigned positive integer */
    actualStepVal = relStepVal;
    if (actualStepVal > pfLen || actualStepVal < 1) {
      print2("?The step must be in the range from 1 to %ld.\n", pfLen);
      actualStepVal = -1;  /* Flag the error */
    }
    goto RETURN_POINT;  /* Already actual step; just return it */
  } else if (!strcmp(relStepCaps, left("FIRST", (long)(strlen(relStepCaps))))) {
    negFlag = 0; /* Scan forwards */
    relStepVal = 0;
  } else if (!strcmp(relStepCaps, left("LAST", (long)(strlen(relStepCaps))))) {
    negFlag = 1; /* Scan backwards */
    relStepVal = 0;
  } else if (relStepCaps[0] == '+') {
    negFlag = 0;
    if (strcmp(right(relStepCaps, 2), str((double)relStepVal))) {
      print2("?The characters after '+' are not a number.\n");
      actualStepVal = -1; /* Error - not a number after the '+' */
      goto RETURN_POINT;
    }
  } else if (relStepCaps[0] == '-') {
    negFlag = 1;
    if (strcmp(right(relStepCaps, 2), str((double)(- relStepVal)))) {
      print2("?The characters after '-' are not a number.\n");
      actualStepVal = -1; /* Error - not a number after the '-' */
      goto RETURN_POINT;
    }
    relStepVal = - relStepVal;
  } else if (!strcmp(relStepCaps, left("ALL", (long)(strlen(relStepCaps))))) {
    if (!allFlag) {
      /* ALL is illegal */
      print2("?You must specify FIRST, LAST, nn, +nn, or -nn.\n");
      actualStepVal = -1; /* Flag that there was an error */
      goto RETURN_POINT;
    }
    actualStepVal = 0; /* 0 is special, meaning "ALL" */
    goto RETURN_POINT;
  } else {
    if (allFlag) {
      print2("?You must specify FIRST, LAST, nn, +nn, -nn, or ALL.\n");
    } else {
      print2("?You must specify FIRST, LAST, nn, +nn, or -nn.\n");
    }
    actualStepVal = -1; /* Flag that there was an error */
    goto RETURN_POINT;
  }

  nmbrLet(&essentialFlags, nmbrGetEssential(pfInProgress));

  /* Get the essential step flags */
  actualStepVal = 0; /* Use zero as flag that step wasn't found */
  if (negFlag) {
    /* Scan proof backwards */
    /* Count back 'relStepVal' unknown steps */
    j = relStepVal + 1;
    for (i = pfLen; i >= 1; i--) {
      if (essentialFlags[i - 1]
          && pfInProgress[i - 1] == -(long)'?') {
        j--;
        if (j == 0) {
          /* Found it */
          actualStepVal = i;
          break;
        }             /* 16-Apr-06 */
      }
    } /* Next i */
  } else {
    /* Scan proof forwards */
    /* Count forward 'relStepVal' unknown steps */
    j = relStepVal + 1;
    for (i = 1; i <= pfLen; i++) {
      if (essentialFlags[i - 1]
          && pfInProgress[i - 1] == -(long)'?') {
        j--;
        if (j == 0) {
          /* Found it */
          actualStepVal = i;
          break;
        }             /* 16-Apr-06 */
      }
    } /* Next i */
  }
  if (actualStepVal == 0) {
    if (relStepVal == 0) {
      print2("?There are no unknown essential steps.\n");
    } else {
      print2("?There are not at least %ld unknown essential steps.\n",
        relStepVal + 1);
    }
    actualStepVal = -1; /* Flag that there was an error */
    goto RETURN_POINT;
  }

 RETURN_POINT:
  /* Deallocate memory */
  let(&relStepCaps, "");
  nmbrLet(&essentialFlags, NULL_NMBRSTRING);

  return actualStepVal;
} /* getStepNum */


/* Added 22-Apr-2015 nm */
/* Convert the actual step numbers of an unassigned step to the relative
   -1, -2, etc. offset for SHOW NEW_PROOF ...  /UNKNOWN, to make it easier
   for the user to ASSIGN the relative step number. A 0 is returned
   for the last unknown step.  The step numbers of known steps are
   unchanged.  */
/* The caller must deallocate the returned nmbrString. */
nmbrString *getRelStepNums(nmbrString *pfInProgress) {
  nmbrString *essentialFlags = NULL_NMBRSTRING;
  nmbrString *relSteps = NULL_NMBRSTRING;
  long i, j, pfLen;

  pfLen = nmbrLen(pfInProgress); /* Get proof length */
  nmbrLet(&relSteps, nmbrSpace(pfLen));  /* Initialize */
  nmbrLet(&essentialFlags, nmbrGetEssential(pfInProgress));
  j = 0;  /* Negative offset (or 0 for last unknown step) */
  for (i = pfLen; i >= 1; i--) {
    if (essentialFlags[i - 1]
        && pfInProgress[i - 1] == -(long)'?') {
      relSteps[i - 1] = j;
      j--; /* It's an essential unknown step; increment negative offset */
    } else {
      relSteps[i - 1] = i; /* Just keep the normal step number */
    }
  }

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

  return relSteps;
} /* getRelStepNums */


/* 19-Sep-2012 nm */
/* This procedure finds the next statement number whose label matches
   stmtName.  Wildcards are allowed.  If uniqueFlag is 1,
   there must be exactly one match, otherwise an error message is printed
   and -1 is returned.  If uniqueFlag is 0, the next match is
   returned, or -1 if there are no more matches.  No error messages are
   printed when uniqueFlag is 0, except for the special case of
   startStmt=1.  For use by PROVE, REPLACE, ASSIGN. */
long getStatementNum(vstring stmtName, /* Possibly with wildcards */
    long startStmt, /* Starting statement number (1 for full scan) */
    long maxStmt, /* Matches must be LESS THAN this statement number */
    flag aAllowed, /* 1 means $a is allowed */
    flag pAllowed, /* 1 means $p is allowed */
    flag eAllowed, /* 1 means $e is allowed */
    flag fAllowed, /* 1 means $f is allowed */
    flag efOnlyForMaxStmt, /* If 1, $e and $f must belong to maxStmt */
    flag uniqueFlag) /* If 1, match must be unique */
{
  flag hasWildcard;
  long matchesFound, matchStmt, matchStmt2, stmt;
  char typ;
  flag laterMatchFound = 0; /* For better error message */ /* 16-Jan-2014 nm */

  hasWildcard = 0;
  /* (Note strpbrk warning in mmpars.c) */
  if (strpbrk(stmtName, "*?=~%#@,") != NULL) {
    /* (See matches() function for processing of these)
       "*" 0 or more char match
       "?" 1 char match
       "=" Most recent PROVE command statement
       "~" Statement range
       "%" List of modified statements
       "#" Internal statement number
       "@" Web page statement number
       "," Comma-separated fields */
    hasWildcard = 1; /* I.e. stmtName is not a simple label */
  }
  matchesFound = 0;
  matchStmt = 1; /* Set to a legal value in case of bug */
  matchStmt2 = 1; /* Set to a legal value in case of bug */

  /**** 18-Jul-2020 nm No longer needed; done in matches() (called by
     matchesList() below) @/
  if (!strcmp(stmtName, "=") && proveStatement != 0) {
     let(&stmtName, g_Statement[proveStatement].labelName);
  }
  *****/

  for (stmt = startStmt; stmt <= g_statements; stmt++) {

    /* 16-Jan-2014 nm */
    if (stmt >= maxStmt) {
      if (matchesFound > 0) break; /* Normal exit when a match was found */
      if (!uniqueFlag) break; /* We only want to scan up to maxStmt anyway */
      /* Otherwise, we continue to see if there is a later match, for
         error message purposes */
    }

    if (!g_Statement[stmt].labelName[0]) continue; /* No label */
    typ = g_Statement[stmt].type;

    if ((!aAllowed && typ == (char)a_)
        ||(!pAllowed && typ == (char)p_)
        ||(!eAllowed && typ == (char)e_)
        ||(!fAllowed && typ == (char)f_)) {
      continue; /* Statement type is not allowed */
    }

    if (hasWildcard) {
      if (!matchesList(g_Statement[stmt].labelName, stmtName, '*', '?')) {
        continue;
      }
    } else {
      /* When hasWildcard = 0, this code is very inefficient - all we need to
         do is call lookupLabel(stmtName) outside of the stmt loop and take
         actions based on whether the label exists and its statement number
         and type compared to what's expected.  However, from a user
         perspective, the current code has no noticeable delay, so there's no
         pressing need to improve it at this point. */
      if (strcmp(stmtName, g_Statement[stmt].labelName)) {
        continue;
      }
    }

    if (efOnlyForMaxStmt) {
      if (maxStmt > g_statements) bug(247); /* Don't set efOnlyForMaxStmt
                                             in case of PROVE call */
      /* If a $e or $f, it must be a hypothesis of the statement
         being proved */
      if (typ == (char)e_ || typ == (char)f_){
        if (!nmbrElementIn(1, g_Statement[maxStmt].reqHypList, stmt) &&
            !nmbrElementIn(1, g_Statement[maxStmt].optHypList, stmt))
            continue;
      }
    }

    /* 16-Jan-2014 nm */
    if (stmt >= maxStmt) {
      /* For error messages:
         This signals that a later match (after the statement being
         proved, in case of ASSIGN) exists, so the user is trying to
         reference a future statement. */
      laterMatchFound = 1;
      break;
    }

    if (matchesFound == 0) {
      /* This is the first match found; save it */
      matchStmt = stmt;
      /* If uniqueFlag is not set, we're done (don't need to check for
         uniqueness) */
    }
    if (matchesFound == 1) {
      /* This is the 2nd match found; save it for error message */
      matchStmt2 = stmt;
    }
    matchesFound++;
    if (!uniqueFlag) break; /* We are just getting the next match, so done */
    if (!hasWildcard) break; /* Since there can only be 1 match, don't
                                bother to continue */
  }

  if (matchesFound == 0) {
    if (!uniqueFlag) {
      if (startStmt == 1) {
        /* For non-unique scan, print only if we started from beginning */
        print2("?No statement label matches \"%s\".\n", stmtName);
      }
    } else if (aAllowed && pAllowed && eAllowed && fAllowed
               && !efOnlyForMaxStmt) {
      print2("?No statement label matches \"%s\".\n", stmtName);
    } else if (!aAllowed && pAllowed && !eAllowed && !fAllowed) {
      /* This is normally the PROVE command */
      print2("?No $p statement label matches \"%s\".\n", stmtName);
    } else if (!eAllowed && !fAllowed) {
      /* This is normally for REPLACE */
      if (!laterMatchFound) {
        print2("?No $a or $p statement label matches \"%s\".\n",
          stmtName);
      } else {
        /* 16-Jan-2014 nm */
        print2(
   "?You must specify a statement that occurs earlier the one being proved.\n");
      }
    } else {
      /* This is normally for ASSIGN */
      if (!laterMatchFound) {
        printLongLine(cat("?A statement label matching \"",
            stmtName,
            "\" was not found or is not a hypothesis of the statement ",
            "being proved.", NULL), "", " ");
      } else {
        /* 16-Jan-2014 nm */
        print2(
   "?You must specify a statement that occurs earlier the one being proved.\n");
      }
    }
  } else if (matchesFound == 2) {
    printLongLine(cat("?This command requires a unique label, but there are ",
        " 2 matches for \"",
        stmtName, "\":  \"", g_Statement[matchStmt].labelName,
        "\" and \"", g_Statement[matchStmt2].labelName, "\".",
        NULL), "", " ");
  } else if (matchesFound > 2) {
    printLongLine(cat("?This command requires a unique label, but there are ",
        str((double)matchesFound), " (allowed) matches for \"",
        stmtName, "\".  The first 2 are \"", g_Statement[matchStmt].labelName,
        "\" and \"", g_Statement[matchStmt2].labelName, "\".",
        "  Use SHOW LABELS \"", stmtName, "\" to see all non-$e matches.",
        NULL), "", " ");
  }
  if (!uniqueFlag && matchesFound > 1) bug(248);
  if (matchesFound != 1) matchStmt = -1; /* Error - no (unique) match */
  return matchStmt;
} /* getStatementNum */




/* Called by help() - prints a help line */
/* THINK C gives compilation error if H() is lower-case h() -- why? */
void H(vstring helpLine)
{
  if (g_printHelp) {
    print2("%s\n", helpLine);
  }
} /* H */



/******** 8/28/00 ***********************************************************/
/******** The MIDI output algorithm is in this function, outputMidi(). ******/
/*** Warning:  If you want to experiment with the MIDI output, please
     confine changes to this function.  Changes to other code
     in this file is not recommended. ***/

void outputMidi(long plen, nmbrString *indentationLevels,
  nmbrString *logicalFlags, vstring g_midiParameter, vstring statementLabel) {

  /* The parameters have the following meanings.  You should treat them as
     read-only input parameters and should not modify the contents of the
     arrays or strings they point to.

       plen = length of proof
       indentationLevels[step] = indentation level in "show proof xxx /full"
           where step varies from 0 to plen-1
       logicalFlags[step] = 0 for formula-building step, 1 for logical step
       g_midiParameter = string passed by user in "midi xxx /parameter <g_midiParameter>"
       statementLabel = label of statement whose proof is being scanned */

  /* This function is called when the user types "midi xxx /parameter
     <g_midiParameter>".  The proof steps of theorem xxx are numbered successively
     from 0 to plen-1.  The arrays indentationLevels[] and logicalFlags[]
     have already been populated for you. */

  /* The purpose of this function is to create an ASCII file called xxx.txt
     that contains the ASCII format for a t2mf input file.  The mf2t package
     is available at http://www.hitsquad.com/smm/programs/mf2t/download.shtml.
     To convert xxx.txt to xxx.mid you type "t2mf xxx.txt xxx.mid". */

  /* To experiment with this function, you can add your own local variables and
     modify the algorithm as you see fit.  The algorithm is essentially
     contained in the loop "for (step = 0; step < plen; step++)" and in the
     initialization of the local variables that this loop uses.  No global
     variables are used inside this function; the only data used is
     that contained in the input parameters. */

/* Larger TEMPO means faster speed */
#define TEMPO 48
/* The minimum and maximum notes for the dynamic range we allow: */
/* (MIDI notes range from 1 to 127, but 28 to 103 seems reasonably pleasant) */
/* MIDI note number 60 is middle C. */
#define MIN_NOTE 28
#define MAX_NOTE 103

  /* Note: "flag" is just "char"; "vstring" is just "char *" - except
     that by this program's convention vstring allocation is controlled
     by string functions in mmvstr.c; and "nmbrString" is just "long *" */

  long step; /* Proof step from 0 to plen-1 */
  long midiKey; /* Current keyboard key to be output */
  long midiNote; /* Current midi note to be output, mapped from midiKey */
  long midiTime; /* Midi time stamp */
  long midiPreviousFormulaStep; /* Note saved from previous step */
  long midiPreviousLogicalStep; /* Note saved from previous step */
  vstring midiFileName = ""; /* All vstrings MUST be initialized to ""! */
  FILE *midiFilePtr; /* Output file pointer */
  long midiBaseline; /* Baseline note */
  long midiMaxIndent; /* Maximum indentation (to find dyn range of notes) */
  long midiMinKey; /* Smallest keyboard key in output */
  long midiMaxKey; /* Largest keyboard key in output */
  long midiKeyInc; /* Keyboard key increment per proof indentation level */
  flag midiSyncopate; /* 1 = syncopate the output */
  flag midiHesitate; /* 1 = silence all repeated notes */
  long midiTempo; /* larger = faster */
  vstring midiLocalParam = ""; /* To manipulate user's parameter string */
  vstring tmpStr = ""; /* Temporary string */
#define ALLKEYSFLAG 1
#define WHITEKEYSFLAG 2
#define BLACKKEYSFLAG 3
  flag keyboardType; /* ALLKEYSFLAG, WHITEKEYSFLAG, or BLACKKEYSFLAG */
  long absMinKey; /* The smallest note we ever want */
  long absMaxKey; /* The largest note we ever want */
  long key2MidiMap[128]; /* Maps keyboard (possiblty with black or
                    white notes missing) to midi notes */
  long keyboardOctave; /* The number of keys spanning an octave */
  long i;

  /******** Define the keyboard to midi maps (added 5/17/04 nm) *************/
  /* The idea here is to map the proof step to pressing "keyboard" keys
     on keyboards that have all keys, or only the white keys, or only the
     black keys.   The default is all keys, the parameter b means black,
     and the parameter w means white. */
#define ALLKEYS 128
#define WHITEKEYS 75
#define BLACKKEYS 53
  long allKeys[ALLKEYS] =
      {  0,   1,   2,   3,   4,   5,   6,   7,   8,   9,  10,  11,
        12,  13,  14,  15,  16,  17,  18,  19,  20,  21,  22,  23,
        24,  25,  26,  27,  28,  29,  30,  31,  32,  33,  34,  35,
        36,  37,  38,  39,  40,  41,  42,  43,  44,  45,  46,  47,
        48,  49,  50,  51,  52,  53,  54,  55,  56,  57,  58,  59,
        60,  61,  62,  63,  64,  65,  66,  67,  68,  69,  70,  71,
        72,  73,  74,  75,  76,  77,  78,  79,  80,  81,  82,  83,
        84,  85,  86,  87,  88,  89,  90,  91,  92,  93,  94,  95,
        96,  97,  98,  99, 100, 101, 102, 103, 104, 105, 106, 107,
       108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119,
       120, 121, 122, 123, 124, 125, 126, 127};
  long whiteKeys[WHITEKEYS] =
      {  0,   2,   4,   5,   7,   9,  11,
        12,  14,  16,  17,  19,  21,  23,
        24,  26,  28,  29,  31,  33,  35,
        36,  38,  40,  41,  43,  45,  47,
        48,  50,  52,  53,  55,  57,  59,
        60,  62,  64,  65,  67,  69,  71,
        72,  74,  76,  77,  79,  81,  83,
        84,  86,  88,  89,  91,  93,  95,
        96,  98, 100, 101, 103, 105, 107,
       108, 110, 112, 113, 115, 117, 119,
       120, 122, 124, 125, 127};
  long blackKeys[BLACKKEYS] =
      {  1,   3,   6,   8,  10,
        13,  15,  18,  20,  22,
        25,  27,  30,  32,  34,
        37,  39,  42,  44,  46,
        49,  51,  54,  56,  58,
        61,  63,  66,  68,  70,
        73,  75,  78,  80,  82,
        85,  87,  90,  92,  94,
        97,  99, 102, 104, 106,
       109, 111, 114, 116, 118,
       121, 123, 126};

  /************* Initialization ***************/

  midiTime = 0; /* MIDI time stamp */
  midiPreviousFormulaStep = 0; /* Note in previous formula-building step */
  midiPreviousLogicalStep = 0; /* Note in previous logical step */
  midiFilePtr = NULL; /* Output file pointer */

  /* Parse the parameter string passed by the user */
  let(&midiLocalParam, edit(g_midiParameter, 32)); /* Convert to uppercase */

  /* Set syncopation */
  if (strchr(midiLocalParam, 'S') != NULL) {
    midiSyncopate = 1; /* Syncopation */
  } else {
    midiSyncopate = 0; /* No syncopation */
  }
  /* Set halting character of syncopation (only has effect if
     syncopation is on) */
  if (strchr(midiLocalParam, 'H') != NULL) {
    midiHesitate = 1; /* Silence all repeated fast notes */
  } else {
    midiHesitate = 0; /* Silence only every other one in a repeated sequence */
  }
  /* Set the tempo: 96=fast, 72=medium, 48=slow */
  if (strchr(midiLocalParam, 'F') != NULL) {
    midiTempo = 2 * TEMPO;  /* Fast */
  } else {
    if (strchr(midiLocalParam, 'M') != NULL) {
      midiTempo = 3 * TEMPO / 2;  /* Medium */
    } else {
      midiTempo = TEMPO; /* Slow */
    }
  }
  /* Get the keyboard type */
  if (strchr(midiLocalParam, 'W') != NULL) {
    keyboardType = WHITEKEYSFLAG;
  } else {
    if (strchr(midiLocalParam, 'B') != NULL) {
      keyboardType = BLACKKEYSFLAG;
    } else {
      keyboardType = ALLKEYSFLAG;
    }
  }
  /* Set the tempo: 96=fast, 48=slow */
  if (strchr(midiLocalParam, 'I') != NULL) {
    /* Do not skip any notes */
    midiKeyInc = 1 + 1;
  } else {
    /* Allow an increment of up to 4 */
    midiKeyInc = 4 + 1;
  }

  /* End of parsing user's parameter string */


  /* Map keyboard key numbers to MIDI notes */
  absMinKey = MIN_NOTE; /* Initialize for ALLKEYSFLAG case */
  absMaxKey = MAX_NOTE;
  keyboardOctave = 12; /* Keyboard keys per octave with no notes skipped */
  switch (keyboardType) {
    case ALLKEYSFLAG:
      for (i = 0; i < 128; i++) key2MidiMap[i] = allKeys[i];
     break;
    case WHITEKEYSFLAG:
      for (i = 0; i < WHITEKEYS; i++) key2MidiMap[i] = whiteKeys[i];
      keyboardOctave = 7;
      /* Get keyboard key for smallest midi note we want */
      for (i = 0; i < WHITEKEYS; i++) {
        if (key2MidiMap[i] >= absMinKey) {
          absMinKey = i;
          break;
        }
      }
      /* Get keyboard key for largest midi note we want */
      for (i = WHITEKEYS - 1; i >= 0; i--) {
        if (key2MidiMap[i] <= absMinKey) {
          absMinKey = i;
          break;
        }
      }
      /* Redundant array bound check for safety */
      if (absMaxKey >= WHITEKEYS) absMaxKey = WHITEKEYS - 1;
      if (absMinKey >= WHITEKEYS) absMinKey = WHITEKEYS - 1;
      break;
    case BLACKKEYSFLAG:
      for (i = 0; i < BLACKKEYS; i++) key2MidiMap[i] = blackKeys[i];
      keyboardOctave = 5;
      /* Get keyboard key for smallest midi note we want */
      for (i = 0; i < BLACKKEYS; i++) {
        if (key2MidiMap[i] >= absMinKey) {
          absMinKey = i;
          break;
        }
      }
      /* Get keyboard key for largest midi note we want */
      for (i = BLACKKEYS - 1; i >= 0; i--) {
        if (key2MidiMap[i] <= absMinKey) {
          absMinKey = i;
          break;
        }
      }
      /* Redundant array bound check for safety */
      if (absMaxKey >= BLACKKEYS) absMaxKey = BLACKKEYS - 1;
      if (absMinKey >= BLACKKEYS) absMinKey = BLACKKEYS - 1;
      break;
  }

  /* Get max indentation, so we can determine the scale factor
     to make midi output fit within dynamic range */
  midiMaxIndent = 0;
  for (step = 0; step < plen; step++) {
    if (indentationLevels[step] > midiMaxIndent)
      midiMaxIndent = indentationLevels[step];
  }

  /* We will use integral note increments multiplied by the indentation
     level.  We pick the largest possible, with a maximum of 4, so that the
     midi output stays within the desired dynamic range.  If the proof has
     *too* large a maximum indentation (not seen so far), the do loop below
     will decrease the note increment to 0, so the MIDI output will just be a
     flat sequence of repeating notes and therefore useless, but at least it
     won't crash the MIDI converter.  */

  /*midiKeyInc = 5;*/ /* Starting note increment, plus 1 */
        /* (This is now set with the I parameter; see above) */
  do { /* Decrement note incr until song will fit MIDI dyn range */
    midiKeyInc--;
    /* Compute the baseline note to which add the proof indentation
      times the midiKeyInc.  The "12" is to allow for the shift
      of one octave down of the sustained notes on "essential"
      (i.e. logical, not formula-building) steps. */
    midiBaseline = ((absMaxKey + absMinKey) / 2) -
      (((midiMaxIndent * midiKeyInc) - keyboardOctave/*12*/) / 2);
    midiMinKey = midiBaseline - keyboardOctave/*12*/;
    midiMaxKey = midiBaseline + (midiMaxIndent * midiKeyInc);
  } while ((midiMinKey < absMinKey || midiMaxKey > absMaxKey) &&
      midiKeyInc > 0);

  /* Open the output file */
  let(&midiFileName, cat(g_Statement[g_showStatement].labelName,
      ".txt", NULL)); /* Create file name from statement label */
  print2("Creating MIDI source file \"%s\"...", midiFileName);

  /* fSafeOpen() renames existing files with ~1,~2,etc.  This way
     existing user files will not be accidentally destroyed. */
  midiFilePtr = fSafeOpen(midiFileName, "w", 0/*noVersioningFlag*/);
  if (midiFilePtr == NULL) {
    print2("?Couldn't open %s\n", midiFileName);
    goto midi_return;
  }

  /* Output the MIDI header */
  fprintf(midiFilePtr, "MFile 0 1 %ld\n", midiTempo);
  fprintf(midiFilePtr, "MTrk\n");

  /* Create a string exactly 38 characters long for the Meta Text
     label (I'm not sure why, but they are in the t2mf examples) */
  let(&tmpStr, cat("Theorem ", statementLabel, " ", g_midiParameter,
      space(30), NULL));
  let(&tmpStr, left(tmpStr, 38));
  fprintf(midiFilePtr, "0 Meta Text \"%s\"\n", tmpStr);
  fprintf(midiFilePtr,
      "0 Meta Copyright \"Released to Public Domain by N. Megill\"\n");

  /************** Scan the proof ************************/

  for (step = 0; step < plen; step++) {
  /* Handle the processing that takes place at each proof step */
    if (!logicalFlags[step]) {

      /*** Process the higher fast notes for formula-building steps ***/
      /* Get the "keyboard" key number */
      midiKey = (midiKeyInc * indentationLevels[step]) + midiBaseline;
      /* Redundant prevention of array bound violation */
      if (midiKey < 0) midiKey = 0;
      if (midiKey > absMaxKey) midiKey = absMaxKey;
      /* Map "keyboard" key to MIDI note */
      midiNote = key2MidiMap[midiKey];
      if (midiPreviousFormulaStep != midiNote || !midiSyncopate) {
        /* Turn note on at the current MIDI time stamp */
        fprintf(midiFilePtr, "%ld On ch=2 n=%ld v=75\n", midiTime, midiNote);
        /* Turn note off at the time stamp + 18 */
        fprintf(midiFilePtr, "%ld On ch=2 n=%ld v=0\n", midiTime + 18,
            midiNote);
        midiPreviousFormulaStep = midiNote;
      } else {
        /* Skip turning on the note to give syncopation */
        /* To prevent skipping two notes in a row, set last note
           to 0 so next step it will not be skipped even if the
           note still doesn't change; this makes the syncopation
           more rhythmic */
        if (!midiHesitate) {
          midiPreviousFormulaStep = 0;
        }
      }
      midiTime += 24; /* Add 24 to the MIDI time stamp */

    } else {

      /*** Process the deeper sustained notes for logical steps ***/
      /* The idea here is to shift the note down 1 octave before
         outputting it, so it is distinguished from formula-
         building notes */
      /* Get the "keyboard" key number */
      midiKey = (midiKeyInc * indentationLevels[step]) + midiBaseline;
      /* Redundant prevention of array bound violation */
      if (midiKey < 0) midiKey = 0;
      if (midiKey > absMaxKey) midiKey = absMaxKey;
      /* Map "keyboard" key to MIDI note */
      midiNote = key2MidiMap[midiKey];
      midiNote = midiNote - 12; /* Down 1 octave */
      if (midiNote < 0) midiNote = 0; /* For safety but should be redundant */
      if (midiPreviousLogicalStep) { /* If 0, it's the first time */
        /* Turn off the previous sustained note */
        fprintf(midiFilePtr, "%ld On ch=1 n=%ld v=0\n", midiTime,
            midiPreviousLogicalStep);
      }
      /* Turn on the new sustained note */
      fprintf(midiFilePtr, "%ld On ch=1 n=%ld v=100\n", midiTime,
          midiNote);
      midiTime += 24; /* Add 24 to the MIDI time stamp */
      midiPreviousLogicalStep = midiNote; /* Save for next step */

    }
  } /* next step */

  /****************** Clean up and close output file ****************/

  /* After the last step, do the file closing stuff */
  /* Sustain the very last note a little longer - sounds better */
  midiTime += 72;
  fprintf(midiFilePtr, "%ld On ch=1 n=%ld v=0\n", midiTime,
      midiPreviousLogicalStep);
  /* Output the MIDI file trailer */
  fprintf(midiFilePtr, "%ld Meta TrkEnd\n", midiTime);
  fprintf(midiFilePtr, "TrkEnd\n");
  fclose(midiFilePtr);
  /* Inform the user the run time of the MIDI file */
  print2(" length = %ld sec\n", (long)(midiTime / (2 * midiTempo)));

 midi_return:
  /* Important: all local vstrings must be deallocated to prevent
     memory leakage */
  let(&midiFileName, "");
  let(&tmpStr, "");
  let(&midiLocalParam, "");

} /* outputMidi */
