+/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1984. */
+static char rcsid[] = "$Header: demo.c,v 2.6 85/08/22 16:01:21 timo Exp $";
+
+/*
+ * B editor -- Editor command processor.
+ */
+
+#include <ctype.h>
+
+#include "b.h"
+#include "feat.h"
+#include "erro.h"
+#include "bobj.h"
+#include "node.h"
+#include "gram.h"
+#include "keys.h"
+#include "supr.h"
+
+#ifdef BTOP
+#include <setjmp.h>
+
+#ifndef CMDPROMPT
+#define CMDPROMPT ">>> " /* Prompt user for immediate command */
+#endif CMDPROMPT
+#endif BTOP
+
+
+value editqueue();
+
+/* Command line flags */
+extern bool dflag;
+extern bool slowterminal;
+
+#ifdef SAVEBUF
+extern char copysavefile[];
+#endif SAVEBUF
+
+
+Visible bool lefttorite;
+ /* Saves some time in nosuggtoqueue() for read from file */
+
+#define MAXHIST 101 /* One more than the number of UNDO's allowed. */
+
+#define Mod(k) (((k)+MAXHIST) % MAXHIST)
+#define Succ(k) (((k)+1) % MAXHIST)
+#define Pred(k) (((k)+MAXHIST-1) % MAXHIST)
+
+Hidden environ *tobesaved;
+Hidden string savewhere;
+
+
+#ifdef BTOP
+
+extern jmp_buf jumpback;
+extern bool interrupted;
+extern bool canjump;
+
+/*
+ * Main loop, called from main program if -t option present.
+ */
+
+Visible Procedure
+mainloop()
+{
+ environ env;
+ environ *ep = &env;
+ FILE *pdown;
+ FILE *pup;
+ int cmdchar;
+
+ savewhere = (string)NULL;
+ tobesaved = (environ*)NULL;
+ start_b(&pdown, &pup);
+ clrenv(ep);
+#ifdef SAVEBUF
+ ep->copybuffer = editqueue(copysavefile);
+ if (ep->copybuffer)
+ ep->copyflag = Yes;
+#endif SAVEBUF
+
+ for (;;) {
+ cmdchar = sleur();
+ if (cmdchar == EOF)
+ break;
+ getinput(ep, cmdchar, pdown, pup);
+ }
+#ifdef SAVEBUF
+ if (ep->copyflag)
+ savequeue(ep->copybuffer, copysavefile);
+ else
+ savequeue(Vnil, copysavefile);
+#endif SAVEBUF
+ Erelease(*ep);
+}
+
+
+/*
+ * Provide input for the interpreter.
+ */
+
+Hidden Procedure
+getinput(ep, cmdchar, pdown, pup)
+ environ *ep;
+ int cmdchar;
+ FILE *pdown;
+ FILE *pup;
+{
+ int n;
+ char buffer[100];
+ char filename[100];
+ int lineno;
+
+
+ switch (cmdchar) {
+
+ case '>': /* Immediate command */
+ case 'E': /* Expression */
+ case 'R': /* Raw input */
+ case 'Y': /* Yes/No */
+ if (cmdchar == '>')
+ setroot("Imm_cmd");
+ else if (cmdchar == 'E')
+ setroot("Expression");
+ else
+ setroot("Raw_input");
+ delfocus(&ep->focus);
+ initshow();
+ if (cmdchar == '>')
+ cmdprompt(CMDPROMPT);
+ editdocument(ep);
+ endshow();
+ top(&ep->focus);
+ ep->mode = WHOLE;
+ if (!interrupted)
+ send(ep->focus, pdown);
+ delete(ep);
+ break;
+
+ case ':':
+ case '=':
+ fgets(buffer, sizeof buffer, pup);
+ if (index(buffer, '+'))
+ n = sscanf(buffer, " +%d %s", &lineno, filename) - 1;
+ else {
+ n = sscanf(buffer, " %s", filename);
+ lineno = 0;
+ }
+ if (n == 1) {
+ initshow();
+ dofile(ep, filename, lineno);
+ endshow();
+ top(&ep->focus);
+ ep->mode = WHOLE;
+ delete(ep);
+ if (!ep->copyflag) {
+ release(ep->copybuffer);
+ ep->copybuffer = Vnil;
+ }
+ }
+ putc('\n', pdown);
+ interrupted = No; /* Interrupts handled locally in editdocument! */
+ break;
+
+ default:
+ printf("[Unrecognized command character '%c' (0%o)]\n",
+ cmdchar&0177, cmdchar);
+
+ }
+}
+
+#endif BTOP
+
+
+#ifdef FILEARGS
+
+/*
+ * Edit a single unit or target, called from main program if file name
+ * arguments are present.
+ */
+
+Visible Procedure
+demo(filename, linenumber)
+ string filename;
+ int linenumber;
+{
+ environ env;
+ environ *ep = &env;
+ bool ok;
+
+ clrenv(ep);
+#ifdef SAVEBUF
+ ep->copybuffer = editqueue(copysavefile);
+ if (ep->copybuffer)
+ ep->copyflag = Yes;
+#endif SAVEBUF
+ initshow();
+ ok = dofile(ep, filename, linenumber);
+ endshow();
+ if (!ok)
+ return No;
+#ifdef SAVEBUF
+ if (ep->copyflag)
+ savequeue(ep->copybuffer, copysavefile);
+ else
+ savequeue(Vnil, copysavefile);
+#endif SAVEBUF
+ Erelease(*ep);
+ return Yes;
+}
+
+#endif !FILEARGS
+
+
+/*
+ * Edit a unit or target, using the environment offered as a parameter.
+ */
+
+Hidden bool
+dofile(ep, filename, linenumber)
+ environ *ep;
+ string filename;
+ int linenumber;
+{
+#ifdef HELPFUL
+ static bool didmessage;
+
+ if (!didmessage) {
+ didmessage = Yes;
+ message("[Press ? or ESC-? for help]");
+ }
+#endif HELPFUL
+#ifdef SAVEPOS
+ if (linenumber <= 0)
+ linenumber = getpos(filename);
+#endif SAVEPOS
+ setroot(filename[0] == '=' ? "Target_edit" : "Unit_edit");
+ savewhere = filename;
+ tobesaved = (environ*)NULL;
+
+ lefttorite = Yes;
+ edit(ep, filename, linenumber);
+#ifdef USERSUGG
+ readsugg(ep->focus);
+#endif USERSUGG
+ lefttorite = No;
+
+ ep->generation = 0;
+ if (!editdocument(ep))
+ return No;
+ if (ep->generation > 0) {
+ if (!save(ep->focus, filename))
+ error("Cannot save unit: %s", unixerror(filename));
+#ifdef USERSUGG
+ writesugg(ep->focus);
+#endif USERSUGG
+ }
+#ifdef SAVEPOS
+ savepos(filename, lineno(ep)+1);
+#endif SAVEPOS
+ savewhere = (char*)NULL;
+ tobesaved = (environ*)NULL;
+ return Yes;
+}
+
+
+/*
+ * Call the editor for a given document.
+ */
+
+Hidden bool
+editdocument(ep)
+ environ *ep;
+{
+ int k;
+ int first = 0;
+ int last = 0;
+ int current = 0;
+ int onscreen = -1;
+ bool reverse = No;
+ environ newenv;
+ int cmd;
+ bool errors = No;
+ int undoage = 0;
+ bool done = No;
+ environ history[MAXHIST];
+
+ Ecopy(*ep, history[0]);
+
+ for (;;) { /* Command interpretation loop */
+ if (onscreen != current)
+ virtupdate(onscreen < 0 ? (environ*)NULL : &history[onscreen],
+ &history[current],
+ reverse && onscreen >= 0 ?
+ history[onscreen].highest : history[current].highest);
+ onscreen = current;
+ if (done)
+ break;
+#ifdef BTOP
+ if (!interrupted && !moreinput())
+#else BTOP
+ if (!moreinput())
+#endif BTOP
+ actupdate(history[current].copyflag ?
+ history[current].copybuffer : Vnil,
+#ifdef RECORDING
+ history[current].newmacro != Vnil,
+#else !RECORDING
+ No,
+#endif !RECORDING
+ No);
+#ifdef BTOP
+ if (interrupted || setjmp(jumpback))
+ break;
+ canjump = Yes;
+#endif BTOP
+ cmd = inchar();
+#ifdef BTOP
+ canjump = No;
+#endif BTOP
+ errors = No;
+
+ switch (cmd) {
+
+#ifndef NDEBUG
+ case Ctl(@): /* Debug exit with variable dump */
+ tobesaved = (environ*)NULL;
+ return No;
+#endif NDEBUG
+
+#ifndef SMALLSYS
+ case Ctl(^): /* Debug status message */
+ dbmess(&history[current]);
+ errors = Yes; /* Causes clear after new keystroke seen */
+ continue;
+#endif !SMALLSYS
+
+ case UNDO:
+ if (current == first)
+ errors = Yes;
+ else {
+ if (onscreen == current)
+ reverse = Yes;
+ current = Pred(current);
+ undoage = Mod(last-current);
+ }
+ break;
+
+ case REDO:
+ if (current == last)
+ errors = Yes;
+ else {
+ if (current == onscreen)
+ reverse = No;
+ if (history[Succ(current)].generation <
+ history[current].generation)
+ error(REDO_OLD); /***** Should refuse altogether??? *****/
+ current = Succ(current);
+ undoage = Mod(last-current);
+ }
+ break;
+
+#ifdef HELPFUL
+ case HELP:
+ if (help())
+ onscreen = -1;
+ break;
+#endif HELPFUL
+
+ case REDRAW:
+ onscreen = -1;
+ trmundefined();
+ break;
+
+ case EOF:
+ done = Yes;
+ break;
+
+ default:
+ Ecopy(history[current], newenv);
+ newenv.highest = Maxintlet;
+ newenv.changed = No;
+ if (cmd != EXIT)
+ errors = !execute(&newenv, cmd) || !checkep(&newenv);
+ else
+ done = Yes;
+ if (errors) {
+ switch (cmd) {
+ case '\r':
+ case '\n':
+ if (newenv.mode == ATEND && !parent(newenv.focus)) {
+ errors = !checkep(&newenv);
+ if (!errors)
+ done = Yes;
+ }
+ break;
+#ifdef HELPFUL
+ case '?':
+ if (help())
+ onscreen = -1;
+#endif HELPFUL
+ }
+ }
+ if (errors)
+ Erelease(newenv);
+ else {
+#ifndef SMALLSYS
+ if (done)
+ done = canexit(&newenv);
+#endif SMALLSYS
+ if (newenv.changed)
+ ++newenv.generation;
+ last = Succ(last);
+ current = Succ(current);
+ if (last == first) {
+ /* Array full (always after a while). Discard "oldest". */
+ if (current == last
+ || undoage < Mod(current-first)) {
+ Erelease(history[first]);
+ first = Succ(first);
+ if (undoage < MAXHIST)
+ ++undoage;
+ }
+ else {
+ last = Pred(last);
+ Erelease(history[last]);
+ }
+ }
+ if (current != last
+ && newenv.highest < history[current].highest)
+ history[current].highest = newenv.highest;
+ /* Move entries beyond current one up. */
+ for (k = last; k != current; k = Pred(k)) {
+ if (Pred(k) == onscreen)
+ onscreen = k;
+ Emove(history[Pred(k)], history[k]);
+ }
+ Ecopy(newenv, history[current]);
+ Erelease(history[current]);
+ }
+ break; /* default */
+
+ } /* switch */
+
+ if (errors && cmd != '?') {
+ if (!slowterminal && isascii(cmd)
+ && (isprint(cmd) || cmd == ' '))
+ error(INS_BAD, cmd);
+ else
+ error((char*)NULL);
+ }
+ if (savewhere)
+ tobesaved = &history[current];
+ } /* for (;;) */
+
+ actupdate(Vnil, No, Yes);
+ Erelease(*ep);
+ Ecopy(history[current], *ep);
+ if (savewhere)
+ tobesaved = ep;
+ for (current = first; current != last; current = Succ(current))
+ Erelease(history[current]);
+ Erelease(history[last]);
+ /* endshow(); */
+ return Yes;
+}
+
+
+/*
+ * Execute a command, return success or failure.
+ */
+
+Hidden bool
+execute(ep, cmd)
+ register environ *ep;
+ register int cmd;
+{
+ register bool spflag = ep->spflag;
+ register int i;
+ environ env;
+ char buf[2];
+ register char *cp;
+#ifdef USERSUGG
+ bool sugg = symbol(tree(ep->focus)) == Suggestion;
+#define ACCSUGG(ep) if (sugg) accsugg(ep)
+#define KILLSUGG(ep) if (sugg) killsugg(ep)
+#else !USERSUGG
+#define ACCSUGG(ep) /* NULL */
+#define KILLSUGG(ep) /* NULL */
+#endif !USERSUGG
+
+#ifdef RECORDING
+ if (ep->newmacro && cmd != USEMACRO && cmd != SAVEMACRO) {
+ buf[0] = cmd;
+ buf[1] = 0;
+ concato(&ep->newmacro, buf);
+ }
+#endif RECORDING
+ ep->spflag = No;
+
+ switch (cmd) {
+
+#ifdef RECORDING
+ case SAVEMACRO:
+ ep->spflag = spflag;
+ if (ep->newmacro) { /* End definition */
+ release(ep->oldmacro);
+ if (ep->newmacro && Length(ep->newmacro) > 0) {
+ ep->oldmacro = ep->newmacro;
+ message(REC_OK);
+ }
+ else {
+ release(ep->newmacro);
+ ep->oldmacro = Vnil;
+ }
+ ep->newmacro = Vnil;
+ }
+ else /* Start definition */
+ ep->newmacro = mk_text("");
+ return Yes;
+
+ case USEMACRO:
+ if (!ep->oldmacro || Length(ep->oldmacro) <= 0) {
+ error(PLB_NOK);
+ return No;
+ }
+ ep->spflag = spflag;
+ cp = Str(ep->oldmacro);
+ for (i = 0; i < Length(ep->oldmacro); ++i) {
+ Ecopy(*ep, env);
+ if (execute(ep, cp[i]&0377) && checkep(ep))
+ Erelease(env);
+ else {
+ Erelease(*ep);
+ Emove(env, *ep);
+ if (!i)
+ return No;
+ error((char*)NULL); /* Just a bell */
+ /* The error must be signalled here, because the overall
+ command (USEMACRO) succeeds, so the main loop
+ doesn't ring the bell; but we want to inform the
+ that not everything was done either. */
+ return Yes;
+ }
+ }
+ return Yes;
+#endif RECORDING
+
+#ifndef SMALLSYS
+ case Ctl(_): /* 'Touch', i.e. set modified flag */
+ ep->changed = Yes;
+ return Yes;
+#endif SMALLSYS
+
+ case GOTO:
+ ACCSUGG(ep);
+#ifdef RECORDING
+ if (ep->newmacro) {
+ error(GOTO_REC);
+ return No;
+ }
+#endif RECORDING
+ return gotocursor(ep);
+
+ case NEXT:
+ ACCSUGG(ep);
+ return next(ep);
+
+ case PREVIOUS:
+ ACCSUGG(ep);
+ return previous(ep);
+
+ case LEFTARROW:
+ ACCSUGG(ep);
+ return leftarrow(ep);
+
+ case RITEARROW:
+ ACCSUGG(ep);
+ return ritearrow(ep);
+
+ case WIDEN:
+ ACCSUGG(ep);
+ return widen(ep);
+
+ case EXTEND:
+ ACCSUGG(ep);
+ return extend(ep);
+
+ case NARROW:
+ ACCSUGG(ep);
+ return narrow(ep);
+
+ case RNARROW:
+ ACCSUGG(ep);
+ return rnarrow(ep);
+
+ case UPARROW:
+ ACCSUGG(ep);
+ return uparrow(ep);
+
+ case DOWNARROW:
+ ACCSUGG(ep);
+ return downarrow(ep);
+
+ case UPLINE:
+ ACCSUGG(ep);
+ return upline(ep);
+
+ case DOWNLINE:
+ ACCSUGG(ep);
+ return downline(ep);
+
+ case COPY:
+ ACCSUGG(ep);
+ ep->spflag = spflag;
+ return copyinout(ep);
+
+ case DELETE:
+ ACCSUGG(ep);
+ return delete(ep);
+
+ case ACCEPT:
+ ACCSUGG(ep);
+ return accept(ep);
+
+ default:
+ if (!isascii(cmd) || !isprint(cmd))
+ return No;
+ ep->spflag = spflag;
+ return ins_char(ep, cmd, islower(cmd) ? toupper(cmd) : -1);
+
+ case ' ':
+ ep->spflag = spflag;
+ return ins_char(ep, ' ', -1);
+
+ case RETURN:
+ case NEWLINE:
+ KILLSUGG(ep);
+ return ins_newline(ep);
+ }
+}
+
+
+/*
+ * Initialize an environment variable. Most things are set to 0 or NULL.
+ */
+
+Hidden Procedure
+clrenv(ep)
+ environ *ep;
+{
+ ep->focus = newpath(Pnil, gram(Optional), 1);
+ ep->mode = WHOLE;
+ ep->copyflag = ep->spflag = ep->changed = No;
+ ep->s1 = ep->s2 = ep->s3 = 0;
+ ep->highest = Maxintlet;
+ ep->copybuffer = Vnil;
+#ifdef RECORDING
+ ep->oldmacro = ep->newmacro = Vnil;
+#endif RECORDING
+ ep->generation = 0;
+ ep->changed = No;
+}
+
+
+/*
+ * Save parse tree and copy buffer.
+ */
+
+Visible Procedure
+enddemo()
+{
+ register environ *ep = tobesaved;
+
+ tobesaved = (environ*)NULL;
+ /* To avoid loops if saving is interrupted. */
+ if (savewhere && ep) {
+ if (ep->generation > 0) {
+ save(ep->focus, savewhere);
+#ifdef USERSUGG
+ writesugg(ep->focus);
+#endif USERSUGG
+ }
+#ifdef SAVEBUF
+ if (ep->copyflag)
+ savequeue(ep->copybuffer, copysavefile);
+ else
+ savequeue(Vnil, copysavefile);
+#endif SAVEBUF
+#ifdef SAVEPOS
+ savepos(savewhere, lineno(ep)+1);
+#endif SAVEPOS
+ }
+#ifdef BTOP
+ waitchild();
+#endif BTOP
+}
+
+
+/*
+ * Find out if the current position is higher in the tree
+ * than `ever' before (as remembered in ep->highest).
+ * The algorithm of pathlength() is repeated here to gain
+ * some efficiency by stopping as soon as it is clear
+ * no change can occur.
+ * (Higher() is called VERY often, so this pays).
+ */
+
+Visible Procedure
+higher(ep)
+ register environ *ep;
+{
+ register path p = ep->focus;
+ register int pl = 0;
+ register int max = ep->highest;
+
+ while (p) {
+ ++pl;
+ if (pl >= max)
+ return;
+ p = parent(p);
+ }
+ ep->highest = pl;
+}
+
+
+/*
+ * Issue debug status message.
+ */
+
+Visible Procedure
+dbmess(ep)
+ register environ *ep;
+{
+#ifndef SMALLSYS
+ char stuff[80];
+ register string str = stuff;
+
+ switch (ep->mode) {
+ case VHOLE:
+ sprintf(stuff, "VHOLE:%d.%d", ep->s1, ep->s2);
+ break;
+ case FHOLE:
+ sprintf(stuff, "FHOLE:%d.%d", ep->s1, ep->s2);
+ break;
+ case ATBEGIN:
+ str = "ATBEGIN";
+ break;
+ case ATEND:
+ str = "ATEND";
+ break;
+ case WHOLE:
+ str = "WHOLE";
+ break;
+ case SUBRANGE:
+ sprintf(stuff, "SUBRANGE:%d.%d-%d", ep->s1, ep->s2, ep->s3);
+ break;
+ case SUBSET:
+ sprintf(stuff, "SUBSET:%d-%d", ep->s1, ep->s2);
+ break;
+ case SUBLIST:
+ sprintf(stuff, "SUBLIST...%d", ep->s3);
+ break;
+ default:
+ sprintf(stuff, "UNKNOWN:%d,%d,%d,%d",
+ ep->mode, ep->s1, ep->s2, ep->s3);
+ }
+ message(
+#ifdef SAVEBUF
+ "%s, %s, wi=%d, hi=%d, (y,x,l)=(%d,%d,%d) %s",
+ symname(symbol(tree(ep->focus))),
+#else !SAVEBUF
+ "%d, %s, wi=%d, hi=%d, (y,x,l)=(%d,%d,%d) %s",
+ symbol(tree(ep->focus)),
+#endif SAVEBUF
+ str, width(tree(ep->focus)), ep->highest,
+ Ycoord(ep->focus), Xcoord(ep->focus), Level(ep->focus),
+ ep->spflag ? "spflag on" : "");
+#endif !SMALLSYS
+}
+
+#ifndef SMALLSYS
+
+Hidden bool
+canexit(ep)
+ environ *ep;
+{
+ environ env;
+
+ shrink(ep);
+ if (ishole(ep))
+ delete(ep);
+ Ecopy(*ep, env);
+ top(&ep->focus);
+ higher(ep);
+ ep->mode = WHOLE;
+ if (findhole(&ep->focus)) {
+ Erelease(env);
+ error(EXIT_HOLES); /* There are holes left */
+ return No;
+ }
+ Erelease(*ep);
+ Emove(env, *ep);
+ return Yes;
+}
+
+
+Hidden bool
+findhole(pp)
+ register path *pp;
+{
+ register node n = tree(*pp);
+
+ if (Type(n) == Tex)
+ return No;
+ if (symbol(n) == Hole)
+ return Yes;
+ if (!down(pp))
+ return No;
+ for (;;) {
+ if (findhole(pp))
+ return Yes;
+ if (!rite(pp))
+ break;
+
+ }
+ up(pp) || Abort();
+ return No;
+}
+
+#endif !SMALLSYS