aboutsummaryrefslogtreecommitdiff
path: root/pkg/ecl/gram.c
diff options
context:
space:
mode:
authorJoseph Hunkeler <jhunkeler@gmail.com>2015-07-08 20:46:52 -0400
committerJoseph Hunkeler <jhunkeler@gmail.com>2015-07-08 20:46:52 -0400
commitfa080de7afc95aa1c19a6e6fc0e0708ced2eadc4 (patch)
treebdda434976bc09c864f2e4fa6f16ba1952b1e555 /pkg/ecl/gram.c
downloadiraf-linux-fa080de7afc95aa1c19a6e6fc0e0708ced2eadc4.tar.gz
Initial commit
Diffstat (limited to 'pkg/ecl/gram.c')
-rw-r--r--pkg/ecl/gram.c1503
1 files changed, 1503 insertions, 0 deletions
diff --git a/pkg/ecl/gram.c b/pkg/ecl/gram.c
new file mode 100644
index 00000000..1bc506dd
--- /dev/null
+++ b/pkg/ecl/gram.c
@@ -0,0 +1,1503 @@
+/* Copyright(c) 1986 Association of Universities for Research in Astronomy Inc.
+ */
+
+#define import_spp
+#define import_libc
+#define import_stdio
+#include <iraf.h>
+
+#include "config.h"
+#include "clmodes.h"
+#include "operand.h"
+#include "mem.h"
+#include "grammar.h"
+#include "opcodes.h"
+#include "param.h"
+#include "task.h"
+#include "errs.h"
+#include "construct.h"
+#include "ytab.h" /* pick up yacc token #defines */
+#include "proto.h"
+
+
+/*
+ * GRAM -- These routines are used by the parser and lex files grammar.y and
+ * grammar.l. Also we handle other things that are very visible to the user
+ * here too, such as cracking and running the intrinsic functions.
+ *
+ * We define our own yywrap() here to set yeof.
+ * TODO: clean up having to keep some of the strings upper, some lower case.
+ */
+
+#define SZ_PIPEFILENAME (6+4+5) /* uparm$ // pipe // XXXXX */
+#define MAX_PIPECODE 30000 /* modulus for pipecodes */
+
+#define TRIM_LEFT 1
+#define TRIM_RIGHT 2
+
+
+int yeof; /* set by yywrap when it sees eof. */
+extern int yylval; /* declared in y.tab.c */
+extern int cldebug;
+
+extern int inarglist; /* set by parser when in argument list */
+extern int parenlevel; /* nesting level of parens */
+extern int get_nscanval();
+extern int do_error; /* runtime error handling */
+int pipetable[MAXPIPES]; /* for maintaining pipe temp files */
+int nextpipe = 0;
+
+char *truestr = "yes"; /* true constant as it appears in ASCII */
+char *falsestr = "no"; /* false " */
+char *nullstr = "";
+char *undefval = ""; /* used in nextfield(), pvaldefined() */
+char *indefstr = "INDEF"; /* input or output for indef operands */
+char *indeflc = "indef"; /* lower case version. */
+char *eofstr = "EOF"; /* list file EOF or input */
+char *eoflc = "eof"; /* lower case version */
+char *errorstr = "error"; /* the error statement */
+char *err_cmdblk; /* Pointer where error detected */
+
+/* Numerical constants.
+ */
+#define BASE_E 2.7182818284590452353
+#define FOURPI 12.566370614359172953
+#define GAMMA .57721566490153286061
+#define HALFPI 1.5707963267948966192
+#define LN_10 2.3025850929940456840
+#define LN_2 .69314718055994530942
+#define LN_PI 1.1447298858494001741
+#define LOG_E .43429448190325182765
+#define PI 3.1415926535897932385
+#define RADIAN 57.295779513082320877
+#define SQRTOF2 1.4142135623730950488
+#define SQRTOFPI 1.7724538509055160273
+#define TWOPI 6.2831853071795864769
+
+/* Physical constants.
+ */
+#define AU 1.49597870691e11 /* m */
+#define GRAV_ACCEL 9.80665e0 /* m / sec^2 */
+#define GRAV_CONST 6.673e-11 /* m^3 / kg s^2 */
+#define LIGHT_YEAR 9.46053620707e15 /* m */
+#define PARSEC 3.08567758135e16 /* m */
+#define SPEED_OF_LIGHT 299792458.0 /* m / sec */
+#define SOLAR_MASS 1.98892e30 /* kg */
+
+
+
+char *epsilonstr = "epsilon";/* a small value; see hlib$mach.h */
+char *epsilonuc = "EPSILON";/* a small value; see hlib$mach.h */
+char *pistr = "PI"; /* pi */
+char *twopistr = "TWOPI"; /* 2 * pi */
+char *fourpistr = "FOURPI"; /* 4 * pi */
+char *halfpistr = "HALFPI"; /* pi / 2 */
+char *sqrtpistr = "SQRTPI"; /* sqrt (pi) */
+char *sqrttwostr = "SQRT2"; /* sqrt (2) */
+char *baseestr = "BASE_E"; /* e */
+char *ln2str = "LN_2"; /* ln (2) */
+char *ln10str = "LN_10"; /* ln (10) */
+char *lnpistr = "LN_PI"; /* ln (pi) */
+char *logestr = "LOG_E"; /* log (e) */
+char *gammastr = "GAMMA"; /* Euler's constant */
+char *radianstr = "RADIAN"; /* Radian conversion factor */
+
+char *austr = "AU"; /* Astronomical Unit */
+char *gaccelstr = "GRAV_ACCEL"; /* Gravitational Acceleration */
+char *gconststr = "GRAV_CONST"; /* Gravitational Constant */
+char *lystr = "LIGHT_YEAR"; /* Light Year (meters) */
+char *parsecstr = "PARSEC"; /* Parsec (meters) */
+char *lightstr = "SPEED_OF_LIGHT"; /* Speed of light in vacuum */
+char *solmassstr = "SOLAR_MASS"; /* Solar Mass (kg) */
+
+
+extern char cmdblk[SZ_CMDBLK+1]; /* current command block (in history.c) */
+extern ErrCom errcom;
+
+
+/* Usually the following routine is provided by the yacc library but we need
+ * our own here to signal the parser that an eof has been read.
+ */
+int
+yywrap (void)
+{
+ yeof = 1;
+ return (1);
+}
+
+/* Yacc calls this when it gets a general error. We are doing all our own
+ * error handling so just provide an entry point and store where the
+ * error occurred in the input stream.
+ */
+/* ARGSUSED */
+void
+yyerror (char *s)
+{
+ extern char *ip_cmdblk;
+
+ if (cldebug)
+ eprintf ("yyerror: %s, ip_cmdblk=%d %s\n", s, ip_cmdblk, ip_cmdblk);
+ err_cmdblk = ip_cmdblk;
+}
+
+
+/* Used by the . command: repeat whatever was last compiled.
+ * All we need to do is advance the pc up to what it would be if the
+ * command were typed in again. See grammar.y '.' rule.
+ */
+void
+rerun (void)
+{
+ register struct codeentry *cp;
+
+ do {
+ cp = coderef (pc);
+ pc += cp->c_length;
+ } while (cp->c_opcode != END);
+}
+
+
+/* CRACKIDENT -- Check given string s against keyword, set yylval, and return
+ * token. Used from grammar when see an identifier or from "?" and "??" lex
+ * rules. Make these look like identifiers for the special help commands.
+ * A few that need more complicated processing are checked separately.
+ * This is much more core efficient than putting the keywords in the
+ * lex spec and also makes the grammer very stable.
+ * TODO: sort keyword list and do binary search if things get slow.
+ * (better yet use a hashed symbol table - this list is getting huge)
+ */
+
+#define const_str(val) (kch == *(val) && !strcmp (s, (val)))
+#define retconst(val) { sprintf (sb, "%g", val); \
+ yylval = addconst (sb, OT_REAL); \
+ return (Y_CONSTANT); \
+ }
+
+int
+crackident (char *s)
+{
+ struct keywords {
+ char *k_name; /* the keyword string itself. */
+ short k_token; /* yacc %token for the keyword */
+ short k_yylval; /* the value associated with token.*/
+ };
+
+ static struct keywords kw[] = {
+
+ /* Control flow keywords.
+ */
+ { "while", Y_WHILE, 0 }, { "if", Y_IF, 0 },
+ { "else", Y_ELSE, 0 }, { "switch", Y_SWITCH, 0 },
+ { "case", Y_CASE, 0 }, { "default", Y_DEFAULT, 0 },
+ { "break", Y_BREAK, 0 }, { "next", Y_NEXT, 0 },
+ { "return", Y_RETURN, 0 }, { "goto", Y_GOTO, 0 },
+ { "for", Y_FOR, 0 }, { "procedure", Y_PROCEDURE, 0 },
+ { "begin", Y_BEGIN, 0 }, { "end", Y_END, 0 },
+ { "iferr", Y_IFERR, 0 }, { "ifnoerr", Y_IFNOERR, 0 },
+ { "then", Y_THEN, 0 },
+
+ /* Parameter and variable types.
+ */
+ { "int", Y_INT, 0 }, { "char", Y_STRING, 0 },
+ { "real", Y_REAL, 0 }, { "string", Y_STRING, 0 },
+ { "file", Y_FILE, 0 }, { "gcur", Y_GCUR, 0 },
+ { "imcur", Y_IMCUR, 0 }, { "ukey", Y_UKEY, 0 },
+ { "pset", Y_PSET, 0 }, { "bool", Y_BOOL, 0 },
+ { "struct", Y_STRUCT, 0 },
+
+ /* debugging commands.
+ */
+ { "d_d", D_D, 0 },
+ { "d_peek", D_PEEK, 0 },
+
+ { "", 0, 0 } /* sentinel; leave it here... */
+ };
+
+ static struct keywords kf[] = {
+ /* Keywords of intrinsic functions that get built into
+ * the grammar. Most intrinsics handled by intrinsic().
+ */
+ { "scan", Y_SCAN, 0 },
+ { "scanf", Y_SCANF, 0 },
+ { "fscan", Y_FSCAN, 0 },
+ { "fscanf", Y_FSCANF, 0 },
+
+ /* sentinel; leave it here... */
+ { "", 0, 0 }
+ };
+
+ register struct keywords *kp;
+ XINT oldtopd;
+ static char sch, kch; /* static storage is faster here */
+ char *scopy; /* non-makelower'd copy */
+ char sb[REALWIDTH];
+
+
+ oldtopd = topd; /* save topd */
+ scopy = comdstr(s); /* make a copy in the dictionary */
+ makelower (scopy); /* make it lower case for compares */
+ topd = oldtopd; /*restore topd but scopy still there!*/
+
+ /* Put the first character of the identifier we are searching for
+ * in local storage to permit fast rejection of keywords without all
+ * the overhead involved in a call to strcmp. This is an easy way
+ * to speed things up several times w/o coding fancy data structures.
+ */
+ sch = *scopy;
+ kch = *s; /* save original string */
+
+ /* Check for and handle special-case keywords first.
+ */
+ if (sch == *truestr && !strcmp (scopy, truestr)) {
+ yylval = addconst (truestr, OT_BOOL);
+ return (Y_CONSTANT);
+ } else if (sch == *falsestr && !strcmp (scopy, falsestr)) {
+ yylval = addconst (falsestr, OT_BOOL);
+ return (Y_CONSTANT);
+ } else if (sch == *indeflc && !strcmp (scopy, indeflc)) {
+ yylval = addconst (scopy, OT_INT);
+ return (Y_CONSTANT);
+ } else if (sch == *eoflc && !strcmp (scopy, eoflc)) {
+ yylval = addconst (CL_EOFSTR, OT_INT);
+ return (Y_CONSTANT);
+ } else if (sch == *errorstr && !strcmp (scopy, errorstr)) {
+ yylval = addconst (errorstr, OT_STRING);
+ return (Y_IDENT);
+
+ /* Check for defined numerical constants. For backwards compatability
+ * we match 'epsilon', however this particular value is deprecated by
+ * the fp_equal() builtin and we assume CL constants will be upper
+ * case strings.
+ */
+ } else if ((sch == *epsilonstr && !strcmp (scopy, epsilonstr)) ||
+ (kch == *epsilonuc && !strcmp (s, epsilonuc))) {
+ sprintf (sb, "%e", EPSILON);
+ yylval = addconst (sb, OT_REAL);
+ return (Y_CONSTANT);
+
+ } else if (const_str (pistr)) { retconst (PI);
+ } else if (const_str (twopistr)) { retconst (TWOPI);
+ } else if (const_str (fourpistr)) { retconst (FOURPI);
+ } else if (const_str (halfpistr)) { retconst (HALFPI);
+ } else if (const_str (sqrtpistr)) { retconst (SQRTOFPI);
+ } else if (const_str (sqrttwostr)) { retconst (SQRTOF2);
+ } else if (const_str (baseestr)) { retconst (BASE_E);
+ } else if (const_str (ln2str)) { retconst (LN_2);
+ } else if (const_str (ln10str)) { retconst (LN_10);
+ } else if (const_str (lnpistr)) { retconst (LN_PI);
+ } else if (const_str (logestr)) { retconst (LOG_E);
+ } else if (const_str (gammastr)) { retconst (GAMMA);
+ } else if (const_str (radianstr)) { retconst (RADIAN);
+
+ } else if (const_str (austr)) { retconst (AU);
+ } else if (const_str (gaccelstr)) { retconst (GRAV_ACCEL);
+ } else if (const_str (gconststr)) { retconst (GRAV_CONST);
+ } else if (const_str (lystr)) { retconst (LIGHT_YEAR);
+ } else if (const_str (parsecstr)) { retconst (PARSEC);
+ } else if (const_str (lightstr)) { retconst (SPEED_OF_LIGHT);
+ } else if (const_str (solmassstr)) { retconst (SOLAR_MASS);
+
+
+ } else if (!inarglist && parenlevel == 0) {
+ /* Search the keyword list; kewords are not recognized in argument
+ * lists and expressions, else unquoted strings like "for" and
+ * "file" will cause syntax errors.
+ */
+ for (kp=kw; (kch = *kp->k_name); kp++)
+ if (kch == sch)
+ if (strcmp (scopy, kp->k_name) == 0) {
+ yylval = kp->k_yylval;
+ return (kp->k_token);
+ }
+
+ } else {
+ /* Search the list of intrinsic functions.
+ */
+ for (kp=kf; (kch = *kp->k_name); kp++)
+ if (kch == sch)
+ if (strcmp (scopy, kp->k_name) == 0) {
+ yylval = kp->k_yylval;
+ return (kp->k_token);
+ }
+ }
+
+ /* S not a keyword, so it's just an identifier.
+ */
+ yylval = addconst (s, OT_STRING); /* use original */
+ return (Y_IDENT);
+}
+
+
+/* ADDCONST -- Called during parsing to convert string s into operand of
+ * type t and push it as an operand onto the dictionary (NOT the operand
+ * stack).
+ * Use dictionary because this routine is called at compile time and the
+ * operand stack is being filled with compiled code; use dictionary as
+ * a quiet workspace.
+ * Convert as per makeop().
+ * Return dictionary index of new operand entry so that it may be used as
+ * ((struct operand *)&dictionary[$1])->o_... in yacc specs.
+ */
+XINT
+addconst (char *s, int t)
+{
+ register struct operand *op;
+ XINT lasttopd;
+
+ lasttopd = topd; /* could just derefenece op */
+ op = (struct operand *) memneed (OPSIZ);
+
+ if (t == OT_STRING) {
+ /* makeop with an OT_STRING type will reuse the
+ * string pointer but we want to compile into the dictionary.
+ * fortunately, it's easy because lex has already removed any
+ * surrounding quotes.
+ */
+ op->o_type = t;
+ op->o_val.v_s = comdstr (s);
+ } else
+ *op = makeop (s, t);
+
+ return (lasttopd);
+}
+
+
+/* LISTPARAMS -- Go through the given pfile and list out its parameters on
+ * t_stdout. Give all non-hidden ones first, then all hidden ones in
+ * parentheses.
+ */
+void
+listparams (struct pfile *pfp)
+{
+ register struct param *pp;
+
+ for (pp = pfp->pf_pp; pp != NULL; pp = pp->p_np)
+ if (!(pp->p_mode & M_HIDDEN))
+ pretty_param (pp, currentask->t_stdout);
+
+ for (pp = pfp->pf_pp; pp != NULL; pp = pp->p_np)
+ if (pp->p_mode & M_HIDDEN)
+ pretty_param (pp, currentask->t_stdout);
+}
+
+
+/* PRETTY_PARAM -- Pretty print the name, value, and prompt string of
+ * a parameter on the output file. Put parens around the name=value string
+ * if a hidden parameter.
+ */
+void
+pretty_param (struct param *pp, FILE *fp)
+{
+ register char ch, *p;
+ char buf[SZ_LINE];
+ int nchars, maxch;
+
+ /* Get terminal dimensions from the environment.
+ */
+ maxch = c_envgeti ("ttyncols") - 1;
+
+ p = buf; /* name = */
+ if (pp->p_mode & M_HIDDEN)
+ *p++ = '(';
+ sprintf (p, "%0.12s = ", pp->p_name);
+ nchars = strlen (buf);
+ while (nchars < 16) {
+ fputc (' ', fp);
+ nchars++;
+ }
+ fputs (buf, fp);
+
+ /* For arrays print the index list.
+ */
+ if (pp->p_type & PT_ARRAY) {
+ int dim, d, amin, amax;
+ short *len, *off;
+ char ibuf[15]; /* Maximum length of an index range should
+ * be 13 e.g. -DDDDD:-DDDDD, plus one for the
+ * terminator, and one for good luck.
+ */
+ buf[0]= '[';
+ buf[1] = '\0';
+
+ dim = pp->p_val.v_a->a_dim;
+ len = &(pp->p_val.v_a->a_len);
+ off = &(pp->p_val.v_a->a_off);
+
+ for (d=0; d<dim; d++) {
+ amin = *(off + 2*d);
+ amax = amin + *(len + 2*d) - 1;
+
+ if (amin != 1)
+ sprintf (ibuf, "%d:%d", amin, amax);
+ else
+ sprintf (ibuf, "%d", amax);
+
+ strcat (buf, ibuf);
+ if (d+1<dim)
+ strcat (buf, ",");
+
+ if (strlen (buf) > SZ_LINE-14)
+ break;
+ }
+ strcat (buf, "]");
+ fputs (buf, fp);
+ nchars += strlen (buf);
+
+ } else if (!(pp->p_valo.o_type & OT_UNDEF)) {
+ /* For scalars print the value if available.
+ */
+ sprop (buf, &pp->p_valo);
+ if ((pp->p_type & OT_BASIC) == OT_STRING && *buf != PF_INDIRECT) {
+ fputc ('"', fp);
+ nchars++;
+ }
+ fputs (buf, fp);
+ nchars += strlen (buf);
+ if ((pp->p_type & OT_BASIC) == OT_STRING && *buf != PF_INDIRECT) {
+ fputc ('"', fp);
+ nchars++;
+ }
+ }
+
+ if (pp->p_mode & M_HIDDEN) {
+ fputc (')', fp);
+ nchars++;
+ }
+ fputc (' ', fp);
+ nchars++;
+
+ /* Advance to next field. */
+ while (nchars < 32) {
+ fputc (' ', fp);
+ nchars++;
+ }
+ /* prompt */
+ for (p=pp->p_prompt; (ch = *p++) != '\0' && nchars < maxch; nchars++)
+ switch (ch) {
+ case '\t':
+ fputs ("\\t", fp);
+ nchars++;
+ break;
+ case '\n':
+ fputs ("\\n", fp);
+ nchars++;
+ break;
+ case '\r':
+ fputs ("\\r", fp);
+ nchars++;
+ break;
+ case '\f':
+ fputs ("\\f", fp);
+ nchars++;
+ break;
+ default:
+ fputc (ch, fp);
+ }
+ fputc ('\n', fp);
+}
+
+
+/* DUMPPARAMS -- Go through the given pfile and list out its parameters on
+ * t_stdout in the form `task.param=value'.
+ */
+void
+dumpparams (struct pfile *pfp)
+{
+ register struct param *pp;
+ register FILE *fp = currentask->t_stdout;
+
+ for (pp = pfp->pf_pp; pp != NULL; pp = pp->p_np)
+ if (!(pp->p_mode & M_HIDDEN))
+ show_param (pfp->pf_ltp, pp, fp);
+
+ for (pp = pfp->pf_pp; pp != NULL; pp = pp->p_np)
+ if (pp->p_mode & M_HIDDEN)
+ show_param (pfp->pf_ltp, pp, fp);
+
+ fputs ("# EOF\n", fp);
+}
+
+
+/* SHOW_PARAM -- Print the name and value of a parameter on the output file
+ * in the format `task.param = value'.
+ */
+void
+show_param (struct ltask *ltp, struct param *pp, FILE *fp)
+{
+ char buf[SZ_LINE+1];
+ int isstr;
+
+ if (ltp)
+ fprintf (fp, "%s.%s", ltp->lt_lname, pp->p_name);
+ else
+ fputs (pp->p_name, fp);
+
+ fputs (" = ", fp);
+
+ if (!(pp->p_valo.o_type & OT_UNDEF)) {
+ sprop (buf, &pp->p_valo);
+ isstr = ((pp->p_type & OT_BASIC) == OT_STRING &&
+ *buf != PF_INDIRECT);
+ if (isstr)
+ fputc ('"', fp);
+ fputs (buf, fp);
+ if (isstr)
+ fputc ('"', fp);
+ }
+
+ fputc ('\n', fp);
+}
+
+
+/* LISTHELP -- List all the (visible) ltasks in the given package in the form
+ * of a sorted table. Used to give menus in response to ? and ?? directives.
+ */
+void
+listhelp (struct package *pkp, int show_invis)
+{
+ static int first_col=7, maxch=20, ncol=0;
+ register struct ltask *ltp;
+ register char *ip, *op;
+
+ char buf[4096], *list[MAXMENU];
+ int nltask, last_col;
+ FILE *fp;
+
+ nltask = 0;
+ last_col = c_envgeti ("ttyncols") - 1;
+
+ fp = currentask->t_stdout;
+ op = buf;
+
+ for (ltp = pkp->pk_ltp; ltp != NULL; ltp = ltp->lt_nlt) {
+ if (ltp->lt_flags & LT_INVIS && show_invis == NO)
+ continue;
+ if (nltask >= MAXMENU)
+ cl_error (E_UERR, "too many ltasks in menu");
+
+ /* Get task name. */
+ list[nltask++] = op;
+ for (ip=ltp->lt_lname; (*op = *ip++); op++)
+ ;
+
+ /* If special task, add character defining task type. */
+ if (showtype()) {
+ if (ltp->lt_flags & LT_DEFPCK)
+ *op++ = '.';
+ else if (ltp->lt_flags & LT_PSET)
+ *op++ = '@';
+ }
+
+ *op++ = EOS;
+ }
+
+ /* Sort the list and output the table. */
+ if (nltask) {
+ strsort (list, nltask);
+ strtable (fp, list, nltask, first_col, last_col, maxch, ncol);
+ }
+}
+
+
+/* LISTALLHELP -- Starting at curpack, list out all packages and their tasks
+ * in a circular fashion until get back to curpack. this is like the search
+ * path works. Label the current package in some way. Serves ?? directive.
+ * TODO: this should be optimized once a nice form is settled on.
+ */
+void
+listallhelp (int show_invis)
+{
+ register struct package *pkp;
+
+ pkp = curpack;
+ do {
+ oprintf (" %s:\n", pkp->pk_name);
+ listhelp (pkp, show_invis);
+ if ((pkp = pkp->pk_npk) == NULL)
+ pkp = reference (package, pachead);
+ } until (pkp == curpack);
+}
+
+
+/* Break a param spec of the form [[pack.]task.]param[.field] into its
+ * component parts. Full is a pointer to the full name. The others are the
+ * addresses of char pointers in the calling routine that are to be set to
+ * point to the starting address, within full, of their respective components.
+ * All dots are set to \0 and serve as eos's for each component.
+ * If any of the parts are absent, the respective pointer is made to point at
+ * the trailing null of full.
+ * The last part, field, is handled by fieldcvt(). it overwrites the first
+ * char of the field component with the proper FN_XXX character; it is not
+ * made into a string by adding an additional \0.
+ * Call error() and do not return if something goes wrong.
+ * Also used to break apart the components of full task names, [pack.]task. In
+ * this case, the task name will be found at p and the package name at t.
+ * Fair enough; just keep them straight when calling.
+ * Modified 3/26/84 by TAM to use a static buffer, rather than mutilating
+ * the input string. This caused problems when programs looped and
+ * executed the same PUSHPARAM (or similar) more than once, e.g.
+ * while (i<10) {= task.param; i += 1; }.
+ * This bug is particularly manifest when accessing arrays in specified tasks,
+ * e.g. = task.array[*]
+ */
+void
+breakout (char *full, char **pk, char **t, char **p, char **f)
+{
+ register char *cp;
+ register int npts, n;
+ char *pts[3];
+ static char buffer[SZ_LINE+1];
+
+ strncpy (buffer, full, SZ_LINE);
+ buffer[SZ_LINE] = '\0';
+
+ for (npts=0, cp=buffer; *cp; cp++) {
+ if (*cp == '.') {
+ if (*(cp+1) == EOS) {
+ *cp = EOS; /* chop dot if last character */
+ break;
+ } else {
+ if (npts > 3)
+ cl_error (E_UERR, "too many dots in param name `%s'",
+ full);
+ pts[npts++] = cp;
+ }
+ }
+ }
+
+ for (n=0; n < npts; n++)
+ *(pts[n]++) = '\0'; /* null over and skip dots */
+
+ switch (npts) {
+ case 0: /* just a simple param name without dots */
+ *p = buffer;
+ *pk = *t = *f = cp;
+ break;
+
+ case 1: /* p.f or t.p depending on f */
+ if (fieldcvt (pts[0])) {
+ /* p.f */
+ *pk = *t = cp; *p = buffer; *f = pts[0];
+ } else {
+ /* t.p */
+ *pk = *f = cp; *t = buffer; *p = pts[0];
+ }
+ break;
+
+ case 2: /* t.p.f or pk.t.p depending on f */
+ if (fieldcvt (pts[1])) {
+ /* t.p.f */
+ *pk = cp; *t = buffer; *p = pts[0]; *f = pts[1];
+ } else {
+ /* pk.t.p */
+ *pk = buffer; *t = pts[0]; *p = pts[1]; *f = cp;
+ }
+ break;
+
+ case 3: /* full spec */
+ *pk = buffer; *t = pts[0]; *p = pts[1]; *f = pts[2];
+ fieldcvt (*f);
+ break;
+ }
+}
+
+
+/* If f is a valid parameter field spec, such as p_val, then overwrite *f
+ * with the proper FN_XXX character and return YES, else return NO.
+ * Let the p_value field also be called p_filename, p_length and p_default.
+ * Call error() if f starts with p_ but is not found or if ambiguous
+ * (and abbrevs are enabled).
+ */
+int
+fieldcvt (register char *f)
+{
+ /* Field name and corresponding code tables.
+ */
+ static char *fntbl[] = {
+ "p_name", "p_type", "p_mode", "p_value", "p_minimum",
+ "p_maximum", "p_prompt", "p_filename", "p_length", "p_default",
+ "p_xtype", NULL
+ };
+ static char fctbl[] = {
+ FN_NAME, FN_TYPE, FN_MODE, FN_VALUE, FN_MIN,
+ FN_MAX, FN_PROMPT, FN_VALUE, FN_LENGTH, FN_VALUE,
+ FN_XTYPE, NULL
+ };
+
+ int kentry;
+
+ /* Do a quick screening first. returning NO just means that f does
+ * not even look like a field name.
+ */
+ if (strncmp (f, "p_", 2))
+ return (NO);
+
+ kentry = keyword (fntbl, f);
+ if (kentry == KWBAD)
+ cl_error (E_UERR, "bad param field `%s'", f);
+ else if (kentry == KWAMBIG)
+ cl_error (E_UERR, "ambiguous param field `%s'", f);
+
+ *f = fctbl[kentry];
+ return (YES);
+}
+
+
+/* Search though string table, tbl, for string s. last pointer in table
+ * should be NULL, ie, tbl[last] == NULL (not *tbl[last] == '\0').
+ * Settle for an abbreviation if they are enabled. Return KWBAD if s
+ * simply not in tbl at all, KWAMBIG if abbreviations are enabled and more
+ * than one entry in tbl would match s, else the ordinal (index) into tbl
+ * at which s matched.
+ */
+int
+keyword (register char *tbl[], register char *s)
+{
+ register int i;
+ register char *kentry;
+ int cand, len;
+
+ i = 0;
+ cand = KWBAD;
+ len = strlen (s);
+
+ if (abbrev()) {
+ for (kentry = tbl[0]; kentry; kentry = tbl[++i])
+ if (!strncmp (s, kentry, len)) {
+ if (kentry[len] == '\0')
+ return (i); /* exact hit */
+ if (cand == KWBAD)
+ cand = i;
+ else
+ cand = KWAMBIG; /* might still hit exact */
+ }
+
+ } else for (kentry = tbl[0]; kentry; kentry = tbl[++i])
+ if (!strcmp (s, kentry))
+ return (i);
+
+ return (cand);
+}
+
+
+/* Given a, possibly abbreviated, function name to run, look it up and
+ * run it if found. it gets nargs arguments from the operand stack.
+ */
+void
+intrfunc (char *fname, int nargs)
+{
+ int op_index, op;
+ int i, n, subi[2];
+ int trim_side = TRIM_LEFT|TRIM_RIGHT;
+ char *trim = " \t";
+ char sbuf[SZ_LINE+1];
+ struct operand o;
+
+ static char *ifnames[] = {
+ "abs", "access", "atan2", "cos", "defpac",
+ "defpar", "deftask", "envget", "exp", "frac",
+ "int", "log", "log10", "nscan", "max",
+ "min", "mod", "nint", "osfn", "radix",
+ "real", "sin", "sqrt", "str", "substr",
+ "tan", "mktemp", "stridx", "strlen", "imaccess",
+ "defvar", "strldx", "strstr", "strlwr", "strupr",
+ "isindef", "strlstr", "not", "or", "xor",
+ "and", "_errpop", "_errpeek", "errmsg", "errcode",
+ "errtask", "asin", "acos", "hypot", "rad",
+ "deg", "sign", "dsin", "dcos", "dtan",
+ "dasin", "dacos", "trim", "triml", "trimr",
+ "fp_equal", "datan2", "strdic",
+ NULL
+ };
+ static int optbl[] = {
+ UNOP|OP_ABS, UNOP|OP_ACCESS, BINOP|OP_ATAN2, UNOP|OP_COS,
+ UNOP|OP_DEFPAC, UNOP|OP_DEFPAR, UNOP|OP_DEFTASK, UNOP|OP_ENVGET,
+ UNOP|OP_EXP, UNOP|OP_FRAC, UNOP|OP_INT, UNOP|OP_LOG,
+ UNOP|OP_LOG10, MULTOP|OP_NSCAN, MULTOP|OP_MAX, MULTOP|OP_MIN,
+ BINOP|OP_MOD, UNOP|OP_NINT, UNOP|OP_OSFN, BINOP|OP_RADIX,
+ UNOP|OP_REAL, UNOP|OP_SIN, UNOP|OP_SQRT, UNOP|OP_STR,
+ MULTOP|OP_SUBSTR, UNOP|OP_TAN, UNOP|OP_MKTEMP, BINOP|OP_STRIDX,
+ UNOP|OP_STRLEN, UNOP|OP_IMACCESS, UNOP|OP_DEFVAR, BINOP|OP_STRLDX,
+ BINOP|OP_STRSTR, UNOP|OP_STRLWR, UNOP|OP_STRUPR, UNOP|OP_ISINDEF,
+ BINOP|OP_STRLSTR, UNOP|OP_BNOT, BINOP|OP_BOR, BINOP|OP_BXOR,
+ BINOP|OP_BAND, MULTOP|OP_ERRPOP, MULTOP|OP_ERRPEEK,MULTOP|OP_ERRMSG,
+ MULTOP|OP_ERRCODE,MULTOP|OP_ERRTASK, UNOP|OP_ASIN, UNOP|OP_ACOS,
+ BINOP|OP_HYPOT, UNOP|OP_RAD, UNOP|OP_DEG, UNOP|OP_SIGN,
+ UNOP|OP_DSIN, UNOP|OP_DCOS, UNOP|OP_DTAN, UNOP|OP_DASIN,
+ UNOP|OP_DACOS, MULTOP|OP_TRIM, MULTOP|OP_TRIML, MULTOP|OP_TRIMR,
+ BINOP|OP_FPEQUAL, BINOP|OP_DATAN2, BINOP|OP_STRDIC
+ };
+
+
+ op_index = keyword (ifnames, fname);
+ if (op_index == KWBAD)
+ cl_error (E_UERR, "unknown function `%s'", fname);
+ if (op_index == KWAMBIG)
+ cl_error (E_UERR, "ambiguous function `%s'", fname);
+
+ op = optbl[op_index];
+
+ /* if do this by shifting the cases and op to the right OP_BITS, this
+ * will compile as a jump table. not worth it until it gets larger.
+ */
+ switch (op & ~OP_MASK) {
+ case UNOP:
+ if (nargs != 1)
+ cl_error (E_UERR, e_onearg, ifnames[op_index]);
+ unop (op & OP_MASK);
+ break;
+
+ case BINOP:
+ if (nargs != 2)
+ cl_error (E_UERR, e_twoargs, ifnames[op_index]);
+ binop (op & OP_MASK);
+ break;
+
+ case MULTOP:
+ /* right now, this is just for min, max, nscan, and substr.
+ * this will have to be smarted up if add other functions.
+ */
+
+ switch (op & OP_MASK) {
+ case OP_NSCAN:
+ if (nargs > 0)
+ cl_error (E_UERR, "nscan has no arguments");
+ o.o_type = OT_INT;
+ o.o_val.v_i = get_nscanval();
+ pushop (&o);
+ break;
+
+ case OP_MAX:
+ case OP_MIN:
+ if (nargs <= 0)
+ cl_error (E_UERR, e_geonearg, ifnames[op_index]);
+ /* just leave top op if its the only one.
+ */
+ if (nargs > 1) {
+ op &= OP_MASK; /* avoid masking for every loop */
+ while (--nargs)
+ binop (op);
+ }
+ break;
+
+ case OP_SUBSTR:
+ if (nargs != 3)
+ cl_error (E_UERR, "substr requires 3 arguments");
+
+ for (n=1; n >= 0; n--) { /* get indices */
+ opcast (OT_INT);
+ o = popop();
+ subi[n] = o.o_val.v_i;
+ }
+
+ opcast (OT_STRING); /* get string arg */
+ o = popop();
+
+ if (subi[1] >= subi[0]) {
+ n = subi[1] - subi[0] + 1;
+ strncpy (sbuf, &o.o_val.v_s[subi[0]-1], n);
+ } else {
+ /* Reverse the string. */
+ n = subi[0] - subi[1] + 1;
+ for (i = 0; i < n; i++)
+ sbuf[i] = o.o_val.v_s[subi[0]-i-1];
+ }
+ sbuf[n] = '\0';
+
+ o.o_val.v_s = sbuf;
+ pushop (&o);
+ break;
+
+ case OP_TRIML:
+ trim_side &= ~TRIM_RIGHT;
+ goto trim_;
+ case OP_TRIMR:
+ trim_side &= ~TRIM_LEFT;
+ goto trim_;
+ case OP_TRIM:
+ {
+ int o1, o2;
+ struct operand istr;
+ char *index();
+ extern void *memset();
+trim_:
+ if (nargs >= 2) {
+ /* Get the chars to trim, otherwise its whitespace. */
+ opcast (OT_STRING);
+ trim = popop().o_val.v_s;
+ }
+ istr = popop();
+
+ o1 = 0;
+ o2 = strlen (istr.o_val.v_s) - 1;
+
+ memset (sbuf, 0, SZ_LINE);
+ if (trim_side & TRIM_LEFT)
+ while (index (trim, (int)istr.o_val.v_s[o1])) o1++;
+ if (trim_side & TRIM_RIGHT) {
+ while (index (trim, (int)istr.o_val.v_s[o2])) o2--;
+ istr.o_val.v_s[++o2] = '\0';
+ }
+ strncpy (sbuf, &istr.o_val.v_s[o1], o2-o1+1);
+
+ o.o_type = OT_STRING;
+ o.o_val.v_s = sbuf;
+ pushop (&o);
+ }
+ break;
+
+ case OP_ERRPOP:
+ if (nargs > 0)
+ cl_error (E_UERR, "errpop has no arguments");
+ o.o_type = OT_INT;
+ o.o_val.v_i = errcom.errflag;
+ do_error = YES;
+ errcom.nhandlers--;
+ pushop (&o);
+ break;
+
+ case OP_ERRPEEK:
+ if (nargs > 0)
+ cl_error (E_UERR, "errpeek has no arguments");
+ o.o_type = OT_INT;
+ o.o_val.v_i = errcom.errflag;
+ pushop (&o);
+ break;
+
+ case OP_ERRMSG:
+ if (nargs > 0)
+ cl_error (E_UERR, "errmsg has no arguments");
+ o.o_type = OT_STRING;
+ o.o_val.v_s = errcom.errmsg;
+ pushop (&o);
+ break;
+
+ case OP_ERRCODE:
+ if (nargs > 0)
+ cl_error (E_UERR, "errcode has no arguments");
+ o.o_type = OT_INT;
+ o.o_val.v_i = errcom.errcode;
+ pushop (&o);
+ break;
+
+ case OP_ERRTASK:
+ if (nargs > 0)
+ cl_error (E_UERR, "errmsg has no arguments");
+ o.o_type = OT_STRING;
+ o.o_val.v_s = errcom.task;
+ pushop (&o);
+ break;
+
+ default:
+ goto err;
+ }
+ break;
+
+ default:
+err: cl_error (E_IERR, e_badsw, op, "intrfunc()");
+ }
+}
+
+
+/* Convert string s to sexagesimal operand, of type OT_REAL. Set opundef()
+ * if conversion is bad somehow. Allow both h:m and h:m:s forms.
+ */
+struct operand
+sexa (char *s)
+{
+ struct operand o;
+ int n, sign;
+ int hr, minutes;
+ float sec;
+ extern double atof();
+
+ o.o_type = OT_REAL;
+ sign = (*s == '-') ? (s++, -1) : 1;
+
+ minutes = 0;
+ sec = 0.;
+ n = sscanf (s, "%d:%d:%hf", &hr, &minutes, &sec);
+ if (n < 1 || minutes < 0 || sec < 0)
+ setopundef (&o);
+ else
+ o.o_val.v_r = sign * (atof (s));
+ /* Old evaluation producing roundoff errors.
+ o.o_val.v_r = sign*(hr + ((float)minutes)/60. + sec/3600.);
+ */
+
+ return (o);
+}
+
+/* Convert a sexagesimal real back to an index range.
+ */
+void
+sexa_to_index (double r, int *i1, int *i2)
+{
+ int sgn;
+
+ if (r < 0) {
+ sgn = -1;
+ r = -r;
+ } else
+ sgn = 1;
+
+ *i1 = (int) r; /* add a little for round-off*/
+ *i2 = (int) (60.0e0 * (r - *i1) + .001);
+ *i1 = sgn * *i1;
+}
+
+
+/* ADDPIPE -- Generate a new pipe file name and push it onto the pipe stack.
+ * The strategy is to generate a unique pipefile name of the form "pipeXXX",
+ * where XXX is an integer of 5 digits or less which is what is saved on the
+ * pipe stack. Return a pointer to the name of the new pipefile.
+ */
+char *
+addpipe (void)
+{
+ static int pipecode = 0;
+ char *pipefile();
+
+ if (pipecode == 0)
+ pipecode = c_getpid();
+
+ /* Get unique pipefile name described by a simple integer.
+ */
+ do {
+ /*
+ * There seems to be a problem with this code in the VMS compiler.
+ * It has been changed to a form which will work for UNIX and VMS.
+ *
+ * pipecode = (pipecode++ % MAX_PIPECODE);
+ */
+ pipecode %= MAX_PIPECODE;
+
+ /* There can be applications where multiple CL are spawned in
+ * relatively short order so that the PIDs are close. Incrementing
+ * the least significant digits can result in duplications. So
+ * instead we use the lower digits as the "unique" part and
+ * increment the higer digits.
+ *
+ * pipecode++;
+ */
+ pipecode += 1000;
+
+ } while (c_access (pipefile(pipecode),0,0) == YES);
+
+ pipetable[nextpipe++] = pipecode;
+ if (nextpipe >= MAXPIPES)
+ cl_error (E_UERR, "Too many pipes");
+
+ return (pipefile (pipecode));
+}
+
+
+/* GETPIPE -- Get the name of the last pipefile.
+ */
+char *
+getpipe (void)
+{
+ char *pipefile();
+
+ if (nextpipe == 0)
+ cl_error (E_IERR, "Pipestack underflow");
+ return (pipefile (pipetable[nextpipe-1]));
+}
+
+
+/* DELPIPES -- Delete N pipefiles (the actual file may not have been created
+ * yet), and pop N pipes off the pipe stack. If N is zero, all pipefiles are
+ * deleted and the pipestack is cleared (i.e., during error recovery).
+ */
+void
+delpipes (register int npipes)
+{
+ register int pipe;
+ char *pipefile();
+
+ if (npipes == 0) {
+ while (nextpipe > 0)
+ c_delete (pipefile (pipetable[--nextpipe]));
+ } else {
+ while (npipes-- > 0) {
+ if ((pipe = --nextpipe) < 0)
+ cl_error (E_IERR, "Pipestack underflow");
+ c_delete (pipefile (pipetable[pipe]));
+ }
+ }
+}
+
+
+/* PIPEFILE -- Given the pipecode, format a pipefile name in static internal
+ * buffer and return pointer to pipefile name to caller.
+ */
+char *
+pipefile (int pipecode)
+{
+ static char fname[SZ_PIPEFILENAME+1];
+ char *dir;
+ char *envget();
+
+ /* Put pipefiles in 'pipes' or 'uparm' if defined, else use tmp. Do
+ * not put pipe files in current directory or pipe commands will fail
+ * when used in someone elses directory.
+ */
+ if (envget ("pipes") != NULL)
+ dir = "pipes$";
+ else if (envget ("uparm") != NULL)
+ dir = "uparm$";
+ else
+ dir = "tmp$";
+ sprintf (fname, "%spipe%d", dir, pipecode);
+
+ return (fname);
+}
+
+
+/* LOOPINCR -- increments the loop counter and stores the destination
+ * address for NEXT statements. It should be called just before the
+ * destination is compiled.
+ */
+void
+loopincr (void)
+{
+ if (nestlevel >= MAX_LOOP)
+ cl_error (E_UERR, "Nesting too deeply.");
+
+ brkdest[nestlevel] = 0;
+ nextdest[nestlevel] = pc;
+ nestlevel++;
+}
+
+
+/* LOOPDECR -- decrements the loop counter, and if the break destination
+ * has been set it resolves the GOTO statement which has been made
+ * the target of BREAK's.
+ */
+void
+loopdecr (void)
+{
+ int p_goto;
+
+ p_goto = brkdest[--nestlevel];
+ if (p_goto != 0)
+ coderef(p_goto)->c_args = pc - p_goto - SZ_CE;
+}
+
+
+/* SETSWITCH -- creates the jumptable which will be used in the SWITCH.
+ * On entry to setswitch the stack contains a pointer to the SWITCH
+ * operand, and pointers to the first and last operands of each
+ * CASE and DEFAULT block, i.e. the CASE and DEFAULT operands and the
+ * GOTO operands which terminate each block.
+ * The jumptable is created at the location of the current pc.
+ */
+void
+setswitch (void)
+{
+ int code, jmp, njump, assgn, oper, delta;
+
+ /* First get the size of the jump table by reading
+ * backwards on the stack until we find the switch
+ * statement.
+ */
+ oper = topcs;
+ code = coderef(stack[oper])->c_opcode;
+ njump = 2;
+
+ while (code != SWITCH) {
+ if (code == CASE)
+ njump++;
+ else if (code != GOTO && code != DEFAULT)
+ cl_error (E_UERR, "Corrupt stack in SWITCH analysis.");
+
+ oper++;
+ code = coderef(stack[oper])->c_opcode;
+ }
+
+ assgn = stack[oper];
+
+ /* To create the jump table we read the control stack
+ * to get the addresses of each of the case statements
+ * and the default statement. The values associated with
+ * each case statement are stored in that operand. The
+ * addresses are popped and transferred to the jump table.
+ * The first location in the jump table is reserved for
+ * the DEFAULT statement and is 0 if this is not defined.
+ * We know the size of the jump table, so as we pop off
+ * the goto statements at the end of the CASE blocks
+ * we can fill in the addresses.
+ */
+ jmp = pc + 1;
+ oper = pop();
+ code = coderef(oper)->c_opcode;
+ stack[pc] = 0;
+
+ while (code != SWITCH) {
+
+ switch (code) {
+ case DEFAULT:
+ stack[pc] = oper-assgn;
+ break;
+
+ case CASE:
+ stack[jmp++] = oper-assgn;
+ break;
+
+ case GOTO:
+ delta = pc + njump - oper - SZ_CE;
+ coderef(oper)->c_args = delta;
+ break;
+
+ default:
+ cl_error (E_UERR, "Corrupt stack in SWITCH analysis.");
+ }
+
+ oper = pop();
+ code = coderef(oper)->c_opcode;
+ }
+
+ stack[jmp] = 0; /* Fill in terminator. */
+
+ /* Put address of jump table in ASSIGN operand.
+ */
+ coderef(oper)->c_args = pc - oper;
+ pc += njump;
+
+ /* Fill in address of GOTO following ASSIGN.
+ */
+ oper += SZ_CE;
+ coderef(oper)->c_args = pc - oper - SZ_CE;
+}
+
+
+/* IN_SWITCH -- determines whether a CASE or DEFAULT block is
+ * legal at the current location.
+ */
+int
+in_switch (void)
+{
+ int oper, code, oper2, code2, status;
+
+ oper = pop();
+ code = coderef(oper)->c_opcode;
+ status = 1;
+
+ switch (code) {
+ case SWITCH:
+ push (oper);
+ break;
+
+ case GOTO:
+ /* Previous operand must be DEFAULT or CASE.
+ */
+ oper2 = pop();
+ code2 = coderef(oper2)->c_opcode;
+ if (code2 != CASE && code2 != DEFAULT)
+ status = 0;
+ push (oper2);
+ push (oper);
+ break;
+
+ default:
+ status = 0;
+ }
+
+ return (status);
+}
+
+
+/* CASESET -- Fill in the values for which the current case block is to be
+ * executed.
+ */
+void
+caseset (memel *parg, int ncaseval)
+{
+ struct operand *o;
+ static char *badcase = "Invalid case constant.";
+ int ival;
+
+ for (ival = 0; ival < ncaseval; ival++) {
+ memel p = pop();
+ o = (struct operand *) p;
+
+ if (o->o_type == OT_STRING) {
+ /* Only chars, not full strings.
+ */
+ if (*o->o_val.v_s == 0)
+ cl_error (E_UERR, badcase);
+ if (*(o->o_val.v_s + 1) != 0)
+ cl_error (E_UERR, badcase);
+
+ *parg++ = (int) *o->o_val.v_s;
+
+ } else if (o->o_type == OT_INT) {
+ *parg++ = o->o_val.v_i;
+
+ } else
+ cl_error (E_UERR, badcase);
+ }
+}
+
+
+/* SETLABEL -- called when a label is first seen. It allocates
+ * space for the label on the dictionary and also copies the
+ * label name onto the dictionary. The label is placed at the
+ * top of a linked list.
+ */
+struct label *
+setlabel (struct operand *name)
+{
+ struct label *p;
+
+ p = (struct label *) memneed (sizeof(struct label));
+ p->l_name = comdstr (name->o_val.v_s);
+
+ if (label1 == NULL)
+ p->l_next = NULL;
+ else
+ p->l_next = label1;
+
+ label1 = p;
+ return (p);
+}
+
+
+/* GETLABEL -- returns the label struct corresponding to the string
+ * name, or NULL if the label has not been defined.
+ */
+struct label *
+getlabel (struct operand *name)
+{
+ struct label *l;
+
+ l = label1;
+ while (l != NULL) {
+ if (!strcmp (name->o_val.v_s, l->l_name))
+ return (l);
+ l = l->l_next;
+ }
+
+ return (NULL);
+}
+
+
+/* SETIGOTO -- maintains the list of indirect goto's.
+ * Note that an indirect GOTO is identical in format to a
+ * normal GOTO. The argument, instead of pointing to the destination
+ * is used as the list pointer. When the destination is defined,
+ * the GOTO is taken out of the indirect list.
+ */
+void
+setigoto (int loc)
+{
+ if (igoto1 < 0)
+ coderef(loc)->c_args = -1;
+ else
+ coderef(loc)->c_args = igoto1;
+
+ igoto1 = loc;
+}
+
+
+/* UNSETIGOTO -- takes a GOTO out of the indirect list so that
+ * the target may be put in the argument.
+ */
+void
+unsetigoto (int loc)
+{
+ int last, curr;
+
+ last = NULL;
+ curr = igoto1;
+
+ while (curr != loc) {
+ last = curr;
+ curr = coderef(curr)->c_args;
+ }
+
+ if (last == NULL)
+ igoto1 = coderef(curr)->c_args;
+ else
+ coderef(last)->c_args = coderef(curr)->c_args;
+}
+
+
+/* MAKE_IMLOOP -- compiles the meta-code for the indexing of arrays in
+ * implicit array loops e.g. a[*,5].
+ */
+int
+make_imloop (int i1, int i2)
+{
+ int mode;
+
+ if (n_oarr) {
+ /* Array limits already defined, check for agreement.
+ */
+ if (i1 != oarr_beg[i_oarr] || i2 != oarr_end[i_oarr])
+ cl_error (E_UERR, "Inconsistent open refs.\n");
+ mode = -1;
+ } else {
+ oarr_beg[i_oarr] = i1;
+ oarr_end[i_oarr] = i2;
+ if (i_oarr)
+ mode = -1;
+ else
+ /* This is the PUSHINDEX which will
+ * initialize the loop variables.
+ */
+ mode = 0;
+ }
+ i_oarr++;
+
+ return (mode);
+}
+
+
+/* Y_TYPEDEF -- Convert a type specifier keyword into a datatype code.
+ */
+int
+y_typedef (char *key)
+{
+ if (strcmp (key, "string") == 0 || strcmp (key, "char") == 0)
+ return (V_STRING);
+ else if (strcmp (key, "int") == 0)
+ return (V_INT);
+ else if (strcmp (key, "real") == 0)
+ return (V_REAL);
+ else if (strcmp (key, "bool") == 0)
+ return (V_BOOL);
+ else if (strcmp (key, "file") == 0)
+ return (V_FILE);
+ else if (strcmp (key, "gcur") == 0)
+ return (V_GCUR);
+ else if (strcmp (key, "imcur") == 0)
+ return (V_IMCUR);
+ else if (strcmp (key, "ukey") == 0)
+ return (V_UKEY);
+ else if (strcmp (key, "pset") == 0)
+ return (V_PSET);
+ else if (strcmp (key, "struct") == 0)
+ return (V_STRUCT);
+ else
+ cl_error (E_UERR, "illegal type specifier `%s'", key);
+ /*NOTREACHED*/
+ return (0);
+}
+
+
+/* P_POSITION -- Called when we get a syntax error in the parser. Print
+ * the current cmdblk and point to the offending token.
+ */
+void
+p_position (void)
+{
+ register int i;
+
+ eprintf ("**: %s ", cmdblk); /* '\n' in cmdblk */
+
+ for (i=0; i < err_cmdblk-cmdblk; i++)
+ eprintf ("%c", ((cmdblk[i] == '\t') ? '\t' : ' ') );
+
+ eprintf ("^\n");
+}