1/*	$OpenBSD: main.c,v 1.87 2017/06/15 13:48:42 bcallah Exp $	*/
2/*	$NetBSD: main.c,v 1.12 1997/02/08 23:54:49 cgd Exp $	*/
3
4/*-
5 * SPDX-License-Identifier: BSD-3-Clause
6 *
7 * Copyright (c) 1989, 1993
8 *	The Regents of the University of California.  All rights reserved.
9 *
10 * This code is derived from software contributed to Berkeley by
11 * Ozan Yigit at York University.
12 *
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
15 * are met:
16 * 1. Redistributions of source code must retain the above copyright
17 *    notice, this list of conditions and the following disclaimer.
18 * 2. Redistributions in binary form must reproduce the above copyright
19 *    notice, this list of conditions and the following disclaimer in the
20 *    documentation and/or other materials provided with the distribution.
21 * 3. Neither the name of the University nor the names of its contributors
22 *    may be used to endorse or promote products derived from this software
23 *    without specific prior written permission.
24 *
25 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
27 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
28 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
29 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
30 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
31 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
34 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
35 * SUCH DAMAGE.
36 */
37
38/*
39 * main.c
40 * Facility: m4 macro processor
41 * by: oz
42 */
43#include <sys/cdefs.h>
44#include <assert.h>
45#include <signal.h>
46#include <err.h>
47#include <errno.h>
48#include <getopt.h>
49#include <unistd.h>
50#include <stdio.h>
51#include <ctype.h>
52#include <string.h>
53#include <stddef.h>
54#include <stdint.h>
55#include <stdlib.h>
56#include <ohash.h>
57#include "mdef.h"
58#include "stdd.h"
59#include "extern.h"
60#include "pathnames.h"
61
62static const char *shortopts = "+D:d::EGgI:o:Pst:U:";
63static const struct option longopts[] = {
64	{ "define",		required_argument,	NULL,	'D' },
65	{ "debug",		optional_argument,	NULL,	'd' },
66	{ "fatal-warnings",	no_argument,		NULL,	'E' },
67	{ "traditional",	no_argument,		NULL,	'G' },
68	{ "gnu",		no_argument,		NULL,	'g' },
69	{ "include",		required_argument,	NULL,	'I' },
70	{ "error-output",	required_argument,	NULL,	'o' },
71	{ "prefix-builtins",	no_argument,		NULL,	'P' },
72	{ "synclines",		no_argument,		NULL,	's' },
73	{ "trace",		required_argument,	NULL,	't' },
74	{ "undefine",		required_argument,	NULL,	'U' },
75	{ NULL, 0, NULL, 0 },
76};
77
78stae *mstack;			/* stack of m4 machine         */
79char *sstack;			/* shadow stack, for string space extension */
80static size_t STACKMAX;		/* current maximum size of stack */
81int sp;				/* current m4  stack pointer   */
82int fp;				/* m4 call frame pointer       */
83struct input_file infile[MAXINP];/* input file stack (0=stdin)  */
84FILE **outfile;			/* diversion array(0=bitbucket)*/
85int maxout;
86FILE *active;			/* active output file pointer  */
87int ilevel = 0;			/* input file stack pointer    */
88int oindex = 0;			/* diversion index..	       */
89const char *null = "";                /* as it says.. just a null..  */
90char **m4wraps = NULL;		/* m4wraps array.	       */
91int maxwraps = 0;		/* size of m4wraps array       */
92int wrapindex = 0;		/* current offset in m4wraps   */
93char lquote[MAXCCHARS+1] = {LQUOTE};	/* left quote character  (`)   */
94char rquote[MAXCCHARS+1] = {RQUOTE};	/* right quote character (')   */
95char scommt[MAXCCHARS+1] = {SCOMMT};	/* start character for comment */
96char ecommt[MAXCCHARS+1] = {ECOMMT};	/* end character for comment   */
97int  synch_lines = 0;		/* line synchronisation for C preprocessor */
98int  prefix_builtins = 0;	/* -P option to prefix builtin keywords */
99int  error_warns = 0;		/* -E option to make warnings exit_code = 1 */
100int  fatal_warns = 0;		/* -E -E option to make warnings fatal */
101
102struct keyblk {
103        const char    *knam;          /* keyword name */
104        int     ktyp;           /* keyword type */
105};
106
107static struct keyblk keywrds[] = {	/* m4 keywords to be installed */
108	{ "include",      INCLTYPE },
109	{ "sinclude",     SINCTYPE },
110	{ "define",       DEFITYPE },
111	{ "defn",         DEFNTYPE },
112	{ "divert",       DIVRTYPE | NOARGS },
113	{ "expr",         EXPRTYPE },
114	{ "eval",         EXPRTYPE },
115	{ "substr",       SUBSTYPE },
116	{ "ifelse",       IFELTYPE },
117	{ "ifdef",        IFDFTYPE },
118	{ "len",          LENGTYPE },
119	{ "incr",         INCRTYPE },
120	{ "decr",         DECRTYPE },
121	{ "dnl",          DNLNTYPE | NOARGS },
122	{ "changequote",  CHNQTYPE | NOARGS },
123	{ "changecom",    CHNCTYPE | NOARGS },
124	{ "index",        INDXTYPE },
125#ifdef EXTENDED
126	{ "paste",        PASTTYPE },
127	{ "spaste",       SPASTYPE },
128	/* Newer extensions, needed to handle gnu-m4 scripts */
129	{ "indir",        INDIRTYPE},
130	{ "builtin",      BUILTINTYPE},
131	{ "patsubst",	  PATSTYPE},
132	{ "regexp",	  REGEXPTYPE},
133	{ "esyscmd",	  ESYSCMDTYPE},
134	{ "__file__",	  FILENAMETYPE | NOARGS},
135	{ "__line__",	  LINETYPE | NOARGS},
136#endif
137	{ "popdef",       POPDTYPE },
138	{ "pushdef",      PUSDTYPE },
139	{ "dumpdef",      DUMPTYPE | NOARGS },
140	{ "shift",        SHIFTYPE | NOARGS },
141	{ "translit",     TRNLTYPE },
142	{ "undefine",     UNDFTYPE },
143	{ "undivert",     UNDVTYPE | NOARGS },
144	{ "divnum",       DIVNTYPE | NOARGS },
145	{ "maketemp",     MKTMTYPE },
146	{ "mkstemp",      MKTMTYPE },
147	{ "errprint",     ERRPTYPE | NOARGS },
148	{ "m4wrap",       M4WRTYPE | NOARGS },
149	{ "m4exit",       EXITTYPE | NOARGS },
150	{ "syscmd",       SYSCTYPE },
151	{ "sysval",       SYSVTYPE | NOARGS },
152	{ "traceon",	  TRACEONTYPE | NOARGS },
153	{ "traceoff",	  TRACEOFFTYPE | NOARGS },
154
155	{ "unix",         SELFTYPE | NOARGS },
156};
157
158#define MAXKEYS	(sizeof(keywrds)/sizeof(struct keyblk))
159
160extern int optind;
161extern char *optarg;
162
163#define MAXRECORD 50
164static struct position {
165	char *name;
166	unsigned long line;
167} quotes[MAXRECORD], paren[MAXRECORD];
168
169static void record(struct position *, int);
170static void dump_stack(struct position *, int);
171
172static void macro(void);
173static void initkwds(void);
174static ndptr inspect(int, char *);
175static int do_look_ahead(int, const char *);
176static void reallyoutputstr(const char *);
177static void reallyputchar(int);
178
179static void enlarge_stack(void);
180
181int main(int, char *[]);
182
183int exit_code = 0;
184
185int
186main(int argc, char *argv[])
187{
188	int c;
189	int n;
190	char *p;
191
192	if (signal(SIGINT, SIG_IGN) != SIG_IGN)
193		signal(SIGINT, onintr);
194
195	init_macros();
196	initspaces();
197	STACKMAX = INITSTACKMAX;
198
199	mstack = xreallocarray(NULL, STACKMAX, sizeof(stae), NULL);
200	sstack = xalloc(STACKMAX, NULL);
201
202	maxout = 0;
203	outfile = NULL;
204	resizedivs(MAXOUT);
205
206	while ((c = getopt_long(argc, argv, shortopts, longopts, NULL)) != -1)
207		switch(c) {
208
209		case 'D':               /* define something..*/
210			for (p = optarg; *p; p++)
211				if (*p == '=')
212					break;
213			if (*p)
214				*p++ = EOS;
215			dodefine(optarg, p);
216			break;
217		case 'E':               /* like GNU m4 1.4.9+ */
218			if (error_warns == 0)
219				error_warns = 1;
220			else
221				fatal_warns = 1;
222			break;
223		case 'I':
224			addtoincludepath(optarg);
225			break;
226		case 'P':
227			prefix_builtins = 1;
228			break;
229		case 'U':               /* undefine...       */
230			macro_popdef(optarg);
231			break;
232		case 'G':
233			mimic_gnu = 0;
234			break;
235		case 'g':
236			mimic_gnu = 1;
237			break;
238		case 'd':
239			set_trace_flags(optarg ? optarg : "aeq");
240			break;
241		case 's':
242			synch_lines = 1;
243			break;
244		case 't':
245			mark_traced(optarg, 1);
246			break;
247		case 'o':
248			trace_file(optarg);
249                        break;
250		case '?':
251			usage();
252		}
253
254        argc -= optind;
255        argv += optind;
256
257	initkwds();
258	if (mimic_gnu)
259		setup_builtin("format", FORMATTYPE);
260
261	active = stdout;		/* default active output     */
262	bbase[0] = bufbase;
263        if (!argc) {
264		sp = -1;		/* stack pointer initialized */
265		fp = 0;			/* frame pointer initialized */
266		set_input(infile+0, stdin, "stdin");
267					/* default input (naturally) */
268		macro();
269	} else
270		for (; argc--; ++argv) {
271			p = *argv;
272			if (p[0] == '-' && p[1] == EOS)
273				set_input(infile, stdin, "stdin");
274			else if (fopen_trypath(infile, p) == NULL)
275				err(1, "%s", p);
276			sp = -1;
277			fp = 0;
278			macro();
279			release_input(infile);
280		}
281
282	if (wrapindex) {
283		int i;
284
285		ilevel = 0;		/* in case m4wrap includes.. */
286		bufbase = bp = buf;	/* use the entire buffer   */
287		if (mimic_gnu) {
288			while (wrapindex != 0) {
289				for (i = 0; i < wrapindex; i++)
290					pbstr(m4wraps[i]);
291				wrapindex =0;
292				macro();
293			}
294		} else {
295			for (i = 0; i < wrapindex; i++) {
296				pbstr(m4wraps[i]);
297				macro();
298			}
299		}
300	}
301
302	if (active != stdout)
303		active = stdout;	/* reset output just in case */
304	for (n = 1; n < maxout; n++)	/* default wrap-up: undivert */
305		if (outfile[n] != NULL)
306			getdiv(n);
307					/* remove bitbucket if used  */
308	if (outfile[0] != NULL) {
309		(void) fclose(outfile[0]);
310	}
311
312	return exit_code;
313}
314
315/*
316 * Look ahead for `token'.
317 * (on input `t == token[0]')
318 * Used for comment and quoting delimiters.
319 * Returns 1 if `token' present; copied to output.
320 *         0 if `token' not found; all characters pushed back
321 */
322static int
323do_look_ahead(int t, const char *token)
324{
325	int i;
326
327	assert((unsigned char)t == (unsigned char)token[0]);
328
329	for (i = 1; *++token; i++) {
330		t = gpbc();
331		if (t == EOF || (unsigned char)t != (unsigned char)*token) {
332			pushback(t);
333			while (--i)
334				pushback(*--token);
335			return 0;
336		}
337	}
338	return 1;
339}
340
341#define LOOK_AHEAD(t, token) (t != EOF &&		\
342    (unsigned char)(t)==(unsigned char)(token)[0] &&	\
343    do_look_ahead(t,token))
344
345/*
346 * macro - the work horse..
347 */
348static void
349macro(void)
350{
351	char token[MAXTOK+1];
352	int t, l;
353	ndptr p;
354	int  nlpar;
355
356	cycle {
357		t = gpbc();
358
359		if (LOOK_AHEAD(t,lquote)) {	/* strip quotes */
360			nlpar = 0;
361			record(quotes, nlpar++);
362			/*
363			 * Opening quote: scan forward until matching
364			 * closing quote has been found.
365			 */
366			do {
367
368				l = gpbc();
369				if (LOOK_AHEAD(l,rquote)) {
370					if (--nlpar > 0)
371						outputstr(rquote);
372				} else if (LOOK_AHEAD(l,lquote)) {
373					record(quotes, nlpar++);
374					outputstr(lquote);
375				} else if (l == EOF) {
376					if (nlpar == 1)
377						warnx("unclosed quote:");
378					else
379						warnx("%d unclosed quotes:", nlpar);
380					dump_stack(quotes, nlpar);
381					exit(1);
382				} else {
383					if (nlpar > 0) {
384						if (sp < 0)
385							reallyputchar(l);
386						else
387							CHRSAVE(l);
388					}
389				}
390			}
391			while (nlpar != 0);
392		} else if (sp < 0 && LOOK_AHEAD(t, scommt)) {
393			reallyoutputstr(scommt);
394
395			for(;;) {
396				t = gpbc();
397				if (LOOK_AHEAD(t, ecommt)) {
398					reallyoutputstr(ecommt);
399					break;
400				}
401				if (t == EOF)
402					break;
403				reallyputchar(t);
404			}
405		} else if (t == '_' || isalpha(t)) {
406			p = inspect(t, token);
407			if (p != NULL)
408				pushback(l = gpbc());
409			if (p == NULL || (l != LPAREN &&
410			    (macro_getdef(p)->type & NEEDARGS) != 0))
411				outputstr(token);
412			else {
413		/*
414		 * real thing.. First build a call frame:
415		 */
416				pushf(fp);	/* previous call frm */
417				pushf(macro_getdef(p)->type); /* type of the call  */
418				pushf(is_traced(p));
419				pushf(0);	/* parenthesis level */
420				fp = sp;	/* new frame pointer */
421		/*
422		 * now push the string arguments:
423		 */
424				pushdef(p);			/* defn string */
425				pushs1((char *)macro_name(p));	/* macro name  */
426				pushs(ep);			/* start next..*/
427
428				if (l != LPAREN && PARLEV == 0) {
429				    /* no bracks  */
430					chrsave(EOS);
431
432					if (sp == (int)STACKMAX)
433						errx(1, "internal stack overflow");
434					eval((const char **) mstack+fp+1, 2,
435					    CALTYP, TRACESTATUS);
436
437					ep = PREVEP;	/* flush strspace */
438					sp = PREVSP;	/* previous sp..  */
439					fp = PREVFP;	/* rewind stack...*/
440				}
441			}
442		} else if (t == EOF) {
443			if (!mimic_gnu /* you can puke right there */
444			    && sp > -1 && ilevel <= 0) {
445				warnx( "unexpected end of input, unclosed parenthesis:");
446				dump_stack(paren, PARLEV);
447				exit(1);
448			}
449			if (ilevel <= 0)
450				break;			/* all done thanks.. */
451			release_input(infile+ilevel--);
452			emit_synchline();
453			bufbase = bbase[ilevel];
454			continue;
455		} else if (sp < 0) {		/* not in a macro at all */
456			reallyputchar(t);	/* output directly..	 */
457		}
458
459		else switch(t) {
460
461		case LPAREN:
462			if (PARLEV > 0)
463				chrsave(t);
464			while (isspace(l = gpbc())) /* skip blank, tab, nl.. */
465				if (PARLEV > 0)
466					chrsave(l);
467			pushback(l);
468			record(paren, PARLEV++);
469			break;
470
471		case RPAREN:
472			if (--PARLEV > 0)
473				chrsave(t);
474			else {			/* end of argument list */
475				chrsave(EOS);
476
477				if (sp == (int)STACKMAX)
478					errx(1, "internal stack overflow");
479
480				eval((const char **) mstack+fp+1, sp-fp,
481				    CALTYP, TRACESTATUS);
482
483				ep = PREVEP;	/* flush strspace */
484				sp = PREVSP;	/* previous sp..  */
485				fp = PREVFP;	/* rewind stack...*/
486			}
487			break;
488
489		case COMMA:
490			if (PARLEV == 1) {
491				chrsave(EOS);		/* new argument   */
492				while (isspace(l = gpbc()))
493					;
494				pushback(l);
495				pushs(ep);
496			} else
497				chrsave(t);
498			break;
499
500		default:
501			if (LOOK_AHEAD(t, scommt)) {
502				char *p;
503				for (p = scommt; *p; p++)
504					chrsave(*p);
505				for(;;) {
506					t = gpbc();
507					if (LOOK_AHEAD(t, ecommt)) {
508						for (p = ecommt; *p; p++)
509							chrsave(*p);
510						break;
511					}
512					if (t == EOF)
513					    break;
514					CHRSAVE(t);
515				}
516			} else
517				CHRSAVE(t);		/* stack the char */
518			break;
519		}
520	}
521}
522
523/*
524 * output string directly, without pushing it for reparses.
525 */
526void
527outputstr(const char *s)
528{
529	if (sp < 0)
530		reallyoutputstr(s);
531	else
532		while (*s)
533			CHRSAVE(*s++);
534}
535
536void
537reallyoutputstr(const char *s)
538{
539	if (synch_lines) {
540		while (*s) {
541			fputc(*s, active);
542			if (*s++ == '\n') {
543				infile[ilevel].synch_lineno++;
544				if (infile[ilevel].synch_lineno !=
545				    infile[ilevel].lineno)
546					do_emit_synchline();
547			}
548		}
549	} else
550		fputs(s, active);
551}
552
553void
554reallyputchar(int c)
555{
556	putc(c, active);
557	if (synch_lines && c == '\n') {
558		infile[ilevel].synch_lineno++;
559		if (infile[ilevel].synch_lineno != infile[ilevel].lineno)
560			do_emit_synchline();
561	}
562}
563
564/*
565 * build an input token..
566 * consider only those starting with _ or A-Za-z.
567 */
568static ndptr
569inspect(int c, char *tp)
570{
571	char *name = tp;
572	char *etp = tp+MAXTOK;
573	ndptr p;
574
575	*tp++ = c;
576
577	while ((isalnum(c = gpbc()) || c == '_') && tp < etp)
578		*tp++ = c;
579	if (c != EOF)
580		PUSHBACK(c);
581	*tp = EOS;
582	/* token is too long, it won't match anything, but it can still
583	 * be output. */
584	if (tp == ep) {
585		outputstr(name);
586		while (isalnum(c = gpbc()) || c == '_') {
587			if (sp < 0)
588				reallyputchar(c);
589			else
590				CHRSAVE(c);
591		}
592		*name = EOS;
593		return NULL;
594	}
595
596	p = ohash_find(&macros, ohash_qlookupi(&macros, name, (const char **)&tp));
597	if (p == NULL)
598		return NULL;
599	if (macro_getdef(p) == NULL)
600		return NULL;
601	return p;
602}
603
604/*
605 * initkwds - initialise m4 keywords as fast as possible.
606 * This very similar to install, but without certain overheads,
607 * such as calling lookup. Malloc is not used for storing the
608 * keyword strings, since we simply use the static pointers
609 * within keywrds block.
610 */
611static void
612initkwds(void)
613{
614	unsigned int type;
615	int i;
616
617	for (i = 0; i < (int)MAXKEYS; i++) {
618		type = keywrds[i].ktyp & TYPEMASK;
619		if ((keywrds[i].ktyp & NOARGS) == 0)
620			type |= NEEDARGS;
621		setup_builtin(keywrds[i].knam, type);
622	}
623}
624
625static void
626record(struct position *t, int lev)
627{
628	if (lev < MAXRECORD) {
629		t[lev].name = CURRENT_NAME;
630		t[lev].line = CURRENT_LINE;
631	}
632}
633
634static void
635dump_stack(struct position *t, int lev)
636{
637	int i;
638
639	for (i = 0; i < lev; i++) {
640		if (i == MAXRECORD) {
641			fprintf(stderr, "   ...\n");
642			break;
643		}
644		fprintf(stderr, "   %s at line %lu\n",
645			t[i].name, t[i].line);
646	}
647}
648
649
650static void
651enlarge_stack(void)
652{
653	STACKMAX += STACKMAX/2;
654	mstack = xreallocarray(mstack, STACKMAX, sizeof(stae),
655	    "Evaluation stack overflow (%lu)",
656	    (unsigned long)STACKMAX);
657	sstack = xrealloc(sstack, STACKMAX,
658	    "Evaluation stack overflow (%lu)",
659	    (unsigned long)STACKMAX);
660}
661