#include "PROTO.h" /* * Copyright (c) 1989 University of Maryland * Department of Computer Science. All rights reserved. * Permission to copy for any purpose is hereby granted * so long as this copyright notice remains intact. */ #ifndef lint static char rcsid[] = "$Header: /usr/src/local/tex/local/mctex/lib/RCS/sdecode.c,v 3.4 89/11/06 15:01:07 chris Exp $"; #endif #include #include #include "types.h" #include "sdecode.h" #include /* for strcmp */ #include /* for free */ static char cclass[256]; /* XXX assumes 8-bit char */ #define CCL_SPACE 0x01 /* white space */ #define CCL_SEMI 0x02 /* `semicolon' (statement separator) */ #define isstopc(c) (cclass[c] != 0) /* both space and semi char */ static int sdset; /* flag says whether cclass[] set up */ extern char *DVIFileName; #ifndef BLOCK_COPY /* * Copy the text at `src' `downward' in memory for `len' bytes. * This is like strcpy or memcpy, but handles one direction of * overlap (when dst < src). */ static void movedown(src, dst, len) register char *src, *dst; register int len; { while (--len >= 0) *dst++ = *src++; } #endif /* * We have a number of states to do word interpretation. Outside words, * we skip blanks and `semicolons'. The first non-blank, non-`semi' enters * WORD state; in WORD state, double quote characters switch to * QUOTE state, after which another double quote switches back to * WORD state, and blanks and `semi' end WORD state, switching to DONE. * While in WORD or QUOTE states, `c produces c for all characters c; * to do this, we use two more states. */ #define S_DULL 0 /* not in a word */ #define S_WORD 1 /* in a word */ #define S_QUOTE 2 /* inside "" in a word */ #define S_BKQT1 3 /* not in "", but just read one ` */ #define S_BKQT2 4 /* in "", but just read one ` */ #define S_DONE 5 /* found end of word */ /* * Structure used to communicate between the various functions. */ /* * `Edit' the text in the buffer in-place. * Return the count of characters resulting from the edit (e.g., * from `foo`"bar' the count would be 7). * Set di_call if we reach state S_DONE by encountering a `semicolon'. * The edited text begins wherever di_cp points. * We begin the edit in the state given by di_lexstate. */ static int canon(register struct decode_info *di) { register int state, c, n; register char *cp, *outp, *s; state = di->di_lexstate; n = di->di_nch; cp = outp = di->di_cp; while (--n >= 0) { c = *cp++; switch (state) { case S_DULL: if (isstopc(c)) continue; state = S_WORD; /* FALLTHROUGH */ case S_WORD: if (c == '"') { state = S_QUOTE; continue; } if (c == '`') { state = S_BKQT1; continue; } /* look for `semicolon' (call) characters */ if (cclass[c] & CCL_SEMI) di->di_call = 1; if (isstopc(c)) { state = S_DONE; goto done; } break; case S_QUOTE: if (c == '`') { state = S_BKQT2; continue; } if (c == '"') { state = S_WORD; continue; } break; case S_BKQT1: state = S_WORD; break; case S_BKQT2: state = S_QUOTE; break; default: panic("sdecode canon"); /* NOTREACHED */ } *outp++ = c; } n = 0; done: c = outp - di->di_cp; /* wrote this many chars */ di->di_lexstate = state;/* save new state */ di->di_nch = n; /* and new counts and pointers */ di->di_cp = cp; return (c); } #define WDMAX BUFSIZ /* when testing, make this small */ /* * Read a word from the input file. * Returns NULL (and sets di_call) at end of \special. * * We gather up to WDMAX + k characters at a time into an input * buffer, and lex them using `canon' above. The result is at most * as long as the original text. Since a single legal word might * be up to WDMAX bytes long, k must be at least 1 (for the trailing * NUL). If the input word is longer, we flush the trailing part, * using the k `extra' bytes (and again k must be at least 1). * * If there is a current keyword and it is in the current buffer * (di_bkw != 0), and we have to move existing buffer text around * or write over it with new file text, we save the keyword and * clear di_bkw. */ static char *word(register struct decode_info *di, int quietly) { register int n; register char *p; register int len, trunc = 0; static char inbuf[WDMAX + 20]; /* k=20 */ len = 0; /* no word bytes yet */ p = di->di_cp; /* where canon() writes (nil if first time) */ di->di_lexstate = S_DULL; /* * Loop invariants: * len is in [0..WDMAX) * di_lexstate != S_DONE */ do { /* if the buffer is empty, refill it */ if (di->di_nch == 0) { /* compute remaining buffer space */ n = sizeof inbuf - len; /* read n bytes or rest of string, whichever smaller */ if (di->di_nfilech < n && (n = di->di_nfilech) == 0) { /* `eof': fake up a call-terminator */ di->di_call = 1; if (di->di_lexstate == S_DULL) return (di->di_word = NULL); break; /* take partial word */ } /* save keyword if this will clobber it */ if (di->di_bkw) { di->di_kw = strsave(di->di_kw); di->di_bkw = 0; } /* if there is some canon text, move it to front */ if (len && p > inbuf) { #ifdef BLOCK_COPY bcopy(p, inbuf, len); #else movedown(p, inbuf, len); #endif } /* canon text is (or will be) at front now */ p = inbuf; di->di_cp = p + len; if (fread(di->di_cp, 1, n, di->di_file) != n) { if (ferror(di->di_file)) error(1, -1, "error reading %s", DVIFileName); GripeUnexpectedDVIEOF(); } di->di_nfilech -= n; di->di_nch = n; } n = canon(di); if ((len += n) > WDMAX) { len = WDMAX; trunc = 1; } } while (di->di_lexstate != S_DONE); p[len] = 0; /* * If the word was too long (and hence truncated), complain * (unless we are quietly skipping over the remains of a * broken \special). */ if (trunc && !quietly) { /* word was too long; complain */ error(0, 0, #if WDMAX > 20 "over-long word in \\special: `%.20s...'", #else "over-long word in \\special: `%s...'", #endif p); error(0, 0, "(truncated to %d characters)", WDMAX); } return (di->di_word = p); } /* * Initialise the character class table. */ extern void SDsetclass(register char *spaces, register char *semis) { register int c; if (spaces == NULL) spaces = "\b\t\n\f\r "; if (semis == NULL) semis = ";\n"; for (c = 0; c < sizeof cclass / sizeof *cclass; c++) cclass[c] = 0; while ((c = *spaces++) != 0) cclass[c] |= CCL_SPACE; while ((c = *semis++) != 0) cclass[c] |= CCL_SEMI; sdset = 1; } /* * Perform a binary search for the given string in the given table. */ static struct sdecode *lookup(register char *str, register struct sdecode *p, register int max) { register struct sdecode *m; register int dir; while (max > 0) { /* * Look at the median; pick the right hand one if * there are two such entries (when `max' is even). * Set dir positive if str is `greater than' m->sd_name, * negative if it is `less'. If not equal, move * left or right accordingly. When max is even and * we move right, we have to use (max-1)/2 since we * broke the tie towards the right. */ m = p + (max >> 1); if ((dir = *str - *m->sd_name) == 0) dir = strcmp(str, m->sd_name); if (dir == 0) /* found */ return (m); if (dir > 0) { /* str > mid: move right */ p = m + 1; max = (max - 1) >> 1; } else /* str < mid: move left */ max >>= 1; } return (NULL); } /* * Decode a series of \special keywords. * * The table points to a sorted list of keywords to match. * On a match, the appropriate function is called with appropriate * arguments. * * Characters in `semi' (if not NULL) act like `statement separators'. * For instance, decoding with semi=>";\n" means that * * \special{foo 1; bar 2 4^^Jbaz 3} * * tries to call foo(1), bar(2,4), and baz(3) (in that order). */ extern void SDecode(FILE *fp, register i32 len, struct sdecode *table, int tsize) { register char *cp; register struct sdecode *tp; register int h; struct decode_info di; /* set defaults, if necessary */ if (!sdset) SDsetclass((char *)NULL, (char *)NULL); di.di_nch = 0; di.di_cp = NULL; di.di_nfilech = len; di.di_file = fp; di.di_bkw = 0; for (;;) { di.di_call = 0; if ((cp = word(&di, 0)) == NULL) return; /* (all done) */ tp = lookup(cp, table, tsize); if (tp == NULL) { error(0, 0, "Warning: unrecognised \\special keyword `%s' ignored", cp); /* di_call is not set, so fall through */ } else { di.di_kw = cp; di.di_bkw = 1; args(tp, &di); if (!di.di_call) error(0, 0, "Warning: extra arguments to \\special `%s' ignored", di.di_kw); if (!di.di_bkw) free(di.di_kw); di.di_bkw = 0; } /* eat extra arguments, or arguments to an unknown function */ while (!di.di_call) (void) word(&di, 1); } } /* * Gather up the arguments to the given function, then (if all goes well) * call that function with those arguments. On return, if di_call is not * set, there were extra arguments left over. */ static void args(register struct sdecode *tp, register struct decode_info *di) { char *fmt; int n, room, f1; i32 *ip, i[4]; double d[2]; switch (tp->sd_args) { case sda_none: (*tp->sd_fn)(tp->sd_name); return; case sda_s: fmt = "s"; if (scan(di, &fmt)) break; (*tp->sd_fn)(tp->sd_name, di->di_word); return; case sda_d: fmt = "d"; if (scan(di, &fmt, &i[0])) break; (*tp->sd_fn)(tp->sd_name, i[0]); return; case sda_f: fmt = "f"; if (scan(di, &fmt, &d[0])) break; (*tp->sd_fn)(tp->sd_name, d[0]); return; case sda_dd: fmt = "dd"; if (scan(di, &fmt, &i[0], &i[1])) break; (*tp->sd_fn)(tp->sd_name, i[0], i[1]); return; case sda_ff: fmt = "ff"; if (scan(di, &fmt, &d[0], &d[1])) break; (*tp->sd_fn)(tp->sd_name, d[0], d[1]); return; case sda_ddddff: fmt = "ddddff"; if (scan(di, &fmt, &i[0], &i[1], &i[2], &i[3], &d[0], &d[1])) break; (*tp->sd_fn)(tp->sd_name, i[0], i[1], i[2], i[3], d[0], d[1]); return; case sda_nd: f1 = 'd'; goto array; case sda_nx: f1 = 'x'; goto array; case sda_rest: (*tp->sd_fn)(tp->sd_name, (i32)di->di_nch, di->di_cp, (i32)di->di_nfilech); di->di_call = 1; di->di_nch = 0; di->di_nfilech = 0; return; default: panic("sdecode args(): sd_argtype"); /* NOTREACHED */ } /* * If we get here, there is a missing or incorrect argument. */ badarg(*fmt, di); return; array: ip = (i32 *)malloc((unsigned)(room = 10) * sizeof(i32)); if (ip == NULL) goto nomem; n = 0; while (!di->di_call && word(di, 0) != NULL) { if (scan_i(di->di_word, f1, &i[0])) { badarg(f1, di); free((char *)ip); return; } if (n == room) { room += 10; ip = (i32 *)realloc((char *)ip, (unsigned)room * sizeof(i32)); if (ip == NULL) goto nomem; } ip[n++] = i[0]; } (*tp->sd_fn)(tp->sd_name, n, ip); free((char *)ip); return; nomem: /* * might as well stop: if malloc() failed, we will probably get * nowhere fast anyway. */ error(1, -1, "ran out of memory allocating %d bytes for \\special `%s'", n * sizeof(i32), di->di_kw); /* NOTREACHED */ } /* * Complain about an argument. Scan() has set di_word to NULL if * it was missing; otherwise it is incorrect. */ static void badarg(int c, struct decode_info *di) { char *s; switch (c) { case 'd': s = "decimal"; break; case 'f': s = "floating"; break; case 's': s = "string"; /* must be `missing', not `bad' */ break; case 'x': s = "hexadecimal"; break; default: panic("sdecode badarg(%c)", c); /* NOTREACHED */ } if (di->di_word == NULL) error(0, 0, "missing %s argument for \\special `%s'", s, di->di_kw); else { error(0, 0, "bad %s argument to \\special `%s': `%s'", s, di->di_kw, di->di_word); error(0, 0, "(this part of the \\special will be ignored)"); while (!di->di_call) (void) word(di, 1); } } static int scan(struct decode_info* di, char** fmtp, ...) { register char *fmt; register int c; va_list ap; va_start(ap, fmtp); fmt = *fmtp; while ((c = *fmt++) != 0) { if (di->di_call || word(di, 0) == NULL) { di->di_word = NULL; goto out; } switch (c) { /* BEWARE, can only handle one string */ case 's': /* always accepted */ break; case 'd': case 'x': if (scan_i(di->di_word, c, va_arg(ap, i32 *))) goto out; break; case 'f': if (scan_d(di->di_word, va_arg(ap, double *))) goto out; break; default: panic("sdecode scan() *fmt=%c", c); /* NOTREACHED */ } } c = 0; out: va_end(ap); *fmtp = fmt - 1; return (c); } static int scan_i(char *p, int c, i32 *result) { #ifdef HAVE_STRTOL char *end; long strtol(); *result = strtol(p, &end, c == 'x' ? 16 : 10); return (*end); #else long l; char junk; if (sscanf(p, c == 'x' ? "%lx%c" : "%ld%c", &l, &junk) != 1) return (1); *result = l; return (0); #endif } static int scan_d(char *p, double *result) { #ifdef HAVE_STRTOD char *end; double strtod(); *result = strtod(p, &end); return (*end); #else char junk; return (sscanf(p, "%lf%c", result, &junk) != 1); #endif }