904 lines
26 KiB
C
904 lines
26 KiB
C
/* File : main.c
|
|
Author : Ozan Yigit
|
|
Updated: 4 May 1992
|
|
Defines: M4 macro processor.
|
|
*/
|
|
|
|
#include "mdef.h"
|
|
#include "extr.h"
|
|
#include "ourlims.h"
|
|
|
|
char chtype[1 - EOF + UCHAR_MAX];
|
|
|
|
#define is_sym1(c) (chtype[(c)-EOF] > 10)
|
|
#define is_sym2(c) (chtype[(c)-EOF] > 0)
|
|
#define is_blnk(c) ((unsigned)((c)-1) < ' ')
|
|
|
|
/*
|
|
* m4 - macro processor
|
|
*
|
|
* PD m4 is based on the macro tool distributed with the software
|
|
* tools (VOS) package, and described in the "SOFTWARE TOOLS" and
|
|
* "SOFTWARE TOOLS IN PASCAL" books. It has been expanded to include
|
|
* most of the command set of SysV m4, the standard UN*X macro processor.
|
|
*
|
|
* Since both PD m4 and UN*X m4 are based on SOFTWARE TOOLS macro,
|
|
* there may be certain implementation similarities between
|
|
* the two. The PD m4 was produced without ANY references to m4
|
|
* sources.
|
|
*
|
|
* References:
|
|
*
|
|
* Software Tools distribution: macro
|
|
*
|
|
* Kernighan, Brian W. and P. J. Plauger, SOFTWARE
|
|
* TOOLS IN PASCAL, Addison-Wesley, Mass. 1981
|
|
*
|
|
* Kernighan, Brian W. and P. J. Plauger, SOFTWARE
|
|
* TOOLS, Addison-Wesley, Mass. 1976
|
|
*
|
|
* Kernighan, Brian W. and Dennis M. Ritchie,
|
|
* THE M4 MACRO PROCESSOR, Unix Programmer's Manual,
|
|
* Seventh Edition, Vol. 2, Bell Telephone Labs, 1979
|
|
*
|
|
* System V man page for M4
|
|
*
|
|
* Modification History:
|
|
*
|
|
* Mar 26 1992 RAOK 1. Eliminated magic numbers 8, 255, 256 in favour
|
|
* of the standard limits CHAR_BIT, UCHAR_MAX, which
|
|
* are in the new header ourlims.h. This is part of
|
|
* the "8-bit-clean M4" project. To the best of my
|
|
* belief, all of the code should work in EBCDIC,
|
|
* ASCII, DEC MNCS, ISO 8859/n, or the Mac character
|
|
* set, as long as chars are unsigned. There are
|
|
* still some places where signed bytes can cause
|
|
* trouble.
|
|
*
|
|
* 2. Changed expr() to use long int rather than int.
|
|
* This is so that we'd get 32-bit arithmetic on a Sun,
|
|
* Encore, PC, Mac &c. As part of this, the code for
|
|
* shifts has been elaborated to yield signed shifts
|
|
* on all machines. The charcon() function didn't work
|
|
* with multi-character literals, although it was meant
|
|
* to. Now it does. pbrad() has been changed so that
|
|
* eval('abcd',0) => abcd, not dcba, which was useless.
|
|
*
|
|
* 3. I finally got sick of the fact that &&, ||, and
|
|
* ?: always evaluate all their arguments. This is
|
|
* consistent with UNIX System V Release 3, but I for
|
|
* one don't see anything to gain by having eval(0&&1/0)
|
|
* crash when it would simply yield 0 in C. Now these
|
|
* operators are more consistent with the C preprocessor.
|
|
*
|
|
* Nov 13 1992 RAOK Added the quoter facility. The purpose of this is
|
|
* to make it easier to generate data for a variety of
|
|
* programming languages, including sh, awk, Lisp, C.
|
|
* There are two holes in the implementation: dumpdef
|
|
* prints junk and undefine doesn't release everything.
|
|
* This was mainly intended as a prototype to show that
|
|
* it could be done.
|
|
*
|
|
* Jun 16 1992 RAOK Added vquote and gave changequote a 3rd argument.
|
|
* The idea of this is to make it possible to quote
|
|
* ANY string, including one with unbalanced ` or '.
|
|
* I also made eval(c,0) convert decimal->ASCII, so
|
|
* that eval(39,0) yields ' and eval(96,0) yields `.
|
|
*
|
|
* Apr 28 1992 RAOK Used gcc to find and fix ANSI clashes, so that
|
|
* PD M4 could be ported to MS-DOS (Turbo C 3).
|
|
* Main known remaining problem: use of mktemp().
|
|
* Also, command line handling needs to be worked out.
|
|
*
|
|
* Mar 26 1992 RAOK PD M4 now accepts file names on the command line
|
|
* just like UNIX M4. Warning: macro calls must NOT
|
|
* cross file boundaries. UNIX M4 doesn't mind;
|
|
* (m4 a b c) and (cat a b c | m4) are just the same
|
|
* except for error messages. PD M4 will report an
|
|
* unexpected EOF if a file ends while a macro call or
|
|
* string is still being parsed. When there is one
|
|
* file name argument, or none, you can't tell the
|
|
* difference, and that's all I need.
|
|
*
|
|
* May 15 1991 RAOK DIVNAM was a string constant, but was changed!
|
|
* Fixed that and a couple of other things to make
|
|
* GCC happy. (Also made "foo$bar" get through.)
|
|
*
|
|
* Apr 17 1991 RAOK There was a major mistake. If you did
|
|
* define(foo, `1 include(bar) 2') where
|
|
* file bar held "-bar-" you would naturally
|
|
* expect "1 -bar- 2" as the output, but you
|
|
* got "1 2-bar-". That is, include file
|
|
* processing was postponed until all macros
|
|
* had been expanded. The macro gpbc() was
|
|
* at fault. I added bb, bbstack[], and the
|
|
* code in main.c and serv.c that maintains
|
|
* them, in order to work around this bug.
|
|
*
|
|
* Apr 12 1991 RAOK inspect() didn't handle overflow well.
|
|
* Added the automatically maintained macro
|
|
* __FILE__, just as in C. To suppress it,
|
|
* define NO__FILE. At some point, $# had
|
|
* been made to return a value that was off
|
|
* by one; it now agrees with SysV M4.
|
|
*
|
|
* Aug 13 1990 RAOK The System V expr() has three arguments:
|
|
* expression [, radix:10 [, mindigits: 1]]
|
|
* Brought in my int2str() and wrote pbrad()
|
|
* to make this work here. With the wrong #
|
|
* of args, acts like System V.
|
|
*
|
|
* Aug 11 1990 RAOK Told expr.c about the Pascal operators
|
|
* not, div, mod, and, or
|
|
* so that Pascal constant expressions could
|
|
* be evaluated. (It still doesn't handle
|
|
* floats.) Fixed a mistake in 'character's.
|
|
*
|
|
* Apr 23 1988 RAOK Sped it up, mainly by making putback() and
|
|
* chrsave() into macros.
|
|
* Finished the -o option (was half done).
|
|
* Added the System V -e (interactive) option.
|
|
*
|
|
* Jan 28 1986 Oz Break the whole thing into little
|
|
* pieces, for easier (?) maintenance.
|
|
*
|
|
* Dec 12 1985 Oz Optimize the code, try to squeeze
|
|
* few microseconds out.. [didn't try very hard]
|
|
*
|
|
* Dec 05 1985 Oz Add getopt interface, define (-D),
|
|
* undefine (-U) options.
|
|
*
|
|
* Oct 21 1985 Oz Clean up various bugs, add comment handling.
|
|
*
|
|
* June 7 1985 Oz Add some of SysV m4 stuff (m4wrap, pushdef,
|
|
* popdef, decr, shift etc.).
|
|
*
|
|
* June 5 1985 Oz Initial cut.
|
|
*
|
|
* Implementation Notes:
|
|
*
|
|
* [1] PD m4 uses a different (and simpler) stack mechanism than the one
|
|
* described in Software Tools and Software Tools in Pascal books.
|
|
* The triple stack nonsense is replaced with a single stack containing
|
|
* the call frames and the arguments. Each frame is back-linked to a
|
|
* previous stack frame, which enables us to rewind the stack after
|
|
* each nested call is completed. Each argument is a character pointer
|
|
* to the beginning of the argument string within the string space.
|
|
* The only exceptions to this are (*) arg 0 and arg 1, which are
|
|
* the macro definition and macro name strings, stored dynamically
|
|
* for the hash table.
|
|
*
|
|
* . .
|
|
* | . | <-- sp | . |
|
|
* +-------+ +-----+
|
|
* | arg 3 ------------------------------->| str |
|
|
* +-------+ | . |
|
|
* | arg 2 --------------+ .
|
|
* +-------+ |
|
|
* * | | |
|
|
* +-------+ | +-----+
|
|
* | plev | <-- fp +---------------->| str |
|
|
* +-------+ | . |
|
|
* | type | .
|
|
* +-------+
|
|
* | prcf -----------+ plev: paren level
|
|
* +-------+ | type: call type
|
|
* | . | | prcf: prev. call frame
|
|
* . |
|
|
* +-------+ |
|
|
* | <----------+
|
|
* +-------+
|
|
*
|
|
* [2] We have three types of null values:
|
|
*
|
|
* nil - nodeblock pointer type 0
|
|
* null - null string ("")
|
|
* NULL - Stdio-defined NULL
|
|
*
|
|
*/
|
|
|
|
char buf[BUFSIZE]; /* push-back buffer */
|
|
char *bp = buf; /* first available character */
|
|
char *bb = buf; /* buffer beginning */
|
|
char *endpbb = buf+BUFSIZE; /* end of push-back buffer */
|
|
stae mstack[STACKMAX+1]; /* stack of m4 machine */
|
|
char strspace[STRSPMAX+1]; /* string space for evaluation */
|
|
char *ep = strspace; /* first free char in strspace */
|
|
char *endest= strspace+STRSPMAX;/* end of string space */
|
|
int sp; /* current m4 stack pointer */
|
|
int fp; /* m4 call frame pointer */
|
|
char *bbstack[MAXINP]; /* stack where bb is saved */
|
|
FILE *infile[MAXINP]; /* input file stack (0=stdin) */
|
|
FILE *outfile[MAXOUT]; /* diversion array(0=bitbucket)*/
|
|
FILE *active; /* active output file pointer */
|
|
int ilevel = 0; /* input file stack pointer */
|
|
int oindex = 0; /* diversion index.. */
|
|
char *null = ""; /* as it says.. just a null.. */
|
|
char *m4wraps = ""; /* m4wrap string default.. */
|
|
char lquote = LQUOTE; /* left quote character (`) */
|
|
char rquote = RQUOTE; /* right quote character (') */
|
|
char vquote = VQUOTE; /* verbatim quote character ^V */
|
|
char scommt = SCOMMT; /* start character for comment */
|
|
char ecommt = ECOMMT; /* end character for comment */
|
|
int strip = 0; /* throw away comments? */
|
|
|
|
/* Definitions of diversion files. The last 6 characters MUST be
|
|
"XXXXXX" -- that is a requirement of mktemp(). The character
|
|
'0' is to be replaced by the diversion number; we assume here
|
|
that it is just before the Xs. If not, you will have to alter
|
|
the definition of UNIQUE.
|
|
*/
|
|
|
|
#if unix
|
|
static char DIVNAM[] = "/tmp/m40XXXXXX";
|
|
#else
|
|
#if vms
|
|
static char DIVNAM[] = "sys$login:m40XXXXXX";
|
|
#else
|
|
static char DIVNAM[] = "M40XXXXXX"; /* was \M4, should it be \\M4? */
|
|
#endif
|
|
#endif
|
|
int UNIQUE = sizeof DIVNAM - 7; /* where to change m4temp. */
|
|
char *m4temp; /* filename for diversions */
|
|
extern char *mktemp();
|
|
|
|
|
|
void cantread(s)
|
|
char *s;
|
|
{
|
|
fprintf(stderr, "m4: %s: ", s);
|
|
error("cannot open for input.");
|
|
}
|
|
|
|
|
|
/* initkwds()
|
|
initialises the hash table to contain all the m4 built-in functions.
|
|
The original version breached module boundaries, but there did not
|
|
seem to be any benefit in that.
|
|
*/
|
|
static void initkwds()
|
|
{
|
|
register int i;
|
|
static struct { char *name; int type; } keyword[] =
|
|
{
|
|
"include", INCLTYPE,
|
|
"sinclude", SINCTYPE,
|
|
"define", DEFITYPE,
|
|
"defn", DEFNTYPE,
|
|
"divert", DIVRTYPE,
|
|
"expr", EXPRTYPE,
|
|
"eval", EXPRTYPE,
|
|
"substr", SUBSTYPE,
|
|
"ifelse", IFELTYPE,
|
|
"ifdef", IFDFTYPE,
|
|
"len", LENGTYPE,
|
|
"incr", INCRTYPE,
|
|
"decr", DECRTYPE,
|
|
"dnl", DNLNTYPE,
|
|
"changequote", CHNQTYPE,
|
|
"changecom", CHNCTYPE,
|
|
"index", INDXTYPE,
|
|
#ifdef EXTENDED
|
|
"paste", PASTTYPE,
|
|
"spaste", SPASTYPE,
|
|
"m4trim", TRIMTYPE,
|
|
"defquote", DEFQTYPE,
|
|
#endif
|
|
"popdef", POPDTYPE,
|
|
"pushdef", PUSDTYPE,
|
|
"dumpdef", DUMPTYPE,
|
|
"shift", SHIFTYPE,
|
|
"translit", TRNLTYPE,
|
|
"undefine", UNDFTYPE,
|
|
"undivert", UNDVTYPE,
|
|
"divnum", DIVNTYPE,
|
|
"maketemp", MKTMTYPE,
|
|
"errprint", ERRPTYPE,
|
|
"m4wrap", M4WRTYPE,
|
|
"m4exit", EXITTYPE,
|
|
#if unix || vms
|
|
"syscmd", SYSCTYPE,
|
|
"sysval", SYSVTYPE,
|
|
#endif
|
|
#if unix
|
|
"unix", MACRTYPE,
|
|
#else
|
|
#if vms
|
|
"vms", MACRTYPE,
|
|
#endif
|
|
#endif
|
|
(char*)0, 0
|
|
};
|
|
|
|
for (i = 0; keyword[i].type != 0; i++)
|
|
addkywd(keyword[i].name, keyword[i].type);
|
|
}
|
|
|
|
|
|
/* inspect(Name)
|
|
Build an input token.., considering only those which start with
|
|
[A-Za-z_]. This is fused with lookup() to speed things up.
|
|
name must point to an array of at least MAXTOK characters.
|
|
*/
|
|
ndptr inspect(name)
|
|
char *name;
|
|
{
|
|
register char *tp = name;
|
|
register char *etp = name+(MAXTOK-1);
|
|
register int c;
|
|
register unsigned long h = 0;
|
|
register ndptr p;
|
|
|
|
while (is_sym2(c = gpbc())) {
|
|
if (tp == etp) error("m4: token too long");
|
|
*tp++ = c, h = (h << 5) + h + c;
|
|
}
|
|
putback(c);
|
|
*tp = EOS;
|
|
for (p = hashtab[h%HASHSIZE]; p != nil; p = p->nxtptr)
|
|
if (strcmp(name, p->name) == 0)
|
|
return p;
|
|
return nil;
|
|
}
|
|
|
|
|
|
/*
|
|
* macro - the work horse..
|
|
*
|
|
*/
|
|
void macro()
|
|
{
|
|
char token[MAXTOK];
|
|
register int t;
|
|
register FILE *op = active;
|
|
static char ovmsg[] = "m4: internal stack overflow";
|
|
|
|
for (;;) {
|
|
t = gpbc();
|
|
if (is_sym1(t)) {
|
|
register char *s;
|
|
register ndptr p;
|
|
|
|
putback(t);
|
|
if ((p = inspect(s = token)) == nil) {
|
|
if (sp < 0) {
|
|
while (t = *s++) putc(t, op);
|
|
} else {
|
|
while (t = *s++) chrsave(t);
|
|
}
|
|
} else {
|
|
/* real thing.. First build a call frame */
|
|
if (sp >= STACKMAX-6) error(ovmsg);
|
|
mstack[1+sp].sfra = fp; /* previous call frm */
|
|
mstack[2+sp].sfra = p->type; /* type of the call */
|
|
mstack[3+sp].sfra = 0; /* parenthesis level */
|
|
fp = sp+3; /* new frame pointer */
|
|
/* now push the string arguments */
|
|
mstack[4+sp].sstr = p->defn; /* defn string */
|
|
mstack[5+sp].sstr = p->name; /* macro name */
|
|
mstack[6+sp].sstr = ep; /* start next.. */
|
|
sp += 6;
|
|
|
|
t = gpbc();
|
|
putback(t);
|
|
if (t != LPAREN) { putback(RPAREN); putback(LPAREN); }
|
|
}
|
|
} else
|
|
if (t == EOF) {
|
|
if (sp >= 0) error("m4: unexpected end of input");
|
|
if (--ilevel < 0) break; /* all done thanks */
|
|
#ifndef NO__FILE
|
|
remhash("__FILE__", TOP);
|
|
#endif
|
|
bb = bbstack[ilevel+1];
|
|
(void) fclose(infile[ilevel+1]);
|
|
} else
|
|
/* non-alpha single-char token seen..
|
|
[the order of else if .. stmts is important.]
|
|
*/
|
|
if (t == lquote) { /* strip quotes */
|
|
register int nlpar;
|
|
|
|
for (nlpar = 1; ; ) {
|
|
t = gpbc();
|
|
if (t == rquote) {
|
|
if (--nlpar == 0) break;
|
|
} else
|
|
if (t == lquote) {
|
|
nlpar++;
|
|
} else {
|
|
if (t == vquote) t = gpbc();
|
|
if (t == EOF) {
|
|
error("m4: missing right quote");
|
|
}
|
|
}
|
|
if (sp < 0) {
|
|
putc(t, op);
|
|
} else {
|
|
chrsave(t);
|
|
}
|
|
}
|
|
} else
|
|
if (sp < 0) { /* not in a macro at all */
|
|
if (t != scommt) { /* not a comment, so */
|
|
putc(t, op); /* copy it to output */
|
|
} else
|
|
if (strip) { /* discard a comment */
|
|
do {
|
|
t = gpbc();
|
|
} while (t != ecommt && t != EOF);
|
|
} else { /* copy comment to output */
|
|
do {
|
|
putc(t, op);
|
|
t = gpbc();
|
|
} while (t != ecommt && t != EOF);
|
|
putc(t, op);
|
|
/* A note on comment handling: this is NOT robust.
|
|
| We should do something safe with comments that
|
|
| are missing their ecommt termination.
|
|
*/
|
|
}
|
|
} else
|
|
switch (t) {
|
|
/* There is a peculiar detail to notice here.
|
|
Layout is _always_ discarded after left parentheses,
|
|
but it is only discarded after commas if they separate
|
|
arguments. For example,
|
|
define(foo,`|$1|$2|')
|
|
foo( a, b) => |a|b|
|
|
foo(( a ), ( b )) => |(a )|(b )|
|
|
foo((a, x), (b, y)) => |(a, x)|(b, y)|
|
|
I find this counter-intuitive, and would expect the code
|
|
for LPAREN to read something like this:
|
|
|
|
if (PARLEV == 0) {
|
|
(* top level left parenthesis: skip layout *)
|
|
do t = gpbc(); while (is_blnk(t));
|
|
putback(t);
|
|
} else {
|
|
(* left parenthesis inside an argument *)
|
|
chrsave(t);
|
|
}
|
|
PARLEV++;
|
|
|
|
However, it turned out that Oz wrote the actual code
|
|
very carefully to mimic the behaviour of "real" m4;
|
|
UNIX m4 really does skip layout after all left parens
|
|
but only some commas in just this fashion. Sigh.
|
|
*/
|
|
case LPAREN:
|
|
if (PARLEV > 0) chrsave(t);
|
|
do t = gpbc(); while (is_blnk(t)); /* skip layout */
|
|
putback(t);
|
|
PARLEV++;
|
|
break;
|
|
|
|
case COMMA:
|
|
if (PARLEV == 1) {
|
|
chrsave(EOS); /* new argument */
|
|
if (sp >= STACKMAX) error(ovmsg);
|
|
do t = gpbc(); while (is_blnk(t)); /* skip layout */
|
|
putback(t);
|
|
mstack[++sp].sstr = ep;
|
|
} else {
|
|
chrsave(t);
|
|
}
|
|
break;
|
|
|
|
case RPAREN:
|
|
if (--PARLEV > 0) {
|
|
chrsave(t);
|
|
} else {
|
|
char **argv = (char **)(mstack+fp+1);
|
|
int argc = sp-fp;
|
|
#if unix | vms
|
|
static int sysval;
|
|
#endif
|
|
|
|
chrsave(EOS); /* last argument */
|
|
if (sp >= STACKMAX) error(ovmsg);
|
|
#ifdef DEBUG
|
|
fprintf(stderr, "argc = %d\n", argc);
|
|
for (t = 0; t < argc; t++)
|
|
fprintf(stderr, "argv[%d] = %s\n", t, argv[t]);
|
|
#endif
|
|
/* If argc == 3 and argv[2] is null, then we
|
|
have a call like `macro_or_builtin()'. We
|
|
adjust argc to avoid further checking..
|
|
*/
|
|
if (argc == 3 && !argv[2][0]) argc--;
|
|
|
|
switch (CALTYP & ~STATIC) {
|
|
case MACRTYPE:
|
|
expand(argv, argc);
|
|
break;
|
|
|
|
case DEFITYPE: /* define(..) */
|
|
for (; argc > 2; argc -= 2, argv += 2)
|
|
dodefine(argv[2], argc > 3 ? argv[3] : null);
|
|
break;
|
|
|
|
case PUSDTYPE: /* pushdef(..) */
|
|
for (; argc > 2; argc -= 2, argv += 2)
|
|
dopushdef(argv[2], argc > 3 ? argv[3] : null);
|
|
break;
|
|
|
|
case DUMPTYPE:
|
|
dodump(argv, argc);
|
|
break;
|
|
|
|
case EXPRTYPE: /* eval(Expr) */
|
|
{ /* evaluate arithmetic expression */
|
|
/* eval([val: 0[, radix:10 [,min: 1]]]) */
|
|
/* excess arguments are ignored */
|
|
/* eval() with no arguments returns 0 */
|
|
/* this is based on V.3 behaviour */
|
|
int min_digits = 1;
|
|
int radix = 10;
|
|
long int value = 0;
|
|
|
|
switch (argc) {
|
|
default:
|
|
/* ignore excess arguments */
|
|
case 5:
|
|
min_digits = expr(argv[4]);
|
|
case 4:
|
|
radix = expr(argv[3]);
|
|
case 3:
|
|
value = expr(argv[2]);
|
|
case 2:
|
|
break;
|
|
}
|
|
pbrad(value, radix, min_digits);
|
|
}
|
|
break;
|
|
|
|
case IFELTYPE: /* ifelse(X,Y,IFX=Y,Else) */
|
|
doifelse(argv, argc);
|
|
break;
|
|
|
|
case IFDFTYPE: /* ifdef(Mac,IfDef[,IfNotDef]) */
|
|
/* select one of two alternatives based on the existence */
|
|
/* of another definition */
|
|
if (argc > 3) {
|
|
if (lookup(argv[2]) != nil) {
|
|
pbstr(argv[3]);
|
|
} else
|
|
if (argc > 4) {
|
|
pbstr(argv[4]);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case LENGTYPE: /* len(Arg) */
|
|
/* find the length of the argument */
|
|
pbnum(argc > 2 ? strlen(argv[2]) : 0);
|
|
break;
|
|
|
|
case INCRTYPE: /* incr(Expr) */
|
|
/* increment the value of the argument */
|
|
if (argc > 2) pbnum(expr(argv[2]) + 1);
|
|
break;
|
|
|
|
case DECRTYPE: /* decr(Expr) */
|
|
/* decrement the value of the argument */
|
|
if (argc > 2) pbnum(expr(argv[2]) - 1);
|
|
break;
|
|
|
|
#if unix || vms
|
|
case SYSCTYPE: /* syscmd(Command) */
|
|
/* execute system command */
|
|
/* Make sure m4 output is NOT interrupted */
|
|
fflush(stdout);
|
|
fflush(stderr);
|
|
|
|
if (argc > 2) sysval = system(argv[2]);
|
|
break;
|
|
|
|
case SYSVTYPE: /* sysval() */
|
|
/* return value of the last system call. */
|
|
pbnum(sysval);
|
|
break;
|
|
#endif
|
|
|
|
case INCLTYPE: /* include(File) */
|
|
for (t = 2; t < argc; t++)
|
|
if (!doincl(argv[t])) cantread(argv[t]);
|
|
break;
|
|
|
|
case SINCTYPE: /* sinclude(File) */
|
|
for (t = 2; t < argc; t++)
|
|
(void) doincl(argv[t]);
|
|
break;
|
|
|
|
#ifdef EXTENDED
|
|
case PASTTYPE: /* paste(File) */
|
|
for (t = 2; t < argc; t++)
|
|
if (!dopaste(argv[t])) cantread(argv[t]);
|
|
break;
|
|
|
|
case SPASTYPE: /* spaste(File) */
|
|
for (t = 2; t < argc; t++)
|
|
(void) dopaste(argv[t]);
|
|
break;
|
|
|
|
case TRIMTYPE: /* m4trim(Source,..) */
|
|
if (argc > 2) m4trim(argv, argc);
|
|
break;
|
|
|
|
case DEFQTYPE: /* defquote(Mac,...) */
|
|
dodefqt(argv, argc);
|
|
break;
|
|
|
|
case QUTRTYPE: /* <quote>(text...) */
|
|
doqutr(argv, argc);
|
|
break;
|
|
#endif
|
|
|
|
case CHNQTYPE: /* changequote([Left[,Right]]) */
|
|
dochq(argv, argc);
|
|
break;
|
|
|
|
case CHNCTYPE: /* changecom([Left[,Right]]) */
|
|
dochc(argv, argc);
|
|
break;
|
|
|
|
case SUBSTYPE: /* substr(Source[,Offset[,Length]]) */
|
|
/* select substring */
|
|
if (argc > 3) dosub(argv, argc);
|
|
break;
|
|
|
|
case SHIFTYPE: /* shift(~args~) */
|
|
/* push back all arguments except the first one */
|
|
/* (i.e. skip argv[2]) */
|
|
if (argc > 3) {
|
|
for (t = argc-1; t > 3; t--) {
|
|
pbqtd(argv[t]);
|
|
putback(',');
|
|
}
|
|
pbqtd(argv[3]);
|
|
}
|
|
break;
|
|
|
|
case DIVRTYPE: /* divert(N) */
|
|
if (argc > 2 && (t = expr(argv[2])) != 0) {
|
|
dodiv(t);
|
|
} else {
|
|
active = stdout;
|
|
oindex = 0;
|
|
}
|
|
op = active;
|
|
break;
|
|
|
|
case UNDVTYPE: /* undivert(N...) */
|
|
doundiv(argv, argc);
|
|
op = active;
|
|
break;
|
|
|
|
case DIVNTYPE: /* divnum() */
|
|
/* return the number of current output diversion */
|
|
pbnum(oindex);
|
|
break;
|
|
|
|
case UNDFTYPE: /* undefine(..) */
|
|
/* undefine a previously defined macro(s) or m4 keyword(s). */
|
|
for (t = 2; t < argc; t++) remhash(argv[t], ALL);
|
|
break;
|
|
|
|
case POPDTYPE: /* popdef(Mac...) */
|
|
/* remove the topmost definitions of macro(s) or m4 keyword(s). */
|
|
for (t = 2; t < argc; t++) remhash(argv[t], TOP);
|
|
break;
|
|
|
|
case MKTMTYPE: /* maketemp(Pattern) */
|
|
/* create a temporary file */
|
|
if (argc > 2) pbstr(mktemp(argv[2]));
|
|
break;
|
|
|
|
case TRNLTYPE: /* translit(Source,Dom,Rng) */
|
|
/* replace all characters in the source string that */
|
|
/* appears in the "from" string with the corresponding */
|
|
/* characters in the "to" string. */
|
|
|
|
if (argc > 3) {
|
|
char temp[MAXTOK];
|
|
|
|
if (argc > 4)
|
|
map(temp, argv[2], argv[3], argv[4]);
|
|
else
|
|
map(temp, argv[2], argv[3], null);
|
|
pbstr(temp);
|
|
} else if (argc > 2)
|
|
pbstr(argv[2]);
|
|
break;
|
|
|
|
case INDXTYPE: /* index(Source,Target) */
|
|
/* find the index of the second argument string in */
|
|
/* the first argument string. -1 if not present. */
|
|
pbnum(argc > 3 ? indx(argv[2], argv[3]) : -1);
|
|
break;
|
|
|
|
case ERRPTYPE: /* errprint(W,...,W) */
|
|
/* print the arguments to stderr file */
|
|
for (t = 2; t < argc; t++) fprintf(stderr, "%s ", argv[t]);
|
|
fprintf(stderr, "\n");
|
|
break;
|
|
|
|
case DNLNTYPE: /* dnl() */
|
|
/* eat upto and including newline */
|
|
while ((t = gpbc()) != '\n' && t != EOF) ;
|
|
break;
|
|
|
|
case M4WRTYPE: /* m4wrap(AtExit) */
|
|
/* set up for wrap-up/wind-down activity. */
|
|
/* NB: if there are several calls to m4wrap */
|
|
/* only the last is effective; strange, but */
|
|
/* that's what System V does. */
|
|
m4wraps = argc > 2 ? strsave(argv[2]) : null;
|
|
break;
|
|
|
|
case EXITTYPE: /* m4exit(Expr) */
|
|
/* immediate exit from m4. */
|
|
killdiv(); /* mustn't forget that one! */
|
|
exit(argc > 2 ? expr(argv[2]) : 0);
|
|
break;
|
|
|
|
case DEFNTYPE: /* defn(Mac) */
|
|
for (t = 2; t < argc; t++)
|
|
dodefn(argv[t]);
|
|
break;
|
|
|
|
default:
|
|
error("m4: major botch in eval.");
|
|
break;
|
|
}
|
|
|
|
ep = PREVEP; /* flush strspace */
|
|
sp = PREVSP; /* previous sp.. */
|
|
fp = PREVFP; /* rewind stack... */
|
|
}
|
|
break;
|
|
|
|
default:
|
|
chrsave(t); /* stack the char */
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
int main(argc, argv)
|
|
int argc;
|
|
char **argv;
|
|
{
|
|
register int c;
|
|
register int n;
|
|
char *p;
|
|
|
|
#ifdef SIGINT
|
|
if (signal(SIGINT, SIG_IGN) != SIG_IGN)
|
|
signal(SIGINT, onintr);
|
|
#endif
|
|
|
|
/* Initialise the chtype[] table.
|
|
'0' .. '9' -> 1..10
|
|
'A' .. 'Z' -> 11..37
|
|
'a' .. 'z' -> 11..37
|
|
'_' -> 38
|
|
all other characters -> 0
|
|
*/
|
|
for (c = EOF; c <= UCHAR_MAX; c++) chtype[c - EOF] = 0;
|
|
for (c = 1, p = "0123456789"; *p; p++, c++)
|
|
chtype[*(unsigned char *)p - EOF] = c;
|
|
for (c = 11, p = "abcdefghijklmnopqrstuvwxyz"; *p; p++, c++)
|
|
chtype[*(unsigned char *)p - EOF] = c;
|
|
for (c = 11, p = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; *p; p++, c++)
|
|
chtype[*(unsigned char *)p - EOF] = c;
|
|
chtype['_' - EOF] = 38;
|
|
|
|
#ifdef NONZEROPAGES
|
|
/* If your system does not initialise global variables to */
|
|
/* 0 bits, do it here. */
|
|
for (n = 0; n < HASHSIZE; n++) hashtab[n] = nil;
|
|
for (n = 0; n < MAXOUT; n++) outfile[n] = NULL;
|
|
#endif
|
|
initkwds();
|
|
|
|
while ((c = getopt(argc, argv, "cetD:U:o:B:H:S:T:")) != EOF) {
|
|
switch (c) {
|
|
#if 0
|
|
case 's': /* enable #line sync in output */
|
|
fprintf(stderr, "m4: this version does not support -s\n");
|
|
exit(2);
|
|
#endif
|
|
|
|
case 'c': /* strip comments */
|
|
strip ^= 1;
|
|
break;
|
|
|
|
case 'e': /* interactive */
|
|
(void) signal(SIGINT, SIG_IGN);
|
|
setbuf(stdout, NULL);
|
|
break;
|
|
|
|
case 'D': /* define something..*/
|
|
for (p = optarg; *p && *p != '='; p++) ;
|
|
if (*p) *p++ = EOS;
|
|
dodefine(optarg, p);
|
|
break;
|
|
|
|
case 'U': /* undefine... */
|
|
remhash(optarg, TOP);
|
|
break;
|
|
|
|
case 'B': case 'H': /* System V compatibility */
|
|
case 'S': case 'T': /* ignore them */
|
|
break;
|
|
|
|
case 'o': /* specific output */
|
|
if (!freopen(optarg, "w", stdout)) {
|
|
perror(optarg);
|
|
exit(1);
|
|
}
|
|
break;
|
|
|
|
case '?':
|
|
default:
|
|
usage();
|
|
}
|
|
}
|
|
|
|
active = stdout; /* default active output */
|
|
m4temp = mktemp(DIVNAM); /* filename for diversions */
|
|
|
|
sp = -1; /* stack pointer initialized */
|
|
fp = 0; /* frame pointer initialized */
|
|
|
|
if (optind == argc) { /* no more args; read stdin */
|
|
infile[0] = stdin; /* default input (naturally) */
|
|
#ifndef NO__FILE
|
|
dodefine("__FILE__", "-"); /* Helas */
|
|
#endif
|
|
macro(); /* process that file */
|
|
} else /* file names in commandline */
|
|
for (; optind < argc; optind++) {
|
|
char *name = argv[optind]; /* next file name */
|
|
infile[0] = fopen(name, "r");
|
|
if (!infile[0]) cantread(name);
|
|
sp = -1; /* stack pointer initialized */
|
|
fp = 0; /* frame pointer initialized */
|
|
ilevel = 0; /* reset input file stack ptr*/
|
|
#ifndef NO__FILE
|
|
dodefine("__FILE__", name);
|
|
#endif
|
|
macro();
|
|
fclose(infile[0]);
|
|
}
|
|
|
|
if (*m4wraps) { /* anything for rundown ?? */
|
|
ilevel = 0; /* in case m4wrap includes.. */
|
|
putback(EOF); /* eof is a must !! */
|
|
pbstr(m4wraps); /* user-defined wrapup act */
|
|
macro(); /* last will and testament */
|
|
}
|
|
|
|
if (active != stdout)
|
|
active = stdout; /* reset output just in case */
|
|
|
|
for (n = 1; n < MAXOUT; n++) /* default wrap-up: undivert */
|
|
if (outfile[n] != NULL) getdiv(n);
|
|
|
|
if (outfile[0] != NULL) { /* remove bitbucket if used */
|
|
(void) fclose(outfile[0]);
|
|
m4temp[UNIQUE] = '0';
|
|
#if unix
|
|
(void) unlink(m4temp);
|
|
#else
|
|
(void) remove(m4temp);
|
|
#endif
|
|
}
|
|
exit(0);
|
|
return 0;
|
|
}
|
|
|