/********** Copyright 1990 Regents of the University of California. All rights reserved. Author: 1985 Wayne A. Christopher, U. C. Berkeley CAD Group **********/ /* The front-end command loop. */ #include "ngspice/ngspice.h" #include "ngspice/cpdefs.h" #include "control.h" #include "com_cdump.h" #include "variable.h" #include "ngspice/fteext.h" /* Return values from doblock(). I am assuming that nobody will use * these characters in a string. */ #define NORMAL '\001' #define BROKEN '\002' #define CONTINUED '\003' #define NORMAL_STR "\001" #define BROKEN_STR "\002" #define CONTINUED_STR "\003" /* Are we waiting for a command? This lets signal handling be * more clever. */ bool cp_cwait = FALSE; char *cp_csep = ";"; bool cp_dounixcom = FALSE; /* We have to keep the control structures in a stack, so that when we * do a 'source', we can push a fresh set onto the top... Actually * there have to be two stacks -- one for the pointer to the list of * control structs, and one for the 'current command' pointer... */ struct control *control[CONTROLSTACKSIZE]; struct control *cend[CONTROLSTACKSIZE]; int stackp = 0; /* If there is an argument, give this to cshpar to use instead of * stdin. In a few places, we call cp_evloop again if it returns 1 and * exit (or close a file) if it returns 0... Because of the way * sources are done, we can't allow the control structures to get * blown away every time we return -- probably every time we type * source at the keyboard and every time a source returns to keyboard * input is ok though -- use ft_controlreset. */ /* Notes by CDHW: * This routine leaked like a sieve because each getcommand() created a * wordlist that was never freed because it might have been added into * the control structure. I've tackled this by making sure that everything * put into the cend[stackp] is a copy. This means that wlist can be * destroyed safely */ /* no redirection after the following commands (we may need more to add here!) */ static char *noredirect[] = { "stop", "define", NULL }; static struct control * findlabel(char *s, struct control *ct) { while (ct) { if ((ct->co_type == CO_LABEL) && eq(s, ct->co_text->wl_word)) break; ct = ct->co_next; } return (ct); } /* This is also in cshpar.c ... */ static void pwlist(wordlist *wlist, char *name) { wordlist *wl; if (!cp_debug) return; fprintf(cp_err, "%s : [ ", name); for (wl = wlist; wl; wl = wl->wl_next) fprintf(cp_err, "%s ", wl->wl_word); fprintf(cp_err, "]\n"); } /* CDHW defined functions */ static void pwlist_echo(wordlist *wlist, char *name) /*CDHW used to perform function of set echo */ { wordlist *wl; if ((!cp_echo)||cp_debug) /* cpdebug prints the same info */ return; fprintf(cp_err, "%s ", name); for (wl = wlist; wl; wl = wl->wl_next) fprintf(cp_err, "%s ", wl->wl_word); fprintf(cp_err, "\n"); } /*CDHW Remove control structure and free the memory its hogging CDHW*/ static void ctl_free(struct control *ctrl) { if (!ctrl) return; wl_free(ctrl->co_cond); ctrl->co_cond = NULL; tfree(ctrl->co_foreachvar); ctrl->co_foreachvar = NULL; wl_free(ctrl->co_text); ctrl->co_text = NULL; ctl_free(ctrl->co_children); ctrl->co_children = NULL; ctl_free(ctrl->co_elseblock); ctrl->co_elseblock = NULL; ctl_free(ctrl->co_next); ctrl->co_next = NULL; tfree(ctrl); ctrl = NULL; } /* Note that we only do io redirection when we get to here - we also * postpone some other things until now. */ static void docommand(wordlist *wlist) { wordlist *rwlist; if (cp_debug) { printf("docommand "); wl_print(wlist, stdout); putc('\n', stdout); } /* Do all the things that used to be done by cshpar when the line * was read... */ wlist = cp_variablesubst(wlist); pwlist(wlist, "After variable substitution"); wlist = cp_bquote(wlist); pwlist(wlist, "After backquote substitution"); wlist = cp_doglob(wlist); pwlist(wlist, "After globbing"); pwlist_echo(wlist, "Becomes >"); if (!wlist || !wlist->wl_word) /*CDHW need to free wlist in second case? CDHW*/ return; /* Now loop through all of the commands given. */ rwlist = wlist; while (wlist) { char *s; int i; struct comm *command; wordlist *nextc, *ee; nextc = wl_find(cp_csep, wlist); if (nextc == wlist) { /* skip leading `;' */ wlist = wlist->wl_next; continue; } /* Temporarily hide the rest of the command... */ ee = wlist->wl_prev; wl_chop(nextc); wl_chop(wlist); /* And do the redirection. */ cp_ioreset(); for (i = 0; noredirect[i]; i++) if (eq(wlist->wl_word, noredirect[i])) break; if (!noredirect[i]) if ((wlist = cp_redirect(wlist)) == NULL) { cp_ioreset(); return; } /* Get rid of all the 8th bits now... */ cp_striplist(wlist); s = wlist->wl_word; /* Look for the command in the command list. */ for (i = 0; cp_coms[i].co_comname; i++) if (strcmp(cp_coms[i].co_comname, s) == 0) break; command = &cp_coms[i]; /* Now give the user-supplied command routine a try... */ if (!command->co_func && cp_oddcomm(s, wlist->wl_next)) goto out; /* If it's not there, try it as a unix command. */ if (!command->co_comname) { if (cp_dounixcom && cp_unixcom(wlist)) goto out; fprintf(cp_err, "%s: no such command available in %s\n", s, cp_program); goto out; /* If it hasn't been implemented */ } else if (!command->co_func) { fprintf(cp_err, "%s: command is not implemented\n", s); goto out; /* If it's there but spiceonly, and this is nutmeg, error. */ } else if (ft_nutmeg && command->co_spiceonly) { fprintf(cp_err, "%s: command available only in spice\n", s); goto out; } /* The command was a valid spice/nutmeg command. */ { int nargs = wl_length(wlist->wl_next); if (nargs < command->co_minargs) { if (command->co_argfn) { command->co_argfn (wlist->wl_next, command); } else { fprintf(cp_err, "%s: too few args.\n", s); } } else if (nargs > command->co_maxargs) { fprintf(cp_err, "%s: too many args.\n", s); } else { command->co_func (wlist->wl_next); } } out: wl_append(ee, wlist); wl_append(wlist, nextc); if (!ee) rwlist = wlist; wlist = nextc; } wl_free(rwlist); /* Do periodic sorts of things... */ cp_periodic(); cp_ioreset(); } /* Execute a block. There can be a number of return values from this routine. * NORMAL indicates a normal termination * BROKEN indicates a break -- if the caller is a breakable loop, * terminate it, otherwise pass the break upwards * CONTINUED indicates a continue -- if the caller is a continuable loop, * continue, else pass the continue upwards * Any other return code is considered a pointer to a string which is * a label somewhere -- if this label is present in the block, * goto it, otherwise pass it up. Note that this prevents jumping * into a loop, which is good. * * Note that here is where we expand variables, ``, and globs for * controls. * * The 'num' argument is used by break n and continue n. */ static char * doblock(struct control *bl, int *num) { struct control *ch, *cn = NULL; wordlist *wl; char *i; int nn; nn = *num + 1; /*CDHW this is a guess... CDHW*/ switch (bl->co_type) { case CO_WHILE: if (!bl->co_children) { fprintf(cp_err, "Warning: Executing empty 'while' block.\n"); fprintf(cp_err, " (Use a label statement as a no-op to suppress this warning.)\n"); } while (bl->co_cond && cp_istrue(bl->co_cond)) { if (!bl->co_children) cp_periodic(); /*CDHW*/ for (ch = bl->co_children; ch; ch = cn) { cn = ch->co_next; i = doblock(ch, &nn); switch (*i) { case NORMAL: break; case BROKEN: /* Break. */ if (nn < 2) { return (NORMAL_STR); } else { *num = nn - 1; return (BROKEN_STR); } case CONTINUED: /* Continue. */ if (nn < 2) { cn = NULL; break; } else { *num = nn - 1; return (CONTINUED_STR); } default: cn = findlabel(i, bl->co_children); if (!cn) return (i); } } } break; case CO_DOWHILE: do { for (ch = bl->co_children; ch; ch = cn) { cn = ch->co_next; i = doblock(ch, &nn); switch (*i) { case NORMAL: break; case BROKEN: /* Break. */ if (nn < 2) { return (NORMAL_STR); } else { *num = nn - 1; return (BROKEN_STR); } case CONTINUED: /* Continue. */ if (nn < 2) { cn = NULL; break; } else { *num = nn - 1; return (CONTINUED_STR); } default: cn = findlabel(i, bl->co_children); if (!cn) return (i); } } } while (bl->co_cond && cp_istrue(bl->co_cond)); break; case CO_REPEAT: if (!bl->co_children) { fprintf(cp_err, "Warning: Executing empty 'repeat' block.\n"); fprintf(cp_err, " (Use a label statement as a no-op to suppress this warning.)\n"); } if (!bl->co_timestodo) bl->co_timestodo = bl->co_numtimes; /*bl->co_numtimes: total repeat count bl->co_numtimes = -1: repeat forever bl->co_timestodo: remaining repeats*/ while ((bl->co_timestodo > 0) || (bl->co_timestodo == -1)) { if (!bl->co_children) cp_periodic(); /*CDHW*/ if (bl->co_timestodo != -1) bl->co_timestodo--; /* loop through all stements inside rpeat ... end */ for (ch = bl->co_children; ch; ch = cn) { cn = ch->co_next; i = doblock(ch, &nn); switch (*i) { case NORMAL: break; case BROKEN: /* Break. */ /* before leaving repeat loop set remaining timestodo to 0 */ bl->co_timestodo = 0; if (nn < 2) { return (NORMAL_STR); } else { *num = nn - 1; return (BROKEN_STR); } case CONTINUED: /* Continue. */ if (nn < 2) { cn = NULL; break; } else { /* before leaving repeat loop set remaining timestodo to 0 */ bl->co_timestodo = 0; *num = nn - 1; return (CONTINUED_STR); } default: cn = findlabel(i, bl->co_children); if (!cn) { /* no label found inside repeat loop: before leaving loop set remaining timestodo to 0 */ bl->co_timestodo = 0; return (i); } } } } break; case CO_IF: if (bl->co_cond && cp_istrue(bl->co_cond)) { for (ch = bl->co_children; ch; ch = cn) { cn = ch->co_next; i = doblock(ch, &nn); if (*i > 2) { cn = findlabel(i, bl->co_children); if (!cn) return (i); } else if (*i != NORMAL) { *num = nn; return (i); } } } else { for (ch = bl->co_elseblock; ch; ch = cn) { cn = ch->co_next; i = doblock(ch, &nn); if (*i > 2) { cn = findlabel(i, bl->co_elseblock); if (!cn) return (i); } else if (*i != NORMAL) { *num = nn; return (i); } } } break; case CO_FOREACH: for (wl = cp_variablesubst(cp_bquote(cp_doglob(wl_copy(bl->co_text)))); wl; wl = wl->wl_next) { cp_vset(bl->co_foreachvar, CP_STRING, wl->wl_word); for (ch = bl->co_children; ch; ch = cn) { cn = ch->co_next; i = doblock(ch, &nn); switch (*i) { case NORMAL: break; case BROKEN: /* Break. */ if (nn < 2) { return (NORMAL_STR); } else { *num = nn - 1; return (BROKEN_STR); } case CONTINUED: /* Continue. */ if (nn < 2) { cn = NULL; break; } else { *num = nn - 1; return (CONTINUED_STR); } default: cn = findlabel(i, bl->co_children); if (!cn) return (i); } } } break; case CO_BREAK: if (bl->co_numtimes > 0) { *num = bl->co_numtimes; return (BROKEN_STR); } else { fprintf(cp_err, "Warning: break %d a no-op\n", bl->co_numtimes); return (NORMAL_STR); } case CO_CONTINUE: if (bl->co_numtimes > 0) { *num = bl->co_numtimes; return (CONTINUED_STR); } else { fprintf(cp_err, "Warning: continue %d a no-op\n", bl->co_numtimes); return (NORMAL_STR); } case CO_GOTO: wl = cp_variablesubst(cp_bquote(cp_doglob( wl_copy(bl->co_text)))); /*CDHW Leak ? CDHW*/ return (wl->wl_word); case CO_LABEL: /* Do nothing. */ cp_periodic(); /*CDHW needed to avoid lock-ups when loop contains only a label CDHW*/ break; case CO_STATEMENT: docommand(wl_copy(bl->co_text)); break; case CO_UNFILLED: /* There was probably an error here... */ fprintf(cp_err, "Warning: ignoring previous error\n"); break; default: fprintf(cp_err, "doblock: Internal Error: bad block type %d\n", bl->co_type); return (NORMAL_STR); } return (NORMAL_STR); } /* Maxiumum number of cheverons used for the alternative prompt */ #define MAX_CHEVRONS 16 /* Get the alternate prompt. Number of chevrons indicates stack depth. Returns NULL when there is no alternate prompt. SJB 28th April 2005 */ char * get_alt_prompt(void) { int i = 0, j; static char buf[MAX_CHEVRONS + 2]; /* includes terminating space & null */ struct control *c; /* if nothing on the command stack return NULL */ if (cend[stackp] == 0) return NULL; /* measure stack depth */ for (c = cend[stackp]->co_parent; c; c = c->co_parent) i++; if (i <= 0) return NULL; /* Avoid overflow of buffer and indicate when we've limited the chevrons by starting with a '+' */ if (i > MAX_CHEVRONS) { i = MAX_CHEVRONS; buf[0] = '+'; } else { buf[0] = '>'; } /* return one chevron per command stack depth */ for (j = 1; j < i; j++) buf[j] = '>'; /* Add space and terminate */ buf[j] = ' '; buf[j + 1] = '\0'; return buf; } /* Get a command. This does all the bookkeeping things like turning * command completion on and off... */ static wordlist * getcommand(char *string) { wordlist *wlist; if (cp_debug) fprintf(cp_err, "calling getcommand %s\n", string ? string : ""); #if !defined(HAVE_GNUREADLINE) && !defined(HAVE_BSDEDITLINE) /* set cp_altprompt for use by the lexer - see parser/lexical.c */ cp_altprompt = get_alt_prompt(); #endif /* !defined(HAVE_GNUREADLINE) && !defined(HAVE_BSDEDITLINE) */ cp_cwait = TRUE; wlist = cp_parse(string); cp_cwait = FALSE; if (cp_debug) { printf("getcommand "); wl_print(wlist, stdout); putc('\n', stdout); } return (wlist); } /* va: TODO: free control structure(s) before overwriting (memory leakage) */ int cp_evloop(char *string) { wordlist *wlist, *ww; struct control *x; char *i; int nn; #define newblock \ do { \ cend[stackp]->co_children = alloc(struct control); \ ZERO(cend[stackp]->co_children, struct control); \ cend[stackp]->co_children->co_parent = cend[stackp]; \ cend[stackp] = cend[stackp]->co_children; \ cend[stackp]->co_type = CO_UNFILLED; \ } while(0) for (;;) { wlist = getcommand(string); if (wlist == NULL) { /* End of file or end of user input. */ if (cend[stackp]->co_parent && !string) { cp_resetcontrol(); continue; } else { return (0); } } if ((wlist->wl_word == NULL) || (*wlist->wl_word == '\0')) { /* User just typed return. */ wl_free(wlist); /* va, avoid memory leak */ if (string) { return (1); } else { cp_event--; continue; } } /* Just a check... */ for (ww = wlist; ww; ww = ww->wl_next) if (!ww->wl_word) { fprintf(cp_err, "cp_evloop: Internal Error: NULL word pointer\n"); continue; } /* Add this to the control structure list. If cend->co_type is * CO_UNFILLED, the last line was the beginning of a block, * and this is the unfilled first statement. */ /* va: TODO: free old structure and its content, before overwriting */ if (cend[stackp] && (cend[stackp]->co_type != CO_UNFILLED)) { cend[stackp]->co_next = alloc(struct control); ZERO(cend[stackp]->co_next, struct control); cend[stackp]->co_next->co_prev = cend[stackp]; cend[stackp]->co_next->co_parent = cend[stackp]->co_parent; cend[stackp] = cend[stackp]->co_next; } else if (!cend[stackp]) { control[stackp] = cend[stackp] = alloc(struct control); ZERO(cend[stackp], struct control); } if (eq(wlist->wl_word, "while")) { cend[stackp]->co_type = CO_WHILE; cend[stackp]->co_cond = wl_copy(wlist->wl_next); /* va, wl_copy */ if (!cend[stackp]->co_cond) { fprintf(stderr, "Error: missing while condition, 'false' will be assumed.\n"); } newblock; } else if (eq(wlist->wl_word, "dowhile")) { cend[stackp]->co_type = CO_DOWHILE; cend[stackp]->co_cond = wl_copy(wlist->wl_next); /* va, wl_copy */ if (!cend[stackp]->co_cond) { /* va: prevent misinterpretation as trigraph sequence with \-sign */ fprintf(stderr, "Error: missing dowhile condition, '?\?\?' will be assumed.\n"); } newblock; } else if (eq(wlist->wl_word, "repeat")) { cend[stackp]->co_type = CO_REPEAT; if (!wlist->wl_next) { cend[stackp]->co_numtimes = -1; } else { char *s; double *dd; struct wordlist *t; /*CDHW*/ /*CDHW wlist = cp_variablesubst(cp_bquote(cp_doglob(wl_copy(wlist)))); Wrong order? Leak? CDHW*/ t = cp_doglob(cp_bquote(cp_variablesubst(wl_copy(wlist)))); /*CDHW leak from cp_doglob? */ s = t->wl_next->wl_word; dd = ft_numparse(&s, FALSE); if (dd) { if (*dd < 0) { fprintf(cp_err, "Error: can't repeat a negative number of times\n"); *dd = 0.0; } cend[stackp]->co_numtimes = (int) *dd; } else { fprintf(cp_err, "Error: bad repeat argument %s\n", t->wl_next->wl_word); /* CDHW */ } wl_free(t); t = NULL; /* CDHW */ } newblock; } else if (eq(wlist->wl_word, "if")) { cend[stackp]->co_type = CO_IF; cend[stackp]->co_cond = wl_copy(wlist->wl_next); /* va, wl_copy */ if (!cend[stackp]->co_cond) { fprintf(stderr, "Error: missing if condition.\n"); } newblock; } else if (eq(wlist->wl_word, "foreach")) { cend[stackp]->co_type = CO_FOREACH; if (wlist->wl_next) { wlist = wlist->wl_next; cend[stackp]->co_foreachvar = copy(wlist->wl_word); wlist = wlist->wl_next; } else { fprintf(stderr, "Error: missing foreach variable.\n"); } wlist = cp_doglob(wlist); /*CDHW Possible leak around here? */ cend[stackp]->co_text = wl_copy(wlist); newblock; } else if (eq(wlist->wl_word, "label")) { cend[stackp]->co_type = CO_LABEL; if (wlist->wl_next) { cend[stackp]->co_text = wl_copy(wlist->wl_next); /* I think of everything, don't I? */ cp_addkword(CT_LABEL, wlist->wl_next->wl_word); if (wlist->wl_next->wl_next) fprintf(cp_err, "Warning: ignored extra junk after label.\n"); } else { fprintf(stderr, "Error: missing label.\n"); } } else if (eq(wlist->wl_word, "goto")) { /* Incidentally, this won't work if the values 1 and 2 ever get * to be valid character pointers -- I think it's reasonably * safe to assume they aren't... */ cend[stackp]->co_type = CO_GOTO; if (wlist->wl_next) { cend[stackp]->co_text = wl_copy(wlist->wl_next); if (wlist->wl_next->wl_next) fprintf(cp_err, "Warning: ignored extra junk after goto.\n"); } else { fprintf(stderr, "Error: missing label.\n"); } } else if (eq(wlist->wl_word, "continue")) { cend[stackp]->co_type = CO_CONTINUE; if (wlist->wl_next) { cend[stackp]->co_numtimes = scannum(wlist-> wl_next->wl_word); if (wlist->wl_next->wl_next) fprintf(cp_err, "Warning: ignored extra junk after continue %d.\n", cend[stackp]->co_numtimes); } else { cend[stackp]->co_numtimes = 1; } } else if (eq(wlist->wl_word, "break")) { cend[stackp]->co_type = CO_BREAK; if (wlist->wl_next) { cend[stackp]->co_numtimes = scannum(wlist-> wl_next->wl_word); if (wlist->wl_next->wl_next) fprintf(cp_err, "Warning: ignored extra junk after break %d.\n", cend[stackp]->co_numtimes); } else { cend[stackp]->co_numtimes = 1; } } else if (eq(wlist->wl_word, "end")) { /* Throw away this thing. */ if (!cend[stackp]->co_parent) { fprintf(stderr, "Error: no block to end.\n"); cend[stackp]->co_type = CO_UNFILLED; } else if (cend[stackp]->co_prev) { cend[stackp]->co_prev->co_next = NULL; x = cend[stackp]; cend[stackp] = cend[stackp]->co_parent; tfree(x); x = NULL; } else { x = cend[stackp]; cend[stackp] = cend[stackp]->co_parent; cend[stackp]->co_children = NULL; tfree(x); x = NULL; } } else if (eq(wlist->wl_word, "else")) { if (!cend[stackp]->co_parent || (cend[stackp]->co_parent->co_type != CO_IF)) { fprintf(stderr, "Error: misplaced else.\n"); cend[stackp]->co_type = CO_UNFILLED; } else { if (cend[stackp]->co_prev) cend[stackp]->co_prev->co_next = NULL; else cend[stackp]->co_parent->co_children = NULL; cend[stackp]->co_parent->co_elseblock = cend[stackp]; cend[stackp]->co_prev = NULL; } } else { cend[stackp]->co_type = CO_STATEMENT; cend[stackp]->co_text = wl_copy(wlist); } if (!cend[stackp]->co_parent) { x = cend[stackp]; /* We have to toss this do-while loop in here so * that gotos at the top level will work. */ do { nn = 0; /* CDHW */ i = doblock(x, &nn); switch (*i) { case NORMAL: break; case BROKEN: fprintf(cp_err, "Error: break not in loop or too many break levels given\n"); break; case CONTINUED: fprintf(cp_err, "Error: continue not in loop or too many continue levels given\n"); break; default: x = findlabel(i, control[stackp]); if (!x) fprintf(cp_err, "Error: label %s not found\n", i); } if (x) x = x->co_next; } while (x); } wl_free(wlist); wlist = NULL; if (string) return (1); /* The return value is irrelevant. */ } wl_free(wlist); wlist = NULL; return (0); /* va: which value? */ } /* This blows away the control structures... */ void cp_free_control(void); /* needed by resetcontrol */ void cp_resetcontrol(void) { fprintf(cp_err, "Warning: clearing control structures\n"); if (cend[stackp] && cend[stackp]->co_parent) fprintf(cp_err, "Warning: EOF before block terminated\n"); /* We probably should free the control structures... */ cp_free_control(); /* va: free it */ control[0] = cend[0] = NULL; stackp = 0; cp_kwswitch(CT_LABEL, NULL); } /* Push or pop a new control structure set... */ void cp_popcontrol(void) { if (cp_debug) fprintf(cp_err, "pop: stackp: %d -> %d\n", stackp, stackp - 1); if (stackp < 1) { fprintf(cp_err, "cp_popcontrol: Internal Error: stack empty\n"); } else { /* va: free unused control structure */ ctl_free(control[stackp]); stackp--; } } void cp_pushcontrol(void) { if (cp_debug) fprintf(cp_err, "push: stackp: %d -> %d\n", stackp, stackp + 1); if (stackp > CONTROLSTACKSIZE - 2) { fprintf(cp_err, "Error: stack overflow -- max depth = %d\n", CONTROLSTACKSIZE); stackp = 0; } else { stackp++; control[stackp] = cend[stackp] = NULL; } } /* And this returns to the top level (for use in the interrupt handlers). */ void cp_toplevel(void) { stackp = 0; if (cend[stackp]) while (cend[stackp]->co_parent) cend[stackp] = cend[stackp]->co_parent; } /* va: This totally frees the control structures */ void cp_free_control(void) { int i; for (i = stackp; i >= 0; i--) ctl_free(control[i]); control[0] = cend[0] = NULL; stackp = 0; }