This commit was manufactured by cvs2svn to create tag 'FreeBSD-release/1.0'.
[unix-history] / usr.bin / m4 / serv.c
index 6a9f709..6b0a4bb 100644 (file)
-/*
- * Copyright (c) 1989 The Regents of the University of California.
- * All rights reserved.
- *
- * This code is derived from software contributed to Berkeley by
- * Ozan Yigit.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- * 3. All advertising materials mentioning features or use of this software
- *    must display the following acknowledgement:
- *     This product includes software developed by the University of
- *     California, Berkeley and its contributors.
- * 4. Neither the name of the University nor the names of its contributors
- *    may be used to endorse or promote products derived from this software
- *    without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-#ifndef lint
-static char sccsid[] = "@(#)serv.c     5.3 (Berkeley) 2/26/91";
-#endif /* not lint */
+/*  File   : serv.c
+    Author : Ozan Yigit
+    Updated: 4 May 1992
+    Defines: Principal built-in macros for PD M4.
+*/
 
 
-/*
- * serv.c
- * Facility: m4 macro processor
- * by: oz
- */
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
 #include "mdef.h"
 #include "mdef.h"
-#include "extr.h" 
-#include "pathnames.h"
-
-extern ndptr lookup();
-extern ndptr addent();
-char *dumpfmt = "`%s'\t`%s'\n"; /* format string for dumpdef   */
-/*
- * expand - user-defined macro expansion
- *
- */
-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
- *
- */
-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 = strdup(defn);
-        p->type = MACRTYPE;
-}
-/*
- * dodefn - push back a quoted definition of
- *      the given name.
- */
-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.
- */
-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 = strdup(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.
- *
- */
-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.
- *
- */
-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.
- *
+#include "extr.h"
+#include "ourlims.h"
+
+#define        ucArgv(n) ((unsigned char *)argv[n])
+
+/*  26-Mar-1993                Made m4trim() 8-bit clean.
+*/
+
+/*  expand(<DS FN A1 ... An>)
+            0  1  2      n+1           -- initial indices in argv[]
+           -1  0  1      n             -- after adjusting argv++, argc--
+    This expands a user-defined macro;  FN is the name of the macro, DS
+    is its definition string, and A1 ... An are its arguments.
+*/
+void expand(argv, argc)
+    char **argv;
+    int argc;
+    {
+       register char *t;
+       register char *p;
+       register int n;
+
+#ifdef DEBUG
+       fprintf(stderr, "expand(%s,%d)\n", argv[1], argc);
+#endif
+       argc--;                 /* discount definition string (-1th arg) */
+       t = *argv++;            /* definition string as a whole */
+       for (p = t; *p++; ) ;
+       p -= 2;                 /* points to last character of definition */
+       while (p > t) {         /* if definition is empty, fails at once  */
+           if (*--p != ARGFLAG) {
+               putback(p[1]);
+           } else {
+               switch (p[1]) {
+                   case '#':
+                       pbnum(argc-1);
+                       break;
+                   case '0': case '1': case '2': case '3': case '4':
+                   case '5': case '6': case '7': case '8': case '9':
+                       if ((n = p[1]-'0') < argc) pbstr(argv[n]);
+                       break;
+                   case '*':           /* push all arguments back */
+                       for (n = argc-1; n > 1; n--) {
+                           pbstr(argv[n]);
+                           putback(',');
+                       }
+                       pbstr(argv[1]);
+                       break;
+                   case '@':           /* push arguments back quoted */
+                       for (n = argc-1; n > 1; n--) {
+                           pbqtd(argv[n]);
+                           putback(',');
+                       }
+                       pbqtd(argv[1]);
+                       break;
+                   case '$':           /* $$ => $ */
+                       break;
+                   default:
+                       putback(p[1]);
+                       putback(p[0]);
+                       break;
+               }
+               p--;
+           }
+       }
+       if (p == t) putback(*p);                /* do last character */
+    }
+
+
+static char nuldefmsg[] = "m4: defining null name.";
+static char recdefmsg[] = "m4: macro defined as itself.";
+
+/*  dodefine(Name, Definition)
+    install Definition as the only definition of Name in the hash table.
  */
  */
-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);
-}
+void dodefine(name, defn)
+    register char *name;
+    register char *defn;
+    {
+       register ndptr p;
+
+       if (!name || !*name) error(nuldefmsg);
+       if (strcmp(name, defn) == 0) error(recdefmsg);
+#ifdef DEBUG
+       fprintf(stderr, "define(%s,--)\n", name);
+#endif
+       if ((p = lookup(name)) == nil) {
+           p = addent(name);
+       } else
+       if (p->defn != null) {          /* what if p->type & STATIC ? */
+           free(p->defn);
+       }
+       p->defn = !defn || !*defn ? null : strsave(defn);
+       p->type = MACRTYPE;
+    }
+
+
+/*  dopushdef(Name, Definition)
+    install Definition as the *first* definition of Name in the hash table,
+    but do not remove any existing definitions.  The new definition will
+    hide any old ones until a popdef() removes it.
+*/
+void dopushdef(name, defn)
+    register char *name;
+    register char *defn;
+    {
+       register ndptr p;
+
+       if (!name || !*name) error(nuldefmsg);
+       if (strcmp(name, defn) == 0) error(recdefmsg);
+#ifdef DEBUG
+       fprintf(stderr, "pushdef(%s,--)\n", name);
+#endif
+       p = addent(name);
+       p->defn = !defn || !*defn ? null : strsave(defn);
+       p->type = MACRTYPE;
+    }
+
+
+/*  dodefn(Name)
+    push back a *quoted* copy of Name's definition.
+*/
+void dodefn(name)
+    char *name;
+    {
+       register ndptr p;
+
+       if ((p = lookup(name)) != nil && p->defn != null) pbqtd(p->defn);
+    }
+
+
+/*  dodump(<? dump>)           dump all definition in the hash table
+    dodump(<? dump F1 ... Fn>) dump the definitions of F1 ... Fn in that order
+    The requested definitions are written to stderr.  What happens to names
+    which have a built-in (numeric) definition?
+*/
+void dodump(argv, argc)
+    register char **argv;
+    register int argc;
+    {
+       register int n;
+       ndptr p;
+       static char dumpfmt[] = "define(`%s',\t`%s')\n";
+
+       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(<? ifelse {x y ifx=y}... [else]>)
+             0 1       2 3 4         [2 when we get to it]
+*/
+void doifelse(argv, argc)
+    register char **argv;
+    register int argc;
+    {
+       for (; argc >= 5; argv += 3, argc -= 3)
+           if (strcmp(argv[2], argv[3]) == 0) {
+               pbstr(argv[4]);
+               return;
+           }
+       if (argc >= 3) pbstr(argv[2]);
+    }
+
+
+/*  doinclude(FileName)
+    include a given file.
+*/
+int doincl(FileName)
+    char *FileName;
+    {
+       if (ilevel+1 == MAXINP) error("m4: too many include files.");
+#ifdef DEBUG
+       fprintf(stderr, "include(%s)\n", FileName);
+#endif
+       if ((infile[ilevel+1] = fopen(FileName, "r")) != NULL) {
+#ifndef        NO__FILE
+           dopushdef("__FILE__", FileName);
+#endif
+           bbstack[ilevel+1] = bb;
+           bb = bp;
+           ilevel++;
+           return 1;
+       } else {
+           return 0;
+       }
+    }
+
+
 #ifdef EXTENDED
 #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);
-}
+/*  dopaste(FileName)
+    copy a given file to the output stream without any macro processing.
+*/
+int dopaste(FileName)
+    char *FileName;
+    {
+       register FILE *pf;
+       register FILE *afil = active;
+       register int c;
+
+       if ((pf = fopen(FileName, "r")) != NULL) {
+           while ((c = getc(pf)) != EOF) putc(c, afil);
+           (void) fclose(pf);
+           return 1;
+       } else {
+           return 0;
+       }
+    }
 #endif
 #endif
-/*
- * dochq - change quote characters
- *
- */
-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
- *
- */
-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
- *
- */
-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
+
+
+/*  dochq(<? changequote [left [right [verbatim]]]>)
+          0 1            2     3      4
+    change the quote characters; to single characters only.
+    Empty arguments result in no change for that parameter.
+    Missing arguments result in defaults:
+       changequote             => ` ' ^V
+       changequote(q)          => q q ^V
+       changequote(l,r)        => l r ^V
+       changequote(l,r,v)      => l r v
+    There isn't any way of switching the verbatim-quote off,
+    but if you make it the same as the right quote it won't
+    be able to do anything (we check for R, L, V in that order).
+*/
+void 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];
+                   if (argc > 4 && *argv[4]) vquote = *argv[4];
+           } else {
+               rquote = lquote;
+           }
+       } else {
+           lquote = LQUOTE;
+           rquote = RQUOTE;
+           vquote = VQUOTE;
+       }
+    }
+
+
+/*  dochc(<? changecomment [left [right]]>)
+           0 1             2     3
+    change the comment delimiters; to single characters only.
+*/
+void 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
+*/
+void dodiv(n)
+    register int n;
+    {
+       if (n < 0 || n >= MAXOUT) n = 0;        /* bitbucket */
+       if (outfile[n] == NULL) {
+           m4temp[UNIQUE] = '0' + n;
+           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.
  *              other outputs, in numerical order.
- */
-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
- *
- */
-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 */
+*/
+void 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 = expr(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(<? substr {offset} [{length}]>)
+    The System V Interface Definition does not say what happens when the
+    offset or length are out of range.  I have chosen to force them into
+    range, with the result that unlike the former version of this code,
+    dosub cannot be tricked into SIGSEGV.
+
+    BUG:  This is not 8-bit clean yet.
+*/
+void dosub(argv, argc)
+    char **argv;
+    int argc;
+    {
+       register int nc;                /* number of characters */
+       register char *ap = argv[2];    /* target string */
+       register int al = strlen(ap);   /* its length */
+       register int df = expr(argv[3]);/* offset */
+
+       if (df < 0) df = 0; else        /* force df back into the range */
+       if (df > al) df = al;           /* 0 <= df <= al */
+       al -= df;                       /* now al limits nc */
+
+       if (argc >= 5) {                /* nc is provided */
+           nc = expr(argv[4]);
+           if (nc < 0) nc = 0; else    /* force nc back into the range */
+           if (nc > al) nc = al;       /* 0 <= nc <= strlen(ap)-df */
+       } else {
+           nc = al;                    /* default is all rest of ap */
+       }
+       ap += df + nc;
+       while (--nc >= 0) putback(*--ap);
+    }
+
+
+/* map(dest, src, from, to)
+    map every character of src that is specified in from 
+    into "to" and replace in dest. (source "src" remains untouched)
+
+    This is a standard implementation of Icon's map(s,from,to) function.
+    Within mapvec, we replace every character of "from" with the
+    corresponding character in "to".  If "to" is shorter than "from",
+    then the corresponding entries are null, which means that those
+    characters disappear altogether.  Furthermore, imagine a call like
+    map(dest, "sourcestring", "srtin", "rn..*"). 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 from 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 the destination string.
+*/
+
+void map(d, s, f, t)
+    char *d, *s, *f, *t;
+    {
+       register unsigned char *dest = (unsigned char *)d;
+       register unsigned char *src  = (unsigned char *)s;
+                unsigned char *from = (unsigned char *)f;
+       register unsigned char *to   = (unsigned char *)t;
+       register unsigned char *tmp;
+       register unsigned char sch, dch;
+       static   unsigned char mapvec[1+UCHAR_MAX] = {1};
+
+       if (mapvec[0]) {
+           register int i;
+           for (i = 0; i <= UCHAR_MAX; i++) mapvec[i] = i;
+       }
+       if (src && *src) {
+           /* create a mapping between "from" and "to" */
+           if (to && *to)
+               for (tmp = from; sch = *tmp++; ) mapvec[sch] = *to++;
+           else
+               for (tmp = from; sch = *tmp++; ) mapvec[sch] = '\0';
+
+           while (sch = *src++) {
+               while ((dch = mapvec[sch]) != sch) sch = dch;
+               if (*dest = dch) dest++;
+           }
+           /* restore all the changed characters */
+           for (tmp = from; sch = *tmp++; ) mapvec[sch] = sch;
+       }
+       *dest = '\0';
+    }
+
+
+#ifdef EXTENDED
+
+/*  m4trim(<? m4trim [string [leading [trailing [middle [rep]]]]]>)
+           0 1       2       3        4         5       6
+    
+    (1) Any prefix consisting of characters in the "leading" set is removed.
+       The default is " \t\n".
+    (2) Any suffix consisting of characters in the "trailing" set is removed.
+       The default is to be the same as leading.
+    (3) Any block of consecutive characters in the "middle" set is replaced
+       by the rep string.  The default for middle is " \t\n", and the
+       default for rep is the first character of middle.
+*/
+void m4trim(argv, argc)
+    char **argv;
+    int argc;
+    {
+       static unsigned char repbuf[2] = " ";
+       static unsigned char layout[] = " \t\n\r\f";
+       unsigned char *string   = argc > 2 ? ucArgv(2) : repbuf+1;
+       unsigned char *leading  = argc > 3 ? ucArgv(3) : layout;
+       unsigned char *trailing = argc > 4 ? ucArgv(4) : leading;
+       unsigned char *middle   = argc > 5 ? ucArgv(5) : trailing;
+       unsigned char *rep      = argc > 6 ? ucArgv(6) :
+                                                (repbuf[0] = *middle, repbuf);
+       static unsigned char sets[1+UCHAR_MAX];
+#      define PREF 1
+#      define SUFF 2
+#      define MIDL 4
+       register int i, n;
+
+       for (i = UCHAR_MAX; i >= 0; ) sets[i--] = 0;
+       while (*leading)  sets[*leading++]  |= PREF;
+       while (*trailing) sets[*trailing++] |= SUFF;
+       while (*middle)   sets[*middle++]   |= MIDL;
+
+       while (*string && sets[*string]&PREF) string++;
+       n = strlen((char *)string);
+       while (n > 0 && sets[string[n-1]]&SUFF) n--;
+       while (n > 0) {
+           i = string[--n];
+           if (sets[i]&MIDL) {
+               pbstr((char*)rep);
+               while (n > 0 && sets[string[n-1]]&MIDL) n--;
+           } else {
+               putback(i);
+           }
+       }
+    }
+
+
+/*  defquote(MacroName # The name of the "quoter" macro to be defined.
+       [, Opener       # default: "'".  The characters to place at the
+                       # beginning of the result.
+       [, Separator    # default: ",".  The characters to place between
+                       # successive arguments.
+       [, Closer       # default: same as Opener.  The characters to
+                       # place at the end of the result.
+       [, Escape       # default: `'  The escape character to put in
+                       # front of things that need escaping.
+       [, Default      # default: simple.  Possible values are
+                       # [lL].* = letter, corresponds to PLAIN1.
+                       # [dD].* = digit,  corresponds to PLAIN2.
+                       # [sS].* = simple, corresponds to SIMPLE.
+                       # [eE].* = escaped,corresponds to SCAPED.
+                       # .*,              corresponds to FANCY
+       [, Letters      # default: `'.  The characters of type "L".
+       [, Digits       # default: `'.  The characters of type "D".
+       [, Simple       # default: `'.  The characters of type "S".
+       [, Escaped      # default: `'.  The characters of type "E".
+       {, Fancy        # default: none.  Each has the form `C'`Repr'
+                       # saying that the character C is to be represented
+                       # as Repr.  Can be used for trigraphs, \n, &c.
+       }]]]]]]]]])
+
+    Examples:
+       defquote(DOUBLEQT, ")
+       defquote(SINGLEQT, ')
+    After these definitions,
+       DOUBLEQT(a, " b", c)    => "a,"" b"",c"
+       SINGLEQT("Don't`, 'he said.") => '"Don''t, he said."'
+    Other examples defining quote styles for several languages will be
+    provided later.
+
+    A quoter is represented in M4 by a special identifying number and a
+    pointer to a Quoter record.  I expect that there will be few quoters
+    but that they will need to go fairly fast.
+
+*/
+
+#define        PLAIN1  0
+#define        PLAIN2  1
+#define        SIMPLE  2
+#define        SCAPED  3
+#define        FANCY   4
+
+struct Quoter
+    {
+       char *opener;
+       char *separator;
+       char *closer;
+       char *escape;
+       char *fancy[1+UCHAR_MAX];
+       char class[1+UCHAR_MAX];
+     };
+
+void freeQuoter(q)
+    struct Quoter *q;
+    {
+       int i;
+
+       free(q->opener);
+       free(q->separator);
+       free(q->closer);
+       free(q->escape);
+       for (i = UCHAR_MAX; i >= 0; i--)
+           if (q->fancy[i]) free(q->fancy[i]);
+       free((char *)q);
+    }
+
+/*  dodefqt(<
+       0       ?
+       1       defquote
+       2       MacroName
+      [        3       Opener
+      [ 4      Separator
+      [ 5      Closer
+      [ 6      Escape
+      [ 7      Default
+      [ 8      Letters
+      [ 9      Digits
+      [10      Simple
+      [11      Escaped
+      [11+i    Fancy[i]        ]]]]]]]]]]>)
+*/
+
+void dodefqt(argv, argc)
+    char **argv;
+    int argc;
+    {
+       struct Quoter q, *r;
+       register int i;
+       register unsigned char *s;
+       register int c;
+       ndptr p;
+
+       if (!(argc > 2 && *argv[2])) error(nuldefmsg);
+       switch (argc > 7 ? argv[7][0] : '\0') {
+           case 'l': case 'L': c = PLAIN1; break;
+           case 'd': case 'D': c = PLAIN2; break;
+           case 'e': case 'E': c = SCAPED; break;
+           case 'f': case 'F': c = FANCY;  break;
+           default:            c = SIMPLE;
+       }
+       for (i = UCHAR_MAX; --i >= 0; ) q.class[i] = c;
+       for (i = UCHAR_MAX; --i >= 0; ) q.fancy[i] = 0;
+       q.opener = strsave(argc > 3 ? argv[3] : "");
+       q.separator = strsave(argc > 4 ? argv[4] : ",");
+       q.closer = strsave(argc > 5 ? argv[5] : q.opener);
+       q.escape = strsave(argc > 6 ? argv[6] : "");
+       if (argc > 8)
+           for (s = (unsigned char *)argv[8]; c = *s++; )
+               q.class[c] = PLAIN1;
+       if (argc > 9)
+           for (s = (unsigned char *)argv[9]; c = *s++; )
+               q.class[c] = PLAIN2;
+       if (argc > 10)
+           for (s = (unsigned char *)argv[10]; c = *s++; )
+               q.class[c] = SIMPLE;
+       if (argc > 11)
+           for (s = (unsigned char *)argv[11]; c = *s++; )
+               q.class[c] = SCAPED;
+       for (i = 12; i < argc; i++) {
+           s = (unsigned char *)argv[i];
+           c = *s++;
+           q.fancy[c] = strsave((char *)s);
+           q.class[c] = FANCY;
+       }
+       /*  Now we have to make sure that the closing quote works.  */
+       if ((c = q.closer[0]) && q.class[c] <= SIMPLE) {
+           if (q.escape[0]) {
+               q.class[c] = SCAPED;
+           } else {
+               char buf[3];
+               buf[0] = c, buf[1] = c, buf[2] = '\0';
+               q.fancy[c] = strsave(buf);
+               q.class[c] = FANCY;
+           }
+       }
+       /*  We also have to make sure that the escape (if any) works.  */
+       if ((c = q.escape[0]) && q.class[c] <= SIMPLE) {
+           q.class[c] = SCAPED;
+       }
+       r = (struct Quoter *)malloc(sizeof *r);
+       if (r == NULL) error("m4: no more memory");
+       *r = q;
+        p = addent(argv[2]);
+        p->defn = (char *)r;
+        p->type = QUTRTYPE;
+    }
+
+
+/*  doqutr(<DB MN A1 ... An>)
+            0  1  2      n+1 argc
+    argv[0] points to the struct Quoter.
+    argv[1] points to the name of this quoting macro
+    argv[2..argc-1] point to the arguments.
+    This applies a user-defined quoting macro.  For example, we could
+    define a macro to produce Prolog identifiers:
+       defquote(plid, ', , ', , simple,
+           abcdefghijklmnopqrstuvwxyz,
+           ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789)
+
+    After doing that,
+       plid(foo)               => foo
+       plid(*)                 => '*'
+       plid(Don't)             => 'Don''t'
+       plid(foo,)              => 'foo'
+*/
+void doqutr(argv, argc)
+    char **argv;
+    int argc;
+    /* DEFINITION-BLOCK MacroName Arg1 ... Argn
+       0                1         2        n-1   argc
+    */
+    {
+       struct Quoter *r = (struct Quoter *)argv[0];
+       char *p;
+       register unsigned char *b, *e;
+       int i;
+       register int c;
+
+       for (;;) {                      /* does not actually loop */
+           if (argc != 3) break;
+           b = ucArgv(2);
+           e = b + strlen((char*)b);
+           if (e == b) break;
+           if (r->class[*b++] != PLAIN1) break;
+           while (b != e && r->class[*b] <= PLAIN2) b++;
+           if (b != e) break;
+           pbstr(argv[2]);             
+           return;
+       }
+
+       p = r->closer;
+       if (argc < 3) {
+           pbstr(p);
+       } else
+       for (i = argc-1; i >= 2; i--) {
+           pbstr(p);
+           b = ucArgv(i);
+           e = b+strlen((char *)b);
+           while (e != b)
+               switch (r->class[c = *--e]) {
+                   case FANCY:
+                       p = r->fancy[c];
+                       if (p) {
+                           pbstr(p);
+                       } else {
+                           pbrad(c, 8, 1);
+                           pbstr(r->escape);
+                       }
+                       break;
+                   case SCAPED:
+                       putback(c);
+                       pbstr(r->escape);
+                       break;
+                   default:
+                       putback(c);
+                       break;
+               }
+           p = r->separator;
+       }
+       pbstr(r->opener);
+    }
+
 #endif
 #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.
- *
- */
-     
-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;
-}