Sindbad~EG File Manager

Current Path : /usr/home/beeson/MathXpert/automode/
Upload File :
Current File : /usr/home/beeson/MathXpert/automode/execute.c

/* M. Beeson, for Windows Mathpert */
/* Execute a given operator, if possible, making changes in the
data structures stored in the document's heap (and referenced in the DLL's).
*/
/* 
7.19.94 original date
1.13.99 last modified
12.30.99 copied a block of code from silent_step into finish_exec, just after the call to
constants_of_integration.
2.3.00  modified embed_comment
3.8.00 added GetTransform
1.18.06 changed handles to pointers etc.
1.18.06 changed access to papyrus to use document instead of GetWindowLong
1.19.06 made execute, finish_exec, wget_arg, wget_two_args, wget_indexarg all take PDOCDATA instead of HWND.
1.21.11 added ++p.totalautosteps
6.7.13  modified finish_exec to flatten the result of factorialrecursion
8.30.17  changed line 386 to avoid untranslated " and "
3.18.23  changed comma to semicolon at that same line 386
1.29.24  modified autostep to call undo if it fails.
2.8.24   modified autostep to write finalremark directly into the document
2.11.24  added finished = 0  just before finalremark is called in autostep
         corrected autofinish, which was calling finalremark twice.
4.16.24  supplied third arg to MakeSelectorMenu  (and simplified the following code accordingly)
5.13.24  added a line at line 503  "if(!d.showstpflag)"
5.30.24  added code at the end of autostep to erase any old final_remark if no new one is being generated
6.11.24  added *nitems = 0 in showStep
9.30.24 made autofinish call set_mathmode(AUTOMODE) at the beginning and MENUMODE at the end.
12.7.24 made autofinish turn off display_progress and turn it back on after.
12.8.24 made autostep return 1 in accordance with its spec.
12.31.24 in autofinish(),corrected the type of save_display_progress.
1.24.25  added the dated line in autostep
1.31.25 added clear_comment_buffer and clear_error_buffer in autostep.
2.15.25  Corrected finished
2.16.25 removed unused 'commandID' and some commented out code that used to call it in showstep().
2.18.25  corrected the size passed to memcpy
*/
#include <string.h>
#include <assert.h>
#include <stdlib.h>    /* itoa                 */
#include <stddef.h>
#include <stdio.h>
#include <math.h>
#include "globals.h"
#include "graphstr.h"
#include "display.h"
#include "mpdoc.h"
#include "checkarg.h"   /* typedef 'condition' */
#include "execute.h"
#include "getarg.h"
#include "cflags.h"
#include "prover.h"     /* interval_as_and     */
#include "exec.h"       /* exec                */
#include "integral.h"   /* constants_of_integration */
#include "operator.h"   /* names of menus      */
#include "automode.h"   /* one_step            */
#include "eqn.h"        /* delete_false        */
#include "pvalaux.h"    /* topflatten          */
#include "optable.h"
#include "factor.h"     /* mark_irreducibles   */
#include "cflags.h"     /* get_comment_buffer, get_error_buffer */
#include "bigrect.h"
#include "errbuf.h"
#include "docdata.h"
#include "probtype.h"
#include "ops.h"        /* changesigns         */
#include "calc.h"       /* difeqn              */
#include "mpmem.h"     /* save_and_reset       */
#include "autosimp.h"  /* GetShowStepOperation */
#include "trig.h"
#include "activedoc.h" /* GetActiveDoc */
#include "autostep.h"
#include "fremark.h"
#include "undo.h"
#include "showstepfocus.h"
#include "autosum.h"    /* set_hinflag */

 
static int integrationop(operation op);
static int totalfailures;  /* counts the total number of failed
                              operators in this Mathpert session */
static void improve(term *t);
static void adjust_eigenvariable(term t);
static void adjust_papyrus_for_comment(PDOCDATA);
static int contains_integral(term t, term *integrand, term *x);

/*________________________________________________________________*/
 term flatten(term t)  // needed in fexec.c
/* if t contains any products or sums that need flattening, do so.  */
{ unsigned short f,n;
  int i;
  term s;
  if(ATOMIC(t))
     return t;  // nothing to do;
  f = FUNCTOR(t);
  n = ARITY(t);  
  s = make_term(f,n);
  for(i=0;i<n;i++)
     ARGREP(s,i,flatten(ARG(i,t)));
  if(COLOR(t))
      SETCOLOR(s,COLOR(t));
  if(f == '+' || f == '*' || f == AND || f == OR)
     return topflatten(s);
  return s;
}
  
/*____________________________________________________________________*/
#if 0
 int execute(PDOCDATA pDocData, operation op)
/* Execute the given operator on history(currentline), or
more precisely on history(activeline), which usually is
history(currentline), but sometimes is not, as documented
under exec() in autosimp.c.

Returns nonzero if it fails, 0 if it succeeds. Return value 3 means,
do not display an error message; it's used when the user has pressed
Cancel in some dialog box to back out of executing this operator.
Return value 2 signifies that an operator failed on purpose, but
a new line of solution should still be generated.

If it succeeds (or fails on purpose)  update currentline
and history(currentline).  Calls needs_arg, get_arg, get_index_arg
as required, and then exec.  Does not do any display of the results
or error messages.

It also clears the comment and error buffers at the beginning
in case something irrelevant was there.
*/
{ term arg,u;
  controldata p;
  char reason[DIMREASONBUFFER];
  int currentline,promptnumber;
  int err;
  char prompt[128];
  condition c;
  term t,next;
  currentline = get_activeline();
  t = history(currentline);
  clear_error_buffer();  /* make sure no irrelevant error messages are there */
  clear_comment_buffer();
  get_controldata(&p);
  if(
     (FUNCTOR(t) == OR || (FUNCTOR(t) == AND && !interval_as_and(t)))
     && p.selected_equation >= 0  /* negative means only one is
                                 visible so work on that one */
     && needs_index_arg(op)
    )
     { /* Are these equations or inequalities?  We need to set the
          prompt correctly before inquiring which one to work on.  */
       unsigned n = ARITY(t);
       unsigned i,nequations=0;
       unsigned f;
       if(get_problemtype() == RELATED_RATES)
          { term s = t;
            if(FUNCTOR(s) == OR)
               { SETFUNCTOR(s,AND,ARITY(s));
                 s = topflatten(s);
                 n = ARITY(s);
                 nequations = n;
                 /* in related rates there are two (sets of) equations
                    connected by an OR;  this is the total number of
                    equations. */
               }
          }
       else
          { for(i=0;i<n;i++)
               { u = ARG(i,t);
                 if(FUNCTOR(u) == MULTIPLICITY)
                    u = ARG(0,u);
                 f = FUNCTOR(u);
                 if(f == '=')
                    ++nequations;
                 else if(f == AND)
                    { if(!interval_as_and(u))
                         break;
                    }
                 else if(f != LE && f != GE && f != NE && f != '<' && f != '>')
                    break;
               }
            if(i<n)
                assert(0);
              /* an AND or OR must have each arg an equation or inequality;
                 or a MULTIPLICITY term whose 0th arg is an equation or inequality */
          }
       if(nequations == n)
          { promptnumber = 36;
            /* english(36) is Work on which equation? */
          }
       else if(nequations == 0)
          { promptnumber = 503;
            /* Work on which inequality? */
          }
       else
          { promptnumber = 504;
            /* Work on which equation or inequality? */
          }
       p.selected_equation = wget_indexarg(pDocData,n,promptnumber);
       if(p.selected_equation == 0)
          return 1;  /* user pressed Cancel */
       set_selected_equation(p.selected_equation);
     }
  if(p.selected_equation &&
     (FUNCTOR(t) == OR || (FUNCTOR(t) == AND && !interval_as_and(t))) &&
     ARITY(t) >= abs(p.selected_equation)
    )
     t = ARG(abs(p.selected_equation)-1,t);
  else if(p.selected_equation)  /* and the arity is too small or t isn't an OR or AND */
     /* even if we were working on a certain equation last line,
        some may have been rejected, and it may even be that u is
        now only a single equation rather than an OR of equations.
     */
     set_selected_equation(0);   /* p.selected_equation is only local */
  c = needs_two_args(op,&promptnumber);
  if(c)
     { err = wget_two_args(pDocData,c,&arg,promptnumber);
       if(err)         /* User backed out of dialog */
          return 3;    /* 3 says, don't generate an error message */
     }
  else
     { c = needs_arg(op, prompt);
       if(c)
          { err = wget_arg(pDocData,c,&arg,prompt);
            if(err)
               { /* User backed out of the GetArg dialog */
                 return 3;  /* 3 says, don't generate an error message */
               }
          }
       else
          SETFUNCTOR(arg,ILLEGAL,0); /* mark it as a dummy arg */
     }
  err = exec(op,arg,&next,reason);
  return finish_exec(pDocData,err,op,next,reason);
}
#endif 
/*__________________________________________________________*/
static int integrationop(operation op)
/* return 1 if we should sometimes introduce a constant of integration
when op is used.  This is called from top.c.   Zero is returned for
operators that have an integral in the output so that a constant
of integration is never needed, as well as for operators that don't
integrate at all. */

/* Any new operators that transform integrals without eliminating them
should be added to this function */

{  if(op.men < basic_integration)
      return 0;
   if(op.men > series_manipulations)
      return 0;
   if(op.men >= series_geom1 && op.men < series_manipulations)
      return 0;
   switch(op.men)
     { case basic_integration:
          switch(op.choice)
             { case 4:
               case 5:
               case 6:
               case 7:  return 0;
               default: return 1;
             }
       case integrate_by_substitution:
          return 0;
       case trig_integration:
          return 1;
       case integrate_exp:
          if(op.choice == 7)  /* intexponential */
             return 0;
          return 1;
       case integrate_by_parts:
          if(op.choice == 8) /* simpleint */
             return 1;
          return 0;
       case definite_integration:
          return 0;
       case trig_substitutions:
          if(op.choice == 9) /* simpleint */
             return 1;
          return 0;
       case trigonometric_integrals:
          return 0;
       case integrate_rational:
          if(op.choice <= 7)
             return 0;
          return 1;
       case simplify_calculus:
          if(op.choice == 12)  /* simpleint */
             return 1;
          return 0;
       case integrate_hyperbolic:
          return 1;
       case series_manipulations:
          if(op.choice == 9)  /* intseries */
             return 1;
          return 0;
       case advanced_sigma_notation:
          if(op.choice == 9)  /* intsigma  */
              return 1;
          return 0;
     }
   return 1;
}
/*______________________________________________________________*/
static void improve(term *t)
/* remove any 'false' arguments from the OR term *next */
{ term temp,next;
  int err;
  next = topflatten(*t);
  err = delete_false(next,&temp);
  if(!err)
     *t = temp;
}
/*______________________________________________________________*/
 void embed_comment(PDOCDATA pDocData, char *comment)
/* Embed the given comment in the document so it will be displayed
and printed.  Comment can contain explicit carriage returns (character 13)
pDocData points to the document data structure.
*/
{ int currentline = get_currentline();
  store_comment(pDocData,currentline,(unsigned char *)comment);
  /* now we must update the history list of permanent memory;
     this is used by undo to 'reset' permament memory. If we
     don't update it here, if a comment is added to line 8, say,
     then the comment which was attached to line 8 (by the above code,
     after line 8 was recorded), will be living in memory that belongs
     to line 9, and hence will be in limbo when line 9 is undone. */
  pDocData->DocProverData.permhistory[currentline] =
    pDocData->DocProverData.nextworkspace;
}

/*____________________________________________________________________*/
#define EQOPS(a,b) ((a).men ==(b).men && (a).choice == (b).choice)

 int think_ahead(PDOCDATA pDocData,operation op, message *msg)
 /* use autosimp to take four steps of the computer's solution.
 It is assumed that 'op' has just failed, causing think_ahead to
 be called.  pDocData points to the active document data.
 Use the result to generate a message to the user explaining
 what should be done.  Only returns the message, doesn't actually print
 it.  Return value 1 if no message can be constructed, 0 if one can.
*/

{ int i,nsteps,flag;
  operation plan[4]; /* next four operators to be used by automode */
  static char buf1[DIMREASONBUFFER + 20];
  if(pDocData->DocControlData.finishedflag)
     return 1;   /* automode is done  */
  /* Run cogitate to generate the plan */
  nsteps = cogitate(pDocData,4,plan,&flag);
   /* flag tells whether automode completes
      or not; we don't care here. */
  if(nsteps == 0)
     { pDocData->DocControlData.finishedflag = 1;
       return 1;   /* automode is done */
     }
  for(i=0;i<nsteps;i++)
     { if(EQOPS(plan[i],op))
          break;  /* user's operator discovered */
       if(cousins(plan[i],op))
          break;  /* good enough, e.g. collect once and collect all */
     }
   if(i==nsteps)
      return 1; /* user's operator not among Mathpert's choices */
   if(i == 0)
      { /* User is using Term Selection, and the operator would
           have applied if a different focus had been chosen.
           This can't happen in menu mode or the user's operator
           would have applied.
        */
        msg->line[0] = english(744);
        msg->line[1] = english(745);
        /* That  operation is a good choice, but you should
        apply it to a different term than the one you selected. */
        msg->nlines = 2;
        return 0;
      }
/*    Now i-1 is how many steps the user has skipped.  But there may
      be duplicates in this list.  Let's get them out: */
   if(i==3)
     { if(EQOPS(plan[0],plan[1]))
           { plan[2] = plan[1]; --i;}
       else if(EQOPS(plan[1],plan[2]))
          --i;
       else if(EQOPS(plan[0],plan[2]))
          --i;
     }
  if(i==2)
     { if(EQOPS(plan[0],plan[1]))
          --i;
     }
  /* Now plan[0]...plan[i-1] are the non-duplicated ops the user missed */
  if(i==1)
     { msg->line[0] = english(790); /* That operation almost applies here, but */
       msg->line[2] = cmdmenu(plan[0].men)[plan[0].choice-1];
       if(msg->line[2][0] == '$')
          { msg->line[1] = english(1990); /* you must first prepare for it by using  */
            msg->nlines = 3;
          }
       else
          { msg->line[1] = english(791); /* you must first prepare for it by using $*/
            msg->line[3] = "$";
            msg->nlines = 4;
          }
       return 0;
     }
  if(i==2)
     { msg->line[0] = english(792); /* You are getting ahead of yourself here. */
       msg->line[1] = english(793); /* Before you are ready to use that operation, */
       msg->line[2] = english(795); /* you have to prepare by using */
       strcpy(buf1,cmdmenu(plan[0].men)[plan[0].choice-1]);
       strcat(buf1, " "); strcat(buf1, english(2420)); strcat(buf1, " "); // 2420 is  "and"
       msg->line[3] = buf1;
       msg->line[4] = cmdmenu(plan[1].men)[plan[1].choice-1];
       msg->nlines = 5;
       return 0;
     }
  else /* i==3 */
     { msg->line[0] = english(797); /* That operator won't work here, */
       msg->line[1] = english(798); /* but you seem to be on the right track */
       msg->line[3] = cmdmenu(plan[0].men)[plan[0].choice-1];
       msg->line[2] = english(1992);  /* You might try */
       msg->nlines = 4;
       return 0;
     }
}
/*____________________________________________________________________*/
 int cogitate(PDOCDATA pDocData, int n, operation *plan, int *flag)
/*  take n more steps in the projected solution
and store the operators used in 'plan', which must have
dimension at least n.  Return the number of steps
actually taken.  Set *flag = 1 if automode completes,
0 if not.   Don't put any automode_only operators into plan,
use get_brother to replace them with menu-mode equivalents.
   If pDocData->DocControlData.showstepflag is nonzero at entrance
(which means it was called from showstep), then do NOT call reset_heap,
because that will destroy the paths in the list of selected terms in
the papyrus structure that have been allocated during cogitate.
Otherwise do call reset_heap, and in any case,
reset all of Mathpert's global machinery at exit, including the
document 'permspace' memory, to the condition it was in
when this function was called.
*/

{ int i,err,nsteps;
  operation op;
  actualop code;
  int savecurrentline = get_currentline();
  int activeline = get_activeline();

  int savedisplay = inq_display_on();
  controldata d,savecontroldata;
  vardata saveVarData;
  char savec[DIM_COMMENT_BUFFER][80];
  char savee[DIM_COMMENT_BUFFER][80];
  char *comment_buffer = get_comment_buffer(0);
  char *error_buffer = get_error_buffer(0);
  void  *savenode = heapmax();
      /* if there are user-selected terms their paths are already on the heap. */
  polyflags savepolyflags = pDocData->DocPolyData;
  int cofi_index = get_cofi_index();
  if(activeline != savecurrentline)
      set_currentline(activeline);
  savecontroldata = d = pDocData->DocControlData;
  saveVarData = pDocData->DocVarData;
  memcpy(savec,comment_buffer, 80 * DIM_COMMENT_BUFFER);
  memcpy(savee,error_buffer, 80 * DIM_COMMENT_BUFFER);
  *flag = 0;
  display_off();
  for(i=0;i<n;i++)
     { err = silent_step(pDocData,1);
       if(!err)
          code = GetShowStepOperation();
       else
          code = NULL;
       if(!d.showstepflag)
          reset_heap(savenode);
       if(err)
           { *flag = 1;
             break;
           }
       if(code)
          code_to_op(code,&plan[i]);
       else
          { op = d.opseq[get_currentline()];
            if(op.men == automode_only ||
               op.men == automode_only2 ||
               op.men == automode_only3
              )
               code_to_op(get_brother(op.men, op.choice-1),&plan[i]);
            else
               plan[i] = op;
          }
     }
  nsteps = i;  /* number of steps taken in auto mode */
  /* Now restore everything to the condition it was
     originally in;  */
  for(i=0;i<nsteps;i++)
     { undo_assumptions();
       undo_letdefns();
       undo_inhibitions();
       decrement_currentline();
     }
  set_cofi_index(cofi_index);
  pDocData->DocProverData.nextworkspace = pDocData->DocProverData.permhistory[savecurrentline];
  if(activeline != savecurrentline)
     set_currentline(savecurrentline);
  else if(savecurrentline != get_currentline())
     assert(0);
  pDocData->DocControlData = savecontroldata;  /* so unpack_flags isn't needed */
  pDocData->DocVarData = saveVarData;   /* including eigenvariable, nvariables, etc. */
  pDocData->DocPolyData = savepolyflags;
  if(savedisplay)
     display_on();
  memcpy(comment_buffer, savec, 80 * DIM_COMMENT_BUFFER);
  memcpy(error_buffer, savee,80 * DIM_COMMENT_BUFFER);
  if(!d.showstepflag)   // added 5.13.24
     reset_heap(savenode);  // added 4.25.24
  return nsteps;
}
/*__________________________________________________________________*/
 int silent_step(PDOCDATA pDocData, int flag)
/* Take one step in automode, updating the document structure but
not doing any visible display of the result.  (Intermediate
displays such as display_progress are turned on or off by
display_off(), display_on(), which control pDocData->display_on.
So 'silent_step' is a bit of a misnomer. )
   The second parameter, flag, is nonzero if this is
called from cogitate, in which case we should NOT embed comments
in the document data structure.
Return 0 for success,
1 if automode can't find anything to do.
   hwndDoc is the document window handle.
*/

{ int err;
  int currentline;
  char reason[DIMREASONBUFFER];
  operation op;
  controldata p = pDocData->DocControlData;
  unsigned char *comment;
  term next;
  term x,integrand;
  unsigned f,i;
  int savenextassumption = get_nextassumption();
  int savenextdefn = get_nextdefn();
  int nextdefn, nextassumption;
  int mathmode;
  char *errmsg;
  mathmode = p.mathmode;
  automode();
  for(i=0;i<p.nfailedops;i++)
     p.model[access_mod(p.failedops[i])].inhibited = 1;
     /* inhibit without pushing a node on the inhibitions list */
     /* This saves the time of trying these ops again in automode */
  err = one_step(pDocData,&next,reason,&op);
  for(i=0;i<p.nfailedops;i++)
     { p.model[access_mod(p.failedops[i])].inhibited=0;
       /* 'release' the inhibition created above */
     }
  get_controldata(&p);  /* one_step can change the control flags, maybe, so
                           retrieve them again now. */
  if(!err && in_loop(next,op)==2)
     /* next is the same as the previous line but a let_defn
        has been made in between. */

     { if(p.nfailedops < 4)
           { p.failedops[p.nfailedops]=access_optable(op.men)[op.choice-1];
             ++p.nfailedops;
           }
        else  /* replace the last failedop */
           p.failedops[p.nfailedops-1]=access_optable(op.men)[op.choice-1];
     }
  errmsg = get_error_buffer(0);
  if(!flag && errmsg[0] == '!')
     { /* this signals we should print the message in the solution */
       /* Note that such 'permanent' error messages should
          be only one line long. */
       embed_comment(pDocData,errmsg+1);  /* skip the '!'  */
       /* This embeds the comment in the document data structure, but
          does not get coordinates into the papyrus structure telling
          where the comment should be painted.  A comment that is left
          by an operator that succeeds is assigned coordinates in solution.c
          when processing the WM_NEWSTEP message. But one that is left
          by an error message is handled here.
        */
        adjust_papyrus_for_comment(pDocData);
     }
  if(err)
     { p.mathmode = mathmode;  /* restore original mode */
       pDocData->DocControlData = p;
       nextdefn = get_nextdefn();
       nextassumption = get_nextassumption();
       if(nextdefn != savenextdefn)
          { increment_currentline();
            undo_letdefns();
            decrement_currentline();
          }
       if(nextassumption != savenextassumption)
          { increment_currentline();
            undo_assumptions();
            decrement_currentline();
          }
       return err;  /* if !flag, this means automode is finished */
     }
  /* Now the operator worked */
  p.nfailedops = 0;
  f = FUNCTOR(next);
  if(f == AND)
     { for(i=0;i<ARITY(next);i++)
          { if(equals(ARG(i,next),falseterm))
               { next = falseterm;
                 f = FUNCTOR(next);
                 break;
               }
          }
       if(f == AND && contains_at_toplevel(next,TRUEFUNCTOR))
          { /* this can happen in RELATED_RATES */
            /* drop the 'true' expressions      */
            int k=0;
            term p = make_term(AND,ARITY(next));
            for(i=0;i<ARITY(next);i++)
               { if(equals(ARG(i,next),trueterm))
                    continue;
                 ARGREP(p,k,ARG(i,next));
                 ++k;
               }
            if(k==0)
               { RELEASE(p);
                 next = trueterm;
               }
            else if(k==1)
               { next = ARG(0,p);
                 RELEASE(p);
               }
            else
               { SETFUNCTOR(p,AND,k);
                 next = p;
               }
            f = FUNCTOR(next);
          }
     }
  else if(f == OR  && p.selected_equation >=0)
     improve(&next);
  else if(p.selected_equation < 0 )
     { int k = abs(p.selected_equation)-1;
       f = FUNCTOR(ARG(k,next));
       if(f==OR || f == AND)
          improve(ARGPTR(next) + (- p.selected_equation - 1));
          /* in case a selected equation has changed to an unflattened OR */
     }
  ++p.autosteps;
  ++p.totalautosteps;
  ++p.totalsteps;
  p.localfailures = 0;  /* reset counter for number of wrong choices in a row */
  if(integrationop(op))
     /* supply constant(s) of integration if necessary */
     next = constants_of_integration(history(get_currentline()),next);
  else if(FUNCTOR(next) == '=' &&
          contains_integral(ARG(0,next),&integrand,&x) &&
          !contains(ARG(1,next),CONSTANTOFINTEGRATION) &&
          !contains(ARG(1,next),INTEGRAL)
         )
     { /* If we've just solved an equation for an integral, the right side needs
       to get a constant of integration.
       */
       next = equation(ARG(0,next),sum(ARG(1,next), constant_of_integration(integrand,x)));
     }
  else if(FUNCTOR(next) == '=' &&
          contains_integral(ARG(1,next),&integrand,&x) &&
          !contains(ARG(0,next),CONSTANTOFINTEGRATION) &&
          !contains(ARG(0,next),INTEGRAL)
         )
     { /* Theoretically the user could manually solve such an equation and wind up
          with the integral on the right and no constant of integration on the left.
          Let's get it right in that case too.
       */
       next = equation(sum(ARG(0,next), constant_of_integration(integrand,x)),ARG(1,next));
     }
  increment_currentline();  /* increment the REAL currentline */
  currentline = get_currentline();
  if(currentline >= (int) pDocData->DocProverData.maxhistory)
     assert(0);
  set_history(currentline,next);
  if(p.factorflag & 0x0100)
     mark_irreducibles(historyp(currentline));
  adjust_eigenvariable(next);
  pack_flags(currentline);   /* store control flags for use by undo */
  store_reason(pDocData,currentline,(const char *)reason);
  pDocData->DocControlData.plan[0].choice = 0;
  pDocData->DocControlData.plan[0].men = 0;
  comment = get_permanent_comment();
  if(comment)
     { store_comment(pDocData, currentline, comment);
       free2(comment);
     }
  else
     store_comment(pDocData, currentline, NULL);
     /* it will be NULL to start with, but if undo has been done,
        it can't be assumed NULL, so in order to avoid the reappearance
        of a comment associated with an undone step, this is necessary.
     */
  pDocData->DocProverData.permhistory[currentline] = pDocData->DocProverData.nextworkspace;
  p.opseq[currentline] = op;
  p.mathmode = mathmode;
  pDocData->DocControlData = p;
  return 0;
}
/*__________________________________________________________________*/
 int finish_exec(PDOCDATA pDocData,int err, operation op, term next, char *reason)
/* the user's operator has been tried.  If it failed, err is nonzero
and next and reason are garbage.  If it succeeded, err is zero and
next is the intended next line, with reason the justification string.
Update the document data structures appropriately, and return err.
hwnd is the document window.
*/
{ controldata p;
  int i;
  int currentline = get_currentline();
  int problemtype = get_problemtype();
  unsigned char *comment;
  unsigned short f;
  term integrand,x;
  get_controldata(&p);
  if(err==0 )
     { if(p.plan[0].choice == op.choice && p.plan[0].men == op.men)
          { /* ShowStep may have left an operation in p.plan[0]; if
               it's the same as the user's operation, we count
               that as if it were an automode step for loop-detection
               purposes.  Otherwise, ShowStep can go into a loop
               instead of duplicating the automode solution. */
            ++p.autosteps;
          }
       else
          p.autosteps = 0;
       p.localfailures = 0;  /* reset counter for number of wrong choices in a row */
       if(integrationop(op))
          /* supply constant(s) of integration if necessary */
          next = constants_of_integration(history(currentline),next);
       else if(FUNCTOR(next) == '=' &&
               contains_integral(ARG(0,next),&integrand,&x) &&
               !contains(ARG(1,next),CONSTANTOFINTEGRATION) &&
               !contains(ARG(1,next),INTEGRAL)
              )
          { /* If we've just solved an equation for an integral, the right side needs
            to get a constant of integration.
            */
            next = equation(ARG(0,next),sum(ARG(1,next), constant_of_integration(integrand,x)));
          }
       else if(FUNCTOR(next) == '=' &&
               contains_integral(ARG(1,next),&integrand,&x) &&
               !contains(ARG(0,next),CONSTANTOFINTEGRATION) &&
               !contains(ARG(0,next),INTEGRAL)
              )
          { /* Theoretically the user could manually solve such an equation and wind up
               with the integral on the right and no constant of integration on the left.
               Let's get it right in that case too.
            */
            next = equation(sum(ARG(0,next), constant_of_integration(integrand,x)),ARG(1,next));
          }
       f = FUNCTOR(next);
       if(f == AND)
          { for(i=0;i<ARITY(next);i++)
               { if(equals(ARG(i,next),falseterm))
                    { next = falseterm;
                      f = FUNCTOR(next);
                      break;
                    }
               }
            if(f == AND && contains_at_toplevel(next,TRUEFUNCTOR))
               { /* this can happen in RELATED_RATES */
                 /* drop the 'true' expressions      */
                 int k=0;
                 term p = make_term(AND,ARITY(next));
                 for(i=0;i<ARITY(next);i++)
                    { if(equals(ARG(i,next),trueterm))
                         continue;
                      ARGREP(p,k,ARG(i,next));
                      ++k;
                    }
                 if(k==0)
                    { RELEASE(p);
                      next = trueterm;
                    }
                 else if(k==1)
                    { next = ARG(0,p);
                      RELEASE(p);
                    }
                 else
                    { SETFUNCTOR(p,AND,k);
                      next = p;
                    }
                 f = FUNCTOR(next);
               }
          }
       else if(f == OR && p.selected_equation >=0)
          improve(&next);
       if((f == AND || f == OR) && p.selected_equation < 0 )
          { int k = abs(p.selected_equation)-1;
            unsigned short g = FUNCTOR(ARG(k,next));
            SET_SELECTED(ARG(k,next));
            if(f == OR && (g == AND || g == OR))
               improve(ARGPTR(next) + (- p.selected_equation - 1));
               /* in case a selected equation has changed to an unflattened OR */
          }
       if(problemtype == LINEAR_EQUATIONS &&
          f == AND &&
          !LINEUP(next) &&
          LINEUP(history(currentline)) &&
          (void *) access_optable(op.men)[op.choice-1] == (void *) changesigns
         )
          SET_LINEUP(next);
       if(problemtype == RELATED_RATES &&
          (void *) access_optable(op.men)[op.choice-1] == (void *) difeqn &&
          FUNCTOR(next) == OR
         )
          { SETFUNCTOR(next,AND,ARITY(next));
            next = topflatten(next);
          }
       increment_currentline();  /* increment the REAL currentline */
       ++currentline;            /* AND the local copy             */
       if((void *) access_optable(op.men)[op.choice-1] == (void *) factorialrecursion)
          // here list all operations that can produce a product from a non-product, or sum from a non-sum
          // as far as I know,  this is a complete list as of 6.7.13 
          {  next = flatten(next);
          } 
       set_history(currentline,next);
       if(p.factorflag & 0x0100)
          mark_irreducibles(historyp(currentline));
       /* In some cases the eigenvariable can have been eliminated,
          e.g. if the last line was an OR of equations, only one of
          which contained the eigenvariable, and checkroot rejected
          that root.  We have to make sure the eigenvariable is
          changed if this happens. */
       adjust_eigenvariable(next);
       pack_flags(currentline);   /* store control flags for use by undo */
       store_reason(pDocData,currentline,(char *)reason);
       p.plan[0].choice = 0;
       p.plan[0].men = 0;
       /* ensure that whatever is there next time is not left over from now. */
       /* Note that we set p.plan rather than pDocData->DocControlData.plan
          because set_controldata(&p) is going to be called soon. */
       if(pDocData->DocControlData.selected_equation >= 0)
          p.selected_equation = 0;  /* set_controldata is going to be called soon */
       p.opseq[currentline] = op;
       /* There might be a comment marked 'permanent' with an initial
          exclamation point, which should be embedded in the solution now. */
       comment = get_permanent_comment();
       if(comment)
          { store_comment(pDocData, currentline, comment);
            free2(comment);
          }
       else
          store_comment(pDocData, currentline, NULL);
          /* it will be NULL to start with, but if undo has been done,
             it can't be assumed NULL, so in order to avoid the reappearance
             of a comment associated with an undone step, this is necessary.
          */
       pDocData->DocProverData.permhistory[currentline] = pDocData->DocProverData.nextworkspace;
     }
  if(err==1)  /* cannot execute the operator chosen */
     { ++p.successivefailures;  /* documented in globals.c */
       ++p.localfailures;
       ++totalfailures;
       if(p.nfailedops < 4)
          { p.failedops[p.nfailedops]=access_optable(op.men)[op.choice-1];
            ++p.nfailedops;
          }
     }
  /* This allows for error values > 1 to exist but not be counted as
     failures, e.g. when a user interrupts a search for factors. */
  set_controldata(&p);
  return err;
}

/*_________________________________________________________________*/
static void adjust_eigenvariable(term t)
/* in case t does not contain the eigenvariable, reset the
eigenvariable by tracing back oldeigen through the definitions
until you come to a variable that is contained in t, or
until you can't go further back.
*/
{ defn *defns;
  int nextdefn;
  int i,count;
  int nvariables = get_nvariables();
  term *varlist;
  term x = get_eigenvariable();
  count = 0;
  if(get_pending())
     return;  /* this prevents changing the eigenvariable while we're
                 just simplifying a cubic discriminant or a substitution
                 in integration-by-substitution */
  while(!contains(t,FUNCTOR(x)) && count <= nvariables)
     { nextdefn = get_nextdefn();
       defns = get_defns();
       varlist = get_varlist();
       for(i=nextdefn-1;i>=0;i--)
          { if(FUNCTOR(defns[i].left) == FUNCTOR(x) ||
               (FUNCTOR(defns[i].left) == AND && contains(defns[i].left,FUNCTOR(x)))
              )
               break;
          }
       if(i < 0)
          { count = nvariables+1;  /* I do not think this can happen */
            break;
          }
       x = varlist[defns[i].oldeigen];
       ++count;
     }
  if(count == 0)
     return;  /* no need to reset eigenvariable */
  if(count <= nvariables) /* so t does contain x */
     { set_eigenvariable(defns[i].oldeigen);
       return;
     }
  /* Just set the eigenvariable to the first variable in t */
  for(i=0;i<nvariables;i++)
     { if(contains(t,FUNCTOR(varlist[i])))
          { set_eigenvariable(i);
            return;
          }
     }
  /* Now t is constant */
  set_eigenvariable(0);
}

/*________________________________________________________________________*/
static void adjust_papyrus_for_comment(PDOCDATA pDocData)
/* there may be a comment at the currentline that has not
got its coordinates assigned in the papyrus yet.  Fix that.
*/
{ Papyrus *pPapyrus;
  int currentline = get_currentline();
  coord shift;
  unsigned char *comment = get_comment(pDocData,currentline);
  if(!comment)
     return;
  pPapyrus = pDocData->papyrus;
  if(pPapyrus ->lines[currentline].comment.y)
     return;
  pPapyrus->lines[currentline+1].formula.x = (unsigned short)(pPapyrus->lines[currentline+1].formula.x + 32);
  pPapyrus->height = (unsigned short)(pPapyrus->height + 32);
  /* all such comments are only one line; we leave 16 coord units
     for one line, and 16 more for a space following the comment.
  */
  if(pPapyrus->lines[currentline+1].formula.x > pPapyrus->bottom)
     { shift =(unsigned short)(pPapyrus->lines[currentline+1].formula.x - pPapyrus->bottom);
       pPapyrus->top += shift;
       pPapyrus->bottom += shift;
     }
}


/*____________________________________________________________________*/
 int autoans(term t, int n, term *ans, int *flag)
/* Run automode to termination on the currently active document.
or for n steps, or until a new variable is introduced in the
computation (but in the assumptions is OK) or a new definition is made,
whichever comes first.  Back up, not taking the step that made a
definition or introduced a new variable (e.g. binomialtheorem introducing
an indexed sum).
  Put the last line in *ans, put a nonzero value in *flag
if automode does terminate, and returning the number of steps taken.
*/

{ int i,err,nsteps,j,k;
  int savecurrentline = get_currentline();
  int savedisplay = inq_display_on();
  controldata savecontroldata;
  vardata saveVarData;
  PDOCDATA pDocData = (PDOCDATA) GetActiveDoc();
  int savenextdefn = get_nextdefn();
  term x = get_eigenvariable();
  term *atomlist1,*atomlist2;
  unsigned long savenextworkspace = pDocData->DocProverData.nextworkspace;
  void  *savenode;
  int nvars1,nvars2;
  int savenextassumption = get_nextassumption();
  int nextassumption;
  char savec[COMMENTBUFLENGTH];
  char savee[ERRBUFLENGTH];
  char *comment_buffer = pDocData->papyrus->comment_buffer[0];
  char *error_buffer = get_error_buffer(0);
  polyflags savepolyflags;
  nvars1 = variablesin(t,&atomlist1); /* so atomlist1 lives below savenode */
  savenode = heapmax();
  increment_currentline();
  memcpy(savec,comment_buffer, COMMENTBUFLENGTH);
  memcpy(savee,error_buffer, ERRBUFLENGTH);
  if(savecurrentline < 0)
      pDocData->DocProverData.permhistory[0] = savenextworkspace;
  permcopy(t,&pDocData->DocProverData.history[get_currentline()]);
  savepolyflags = pDocData->DocPolyData;
  savecontroldata = pDocData->DocControlData;
  saveVarData = pDocData->DocVarData;
  *flag = 0;
  display_off();
  for(i=0;i<n;i++)
     { err = silent_step(pDocData,1);
       reset_heap(savenode);
       if(err)
           { *flag = 1;
             break;
           }
       if(get_nextdefn() > savenextdefn ||
          !equals(x,get_eigenvariable())
         )
           { undo_assumptions();
             undo_letdefns();
             undo_inhibitions();
             decrement_currentline();
             break;
           }
       nvars2 = variablesin(pDocData->DocProverData.history[get_currentline()],&atomlist2);
       /* is there any variable in atomlist2 which is not in atomlist1 ?  If so back
          up one step and return. */
       /* This can happen e.g. if binomialtheorem has been used. */
       for(j=0;j<nvars2;j++)
          { for(k=0;k<nvars1;k++)
               { if(equals(atomlist1[k],atomlist2[j]))
                    break;
               }
            if(k==nvars1)
               { /* variable not found */
                 undo_assumptions();
                 undo_letdefns();
                 undo_inhibitions();
                 decrement_currentline();
                 break;
               }
          }
       free2(atomlist2);
       if(j < nvars2)
          break;
     }
  nsteps = i;  /* number of steps taken in auto mode */

  save_and_reset(pDocData->DocProverData.history[get_currentline()],savenode,ans);
  /* Now restore everything to the condition it was
     originally in;  */
  for(i=0;i<nsteps;i++)
     { undo_assumptions();
       undo_letdefns();
       undo_inhibitions();
       decrement_currentline();
     }
  nextassumption = get_nextassumption();
  if(nextassumption != savenextassumption)
     assert(0);
  pDocData->DocProverData.nextworkspace = savenextworkspace;
  assert(savecurrentline +1 == get_currentline());
  pDocData->DocControlData = savecontroldata;  /* so unpack_flags isn't needed */
  pDocData->DocVarData = saveVarData;   /* including eigenvariable, nvariables, etc. */
  pDocData->DocPolyData = savepolyflags;
  if(savedisplay)
     display_on();
  memcpy(comment_buffer, savec, COMMENTBUFLENGTH);
  memcpy(error_buffer, savee,ERRBUFLENGTH);
  decrement_currentline();  /* to match the original increment */
  free2(atomlist1);  /* it lives below savenode so save_and_reset didn't recover it */
  return nsteps;
}

/*___________________________________________________________________________*/
static int contains_integral(term t, term *integrand, term *x)
/* if t contains an indefinite integral either at toplevel, or
as a summand, or as a factor of a summand, return 1, with
the integrand and integration variable in *integrand and *x
If not return 0.
*/

{ unsigned short n,i;
  term u;
  int rval;
  if(FUNCTOR(t) == INTEGRAL && ARITY(t) == 2)
     { *x = ARG(1,t);
       *integrand = ARG(0,t);
       return 1;
     }
  if(FUNCTOR(t) == '*')
     { n = ARITY(t);
       for(i=0;i<n;i++)
          { u = ARG(i,t);
            if(FUNCTOR(u) == INTEGRAL && ARITY(u) == 2)
                { *x = ARG(1,u);
                  *integrand = ARG(0,u);
                  return 1;
                }
          }
       return 0;
     }
  if(FUNCTOR(t) == '-')
     return contains_integral(ARG(0,t),integrand,x);
  if(FUNCTOR(t) == '+')
     { n = ARITY(t);
       for(i=0;i<n;i++)
          { rval = contains_integral(ARG(i,t),integrand,x);
            if(rval)
               return rval;
          }
       return 0;
     }
  return 0;
}
/*_____________________________________________________________________________*/
 int GetTransform(term a, term b, actualop *op, term *ans, char *reason)
/* find an operation that will transform a into b, or take it closer to b at least,
and return the operation in op,  the result of the operation (applied to a) in ans,
and the reason it gives in reason. Return 0 for success;
*/
{ actualop ops[50];
  int i=0,k,nops,err;
  term arg;
  SETFUNCTOR(arg,0,0);
  if(FUNCTOR(b) == '^' && equals(ARG(1,b),two) && 
     FUNCTOR(a) == '+'
    )
     { switch(FUNCTOR(ARG(0,b)))
          { case COS:
               ops[i] = sinsquare2; ++i;
               break;
            case SIN:
               ops[i] = sinsquare3; ++i;
               break;
            case SEC:
               ops[i] = tansquare1; ++i;
               break;
            case TAN:
               ops[i] = tansquare2; ++i;
               break;
          }
     }
  pre_ops(a,ops+i,&k);
  k += i;
  SETFUNCTOR(arg,ILLEGAL,0);
  post_ops(a,&arg,ops+k,&nops);
  nops += k;
  for(i=0;i<nops;i++)
     { err = (ops[i])(a,arg,ans,reason);
       if(!err)
          { *op = ops[i];
            return 0;
          }
     }
  return 1;
}
/*___________________________________________________________________*/
int autostep(PSYMBOLDATA pSymbolData)
/* Take one autostep, if possible,  and update the document accordingly.
Return 0 if one autostep is possible.
Return 1 if no autostep is possible and the problem is solved and the
         last step was not taken in automode
Return 2 if no autostep is possible and the problem is solved and the
         last step was taken in automode.
Return 3 if no autostep is possible and the problem is not solved.
*/
{
  int err;
  controldata d;
  int finished;
  void *savenode = heapmax();
  long wordsleft = (pSymbolData->DocProverData.maxworkspace - pSymbolData->DocProverData.nextworkspace);
  /* this measures memory in 4-byte words */
  if(wordsleft < 2048)  /* 2048 words = 8 K bytes */
        {
          assert(0);
        }
  err = silent_step(pSymbolData,0);
  clear_error_buffer();  // don't show errors from operations tried in silent_step.
  clear_comment_buffer();
  if(err)
     { finished = 0;
       /* Automode can do nothing more with this problem */
       /* The attempt to generate another line may have
          created variables or definitions that need to
          be removed.  The easiest way to do this is just
          to increment currentline and then call undo().
       */
       int oldcurrentline = get_currentline();
       increment_currentline();
       get_controldata(&d);
       ++d.autosteps;  /* because undo will decrement it in the next line */
       set_controldata(&d);
       pSymbolData->DocProverData.history[oldcurrentline+1] = zero;
        // just so there is SOMETHING there, otherwise undo may crash.  // 1.24.25
       undo(pSymbolData);
      // Now we need to determine if it's solved or not
      int currentline = get_currentline();
      if(currentline != oldcurrentline)
         assert(0);
      operation plan;
      const char *remark =final_remark(&finished,&plan);
      assert(strlen(remark) < MAXFINALREMARK);
      strncpy(pSymbolData->papyrus->finalremark,  remark,MAXFINALREMARK);
      get_controldata(&d);  /* again because undo changed d.autosteps */
      reset_heap(savenode);
      if(finished == 1)
         { if(d.autosteps == 0 && currentline > 0)
              { /* last step not taken in automode */
                return 1;   /* Interface can play the sound of cheering, for example. */
              }
           return 1;
         }
      if(finished == 3)
         { /* last step taken in automode */
           //  english(1510); // "Finished"
           return 2;
         }
      if(finished == 0)
         { /* Not finished yet. */
           // english(1681);  /* Not yet */
           return 3;
         }
    }
  // one autostep was possible
  pSymbolData->papyrus->finalremark[0] = '\0';
     // There might have been a remark there if the Finished button had been pressed,
     // and if now AutoStep has been pressed,  no new final_remark will be generated,
     // until Finished is pressed again, or AutoFinish,
     // and the old final_remark will not be appropriate, as now the solution may well be finished
  return 0;
}
/*___________________________________________________________________*/
int finished(PSYMBOLDATA pDocData, char *remark, int *percent)
/* check whether AutoStep can take one or two more steps,
   then compute the "final remark"  accordingly and write it
   into remark. Return 1 if the problem is acceptably finished
   (even if automode would do more)  and 0 if not.  Place in *percent
   the percentage of steps taken by the user in term selection mode.
   Show Step counts as user steps.
*/

{
  operation plan[4];
  int flag;
  int nsteps = cogitate(pDocData,4,plan,&flag);
    // Now flag is nonzero if and only if automode can't find anything more to do.
  // But if automode failed to solve it, flag might be 1 and the problem still not solved.
  // Also, automode might not be done, but the problem might be acceptably solved.
  // So we have to check both those possibilities.

  if (flag)
     { // it's finished
       pDocData->DocControlData.finishedflag = 1;  // whether automode can do more
     }
  else
     {
       pDocData->DocControlData.finishedflag = 0;
     }
  char *remark2 = (char *) final_remark(&nsteps, plan);
  // final_remark changes nsteps; nonzero means answer is acceptable
  pDocData->DocControlData.solvedflag = nsteps ? 1 : 0;
  remark[0] = '\0';  // make sure we can strncat to it
  if(!strstr(remark2,"✅") && !strstr(remark2,"❌"))
     {
       if(pDocData->DocControlData.solvedflag)
          strcpy(remark,"✅");
       else
          strcpy(remark,"❌");
    }
  strncat(remark,remark2, MAXFINALREMARK-4);
  remark[MAXFINALREMARK-1] = '\0';  // ensure null termination even if remark is truncated.
  printf("Final remark: %s\n",remark);  // for debugging
  // After final_remark, nsteps has been changed and now is 0,1, or 3, which mean
  // not finished, finished,  and ok (acceptable)
  int autosteps = pDocData->DocControlData.totalautosteps;
  int totalsteps = pDocData->DocControlData.totalsteps;
  *percent = round(100*( (double)(totalsteps-autosteps)/(double)autosteps)); // percent of user steps
  return pDocData->DocControlData.solvedflag;
  
}
         

/*___________________________________________________________________*/
#define MAXAUTODEPTH 110  // max number of steps in autofinish
// we need MAXAUTODEPTH to prevent nospace errors

int autofinish(PSYMBOLDATA pSymbolData)
/* Take as many autosteps as possible,  and update the document accordingly.
Return 0 if autofinish can solve the problem (or it's already solved)
Return 1 if problem is not solved after autofinish
*/
{
  int err=0;
  controldata d;
  int finished;
  set_mathmode(AUTOMODE);
  term *save_display_progress = pSymbolData->progress;
  pSymbolData->progress = 0;  // turn off display_progress;
  void *savenode = heapmax();
  long wordsleft = (pSymbolData->DocProverData.maxworkspace - pSymbolData->DocProverData.nextworkspace);
  /* this measures memory in 4-byte words */
  // int topic = pSymbolData->currenttopic;   // used two lines below if console printout is desired
  int problemnumber = pSymbolData->problemnumbers[SOURCE_MATHPERT];
  // printf("Starting problem %d of topic %d\n",problemnumber, topic);
  int count = 0;
  while(!err && count < MAXAUTODEPTH && wordsleft >= 2048)  /* 2048 words = 8 K bytes */
     { void *savenode = heapmax();
       err = silent_step(pSymbolData,0);
       reset_heap(savenode); // the new data has been permalloced in the document
       wordsleft = (pSymbolData->DocProverData.maxworkspace - pSymbolData->DocProverData.nextworkspace);
       ++count;
       // printf("Autofinish line 1276, count = %d, wordsleft = %ld\n",count,wordsleft);
     }
  /* Automode can do nothing more with this problem */
       /* The attempt to generate another line may have
          created variables or definitions that need to
          be removed.  The easiest way to do this is just
          to increment currentline and then call undo().
       */
  increment_currentline();
  get_controldata(&d);
  ++d.autosteps;  /* because undo will decrement it in the next line */
  set_controldata(&d);
  undo(pSymbolData);
      // Now we need to determine if it's solved or not
  int currentline = get_currentline();
  operation plan;
  finished = 0;  // that tells final_remark that automode can't do anything more
  pSymbolData->DocControlData.finishedflag = 1;  // That gets the final remark visible
  char *remark;
  if(wordsleft < 2048 || count >= MAXAUTODEPTH)
     remark = (char *) english(796);  // "MathXpert gave up, sorry."
  else
     remark = (char *) final_remark(&finished,&plan);
  assert(strlen(remark) < MAXFINALREMARK);
  // don't just use snprintf, as I want to know if this fails!
  snprintf(pSymbolData->papyrus->finalremark, MAXFINALREMARK, "%s",remark);
       // sprintf can't overwrite buffer here because final_remark uses MAXFINALREMARK
       // which also conrols the size of papyrus.finalremark
  pSymbolData->DocControlData.finishedflag = 1;
  get_controldata(&d);  /* again because undo changed d.autosteps */
  reset_heap(savenode);
  set_mathmode(MENUMODE);
  pSymbolData->progress = save_display_progress;
  if(finished == 1)
     { if(d.autosteps == 0 && currentline > 0)
          { /* last step not taken in automode */
            return 1;   /* Interface can play the sound of cheering, for example. */
          }
       return 0;
     }
  if(finished == 3)
     { /* last step taken in automode */
           //  english(1510); // "Finished"
        printf("%d,",problemnumber);
           return 2;
     }
  if(finished == 0)
     { /* Not finished yet. */
           // english(1681);  /* Not yet */
       return 3;
     }
  assert(0);
  return 0;  // avoid warning message
}

/*_______________________________________________________________*/

int showstep(PDOCDATA pDocData, char **menuitems, int *nitems, int *indexToHighlight,
  char *finalremark, int maxlength)
/* called when the showStep message is processed.
   The document has already been activated.
   menuitems will be passed as an array of size at least 64 char *.
This function returns the Term Selection menu in menuitems (as <svg> elements),
and the number of items in that menu in nitems.  It also (indirectly through
calling cogitate) finds the term(s) that should be selected and marks it (or them)
selected in pDocData->papyrus->selected.  It also finds the menuitem to highlight
in the Term Selection menu, and returns its index in indexToHighlight.
It's up to the interface to create and display the Term Selection Menu.  T
he menu will be sent to PHP by process_showStep().
   In Windows MathXpert, the cursor is animated to show what to select; but
security meaures prevent that in Javascript, so Web MathXpert won't do it.  
  In the event that automode can't find anything worth doing,  there won't be
a TermSelectionMenu to display.  In that event, the function will fill in
finalmessage and finished;  finalmessage will contain a final message to display to the user,
and finished will contain (the selected language for)
 "Not yet" or "Finished" or "That's a good answer".  The last two args must be passed
 with enough space to hold these strings (which will not be very long).
 The return value will be 0 if automode found something to do, and 1 if not.
The indirect return *indexToHighlight receives a 0-based index of the item
that is to be highlighted in the Term Selection Menu.  If no item on that
menu corresponds to the op automode needs, *indexToHighlight will contain -1.
That undesirable situation will be logged to the server console.
*/
{ operation op,op2;
  actualop code;
  int flag,i,nsteps;
  BIGRECT active_area;
  int oldflag = pDocData->DocControlData.showstepflag;
  pDocData->DocControlData.showstepflag = 1;  // necesssary to get autosimp to store a selected term
  int currentline = get_activeline();
  InitializeShowStep();
  SetActiveLine(pDocData,&active_area);
  /* locates the current line, setting the static global ActiveLine in sstep.c */
  set_hintflag(1); /* this makes cogitate use factorbypolydiv which it doesn't otherwise */
  pDocData->DocControlData.showstepflag = 1;
  automode();  /* certain operations, such as a^2-b^2 = (a-b)(a+b)
                work more generally in Term Selection Mode, e.g. this
                one will work on 3x^2-27, which it won't touch in auto mode.
                If ShowStep is to duplicate the automode solution, we have
                to run cogitate in auto mode. */

  nsteps = cogitate(pDocData,1,&op,&flag);  /* runs silent_step and restores all
           memory and global variables to conditions before it was called. */
  menumode();
  if(currentline != get_activeline())
     assert(0);  /* check up on cogitate */
  pDocData->DocControlData.showstepflag = 0;
  pDocData->DocControlData.plan[0] = op;
  code = GetShowStepOperation();
  if(code != NULL)
     code_to_op(code,&op);
  set_hintflag(0);
  if(nsteps == 0)
     { /* automode finds nothing worth doing */
       int finished = 0;
       const char *message = final_remark(&finished,&op);
       strncpy(finalremark,(char *)message,maxlength);
       pDocData->DocControlData.showstepflag = oldflag;  // restore previous value
       pDocData->DocControlData.finishedflag = 1;  // this makes the finalremark visible.
       *nitems = 0;
       *indexToHighlight = 0; // not -1 as that is logged as an error.
       return 1;
     }
#if 0
// These things won't be possible in Web MathXpert,
// because Javascript is forbidden to get or change the cursor position
// for security reasons. 
  GetCursorPos(&p);  /* p is in screen coordinates */
  for(marker = pPapyrus->selected; marker; marker=marker->next)
     { r = marker->data.r;
       corner1.x = (int)r.left;
       corner1.y = (int) r.top;
       corner2.x = (int) r.right;
       corner2.y = (int) r.bottom;
       ClientToScreen(hSolution,&corner1);
       ClientToScreen(hSolution,&corner2);
       animate_cursor(p,corner1);
       wait(200);  /* a little pause here looks good */
       animate_selection(hSolution,corner1,corner2);
       p = corner2;
     }
  UpdateWindow(hSolution);
#endif
  operation menuops[64];
  *nitems = MakeSelectorMenu(pDocData,menuitems,menuops);
  *indexToHighlight = -1;  // set to something >= 0 below if item is on the menu
  /* is op on the menu?  */
  for(i=0;i<*nitems;i++)
     {
       if(op.men == menuops[i].men && op.choice-1 == menuops[i].choice)
          break;
     }
  if(i < *nitems)
     { *indexToHighlight = i;
       return 0;
     }
  if(i==*nitems)
     { /* operation not found on menu.  This can happen e.g. when
          op is "subtract two equations" and what we want on the
          menu is "subtract selected equation".  */
       code = associate(op);
       if(code)
          { /* look for the associate instead */
            code_to_op(code,&op2);
            int k;
            for(k=0;k<*nitems;k++)
               {
                 if(op2.men == menuops[k].men && op2.choice-1 == menuops[k].choice)
                    break;
               }
            if(k < *nitems)
               { *indexToHighlight = k;
                 return 0;
               }
          }
     }
  assert(*indexToHighlight == -1);  // since it hasn't been set to anything else
  printf("Oops, required op (%d,%d) was not on Term Selection Menu in showStep\n", op.men, op.choice);
  pDocData->DocControlData.showstepflag = oldflag; // restore previous value
  return 0;
}

/*________________________________________________________________*/
#define MAXOPMSG 400
static char opmsg[MAXOPMSG];

char *operator_errmsg(operation op)
/* construct an error message for display when an operator has failed. */
{ message msg;
  PDOCDATA pDocData;
  char *temp;
  int i,j,err;
#if 0
  if(autotesting() == 3 || supertesting())
     return "Oops, you should not see error messages during random autotesting.";
     /* without this it calls think_ahead, which then instead of using automode
        normally, continues to use it in random test mode. */
#endif
  memset(opmsg,0,MAXOPMSG);
  for(i=0;i<4;i++)
     { temp = get_error_buffer(i);
       if(temp[0] == '\0')
          break;
       if(temp[0] == '!')
          ++temp;  /* skip the '!'  */
       if(i>0) /* add space */
          strcat(opmsg, " ");
       strcat(opmsg,temp);
     }
  if(i)  /* message in error_buffer */
     return opmsg;
  /* no message in error_buffer */
  /* Now see what automode thinks should be done next */
  pDocData = GetActiveDoc();
  err = think_ahead(pDocData,op,&msg);
  if(!err)
     { /* msg is an array of strings; copy them into opmsg.  Add spaces
          at the ends of the strings if not present already. */
       opmsg[0] = '\0';
       for(j=0;j<msg.nlines;j++)
          { strcat(opmsg, msg.line[j]);
            if(j < msg.nlines-1 && opmsg[strlen(opmsg)-1] != 32)
               strcat(opmsg," ");
          }
       if(opmsg[strlen(opmsg)-1] != '.')
          strcat(opmsg, ".");   /* think_ahead doesn't supply a final period. */
       assert(strlen(opmsg) < MAXOPMSG);
       return opmsg;
     }
  /* No message left by the operator, and think_ahead failed, too;
     so just generate the default error message */
  return (char *) english(8);
       /* Sorry, that operation can't be applied here. */
}

Sindbad File Manager Version 1.0, Coded By Sindbad EG ~ The Terrorists