1 /*-
2  * Copyright (c) 1999 Timmy M. Vanderhoek
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  *
26  */
27 
28 #ifndef lint
29 static const char rcsid[] =
30   "$FreeBSD: src/usr.bin/more/ncommand.c,v 1.11 2000/05/14 03:30:58 hoek Exp $";
31 #endif /* not lint */
32 
33 /*
34  * These functions handle evaluation of primitive commands.  In general,
35  * commands either come from macro.h as it expands user input, or
36  * directly from a .morerc file (in which case only a limited set of
37  * commands is valid.
38  *
39  * Commands are matched by command() against a command table.  The rest
40  * of the command line string passed to command() is then passed to a
41  * function corresponding to the given command.  The specific command
42  * function evaluates the remainder of the command string with the help
43  * of getstr() and getint(), both of which also handle variable expansion
44  * into a single word.  Specific command functions should not try grokking
45  * the command string by themselves.
46  *
47  * A command and its arguments are terminated by either a NUL or a ';'.
48  * This is recognized by both getstr() and getint().  Specific command
49  * functions return a pointer to the end of the command (and its arguments)
50  * thus allowing command() to accept commands that are chained together
51  * by semicolons.  If a specific command fails, it returns NULL preventing
52  * any proceeding commands (chained together with ';') from being parsed.
53  * This can be considered as a feature.
54  *
55  * All variable-access functions and variable state are internal to
56  * ncommand.c.  The sole exceptions are setvar() and setvari().
57  */
58 
59 #include <sys/param.h>
60 
61 #include <assert.h>
62 #include <ctype.h>
63 #include <stdarg.h>
64 #include <stdlib.h>
65 #include <string.h>
66 #include <stdio.h>
67 
68 #include "less.h"
69 #include "pathnames.h"
70 
71 static int getint __P((long *numb, const char **line));
72 
73 static getstr_free();
74 static void **getstr_raisectxt();
75 
76 /* The internal command table. */
77 
78 static const char *cscroll(), *cquit(), *cerror(), *ceval(), *cset(),
79                   *cflush(), *cmacro(), *caskfile(), *cusercom(),
80                   *ctags(), *chscroll(), *cgomisc(), *cgoend(), *csearch(),
81                   *cstat(), *cdeftog(), *ccondition(), *chelp(), *cfile(),
82                   *cfile_list(), *cedit(), *cmark(), *creadrc(), *clistbmarks();
83 
84 /* An enum identifying each command */
85 enum cident {
86 	DEFTOG,          /* Initialize toggle values */
87 	EVAL,            /* Evaluate a subexpression */
88 	SET,             /* Set a variable */
89 	MACRO,           /* Create a new macro */
90 	ERROR,           /* Print a notification message */
91 	CONDITION,       /* Condition evaluation of (almost) _all_ commands */
92 	CONDITION_N,     /* CONDITION with an inverse truth table */
93 	CONDITION_TOGGLE,/* Switch to the reverse sense of the last condition */
94 	USERCOM,         /* Get the user to type in a direct command */
95 	READRC,          /* Read-in a named rc file */
96 	QUIT,            /* Quit */
97 	HELP,            /* Help */
98 	FLUSH,           /* Flush file buffer and friends */
99 	REPAINT,         /* Redraw the screen (useful if it got trashed) */
100 	FORW_SCROLL,     /* Scroll forward N lines */
101 	BACK_SCROLL,     /* Scroll backward N lines */
102 	FORW,            /* Jump or scroll forward N lines */
103 	BACK,            /* Jump or scroll backwards N lines */
104 	LSCROLL,         /* Scroll horizontally leftwards */
105 	RSCROLL,         /* Scroll horizontally to the right */
106 	GOLINE,          /* Goto line number N */
107 	GOPERCENT,       /* Goto percent N of the file */
108 	GOEND,           /* Goto the end of the file */
109 	EDIT,            /* Edit the current file, using getenv(EDITOR) */
110 	ASKFILE,         /* Ask for a different, new file */
111 	CFILE,           /* Page/view the N'th next or prev file */
112 	FILE_LIST,       /* List the files that CFILE moves around in */
113 	STAT,            /* List detailed file statistics in prompt */
114 	MAGICASKSEARCH,  /* Ask for a regexp search string */
115 	SEARCH,          /* Search for a regexp */
116 	RESEARCH,        /* Search for the next N'th occurrence */
117 	SETMARK,         /* Set a bookmark to the current position */
118 	GOMARK,          /* Goto a previously set bookmark */
119 	LISTBMARKS,      /* List the bookmark hints */
120 	ASKFTAG,         /* Ask for a tag to goto */
121 	NEXTFTAG,        /* Move forward N in the tag queue */
122 	PREVFTAG,        /* Move backwards N in the tag queue */
123 };
124 
125 static struct ctable {
126 	const char *cname;
127 	enum cident cident;
128 	const char * (*cfunc)(enum cident, const char *args);
129 } ctable[] = {
130 	{ "deftog",           DEFTOG,           cdeftog },
131 	{ "eval",             EVAL,             ceval },
132 	{ "set",              SET,              cset },
133 	{ "macro",            MACRO,            cmacro },
134 	{ "error",            ERROR,            cerror },
135 	{ "condition",        CONDITION,        ccondition },
136 	{ "condition_!",      CONDITION_N,      ccondition },
137 	{ "condition_toggle", CONDITION_TOGGLE, ccondition },
138 	{ "condition_else",   CONDITION_TOGGLE, ccondition },
139 	{ "usercom",          USERCOM,          cusercom },
140 	{ "readrc",           READRC,           creadrc },
141 	{ "quit",             QUIT,             cquit },
142 	{ "help",             HELP,             chelp },
143 	{ "flush",            FLUSH,            cflush },
144 	{ "repaint",          REPAINT,          cflush },
145 	{ "forw_scroll",      FORW_SCROLL,      cscroll },
146 	{ "back_scroll",      BACK_SCROLL,      cscroll },
147 	{ "forw",             FORW,             cscroll },
148 	{ "back",             BACK,             cscroll },
149 	{ "rscroll",          RSCROLL,          chscroll },
150 	{ "lscroll",          LSCROLL,          chscroll },
151 	{ "goline",           GOLINE,           cgomisc },
152 	{ "gopercent",        GOPERCENT,        cgomisc },
153 	{ "goend",            GOEND,            cgoend },
154 	{ "edit",             EDIT,             cedit },
155 	{ "askfile",          ASKFILE,          caskfile },
156 	{ "file",             CFILE,            cfile },
157 	{ "file_list",        FILE_LIST,        cfile_list },
158 	{ "stat",             STAT,             cstat },
159 	{ "magicasksearch",   MAGICASKSEARCH,   csearch },
160 	{ "search",           SEARCH,           csearch },
161 	{ "research",         RESEARCH,         csearch },
162 	{ "setmark",          SETMARK,          cmark },
163 	{ "gomark",           GOMARK,           cmark },
164 	{ "listbmarks",       LISTBMARKS,       clistbmarks },
165 	{ "asktag",           ASKFTAG,          ctags },
166 	{ "nexttag",          NEXTFTAG,         ctags },
167 	{ "prevtag",          PREVFTAG,         ctags },
168 };
169 
170 
171 /* I believe this is just for cosmetic purposes. */
172 #define CMD_EXEC lower_left(); flush()
173 
174 
175 /*
176  * Prototypes are for people who can't program.
177  */
178 
179 
180 /*
181  * The main command string evaluator.  Returns -1 if an error occurred
182  * in the command or in executing the command, returns 0 otherwise.  If an
183  * error occurs while evaluating a command line containing multiple commands,
184  * commands after the error are not processed.  Multiple commands may be
185  * separated by ';' or '\n'.  (Multiple commands may also be separated by
186  * a ' ', but this is really a bug...)
187  */
188 int
command(line)189 command(line)
190 	const char *line;
191 {
192 	struct ctable *i;
193 
194 donextcommand:
195 
196 	while (isspace(*line) || *line == ';' || *line == '\n') line++;
197 	if (!*line)
198 		return 0;
199 
200 	for (i = ctable; i != ctable + sizeof(ctable) / sizeof(struct ctable);
201 	    i++) {
202 		if (!strncmp(i->cname, line, strlen(i->cname)) &&
203 		    (line[strlen(i->cname)] == ' ' ||
204 		     line[strlen(i->cname)] == ';' ||
205 		     line[strlen(i->cname)] == '\0')) {
206 			/* Found a match! */
207 			void **ctxt;
208 			CMD_EXEC;
209 			ctxt = getstr_raisectxt();
210 			line = i->cfunc (i->cident, line + strlen(i->cname));
211 			getstr_free(ctxt);
212 			if (!line)
213 				return -1;  /* error evaluating command */
214 			goto donextcommand;
215 		}
216 	}
217 
218 	SETERRSTR(E_BOGCOM, "invalid command: ``%s''", line);
219 	(void) command("condition true");
220 	return -1;
221 }
222 
223 
224 /*****************************************************************************
225  *
226  * Functions to help specific command functions to parse their arguments.
227  *
228  * The three functions here, getstr(), getint(), and gettog() could in theory
229  * have vastly different concepts of what a number is, and what a string is,
230  * etc., but in practice they don't.
231  */
232 
233 static char *readvar();
234 static const char *getvar(char *, int);
235 
236 #define NCTXTS 128
237 void *getstr_ctxts[NCTXTS];  /* could easily be made dynamic... */
238 void **getstr_curctxt = getstr_ctxts;
239 
240 /*
241  * Read a single argument string from a command string.  This understands
242  * $variables, "double quotes", 'single quotes', and backslash escapes
243  * for \\, \$, \n, \e, \t, and \".  A string may be delimited by double
244  * quotes or spaces, not both (duh).  It may be worthwhile to add
245  * another quotation style in which arithmetic expressions are expanded.
246  * Currently an arithmetic expression is expanded iff it is the only
247  * component of the string.
248  *
249  * Returns a pointer to the beginning of the string or NULL if it was unable to
250  * read a string.  The line is modified to point somewhere between the end of
251  * the command argument just read-in and the beginning of the next command
252  * argument (if any).  The returned pointer will be free()'d by calling
253  * getstr_free().
254  */
255 static char *
getstr(line)256 getstr(line)
257 	const char **line;	/* Where to look for the return string */
258 {
259 	int doquotes = 0;  /* Doing a double-quote string */
260 	char *retr;
261 
262 	if (getstr_curctxt - getstr_ctxts == NCTXTS) {
263 		SETERRSTR(E_COMPLIM,
264 		    "compile-time limit exceeded: command contexts");
265 		return NULL;  /* wouldn't be able to register return pointer */
266 	}
267 
268 	while (isspace(**line)) (*line)++;
269 	if (**line == '\'') {
270 		/* Read until closing quote or '\0'. */
271 		char *nextw = retr = malloc(1);
272 		const char *c = ++(*line);
273 		int l;
274 		for (; *c; c++) {
275 			if (*c == '\'') {
276 				if (c[-1] == '\\') {
277 					nextw[-1] = '\'';
278 					continue;
279 				} else {
280 					*nextw = '\0';
281 					*line = c + 1;
282 					*getstr_curctxt = retr;
283 					getstr_curctxt++;
284 					return retr;
285 				}
286 			}
287 			l = nextw - retr;
288 			/* XXX How many realloc()'s can you make per second? */
289 			if (!(retr = reallocf(retr, c - *line + 250))) {
290 				SETERR (E_MALLOC);
291 				return NULL;
292 			}
293 			nextw = retr + l;
294 			*nextw = *c;
295 			nextw++;
296 		}
297 		SETERR(E_CANTPARSE);
298 		return NULL;
299 	}
300 	if (**line == '"') {
301 		doquotes = 1;
302 		(*line)++;
303 	}
304 	if (**line == '(') {
305 		/* An arithmetic expression instead of a string...  Well, I
306 		 * guess this is valid.  See comment leading this function. */
307 		long n;
308 		if (getint(&n, line))
309 			return NULL;
310 		retr = NULL;
311 		asprintf(&retr, "%ld", n);
312 		if (!retr)
313 			SETERR (E_MALLOC);
314 		*getstr_curctxt = retr;
315 		getstr_curctxt++;
316 		return retr;
317 	}
318 
319 	if (!FMALLOC(1, retr))
320 		return NULL;
321 	*retr = '\0';
322 	for (;;) {
323 		char *c, hack[2];
324 
325 		switch (**line) {
326 		case '\\':
327 			switch (*(*line + 1)) {
328 			case '\\': case '$': case '\'':
329 			case ' ': case ';':
330 				hack[0] = *(*line + 1);
331 				hack[1] = '\0';
332 				c = hack;
333 				(*line) += 2;
334 				break;
335 			case 't':
336 				c = "\t";
337 				(*line) += 2;
338 				break;
339 			case 'n':
340 				c = "\n";
341 				(*line) += 2;
342 				break;
343 			case 'e':
344 				c = "\e";
345 				(*line) += 2;
346 				break;
347 			case '"':
348 				if (doquotes) {
349 					c = "\"";
350 					(*line) += 2;
351 					break;
352 				} else
353 					; /* fallthrough */
354 			default:
355 				c = "\\";
356 				(*line)++;
357 				break;
358 			}
359 			break;
360 		case '$':
361 			(*line)++;
362 			if (!(c = readvar(line))) {
363 				free (retr);
364 				return NULL;
365 			}
366 			break;
367 		case ' ': case '\t': case ';':
368 			if (!doquotes) {
369 				doquotes = 1;
370 		case '"':
371 				if (doquotes) {
372 					/* The end of the string */
373 					(*line)++;
374 		case '\0':
375 					*getstr_curctxt = retr;
376 					getstr_curctxt++;
377 					return retr;
378 				}
379 			}
380 			/* fallthrough */
381 		default:
382 			hack[0] = **line;
383 			hack[1] = '\0';
384 			c = hack;
385 			(*line)++;
386 			break;
387 		}
388 
389 		retr = reallocf(retr, strlen(retr) + strlen(c) + 1);
390 		if (!retr) {
391 			SETERR (E_MALLOC);
392 			return NULL;
393 		}
394 		strcat(retr, c);
395 	}
396 }
397 
398 /*
399  * Returns a new context that should be passed to getstr_free() so that
400  * getstr_free() only free()'s memory from that particular context.
401  */
402 static void **
getstr_raisectxt()403 getstr_raisectxt()
404 {
405 	return getstr_curctxt;
406 }
407 
408 /*
409  * Calls free() on all memory from context or higher.
410  */
411 static
getstr_free(context)412 getstr_free(context)
413 	void **context;
414 {
415 	while (getstr_curctxt != context) {
416 		getstr_curctxt--;
417 		free (*getstr_curctxt);
418 	}
419 }
420 
421 /*
422  * Reads an integer value from a command string.  Typed numbers must be
423  * in base10.  If a '(' is found as the first character of the integer value,
424  * then getint() will read until a closing ')' unless interupted by an
425  * end-of-command marker (error).  The parentheses are expected to contain a
426  * simple arithmetic statement involving only one '*', '/', etc. operation.  The
427  * rightmost digit or the closing parenthesis should be followed by either a
428  * space or an end-of-command marker.
429  *
430  * Returns 0 on success, -1 on failure.  The line will be modified to just
431  * after the last piece of text parsed.
432  *
433  * XXX We may add support for negative numbers, someday...
434  */
435 static int
getint(numb,line)436 getint(numb, line)
437 	long *numb;	/* The read-in number is returned through this */
438 	const char **line;	/* The command line from which to read numb */
439 {
440 	long n;
441 	int j;
442 	char *p;
443 	const char *t;
444 
445 	while (isspace(**line)) (*line)++;
446 
447 	switch (**line) {
448 	case '(':
449 		(*line)++;
450 		if (getint(numb, line))
451 			return -1;
452 		while (isspace(**line)) (*line)++;
453 		j = **line;
454 		(*line)++;
455 		if (j == ')')
456 			return 0;
457 		if (**line == '=' && (j == '!' || j == '=')
458 		   || j == '&' && **line == '&' || j == '|' && **line == '|')
459 			j = (j << 8) + *((*line)++);
460 		if (getint(&n, line))
461 			return -1;
462 		while (isspace(**line)) (*line)++;
463 		if (**line != ')') {
464 			SETERRSTR (E_BADMATH,
465 			    "missing arithmetic close parenthesis");
466 			return -1;
467 		} else
468 			(*line)++;
469 		switch (j) {
470 		case ('!' << 8) + '=':
471 			*numb = *numb != n;
472 			return 0;
473 		case ('=' << 8) + '=':
474 			*numb = *numb == n;
475 			return 0;
476 		case ('&' << 8) + '&':
477 			*numb = *numb && n;
478 			return 0;
479 		case ('|' << 8) + '|':
480 			*numb = *numb || n;
481 			return 0;
482 		case '+':
483 			*numb += n;
484 			return 0;
485 		case '-':
486 			*numb -= n;
487 			return 0;
488 		case '*':
489 			*numb *= n;
490 			return 0;
491 		case '/':
492 			if (n == 0)
493 				*numb = 1;
494 			else
495 				*numb /= n;
496 			return 0;
497 		default:
498 			SETERRSTR (E_BADMATH,
499 			   "bad arithmetic operator: ``%c''", j);
500 			return -1;
501 		}
502 	case '$':
503 		t = (*line)++;
504 		if (!(p = readvar(line)))
505 			return -1;
506 		if (!isdigit(*p)) {
507 			SETERRSTR (E_BADMATH,
508 			    "non-number found (``%s'') "
509 			    "after expanding variable at ``%s''", p, t);
510 			return -1;
511 		}
512 		*numb = atol(p);
513 		return 0;
514 	case '9': case '0': case '8': case '1': case '7': case '2': case '6':
515 	case '3': case '5': case '4':
516 		*numb = atol(*line);
517 		while (isdigit(**line)) (*line)++;
518 		return 0;
519 	case '"': case '\'':
520 		/* Uh-oh.  It's really a string.  We'll go through getstr()
521 		 * and hope for the best, but this isn't looking good. */
522 		if (!(p = getstr(line)))
523 			return -1;
524 		*numb = atol(p);
525 		return 0;
526 	default:
527 		SETERRSTR (E_BADMATH,
528 		    "non-number found, number expected, before parsing ``%s''",
529 		    *line);
530 		return -1;
531 	}
532 }
533 
534 /*
535  * Read an argument from the command string and match that argument against
536  * a series of legitimate values.  For example,
537  *
538  * command <<opt0|opt1|opt2>>
539  *
540  * This command by be given to the command() processor as a variant of either
541  * "command opt1" or "command 4", both of which will cause this function to
542  * return the value 1.  This function returns -1 on failure.
543  *
544  * Note that an option (eg. "opt1") must _not_ start with a digit!!
545  */
546 static int
gettog(const char ** line,int nopts,...)547 gettog(const char **line, int nopts, ...)
548 {
549 	char *str;
550 	int n;
551 	va_list opts;
552 
553 	if (!(str = getstr(line)))
554 		return -1;
555 
556 	if (isdigit(*str)) {
557 		n = atol(str) % nopts;
558 		return n;
559 	}
560 
561 	va_start(opts, nopts);
562 	for (n=0; n < nopts; n++) {
563 		if (!strcasecmp(str, va_arg(opts, const char *))) {
564 			va_end(opts);
565 			return n;
566 		}
567 	}
568 	va_end(opts);
569 	SETERR (E_NOTOG);  /* XXX would be nice to list valid toggles... */
570 	return -1;
571 }
572 
573 /*
574  * A companion function for gettog().  Example,
575  *
576  * optnumb = gettog(&args, 3, "opt1", "opt2", "opt3");
577  * settog("_lastoptnumb", optnumb, "opt1", "opt2", "opt3");
578  *
579  * And the variable named _lastoptnumb_s will be set to one of "opt1", "opt2",
580  * or "opt3" as per the value of optnumb.  The variable _lastoptnumb_n will
581  * also be set to a corresponding value.  The optnumb argument had better
582  * be within the correct range (between 0 and 2 in the above example)!!
583  */
584 void
settog(const char * varname,int optval,int nargs,...)585 settog(const char *varname, int optval, int nargs, ...)
586 {
587 	va_list opts;
588 	char *s;
589 	int optval_orig = optval;
590 
591 	assert(optval < nargs); assert(optval >= 0);
592 	if (!(s = malloc(strlen(varname) + 3)))
593 		return;
594 	strcpy (s, varname);
595 	va_start(opts, nargs);
596 	for (; optval; optval--) va_arg(opts, const char *);
597 	s[strlen(varname)] = '_';
598 	s[strlen(varname) + 1] = 's';
599 	s[strlen(varname) + 2] = '\0';
600 	(void) setvar(s, va_arg(opts, const char *));
601 	s[strlen(varname) + 1] = 'n';
602 	(void) setvari(s, (long) optval_orig);
603 	clear_error();
604 	va_end(opts);
605 	free(s);
606 }
607 
608 /*
609  * Read {text} and return the string associated with the variable named
610  * <<text>>.  Returns NULL on failure.
611  */
612 static char *
readvar(line)613 readvar(line)
614 	char **line;
615 {
616 	int vlength;
617 	char *vstart;
618 
619 	if (**line != '{') {
620 		SETERR (E_BADVAR);
621 		return NULL;
622 	}
623 	(*line)++;
624 	for (vlength = 0, vstart = *line; **line &&
625 	    (isprint(**line) && **line != '{' && **line != '}'); (*line)++)
626 		vlength++;
627 	if (**line != '}' || vlength == 0) {
628 		SETERRSTR (E_BADVAR,
629 		    "bad character ``%c'' in variable ``%.*s''", **line,
630 		    vlength, vstart);
631 		return NULL;
632 	}
633 	(*line)++;
634 	return getvar(vstart, vlength);
635 }
636 
637 
638 /*****************************************************************************
639  *
640  * Track variables.
641  *
642  */
643 
644 static struct vble {
645 	struct vble *next;
646 	char *name;
647 	char *value;
648 } *vble_l;  /* linked-list of existing variables */
649 
650 /*
651  * Return a pointer to the string that variable var represents.  Returns
652  * NULL if a match could not be found and sets erreur.  The returned
653  * pointer is valid until any calls to setvar() or until a call to
654  * getstr_free() frees the current context.
655  */
656 static const char *
getvar(var,len)657 getvar(var, len)
658 	char *var;
659 	int len;  /* strncmp(var, varmatch, len); is used to match variables */
660 {
661 	struct vble *i;
662 	char tcap[3];
663 	char *s, *retr;
664 	char *gettermcap();
665 
666 	if (sscanf (var, "_termcap_%2s", tcap) == 1 &&
667 	    len == sizeof "_termcap_XX" - 1) {
668 		/* Magic termcap variable */
669 		if ((s = gettermcap(tcap))) {
670 			if (getstr_curctxt - getstr_ctxts == NCTXTS) {
671 				SETERRSTR(E_COMPLIM,
672 		    		    "compile-time limit exceeded: "
673 				    "command contexts");
674 				return NULL;
675 			}
676 			if (!FMALLOC(strlen(s) + 1, retr))
677 				return NULL;
678 			strcpy (retr, s);
679 			*getstr_curctxt = retr;
680 			getstr_curctxt++;
681 			return retr;
682 		} else {
683 			return "";
684 		}
685 	}
686 
687 	for (i = vble_l; i; i = i->next) {
688 		if (!strncasecmp (i->name, var, len))
689 			return i->value;
690 	}
691 
692 	SETERRSTR (E_BADVAR, "variable ``%.*s'' not set", len, var);
693 	return NULL;
694 }
695 
696 /*
697  * Set variable var to val.  Both var and val may be destroyed by the
698  * caller after setvar().
699  *
700  * Returns -1 on failure, 0 on success.
701  */
702 int
setvar(var,val)703 setvar(var, val)
704 	char *var;  /* variable to set */
705 	char *val;  /* value to set variable to */
706 {
707 	struct vble *i, *last;
708 	char *var_n, *val_n;
709 	char *c;
710 
711 	for (c = var; *c && (isprint(*c) && *c != '{' && *c != '}'); c++) ;
712 	if (*c) {
713 		SETERRSTR (E_BADVAR,
714 		    "bad character ``%c'' in variable ``%s''", *c, var);
715 		return -1;
716 	}
717 
718 	for (i = vble_l; i; last = i, i = i->next) {
719 		if (!strcasecmp (i->name, var)) {
720 			if (!FMALLOC(strlen(val) + 1, val_n))
721 				return -1;
722 			free(i->value);
723 			i->value = val_n;
724 			strcpy(i->value, val);
725 			return 0;
726 		}
727 	}
728 
729 	/* Need to add another variable to the list vble_l */
730 	if (!FMALLOC(strlen(var) + 1, var_n))
731 		return -1;
732 	if (!FMALLOC(strlen(val) + 1, val_n)) {
733 		free(var_n);
734 		return -1;
735 	}
736 	if (!vble_l) {
737 		if (!FMALLOC(sizeof(struct vble), vble_l)) {
738 			free(var_n), free(val_n);
739 			return -1;
740 		}
741 		i = vble_l;
742 	} else {
743 		if (!FMALLOC(sizeof(struct vble), last->next)) {
744 			free(var_n), free(val_n);
745 			return -1;
746 		}
747 		i = last->next;
748 	}
749 	i->next = NULL;
750 	i->name = var_n; strcpy(i->name, var);
751 	i->value = val_n; strcpy(i->value, val);
752 	return 0;
753 }
754 
755 /*
756  * Set or reset, as appropriate, variable var to val.
757  */
758 int
setvari(var,val)759 setvari(var, val)
760 	const char *var;
761 	long val;
762 {
763 	char n[21];  /* XXX */
764 	snprintf(n, sizeof(n), "%ld", val);
765 	n[20] = '\0';
766 	setvar(var, n);
767 }
768 
769 
770 /*****************************************************************************
771  *
772  * Specific command functions.  These aren't actually individual functions,
773  * since using a gigantic switch statement is faster to type, but they
774  * pretend to be individual functions.
775  *
776  */
777 
778 int condition_eval = 1;  /* false if we just parse commands, but do nothing */
779 
780 #define ARGSTR(v) do {                                                         \
781 		if (!((v) = getstr(&args))) return NULL;                       \
782 	} while (0)
783 #define ARGNUM(v) do {                                                         \
784 		if (getint(&(v), &args)) return NULL;                          \
785 	} while (0)
786 /* semi-gratuitous use of GNU cpp extension */
787 #define ARGTOG(v, n, togs...) do {                                             \
788 		if (((v) = gettog(&args, n, togs)) == -1)                      \
789 			return NULL;                                           \
790 	} while (0)
791 #define ENDPARSE do {                                                          \
792 		if (!condition_eval) return args;                              \
793 	} while (0)
794 
795 /*
796  * deftog
797  *
798  * Set all toggle options to their default values, provided the toggle option
799  * is registered with this function.  This command is meant to be used at the
800  * beginning of the startup command list.
801  */
802 static const char *
cdeftog(cident,args)803 cdeftog(cident, args)
804 	enum cident cident;
805 	const char *args;
806 {
807 	extern int horiz_off, wraplines;
808 
809 	ENDPARSE;
810 	settog("_statprompt", 1, 2, "on", "off");
811 	settog("_ls_direction", 0, 2, "forw", "back");
812 	settog("_ls_sense", 0, 2, "noinvert", "invert");
813 	setvari("_curhscroll", (long) horiz_off);
814 	settog("_wraplines", wraplines, 2, "off", "on");
815 	setvar("_ls_regexp", "");
816 	/*
817 	 * not present: _file_direction
818 	 */
819 	return args;
820 }
821 
822 /*
823  * eval <<string>>
824  *
825  * Passes string back into the command evaluator.
826  */
827 static const char *
ceval(cident,args)828 ceval(cident, args)
829 	enum cident cident;
830 	const char *args;
831 {
832 	const char *com;
833 
834 	ARGSTR(com);  /* The command line to evaluate */
835 	ENDPARSE;
836 
837 	/* It's not clear what to do with the command() return code */
838 	(void) command(com);
839 
840 	return args;
841 }
842 
843 /*
844  * set <<variablename>> <<variablestring>>
845  *
846  * Sets variable variablename to string variablestring.
847  */
848 static const char *
cset(cident,args)849 cset(cident, args)
850 	enum cident cident;
851 	const char *args;
852 {
853 	const char *str, *var;
854 
855 	ARGSTR(var);  /* name of variable to set */
856 	ARGSTR(str);  /* value to set variable to */
857 	ENDPARSE;
858 
859 	if (*var == '_') {
860 		SETERRSTR (E_BADVAR,
861 		    "variables beginning with '_' are reserved");
862 		return NULL;
863 	}
864 	if (setvar(var, str))
865 		return NULL;
866 
867 	return args;
868 }
869 
870 /*
871  * macro <<default_number>> <<keys>> <<command>>
872  *
873  * Associates the macro keys with command.
874  */
875 static const char *
cmacro(cident,args)876 cmacro(cident, args)
877 	enum cident cident;
878 	const char *args;
879 {
880 	const char *keys, *com;
881 	long num;
882 
883 	ARGNUM(num);   /* the default number N for this macro */
884 	ARGSTR(keys);  /* string of keys representing a macro */
885 	ARGSTR(com);   /* command line to associate with macro */
886 	ENDPARSE;
887 
888 	if (setmacro(keys, com))
889 		return NULL;
890 	if (setmacnumb(keys, num))
891 		return NULL;
892 
893 	return args;
894 }
895 
896 /*
897  * error <<string>>
898  *
899  * Prints a notification message.
900  */
901 static const char *
cerror(cident,args)902 cerror(cident, args)
903 	enum cident cident;
904 	const char *args;
905 {
906 	char *s;
907 
908 	ARGSTR(s);  /* error message */
909 	ENDPARSE;
910 	error(s);
911 	return args;
912 }
913 
914 /*
915  * condition <<boolean>>
916  * condition_! <<boolean>>
917  *
918  * If boolean is false, causes all commands except for other condition
919  * commands to be ignored.  The <<boolean>> may be specified as a number
920  * (in which case even numbers are true, odd numbers are false), or one
921  * of "on", "off", "true", and "false".
922  */
923 static const char *
ccondition(cident,args)924 ccondition(cident, args)
925 	enum cident cident;
926 	const char *args;
927 {
928 	/* ENDPARSE; */
929 
930 	if (cident == CONDITION_TOGGLE) {
931 		condition_eval = !condition_eval;
932 		return args;
933 	}
934 
935 	switch (gettog(&args, 4, "off", "on", "false", "true")) {
936 	case 0: case 2:
937 		condition_eval = 0;
938 		break;
939 	case 1: case 3:
940 		condition_eval = 1;
941 		break;
942 	case -1:
943 		return NULL;
944 	}
945 	if (cident == CONDITION_N)
946 		condition_eval = !condition_eval;
947 	return args;
948 }
949 
950 /*
951  * usercom
952  *
953  * Accept a direct command from the user's terminal.
954  */
955 static const char *
cusercom(cident,args)956 cusercom(cident, args)
957 	enum cident cident;
958 	const char *args;
959 {
960 	char buf[125];  /* XXX should avoid static buffer... */
961 
962 	ENDPARSE;
963 	if (getinput("Command: ", buf, sizeof(buf)))
964 		return args;  /* abort the command */
965 	if (command(buf))
966 		return NULL;
967 
968 	return args;
969 }
970 
971 /*
972  * readrc <<filename>>
973  *
974  * Read-in rc commands from the named file.
975  */
976 static const char *
creadrc(cident,args)977 creadrc(cident, args)
978 	enum cident cident;
979 	const char *args;
980 {
981 	const char *file;
982 	FILE *fd;
983 
984 	ARGSTR(file);
985 	ENDPARSE;
986 
987 	if (!*file)
988 		return args;
989 	/*
990 	 * Should perhaps warn user if file perms or ownership look suspicious.
991 	 */
992 	fd = fopen(file, "r");
993 	if (!fd) {
994 		SETERRSTR (E_NULL, "could not open file ``%s''", file);
995 		return NULL;
996 	}
997 	readrc(fd);
998 	fclose(fd);
999 
1000 	return args;
1001 }
1002 
1003 /*
1004  * quit
1005  *
1006  * Performs as advertised.
1007  */
1008 static const char *
cquit(cident,args)1009 cquit(cident, args)
1010 	enum cident cident;
1011 	const char *args;
1012 {
1013 	ENDPARSE;
1014 	quit();
1015 	return NULL;  /* oh boy... */
1016 }
1017 
1018 /*
1019  * help
1020  *
1021  * Doesn't do much.
1022  */
1023 static const char *
chelp(cident,args)1024 chelp(cident, args)
1025 	enum cident cident;
1026 	const char *args;
1027 {
1028 	extern int ac, curr_ac;
1029 	extern char **av;
1030 
1031 	ENDPARSE;
1032 	if (ac > 0 && !strcmp(_PATH_HELPFILE, av[curr_ac])) {
1033 		SETERRSTR(E_NULL, "already viewing help");
1034 		return NULL;
1035 	}
1036 	help();
1037 	return args;
1038 }
1039 
1040 /*
1041  * flush
1042  * repaint
1043  *
1044  * Flushes the file buffer, provided we are not reading from a pipe.
1045  * Frees any other memory that I can get my hands on from here.
1046  */
1047 static const char *
cflush(cident,args)1048 cflush(cident, args)
1049 	enum cident cident;
1050 	const char *args;
1051 {
1052 	extern int ispipe;
1053 
1054 	ENDPARSE;
1055 	if (cident == FLUSH && !ispipe) {
1056 		ch_init(0, 0);  /* XXX should this be ch_init(ctags,0) */
1057 		clr_linenum();
1058 	}
1059 	repaint();
1060 
1061 	return args;
1062 }
1063 
1064 /*
1065  * forw_scroll <<n>>
1066  * back_scroll <<n>>
1067  * forw <<n>>
1068  * back <<n>>
1069  *
1070  * Move forward number n lines.  The _scroll variants force a scroll, the
1071  * others may scroll or may just redraw the screen at the appropriate location,
1072  * whichever is faster.
1073  */
1074 static const char *
cscroll(cident,args)1075 cscroll(cident, args)
1076 	enum cident cident;
1077 	const char *args;
1078 {
1079 	long n;
1080 	char *retr;
1081 
1082 	ARGNUM(n);  /* number of lines to move by */
1083 	ENDPARSE;
1084 
1085 	switch (cident) {
1086 	case FORW_SCROLL:
1087 		forward(n, 0);
1088 		break;
1089 	case BACK_SCROLL:
1090 		backward(n, 0);
1091 		break;
1092 	case FORW:
1093 		forward(n, 1);
1094 		break;
1095 	case BACK:
1096 		backward(n, 1);
1097 		break;
1098 	}
1099 
1100 	return args;
1101 }
1102 
1103 /*
1104  * rscroll <<n>>
1105  * lscroll <<n>>
1106  *
1107  * Scroll left or right by n lines.
1108  */
1109 static const char *
chscroll(cident,args)1110 chscroll(cident, args)
1111 	enum cident cident;
1112 	const char *args;
1113 {
1114 	long n;
1115 	char *retr;
1116 	extern int horiz_off, wraplines;
1117 
1118 	ARGNUM(n);  /* Number of columns to scroll by */
1119 	ENDPARSE;
1120 
1121 	if (n == 0)
1122 		return args;
1123 
1124 	switch (cident) {
1125 	case RSCROLL:
1126 		if (wraplines) {
1127 			wraplines = 0;
1128 			assert (horiz_off == 0);
1129 		} else {
1130 			horiz_off += n;
1131 			if (horiz_off < 0)
1132 				horiz_off = INT_MAX;  /* disaster control */
1133 		}
1134 		break;
1135 	case LSCROLL:
1136 		if (horiz_off != 0) {
1137 			horiz_off -= n;
1138 			if (horiz_off < 0)
1139 				horiz_off = 0;
1140 		} else
1141 			wraplines = 1;
1142 		break;
1143 	}
1144 	repaint();  /* screen_trashed = 1 */
1145 	setvari("_curhscroll", (long) horiz_off);
1146 	settog("_wraplines", wraplines, 2, "off", "on");
1147 
1148 	return args;
1149 }
1150 
1151 /*
1152  * goline <<line>>
1153  * gopercent <<percent>>
1154  *
1155  * Goto the line numbered <<line>>, if possible.  Goto <<percent>> percent of
1156  * the file.  Whole-numbered percents only, of course.
1157  */
1158 static const char *
cgomisc(cident,args)1159 cgomisc(cident, args)
1160 	enum cident cident;
1161 	const char *args;
1162 {
1163 	long n;
1164 
1165 	ARGNUM(n);  /* number N */
1166 	ENDPARSE;
1167 
1168 	switch (cident) {
1169 	case GOLINE:
1170 		jump_back(n);
1171 		break;
1172 	case GOPERCENT:
1173 		if (n > 100)
1174 			n = 100;
1175 		jump_percent(n);
1176 		break;
1177 	}
1178 	return args;
1179 }
1180 
1181 /*
1182  * listbmarks
1183  *
1184  * Give some hints on where each of the bookmarks lead to.
1185  */
1186 static const char *
clistbmarks(cident,args)1187 clistbmarks(cident, args)
1188 	enum cident cident;
1189 	const char *args;
1190 {
1191 	ENDPARSE;
1192 
1193 	if (marks_bookhints())
1194 		return NULL;
1195 	return args;
1196 }
1197 
1198 /*
1199  * goend
1200  *
1201  * Goto the end of the file.  Future variation should include the GNU less(1)-
1202  * style follow a-la tail(1).
1203  */
1204 static const char *
cgoend(cident,args)1205 cgoend(cident, args)
1206 	enum cident cident;
1207 	const char *args;
1208 {
1209 	ENDPARSE;
1210 	jump_forw();
1211 	return args;
1212 }
1213 
1214 /*
1215  * edit
1216  *
1217  * Edits the current file with a word editor.  This command is just begging
1218  * to be extended to allow the user to specify an editor.  Additionally, this
1219  * would require some kind of getenv command or similar change.
1220  */
1221 static const char *
cedit(cident,args)1222 cedit(cident, args)
1223 	enum cident cident;
1224 	const char *args;
1225 {
1226 	extern ispipe;
1227 
1228 	ENDPARSE;
1229 	if (ispipe) {
1230 		SETERRSTR(E_NULL, "cannot edit standard input");
1231 		return NULL;
1232 	}
1233 	editfile();
1234 	/*
1235 	 * XXX less-than brilliant things happen if the user while editing
1236 	 * deletes a large section at the end of the file where we think we
1237 	 * are currently viewing...
1238 	 */
1239 	ch_init(0, 0);  /* Clear the internal file buffer */
1240 	clr_linenum();
1241 	return args;
1242 }
1243 
1244 /*
1245  * askfile
1246  *
1247  * Loads a new file.  Queries the user for the name of the new file.
1248  */
1249 static const char *
caskfile(cident,args)1250 caskfile(cident, args)
1251 	enum cident cident;
1252 	const char *args;
1253 {
1254 	char buf[MAXPATHLEN + 1];
1255 
1256 	ENDPARSE;
1257 	if (getinput("Examine: ", buf, sizeof(buf)))
1258 		return args;  /* abort */
1259 	/* Abort is different from a "" answer in that "" causes the current
1260 	 * file to be re-opened. */
1261 	/* XXX should modify this() or edit() to handle lists of file, ie.
1262 	 * the type of lists that I get if I try to glob("*") */
1263 	(void)edit(glob(buf), FORCE_OPEN);
1264 
1265 	return args;
1266 }
1267 
1268 /*
1269  * file <<next|previous>> <<N>>
1270  *
1271  * Loads the N'th next or previous file, typically from the list of files
1272  * given on the command line.
1273  */
1274 static const char *
cfile(cident,args)1275 cfile(cident, args)
1276 	enum cident cident;
1277 	const char *args;
1278 {
1279 	enum { FORW=0, BACK=1 } direction;
1280 	long N;
1281 
1282 	ARGTOG(direction, 10, "next", "previous", "forward", "backward",
1283 	    "forwards", "backwards", "next", "prev", "forw", "back");
1284 	ARGNUM(N);
1285 	ENDPARSE;
1286 	direction %= 2;
1287 
1288 	/* next_file() and prev_file() call error() directly (bad) */
1289 	switch (direction) {
1290 	case FORW:
1291 		next_file(N);
1292 		break;
1293 	case BACK:
1294 		prev_file(N);
1295 		break;
1296 	}
1297 	settog("_file_direction", direction, 2, "next", "previous");
1298 	return args;
1299 }
1300 
1301 /*
1302  * file_list
1303  *
1304  * Lists the files the "file next" and "file prev" are moving around in.
1305  */
1306 static const char *
cfile_list(cident,args)1307 cfile_list(cident, args)
1308 	enum cident cident;
1309 	const char *args;
1310 {
1311 	ENDPARSE;
1312 	showlist();
1313 	repaint();  /* screen_trashed = 1; */
1314 	return args;
1315 }
1316 
1317 /*
1318  * stat <<on|off>>
1319  *
1320  * Display the detailed statistics as part of the prompt.  The toggle option
1321  * variable is called _statprompt (giving ${_statprompt_s} and
1322  * ${_statprompt_n}).
1323  */
1324 static const char *
cstat(cident,args)1325 cstat(cident, args)
1326 	enum cident cident;
1327 	const char *args;
1328 {
1329 	int onoff;
1330 
1331 	ARGTOG(onoff, 2, "on", "off");
1332 	ENDPARSE;
1333 	statprompt(onoff);
1334 	settog("_statprompt", onoff, 2, "on", "off");
1335 	return args;
1336 }
1337 
1338 /*
1339  * magicasksearch <<forw|back>> <<n>>
1340  * search <<forw|back>> <<n>> <<<noinvert|invert>> <searchstring>>
1341  * research <<forw|back>> <<n>>
1342  *
1343  * Arguments specifying an option (ie. <<forw|back>> and <<noinvert|invert>>
1344  * may be specified either as text (eg. "forw"), or as a number, in which case
1345  * even numbers specify the former setting and odd numbers the latter setting.
1346  *
1347  * The magicasksearch will ask the user for a regexp and intuit whether they
1348  * want to invert the sense of matching or not: if the first character of the
1349  * regexp is a '!', it is removed and the sense is inverted.  If the regexp
1350  * entered is null, then we will use ${_ls_regexp} (error if not set).
1351  *
1352  * The toggle options are called _ls_direction and _ls_sense.  In addition,
1353  * ${_ls_regexp} is set to the regexp used.  These variables are only set
1354  * when the search and magicsearch commands are used.
1355  */
1356 static const char *
csearch(cident,args)1357 csearch(cident, args)
1358 	enum cident cident;
1359 	const char *args;
1360 {
1361 	char buf[100], *str;
1362 	enum { FORW=0, BACK=1 } direction;
1363 	static enum { NOINVERT=0, INVERT=1 } sense;
1364 	long N;
1365 	int abrt = 0;
1366 
1367 	ARGTOG(direction, 6, "forw", "back", "forward", "backward",
1368 	    "forwards", "backwards");
1369 	ARGNUM(N);
1370 	if (cident == SEARCH) {
1371 		ARGTOG(sense, 2, "noinvert", "invert");
1372 		ARGSTR(str);
1373 	}
1374 	ENDPARSE;
1375 	direction %= 2;
1376 
1377 	/* Get the search string, one way or another */
1378 	switch (cident) {
1379 	case MAGICASKSEARCH:
1380 		biggetinputhack();  /* It's magic, boys */
1381 		if (direction == FORW)
1382 			abrt = getinput("Search: /", buf, 2);
1383 		else
1384 			abrt = getinput("Search: ?", buf, 2);
1385 		switch (*buf) {
1386 		case '!':
1387 			/* Magic */
1388 			if (direction == FORW)
1389 				abrt = getinput("Search: !/", buf, sizeof(buf));
1390 			else
1391 				abrt = getinput("Search: !?", buf, sizeof(buf));
1392 			sense = INVERT;
1393 			break;
1394 		default:
1395 			/* No magic */
1396 			ungetcc(*buf);
1397 			if (direction == FORW)
1398 				abrt = getinput("Search: /", buf, sizeof(buf));
1399 			else
1400 				abrt = getinput("Search: ?", buf, sizeof(buf));
1401 		case '\0':
1402 			sense = NOINVERT;
1403 			break;
1404 		}
1405 		str = buf;
1406 		break;
1407 	case SEARCH:
1408 		break;
1409 	case RESEARCH:
1410 		str = NULL;
1411 		break;
1412 	}
1413 
1414 	if (abrt)
1415 		return args;
1416 
1417 	if (cident == SEARCH || cident == MAGICASKSEARCH) {
1418 		settog("_ls_direction", direction, 2, "forw", "back");
1419 		settog("_ls_sense", sense, 2, "noinvert", "invert");
1420 		if (*str)
1421 			setvar("_ls_regexp", str);
1422 	}
1423 	/*
1424 	 * XXX Currently search() contains magic to deal with (*str=='\0').
1425 	 * This magic should be moved into this function so that we can work
1426 	 * as described in the function comment header.
1427 	 */
1428 	search(!direction, str, N, !sense);
1429 	return args;
1430 }
1431 
1432 /*
1433  * setmark <<character>>
1434  * gomark <<character>>
1435  *
1436  * Set a marker at the current position, or goto a previously set marker.
1437  * Character may be a-z, or '?' to ask the user to enter a character.  The
1438  * special mark '\'' may not be set, but may be the target of a goto.
1439  */
1440 static const char *
cmark(cident,args)1441 cmark(cident, args)
1442 	enum cident cident;
1443 	const char *args;
1444 {
1445 	char smark[2];
1446 	const char *mark;
1447 
1448 	ARGSTR(mark);
1449 	ENDPARSE;
1450 
1451 	/* gomark() and setmark() will further check mark's validity */
1452 	if (!*mark || mark[1]) {
1453 		SETERRSTR(E_NULL, "bad mark character");
1454 		return NULL;
1455 	}
1456 
1457 	if (*mark == '?') {
1458 		biggetinputhack();  /* so getinput() returns after one char */
1459 		switch (cident) {
1460 		case GOMARK:
1461 			getinput("goto mark: ", smark, sizeof smark);
1462 			break;
1463 		case SETMARK:
1464 			getinput("set mark: ", smark, sizeof smark);
1465 			break;
1466 		}
1467 		if (!*smark)
1468 			return args;
1469 		mark = smark;
1470 	}
1471 
1472 	switch (cident) {
1473 	case GOMARK:
1474 		gomark(*mark);
1475 		break;
1476 	case SETMARK:
1477 		setmark(*mark);
1478 		break;
1479 	}
1480 	return args;
1481 }
1482 
1483 /*
1484  * asktag
1485  * nexttag <<number>>
1486  * prevtag <<number>>
1487  *
1488  * Asks the user for a tag, or moves around the tag queue.
1489  */
1490 static const char *
ctags(cident,args)1491 ctags(cident, args)
1492 	enum cident cident;
1493 	const char *args;
1494 {
1495 	extern char *tagfile;  /* XXX No reason for this to be a global... */
1496 	long n;
1497 
1498 	if (cident != ASKFTAG)
1499 		ARGNUM(n);
1500 	ENDPARSE;
1501 
1502 	if (cident == ASKFTAG) {
1503 		char buf[100];  /* XXX should do something else... */
1504 		getinput("Tag: ", buf, sizeof(buf));
1505 		if (!*buf)
1506 			return args;
1507 		findtag(buf);
1508 	} else {
1509 		switch (cident) {
1510 		case NEXTFTAG:
1511 			nexttag(n);
1512 			break;
1513 		case PREVFTAG:
1514 			prevtag(n);
1515 			break;
1516 		}
1517 	}
1518 
1519 	/* Load the tagfile and position ourselves. */
1520 	if (tagfile == NULL)
1521 		return NULL;
1522 	if (edit(tagfile, NO_FORCE_OPEN))
1523 		tagsearch();
1524 	return args;  /* tag stuff still calls error() on its own */
1525 }
1526