Path: utzoo!attcan!uunet!husc6!purdue!i.cc.purdue.edu!j.cc.purdue.edu!ain From: ain@j.cc.purdue.edu (Patrick White) Newsgroups: comp.sources.amiga Subject: M4 (sources 2 of 2) Keywords: m4, macro, processor Message-ID: <7175@j.cc.purdue.edu> Date: 1 Jun 88 13:00:21 GMT Organization: PUCC Land, USA Lines: 1272 Approved: ain@j.cc.purdue.edu (Patrick White) Submitted by: kuhling!jonasf (Jonas Flygare) Summary: unix m4 look-alike macro processor. Poster Boy: Patrick White (ain@j.cc.purdue.edu) Archive Name: sources/amiga/volume5/m4.src.sh2.Z tested. NOTES: I undid the shar to undo the uuencoded compressed files, and to separate the docs from everything else. I nroffed the docs so everybody gets a readable copy of the docs. A patch to some of the test files was included with the origional posting.. I applied the patch to the files and excluded the patch from this posting. . -- Pat White (co-moderator comp.sources/binaries.amiga) ARPA/UUCP: j.cc.purdue.edu!ain BITNET: PATWHITE@PURCCVM PHONE: (317) 743-8421 U.S. Mail: 320 Brown St. apt. 406, West Lafayette, IN 47906 ======================================== # This is a shell archive. # Remove everything above and including the cut line. # Then run the rest of the file through sh. #----cut here-----cut here-----cut here-----cut here----# #!/bin/sh # shar: Shell Archiver # Run the following text with /bin/sh to create: # serv.c # main.c.orig # m4.1 # This archive created: Mon May 16 09:27:49 1988 # By: Patrick White (PUCC Land, USA) echo shar: extracting serv.c '(11602 characters)' cat << \SHAR_EOF > serv.c /* * serv.c * Facility: m4 macro processor * by: oz */ #include "mdef.h" #include "extr.h" extern ndptr lookup(); extern ndptr addent(); extern char *strsave(); char *dumpfmt = "`%s'\t`%s'\n"; /* format string for dumpdef */ /* * expand - user-defined macro expansion * */ int expand(argv, argc) register char *argv[]; register int argc; { register char *t; register char *p; register int n; register int argno; t = argv[0]; /* defn string as a whole */ p = t; while (*p) p++; p--; /* last character of defn */ while (p > t) { if (*(p-1) != ARGFLAG) putback(*p); else { switch (*p) { case '#': pbnum(argc-2); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': if ((argno = *p - '0') < argc-1) pbstr(argv[argno+1]); break; case '*': for (n = argc - 1; n > 2; n--) { pbstr(argv[n]); putback(','); } pbstr(argv[2]); break; default : putback(*p); break; } p--; } p--; } if (p == t) /* do last character */ putback(*p); } /* * dodefine - install definition in the table * */ int dodefine(name, defn) register char *name; register char *defn; { register ndptr p; if (!*name) error("m4: null definition."); if (strcmp(name, defn) == 0) error("m4: recursive definition."); if ((p = lookup(name)) == nil) p = addent(name); else if (p->defn != null) free(p->defn); if (!*defn) p->defn = null; else p->defn = strsave(defn); p->type = MACRTYPE; } /* * dodefn - push back a quoted definition of * the given name. */ int dodefn(name) char *name; { register ndptr p; if ((p = lookup(name)) != nil && p->defn != null) { putback(rquote); pbstr(p->defn); putback(lquote); } } /* * dopushdef - install a definition in the hash table * without removing a previous definition. Since * each new entry is entered in *front* of the * hash bucket, it hides a previous definition from * lookup. */ int dopushdef(name, defn) register char *name; register char *defn; { register ndptr p; if (!*name) error("m4: null definition"); if (strcmp(name, defn) == 0) error("m4: recursive definition."); p = addent(name); if (!*defn) p->defn = null; else p->defn = strsave(defn); p->type = MACRTYPE; } /* * dodumpdef - dump the specified definitions in the hash * table to stderr. If nothing is specified, the entire * hash table is dumped. * */ int dodump(argv, argc) register char *argv[]; register int argc; { register int n; ndptr p; if (argc > 2) { for (n = 2; n < argc; n++) if ((p = lookup(argv[n])) != nil) fprintf(stderr, dumpfmt, p->name, p->defn); } else { for (n = 0; n < HASHSIZE; n++) for (p = hashtab[n]; p != nil; p = p->nxtptr) fprintf(stderr, dumpfmt, p->name, p->defn); } } /* * doifelse - select one of two alternatives - loop. * */ int doifelse(argv,argc) register char *argv[]; register int argc; { cycle { if (strcmp(argv[2], argv[3]) == 0) pbstr(argv[4]); else if (argc == 6) pbstr(argv[5]); else if (argc > 6) { argv += 3; argc -= 3; continue; } break; } } /* * doinclude - include a given file. * */ doincl(ifile) char *ifile; { if (ilevel+1 == MAXINP) error("m4: too many include files."); if ((infile[ilevel+1] = fopen(ifile, "r")) != NULL) { ilevel++; return (1); } else return (0); } #ifdef EXTENDED /* * dopaste - include a given file without any * macro processing. */ dopaste(pfile) char *pfile; { FILE *pf; register int c; if ((pf = fopen(pfile, "r")) != NULL) { while((c = getc(pf)) != EOF) putc(c, active); (void) fclose(pf); return(1); } else return(0); } #endif /* * dochq - change quote characters * */ int dochq(argv, argc) register char *argv[]; register int argc; { if (argc > 2) { if (*argv[2]) lquote = *argv[2]; if (argc > 3) { if (*argv[3]) rquote = *argv[3]; } else rquote = lquote; } else { lquote = LQUOTE; rquote = RQUOTE; } } /* * dochc - change comment characters * */ int dochc(argv, argc) register char *argv[]; register int argc; { if (argc > 2) { if (*argv[2]) scommt = *argv[2]; if (argc > 3) { if (*argv[3]) ecommt = *argv[3]; } else ecommt = ECOMMT; } else { scommt = SCOMMT; ecommt = ECOMMT; } } /* * dodivert - divert the output to a temporary file * */ int dodiv(n) register int n; { if (n < 0 || n >= MAXOUT) n = 0; /* bitbucket */ if (outfile[n] == NULL) { m4temp[UNIQUE] = n + '0'; if ((outfile[n] = fopen(m4temp, "w")) == NULL) error("m4: cannot divert."); } oindex = n; active = outfile[n]; } /* * doundivert - undivert a specified output, or all * other outputs, in numerical order. */ int doundiv(argv, argc) register char *argv[]; register int argc; { register int ind; register int n; if (argc > 2) { for (ind = 2; ind < argc; ind++) { n = atoi(argv[ind]); if (n > 0 && n < MAXOUT && outfile[n] != NULL) getdiv(n); } } else for (n = 1; n < MAXOUT; n++) if (outfile[n] != NULL) getdiv(n); } /* * dosub - select substring * */ int dosub (argv, argc) register char *argv[]; register int argc; { register char *ap, *fc, *k; register int nc; if (argc < 5) nc = MAXTOK; else #ifdef EXPR nc = expr(argv[4]); #else nc = atoi(argv[4]); #endif ap = argv[2]; /* target string */ #ifdef EXPR fc = ap + expr(argv[3]); /* first char */ #else fc = ap + atoi(argv[3]); /* first char */ #endif if (fc >= ap && fc < ap+strlen(ap)) for (k = fc+min(nc,strlen(fc))-1; k >= fc; k--) putback(*k); } /* * map: * map every character of s1 that is specified in from * into s3 and replace in s. (source s1 remains untouched) * * This is a standard implementation of map(s,from,to) function of ICON * language. Within mapvec, we replace every character of "from" with * the corresponding character in "to". If "to" is shorter than "from", * than the corresponding entries are null, which means that those * characters dissapear altogether. Furthermore, imagine * map(dest, "sourcestring", "srtin", "rn..*") type call. In this case, * `s' maps to `r', `r' maps to `n' and `n' maps to `*'. Thus, `s' * ultimately maps to `*'. In order to achieve this effect in an efficient * manner (i.e. without multiple passes over the destination string), we * loop over mapvec, starting with the initial source character. if the * character value (dch) in this location is different than the source * character (sch), sch becomes dch, once again to index into mapvec, until * the character value stabilizes (i.e. sch = dch, in other words * mapvec[n] == n). Even if the entry in the mapvec is null for an ordinary * character, it will stabilize, since mapvec[0] == 0 at all times. At the * end, we restore mapvec* back to normal where mapvec[n] == n for * 0 <= n <= 127. This strategy, along with the restoration of mapvec, is * about 5 times faster than any algorithm that makes multiple passes over * destination string. * */ int map(dest,src,from,to) register char *dest; register char *src; register char *from; register char *to; { register char *tmp; register char sch, dch; static char mapvec[128] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127 }; if (*src) { tmp = from; /* * create a mapping between "from" and "to" */ while (*from) mapvec[*from++] = (*to) ? *to++ : (char) 0; while (*src) { sch = *src++; dch = mapvec[sch]; while (dch != sch) { sch = dch; dch = mapvec[sch]; } if (*dest = dch) dest++; } /* * restore all the changed characters */ while (*tmp) { mapvec[*tmp] = *tmp; tmp++; } } *dest = (char) 0; } SHAR_EOF if test 11602 -ne "`wc -c serv.c`" then echo shar: error transmitting serv.c '(should have been 11602 characters)' fi echo shar: extracting main.c.orig '(11102 characters)' cat << \SHAR_EOF > main.c.orig /* * main.c * Facility: m4 macro processor * by: oz */ #include "mdef.h" /* * 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: * * 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.. * * 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 * */ #ifdef MYMKTMP int mytmpnum=000000; /* used in mktemp() */ #endif ndptr hashtab[HASHSIZE]; /* hash table for macros etc. */ char buf[BUFSIZE]; /* push-back buffer */ char *bp = buf; /* first available character */ 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 */ FILE *infile[MAXINP]; /* input file stack (0=stdin) */ FILE *outfile[MAXOUT]; /* diversion array(0=bitbucket)*/ FILE *active; /* active output file pointer */ char *m4temp; /* filename for diversions */ 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 scommt = SCOMMT; /* start character for comment */ char ecommt = ECOMMT; /* end character for comment */ struct keyblk keywrds[] = { /* m4 keywords to be installed */ "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, #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 }; #define MAXKEYS (sizeof(keywrds)/sizeof(struct keyblk)) extern ndptr lookup(); extern ndptr addent(); extern int onintr(); extern char *malloc(); extern char *mktemp(); extern int optind; extern char *optarg; main(argc,argv) char *argv[]; { register int c; register int n; char *p; if (signal(SIGINT, SIG_IGN) != SIG_IGN) signal(SIGINT, onintr); #ifdef NONZEROPAGES initm4(); #endif initkwds(); while ((c = getopt(argc, argv, "tD:U:o:")) != EOF) switch(c) { case 'D': /* define something..*/ for (p = optarg; *p; p++) if (*p == '=') break; if (*p) *p++ = EOS; dodefine(optarg, p); break; case 'U': /* undefine... */ remhash(optarg, TOP); break; case 'o': /* specific output */ case '?': default: usage(); } infile[0] = stdin; /* default input (naturally) */ active = stdout; /* default active output */ m4temp = mktemp(DIVNAM); /* filename for diversions */ sp = -1; /* stack pointer initialized */ fp = 0; /* frame pointer initialized */ macro(); /* get some work done here */ 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 */ } else /* default wrap-up: undivert */ for (n = 1; n < MAXOUT; n++) if (outfile[n] != NULL) getdiv(n); /* remove bitbucket if used */ if (outfile[0] != NULL) { (void) fclose(outfile[0]); m4temp[UNIQUE] = '0'; #if vms (void) remove(m4temp); #else (void) unlink(m4temp); #endif } exit(0); } ndptr inspect(); /* forward ... */ /* * macro - the work horse.. * */ macro() { char token[MAXTOK]; register char *s; register int t, l; register ndptr p; register int nlpar; cycle { if ((t = gpbc()) == '_' || isalpha(t)) { putback(t); if ((p = inspect(s = token)) == nil) { if (sp < 0) while (*s) putc(*s++, active); else while (*s) chrsave(*s++); } else { /* * real thing.. First build a call frame: * */ pushf(fp); /* previous call frm */ pushf(p->type); /* type of the call */ pushf(0); /* parenthesis level */ fp = sp; /* new frame pointer */ /* * now push the string arguments: * */ pushs(p->defn); /* defn string */ pushs(p->name); /* macro name */ pushs(ep); /* start next..*/ putback(l = gpbc()); if (l != LPAREN) { /* add bracks */ putback(RPAREN); putback(LPAREN); } } } else if (t == EOF) { if (sp > -1) error("m4: unexpected end of input"); if (--ilevel < 0) break; /* all done thanks.. */ (void) fclose(infile[ilevel+1]); continue; } /* * non-alpha single-char token seen.. * [the order of else if .. stmts is * important.] * */ else if (t == lquote) { /* strip quotes */ nlpar = 1; do { if ((l = gpbc()) == rquote) nlpar--; else if (l == lquote) nlpar++; else if (l == EOF) error("m4: missing right quote"); if (nlpar > 0) chrsave(l); } while (nlpar != 0); } else if (sp < 0) { /* not in a macro at all */ if (t == scommt) { /* comment handling here */ putc(t, active); while ((t = gpbc()) != ecommt) putc(t, active); } putc(t, active); /* output directly.. */ } else switch(t) { case LPAREN: if (PARLEV > 0) chrsave(t); while (isspace(l = gpbc())) ; /* skip blank, tab, nl.. */ putback(l); PARLEV++; break; case RPAREN: if (--PARLEV > 0) chrsave(t); else { /* end of argument list */ chrsave(EOS); if (sp == STACKMAX) error("m4: internal stack overflow"); if (CALTYP == MACRTYPE) expand(mstack+fp+1, sp-fp); else eval(mstack+fp+1, sp-fp, CALTYP); ep = PREVEP; /* flush strspace */ sp = PREVSP; /* previous sp.. */ fp = PREVFP; /* rewind stack...*/ } break; case COMMA: if (PARLEV == 1) { chrsave(EOS); /* new argument */ while (isspace(l = gpbc())) ; putback(l); pushs(ep); } break; default: chrsave(t); /* stack the char */ break; } } } /* * build an input token.. * consider only those starting with _ or A-Za-z. This is a * combo with lookup to speed things up. */ ndptr inspect(tp) register char *tp; { register int h = 0; register char c; register char *name = tp; register char *etp = tp+MAXTOK; register ndptr p; while (tp < etp && (isalnum(c = gpbc()) || c == '_')) h += (*tp++ = c); putback(c); if (tp == etp) error("m4: token too long"); *tp = EOS; for (p = hashtab[h%HASHSIZE]; p != nil; p = p->nxtptr) if (strcmp(name, p->name) == 0) break; return(p); } #ifdef NONZEROPAGES /* * initm4 - initialize various tables. Useful only if your system * does not know anything about demand-zero pages. * */ initm4() { register int i; for (i = 0; i < HASHSIZE; i++) hashtab[i] = nil; for (i = 0; i < MAXOUT; i++) outfile[i] = NULL; } #endif /* * initkwds - initialise m4 keywords as fast as possible. * This very similar to install, but without certain overheads, * such as calling lookup. Malloc is not used for storing the * keyword strings, since we simply use the static pointers * within keywrds block. We also assume that there is enough memory * to at least install the keywords (i.e. malloc won't fail). * */ initkwds() { register int i; register int h; register ndptr p; for (i = 0; i < MAXKEYS; i++) { h = hash(keywrds[i].knam); p = (ndptr) malloc(sizeof(struct ndblock)); p->nxtptr = hashtab[h]; hashtab[h] = p; p->name = keywrds[i].knam; p->defn = null; p->type = keywrds[i].ktyp | STATIC; } } SHAR_EOF if test 11102 -ne "`wc -c main.c.orig`" then echo shar: error transmitting main.c.orig '(should have been 11102 characters)' fi echo shar: extracting m4.1 '(9643 characters)' cat << \SHAR_EOF > m4.1 .TH M4 local "30 Aug 1987" .DA 08 Jan 1986 .SH NAME pd m4 \- macro processor .SH ORIGIN MetaSystems .SH SYNOPSIS .BI m4 "[ options ]" .SH DESCRIPTION .I Pd M4 is a un*x M4 look-alike macro processor intended as a front end for Ratfor, Pascal, and other languages that do not have a built-in macro processing capability. Pd M4 reads standard input, the processed text is written on the standard output. .PP The options and their effects are as follows: .TP \f3\-D\fP\f2name\^\fP[\f3=\fP\f2val\^\fP] Defines .I name to .I val or to null in .IR val 's absence. .TP .BI \-U name undefines .IR name . .PP Macro calls have the form: .PP .RS \fBname\fI(arg1,arg2, .\|.\|., argn)\fR .RE .PP The .B ( must immediately follow the name of the macro. If the name of a defined macro is not followed by a .BR ( , it is taken to be a call of that macro with no arguments, i.e. name(). Potential macro names consist of alphabetic letters and digits. .PP Leading unquoted blanks, tabs and newlines are ignored while collecting arguments. Left and right single quotes are used to quote strings. The value of a quoted string is the string stripped of the quotes. .PP When a macro name is recognized, its arguments are collected by searching for a matching .BR ) . If fewer arguments are supplied than are in the macro definition, the trailing arguments are taken to be null. Macro evaluation proceeds normally during the collection of the arguments, and any commas or right parentheses which happen to turn up within the value of a nested call are as effective as those in the original input text. (This is typically referred as .I inside-out macro expansion.) After argument collection, the value of the macro is pushed back onto the input stream and rescanned. .PP .I Pd M4 makes available the following built-in macros. They may be redefined, but once this is done the original meaning is lost. Their values are null unless otherwise stated. .de MC .TP 14 .B \\$1 usage: \\fI\\$1\\$2\\fR .br .. .MC define "(name [, val])" the second argument is installed as the value of the macro whose name is the first argument. If there is no second argument, the value is null. Each occurrence of .BI $ n in the replacement text, where .I n is a digit, is replaced by the .IR n -th argument. Argument 0 is the name of the macro; missing arguments are replaced by the null string. .MC defn "(name [, name ...]) returns the quoted definition of its argument(s). Useful in renaming macros. .MC undefine "(name [, name ...])" removes the definition of the macro(s) named. If there is more than one definition for the named macro, (due to previous use of .IR pushdef ) all definitions are removed. .MC pushdef "(name [, val])" like .IR define , but saves any previous definition by stacking the current definition. .MC popdef "(name [, name ...])" removes current definition of its argument(s), exposing the previous one if any. .MC ifdef "(name, if-def [, ifnot-def])" if the first argument is defined, the value is the second argument, otherwise the third. If there is no third argument, the value is null. A word indicating the current operating system is predefined. (e.g. .I unix or .IR vms ) .MC shift "(arg, arg, arg, ...)" returns all but its first argument. The other arguments are quoted and pushed back with commas in between. The quoting nullifies the effect of the extra scan that will subsequently be performed. .MC changequote "(lqchar, rqchar)" change quote symbols to the first and second arguments. With no arguments, the quotes are reset back to the default characters. (i.e., \*`\|\*'). .MC changecom "(lcchar, rcchar)" change left and right comment markers from the default .B # and .BR newline . With no arguments, the comment mechanism is reset back to the default characters. With one argument, the left marker becomes the argument and the right marker becomes newline. With two arguments, both markers are affected. .MC divert "(divnum)" .I m4 maintains 10 output streams, numbered 0-9. initially stream 0 is the current stream. The .I divert macro changes the current output stream to its (digit-string) argument. Output diverted to a stream other than 0 through 9 disappears into bitbucket. .MC undivert "([divnum [, divnum ...]])" causes immediate output of text from diversions named as argument(s), or all diversions if no argument. Text may be undiverted into another diversion. Undiverting discards the diverted text. At the end of input processing, .I M4 forces an automatic .IR undivert , unless .I m4wrap is defined. .MC divnum "()" returns the value of the current output stream. .MC dnl "()" reads and discards characters up to and including the next newline. .MC ifelse "(arg, arg, if-same [, ifnot-same | arg, arg ...])" has three or more arguments. If the first argument is the same string as the second, then the value is the third argument. If not, and if there are more than four arguments, the process is repeated with arguments 4, 5, 6 and 7. Otherwise, the value is either the fourth string, or, if it is not present, null. .MC incr "(num)" returns the value of its argument incremented by 1. The value of the argument is calculated by interpreting an initial digit-string as a decimal number. .MC decr "(num)" returns the value of its argument decremented by 1. .MC eval "(expression)" evaluates its argument as a constant expression, using integer arithmetic. The evaluation mechanism is very similar to that of .I cpp (#if expression). The expression can involve only integer constants and character constants, possibly connected by the binary operators .nf .ft B * / % + - >> << < > <= >= == != & ^ | && || .ft R .fi or the unary operators \fB\- ~ !\fR or by the ternary operator \fB ? : \fR. Parentheses may be used for grouping. Octal numbers may be specified as in C. .MC len "(string)" returns the number of characters in its argument. .MC index "(search-string, string)" returns the position in its first argument where the second argument begins (zero origin), or \-1 if the second argument does not occur. .MC substr "(string, index [, length])" returns a substring of its first argument. The second argument is a zero origin number selecting the first character (internally treated as an expression); the third argument indicates the length of the substring. A missing third argument is taken to be large enough to extend to the end of the first string. .MC translit "(source, from [, to])" transliterates the characters in its first argument from the set given by the second argument to the set given by the third. If the third argument is shorter than the second, all extra characters in the second argument are deleted from the first argument. If the third argument is missing altogether, all characters in the second argument are deleted from the first argument. .MC include "(filename)" returns the contents of the file named in the argument. .MC sinclude "(filename)" is identical to .IR include , except that it says nothing if the file is inaccessible. .MC paste "(filename)" returns the contents of the file named in the argument without any processing, unlike .IR include. .MC spaste "(filename)" is identical to .IR paste , except that it says nothing if the file is inaccessible. .MC syscmd "(command)" executes the .SM UNIX command given in the first argument. No value is returned. .MC sysval "()" is the return code from the last call to .IR syscmd . .MC maketemp "(string)" fills in a string of .SM XXXXXX in its argument with the current process .SM ID\*S. .MC m4exit "([exitcode])" causes immediate exit from .IR m4 . Argument 1, if given, is the exit code; the default is 0. .MC m4wrap "(m4-macro-or-built-in)" argument 1 will be pushed back at final .BR EOF ; example: m4wrap(`dumptable()'). .MC errprint "(str [, str, str, ...])" prints its argument(s) on stderr. If there is more than one argument, each argument is separated by a space during the output. .MC dumpdef "([name, name, ...])" prints current names and definitions, for the named items, or for all if no arguments are given. .dt .SH AUTHOR Ozan S. Yigit (oz) .SH BUGS Pd M4 is distributed at the source level, and does not require an expensive license agreement. .PP A sufficiently complex M4 macro set is about as readable as .BR APL . .PP All complex uses of M4 require the ability to program in deep recursion. Previous lisp experience is recommended. .PP Pd M4 is slower than V7 M4. .SH EXAMPLES .PP The following macro program illustrates the type of things that can be done with M4. .PP .RS .nf \fBchangequote\fR(<,>) \fBdefine\fR(HASHVAL,99) \fBdnl\fR \fBdefine\fR(hash,<\fBexpr\fR(str(\fBsubstr\fR($1,1),0)%HASHVAL)>) \fBdnl\fR \fBdefine\fR(str, <\fBifelse\fR($1,",$2,,1),<\fBexpr\fR($2+'\fBsubstr\fR($1,0,1)')>)>) >) \fBdnl\fR \fBdefine\fR(KEYWORD,<$1,hash($1),>) \fBdnl\fR \fBdefine\fR(TSTART, ) \fBdnl\fR \fBdefine\fR(TEND,< "",0 };>) \fBdnl\fR .fi .RE .PP Thus a keyword table containing the keyword string and its pre-calculated hash value may be generated thus: .PP .RS .nf TSTART KEYWORD("foo") KEYWORD("bar") KEYWORD("baz") TEND .fi .RE .PP which will expand into: .RS .nf struct prehash { char *keyword; int hashval; } keytab[] = { "foo",27, "bar",12, "baz",20, "",0 }; .fi .RE .PP Presumably, such a table would speed up the installation of the keywords into a dynamic hash table. (Note that the above macro cannot be used with .IR M4 , since .B eval does not handle character constants.) .SH SEE ALSO cc(1), m4(1), cpp(1). .I "The M4 Macro Processor\^" by B. W. Kernighan and D. M. Ritchie. SHAR_EOF if test 9643 -ne "`wc -c m4.1`" then echo shar: error transmitting m4.1 '(should have been 9643 characters)' fi # End of shell archive exit 0