Subversion Repositories planix.SVN

Rev

Rev 2 | Blame | Compare with Previous | Last modification | View Log | RSS feed

/*
 * sed -- stream editor
 */
#include <u.h>
#include <libc.h>
#include <bio.h>
#include <regexp.h>

enum {
        DEPTH           = 20,           /* max nesting depth of {} */
        MAXCMDS         = 512,          /* max sed commands */
        ADDSIZE         = 10000,        /* size of add & read buffer */
        MAXADDS         = 20,           /* max pending adds and reads */
        LBSIZE          = 8192,         /* input line size */
        LABSIZE         = 50,           /* max number of labels */
        MAXSUB          = 10,           /* max number of sub reg exp */
        MAXFILES        = 120,          /* max output files */
};

/*
 * An address is a line #, a R.E., "$", a reference to the last
 * R.E., or nothing.
 */
typedef struct {
        enum {
                A_NONE,
                A_DOL,
                A_LINE,
                A_RE,
                A_LAST,
        }type;
        union {
                long    line;           /* Line # */
                Reprog  *rp;            /* Compiled R.E. */
        };
} Addr;

typedef struct  SEDCOM {
        Addr    ad1;                    /* optional start address */
        Addr    ad2;                    /* optional end address */
        union {
                Reprog  *re1;           /* compiled R.E. */
                Rune    *text;          /* added text or file name */
                struct  SEDCOM  *lb1;   /* destination command of branch */
        };
        Rune    *rhs;                   /* Right-hand side of substitution */
        Biobuf* fcode;                  /* File ID for read and write */
        char    command;                /* command code -see below */
        char    gfl;                    /* 'Global' flag for substitutions */
        char    pfl;                    /* 'print' flag for substitutions */
        char    active;                 /* 1 => data between start and end */
        char    negfl;                  /* negation flag */
} SedCom;

/* Command Codes for field SedCom.command */
#define ACOM    01
#define BCOM    020
#define CCOM    02
#define CDCOM   025
#define CNCOM   022
#define COCOM   017
#define CPCOM   023
#define DCOM    03
#define ECOM    015
#define EQCOM   013
#define FCOM    016
#define GCOM    027
#define CGCOM   030
#define HCOM    031
#define CHCOM   032
#define ICOM    04
#define LCOM    05
#define NCOM    012
#define PCOM    010
#define QCOM    011
#define RCOM    06
#define SCOM    07
#define TCOM    021
#define WCOM    014
#define CWCOM   024
#define YCOM    026
#define XCOM    033

typedef struct label {                  /* Label symbol table */
        Rune    uninm[9];               /* Label name */
        SedCom  *chain;
        SedCom  *address;               /* Command associated with label */
} Label;

typedef struct  FILE_CACHE {            /* Data file control block */
        struct FILE_CACHE *next;        /* Forward Link */
        char    *name;                  /* Name of file */
} FileCache;

SedCom pspace[MAXCMDS];                 /* Command storage */
SedCom *pend = pspace+MAXCMDS;          /* End of command storage */
SedCom *rep = pspace;                   /* Current fill point */

Reprog  *lastre = 0;                    /* Last regular expression */
Resub   subexp[MAXSUB];                 /* sub-patterns of pattern match*/

Rune    addspace[ADDSIZE];              /* Buffer for a, c, & i commands */
Rune    *addend = addspace+ADDSIZE;

SedCom  *abuf[MAXADDS];                 /* Queue of pending adds & reads */
SedCom  **aptr = abuf;

struct {                                /* Sed program input control block */
        enum PTYPE {                    /* Either on command line or in file */
                P_ARG,
                P_FILE,
        } type;
        union PCTL {                    /* Pointer to data */
                Biobuf  *bp;
                char    *curr;
        };
} prog;

Rune    genbuf[LBSIZE];                 /* Miscellaneous buffer */

FileCache       *fhead = 0;             /* Head of File Cache Chain */
FileCache       *ftail = 0;             /* Tail of File Cache Chain */

Rune    *loc1;                          /* Start of pattern match */
Rune    *loc2;                          /* End of pattern match */
Rune    seof;                           /* Pattern delimiter char */

Rune    linebuf[LBSIZE+1];              /* Input data buffer */
Rune    *lbend = linebuf+LBSIZE;        /* End of buffer */
Rune    *spend = linebuf;               /* End of input data */
Rune    *cp;                            /* Current scan point in linebuf */

Rune    holdsp[LBSIZE+1];               /* Hold buffer */
Rune    *hend = holdsp+LBSIZE;          /* End of hold buffer */
Rune    *hspend = holdsp;               /* End of hold data */

int     nflag;                          /* Command line flags */
int     gflag;

int     dolflag;                        /* Set when at true EOF */
int     sflag;                          /* Set when substitution done */
int     jflag;                          /* Set when jump required */
int     delflag;                        /* Delete current line when set */

long    lnum = 0;                       /* Input line count */

char    fname[MAXFILES][40];            /* File name cache */
Biobuf  *fcode[MAXFILES];               /* File ID cache */
int     nfiles = 0;                     /* Cache fill point */

Biobuf  fout;                           /* Output stream */
Biobuf  stdin;                          /* Default input */
Biobuf* f = 0;                          /* Input data */

Label   ltab[LABSIZE];                  /* Label name symbol table */
Label   *labend = ltab+LABSIZE;         /* End of label table */
Label   *lab = ltab+1;                  /* Current Fill point */

int     depth = 0;                      /* {} stack pointer */

Rune    bad;                            /* Dummy err ptr reference */
Rune    *badp = &bad;


char    CGMES[]  =      "%S command garbled: %S";
char    TMMES[]  =      "Too much text: %S";
char    LTL[]    =      "Label too long: %S";
char    AD0MES[] =      "No addresses allowed: %S";
char    AD1MES[] =      "Only one address allowed: %S";

void    address(Addr *);
void    arout(void);
int     cmp(char *, char *);
int     rcmp(Rune *, Rune *);
void    command(SedCom *);
Reprog  *compile(void);
Rune    *compsub(Rune *, Rune *);
void    dechain(void);
void    dosub(Rune *);
int     ecmp(Rune *, Rune *, int);
void    enroll(char *);
void    errexit(void);
int     executable(SedCom *);
void    execute(void);
void    fcomp(void);
long    getrune(void);
Rune    *gline(Rune *);
int     match(Reprog *, Rune *);
void    newfile(enum PTYPE, char *);
int     opendata(void);
Biobuf  *open_file(char *);
Rune    *place(Rune *, Rune *, Rune *);
void    quit(char *, ...);
int     rline(Rune *, Rune *);
Label   *search(Label *);
int     substitute(SedCom *);
char    *text(char *);
Rune    *stext(Rune *, Rune *);
int     ycomp(SedCom *);
char *  trans(int c);
void    putline(Biobuf *bp, Rune *buf, int n);

void
main(int argc, char **argv)
{
        int compfl;

        lnum = 0;
        Binit(&fout, 1, OWRITE);
        fcode[nfiles++] = &fout;
        compfl = 0;

        if(argc == 1)
                exits(0);
        ARGBEGIN{
        case 'e':
                if (argc <= 1)
                        quit("missing pattern");
                newfile(P_ARG, ARGF());
                fcomp();
                compfl = 1;
                continue;
        case 'f':
                if(argc <= 1)
                        quit("no pattern-file");
                newfile(P_FILE, ARGF());
                fcomp();
                compfl = 1;
                continue;
        case 'g':
                gflag++;
                continue;
        case 'n':
                nflag++;
                continue;
        default:
                fprint(2, "sed: Unknown flag: %c\n", ARGC());
                continue;
        } ARGEND

        if(compfl == 0) {
                if (--argc < 0)
                        quit("missing pattern");
                newfile(P_ARG, *argv++);
                fcomp();
        }

        if(depth)
                quit("Too many {'s");

        ltab[0].address = rep;

        dechain();

        if(argc <= 0)
                enroll(0);              /* Add stdin to cache */
        else
                while(--argc >= 0)
                        enroll(*argv++);
        execute();
        exits(0);
}

void
fcomp(void)
{
        int     i;
        Label   *lpt;
        Rune    *tp;
        SedCom  *pt, *pt1;
        static Rune     *p = addspace;
        static SedCom   **cmpend[DEPTH];        /* stack of {} operations */

        while (rline(linebuf, lbend) >= 0) {
                cp = linebuf;
comploop:
                while(*cp == L' ' || *cp == L'\t')
                        cp++;
                if(*cp == L'\0' || *cp == L'#')
                        continue;
                if(*cp == L';') {
                        cp++;
                        goto comploop;
                }

                address(&rep->ad1);
                if (rep->ad1.type != A_NONE) {
                        if (rep->ad1.type == A_LAST) {
                                if (!lastre)
                                        quit("First RE may not be null");
                                rep->ad1.type = A_RE;
                                rep->ad1.rp = lastre;
                        }
                        if(*cp == L',' || *cp == L';') {
                                cp++;
                                address(&rep->ad2);
                                if (rep->ad2.type == A_LAST) {
                                        rep->ad2.type = A_RE;
                                        rep->ad2.rp = lastre;
                                }
                        } else
                                rep->ad2.type = A_NONE;
                }
                while(*cp == L' ' || *cp == L'\t')
                        cp++;

swit:
                switch(*cp++) {
                default:
                        quit("Unrecognized command: %S", linebuf);

                case '!':
                        rep->negfl = 1;
                        goto swit;

                case '{':
                        rep->command = BCOM;
                        rep->negfl = !rep->negfl;
                        cmpend[depth++] = &rep->lb1;
                        if(++rep >= pend)
                                quit("Too many commands: %S", linebuf);
                        if(*cp == '\0')
                                continue;
                        goto comploop;

                case '}':
                        if(rep->ad1.type != A_NONE)
                                quit(AD0MES, linebuf);
                        if(--depth < 0)
                                quit("Too many }'s");
                        *cmpend[depth] = rep;
                        if(*cp == 0)
                                continue;
                        goto comploop;

                case '=':
                        rep->command = EQCOM;
                        if(rep->ad2.type != A_NONE)
                                quit(AD1MES, linebuf);
                        break;

                case ':':
                        if(rep->ad1.type != A_NONE)
                                quit(AD0MES, linebuf);

                        while(*cp == L' ')
                                cp++;
                        tp = lab->uninm;
                        while (*cp && *cp != L';' && *cp != L' ' &&
                            *cp != L'\t' && *cp != L'#') {
                                *tp++ = *cp++;
                                if(tp >= &lab->uninm[8])
                                        quit(LTL, linebuf);
                        }
                        *tp = L'\0';

                        if (*lab->uninm == L'\0')               /* no label? */
                                quit(CGMES, L":", linebuf);
                        if(lpt = search(lab)) {
                                if(lpt->address)
                                        quit("Duplicate labels: %S", linebuf);
                        } else {
                                lab->chain = 0;
                                lpt = lab;
                                if(++lab >= labend)
                                        quit("Too many labels: %S", linebuf);
                        }
                        lpt->address = rep;
                        if (*cp == L'#')
                                continue;
                        rep--;                  /* reuse this slot */
                        break;

                case 'a':
                        rep->command = ACOM;
                        if(rep->ad2.type != A_NONE)
                                quit(AD1MES, linebuf);
                        if(*cp == L'\\')
                                cp++;
                        if(*cp++ != L'\n')
                                quit(CGMES, L"a", linebuf);
                        rep->text = p;
                        p = stext(p, addend);
                        break;
                case 'c':
                        rep->command = CCOM;
                        if(*cp == L'\\')
                                cp++;
                        if(*cp++ != L'\n')
                                quit(CGMES, L"c", linebuf);
                        rep->text = p;
                        p = stext(p, addend);
                        break;
                case 'i':
                        rep->command = ICOM;
                        if(rep->ad2.type != A_NONE)
                                quit(AD1MES, linebuf);
                        if(*cp == L'\\')
                                cp++;
                        if(*cp++ != L'\n')
                                quit(CGMES, L"i", linebuf);
                        rep->text = p;
                        p = stext(p, addend);
                        break;

                case 'g':
                        rep->command = GCOM;
                        break;

                case 'G':
                        rep->command = CGCOM;
                        break;

                case 'h':
                        rep->command = HCOM;
                        break;

                case 'H':
                        rep->command = CHCOM;
                        break;

                case 't':
                        rep->command = TCOM;
                        goto jtcommon;

                case 'b':
                        rep->command = BCOM;
jtcommon:
                        while(*cp == L' ')
                                cp++;
                        if(*cp == L'\0' || *cp == L';') {
                                /* no label; jump to end */
                                if(pt = ltab[0].chain) {
                                        while((pt1 = pt->lb1) != nil)
                                                pt = pt1;
                                        pt->lb1 = rep;
                                } else
                                        ltab[0].chain = rep;
                                break;
                        }

                        /* copy label into lab->uninm */
                        tp = lab->uninm;
                        while((*tp = *cp++) != L'\0' && *tp != L';')
                                if(++tp >= &lab->uninm[8])
                                        quit(LTL, linebuf);
                        cp--;
                        *tp = L'\0';

                        if (*lab->uninm == L'\0')
                                /* shouldn't get here */
                                quit(CGMES, L"b or t", linebuf);
                        if((lpt = search(lab)) != nil) {
                                if(lpt->address)
                                        rep->lb1 = lpt->address;
                                else {
                                        for(pt = lpt->chain; pt != nil &&
                                            (pt1 = pt->lb1) != nil; pt = pt1)
                                                ;
                                        if (pt)
                                                pt->lb1 = rep;
                                }
                        } else {                        /* add new label */
                                lab->chain = rep;
                                lab->address = 0;
                                if(++lab >= labend)
                                        quit("Too many labels: %S", linebuf);
                        }
                        break;

                case 'n':
                        rep->command = NCOM;
                        break;

                case 'N':
                        rep->command = CNCOM;
                        break;

                case 'p':
                        rep->command = PCOM;
                        break;

                case 'P':
                        rep->command = CPCOM;
                        break;

                case 'r':
                        rep->command = RCOM;
                        if(rep->ad2.type != A_NONE)
                                quit(AD1MES, linebuf);
                        if(*cp++ != L' ')
                                quit(CGMES, L"r", linebuf);
                        rep->text = p;
                        p = stext(p, addend);
                        break;

                case 'd':
                        rep->command = DCOM;
                        break;

                case 'D':
                        rep->command = CDCOM;
                        rep->lb1 = pspace;
                        break;

                case 'q':
                        rep->command = QCOM;
                        if(rep->ad2.type != A_NONE)
                                quit(AD1MES, linebuf);
                        break;

                case 'l':
                        rep->command = LCOM;
                        break;

                case 's':
                        rep->command = SCOM;
                        seof = *cp++;
                        if ((rep->re1 = compile()) == 0) {
                                if(!lastre)
                                        quit("First RE may not be null.");
                                rep->re1 = lastre;
                        }
                        rep->rhs = p;
                        if((p = compsub(p, addend)) == 0)
                                quit(CGMES, L"s", linebuf);
                        if(*cp == L'g') {
                                cp++;
                                rep->gfl++;
                        } else if(gflag)
                                rep->gfl++;

                        if(*cp == L'p') {
                                cp++;
                                rep->pfl = 1;
                        }

                        if(*cp == L'P') {
                                cp++;
                                rep->pfl = 2;
                        }

                        if(*cp == L'w') {
                                cp++;
                                if(*cp++ !=  L' ')
                                        quit(CGMES, L"s", linebuf);
                                text(fname[nfiles]);
                                for(i = nfiles - 1; i >= 0; i--)
                                        if(cmp(fname[nfiles], fname[i]) == 0) {
                                                rep->fcode = fcode[i];
                                                goto done;
                                        }
                                if(nfiles >= MAXFILES)
                                        quit("Too many files in w commands 1");
                                rep->fcode = open_file(fname[nfiles]);
                        }
                        break;

                case 'w':
                        rep->command = WCOM;
                        if(*cp++ != L' ')
                                quit(CGMES, L"w", linebuf);
                        text(fname[nfiles]);
                        for(i = nfiles - 1; i >= 0; i--)
                                if(cmp(fname[nfiles], fname[i]) == 0) {
                                        rep->fcode = fcode[i];
                                        goto done;
                                }
                        if(nfiles >= MAXFILES){
                                fprint(2, "sed: Too many files in w commands 2 \n");
                                fprint(2, "nfiles = %d; MAXF = %d\n",
                                        nfiles, MAXFILES);
                                errexit();
                        }
                        rep->fcode = open_file(fname[nfiles]);
                        break;

                case 'x':
                        rep->command = XCOM;
                        break;

                case 'y':
                        rep->command = YCOM;
                        seof = *cp++;
                        if (ycomp(rep) == 0)
                                quit(CGMES, L"y", linebuf);
                        break;

                }
done:
                if(++rep >= pend)
                        quit("Too many commands, last: %S", linebuf);
                if(*cp++ != L'\0') {
                        if(cp[-1] == L';')
                                goto comploop;
                        quit(CGMES, cp - 1, linebuf);
                }
        }
}

Biobuf *
open_file(char *name)
{
        int fd;
        Biobuf *bp;

        if ((bp = malloc(sizeof(Biobuf))) == 0)
                quit("Out of memory");
        if ((fd = open(name, OWRITE)) < 0 &&
            (fd = create(name, OWRITE, 0666)) < 0)
                quit("Cannot create %s", name);
        Binit(bp, fd, OWRITE);
        Bseek(bp, 0, 2);
        fcode[nfiles++] = bp;
        return bp;
}

Rune *
compsub(Rune *rhs, Rune *end)
{
        Rune r;

        while ((r = *cp++) != '\0') {
                if(r == '\\') {
                        if (rhs < end)
                                *rhs++ = Runemax;
                        else
                                return 0;
                        r = *cp++;
                        if(r == 'n')
                                r = '\n';
                } else {
                        if(r == seof) {
                                if (rhs < end)
                                        *rhs++ = '\0';
                                else
                                        return 0;
                                return rhs;
                        }
                }
                if (rhs < end)
                        *rhs++ = r;
                else
                        return 0;
        }
        return 0;
}

Reprog *
compile(void)
{
        Rune c;
        char *ep;
        char expbuf[512];

        if((c = *cp++) == seof)         /* L'//' */
                return 0;
        ep = expbuf;
        do {
                if (c == L'\0' || c == L'\n')
                        quit(TMMES, linebuf);
                if (c == L'\\') {
                        if (ep >= expbuf+sizeof(expbuf))
                                quit(TMMES, linebuf);
                        ep += runetochar(ep, &c);
                        if ((c = *cp++) == L'n')
                                c = L'\n';
                }
                if (ep >= expbuf + sizeof(expbuf))
                        quit(TMMES, linebuf);
                ep += runetochar(ep, &c);
        } while ((c = *cp++) != seof);
        *ep = 0;
        return lastre = regcomp(expbuf);
}

void
regerror(char *s)
{
        USED(s);
        quit(CGMES, L"r.e.-using", linebuf);
}

void
newfile(enum PTYPE type, char *name)
{
        if (type == P_ARG)
                prog.curr = name;
        else if ((prog.bp = Bopen(name, OREAD)) == 0)
                quit("Cannot open pattern-file: %s\n", name);
        prog.type = type;
}

int
rline(Rune *buf, Rune *end)
{
        long c;
        Rune r;

        while ((c = getrune()) >= 0) {
                r = c;
                if (r == '\\') {
                        if (buf <= end)
                                *buf++ = r;
                        if ((c = getrune()) < 0)
                                break;
                        r = c;
                } else if (r == '\n') {
                        *buf = '\0';
                        return 1;
                }
                if (buf <= end)
                        *buf++ = r;
        }
        *buf = '\0';
        return -1;
}

long
getrune(void)
{
        long c;
        Rune r;
        char *p;

        if (prog.type == P_ARG) {
                if ((p = prog.curr) != 0) {
                        if (*p) {
                                prog.curr += chartorune(&r, p);
                                c = r;
                        } else {
                                c = '\n';       /* fake an end-of-line */
                                prog.curr = 0;
                        }
                } else
                        c = -1;
        } else if ((c = Bgetrune(prog.bp)) < 0)
                Bterm(prog.bp);
        return c;
}

void
address(Addr *ap)
{
        int c;
        long lno;

        if((c = *cp++) == '$')
                ap->type = A_DOL;
        else if(c == '/') {
                seof = c;
                if (ap->rp = compile())
                        ap->type = A_RE;
                else
                        ap->type = A_LAST;
        }
        else if (c >= '0' && c <= '9') {
                lno = c - '0';
                while ((c = *cp) >= '0' && c <= '9')
                        lno = lno*10 + *cp++ - '0';
                if(!lno)
                        quit("line number 0 is illegal",0);
                ap->type = A_LINE;
                ap->line = lno;
        }
        else {
                cp--;
                ap->type = A_NONE;
        }
}

cmp(char *a, char *b)           /* compare characters */
{
        while(*a == *b++)
                if (*a == '\0')
                        return 0;
                else
                        a++;
        return 1;
}
rcmp(Rune *a, Rune *b)          /* compare runes */
{
        while(*a == *b++)
                if (*a == '\0')
                        return 0;
                else
                        a++;
        return 1;
}

char *
text(char *p)           /* extract character string */
{
        Rune r;

        while(*cp == ' ' || *cp == '\t')
                cp++;
        while (*cp) {
                if ((r = *cp++) == '\\' && (r = *cp++) == '\0')
                        break;
                if (r == '\n')
                        while (*cp == ' ' || *cp == '\t')
                                cp++;
                p += runetochar(p, &r);
        }
        *p++ = '\0';
        return p;
}

Rune *
stext(Rune *p, Rune *end)               /* extract rune string */
{
        while(*cp == L' ' || *cp == L'\t')
                cp++;
        while (*cp) {
                if (*cp == L'\\' && *++cp == L'\0')
                        break;
                if (p >= end-1)
                        quit(TMMES, linebuf);
                if ((*p++ = *cp++) == L'\n')
                        while(*cp == L' ' || *cp == L'\t')
                                cp++;
        }
        *p++ = 0;
        return p;
}


Label *
search(Label *ptr)
{
        Label   *rp;

        for (rp = ltab; rp < ptr; rp++)
                if(rcmp(rp->uninm, ptr->uninm) == 0)
                        return(rp);
        return(0);
}

void
dechain(void)
{
        Label   *lptr;
        SedCom  *rptr, *trptr;

        for(lptr = ltab; lptr < lab; lptr++) {
                if(lptr->address == 0)
                        quit("Undefined label: %S", lptr->uninm);
                if(lptr->chain) {
                        rptr = lptr->chain;
                        while((trptr = rptr->lb1) != nil) {
                                rptr->lb1 = lptr->address;
                                rptr = trptr;
                        }
                        rptr->lb1 = lptr->address;
                }
        }
}

int
ycomp(SedCom *r)
{
        int i;
        Rune *rp, *sp, *tsp;
        Rune c, highc;

        highc = 0;
        for(tsp = cp; *tsp != seof; tsp++) {
                if(*tsp == L'\\')
                        tsp++;
                if(*tsp == L'\n' || *tsp == L'\0')
                        return 0;
                if (*tsp > highc)
                        highc = *tsp;
        }
        tsp++;
        if ((rp = r->text = (Rune *)malloc(sizeof(Rune) * (highc+2))) == nil)
                quit("Out of memory");
        *rp++ = highc;                          /* save upper bound */
        for (i = 0; i <= highc; i++)
                rp[i] = i;
        sp = cp;
        while((c = *sp++) != seof) {
                if(c == L'\\' && *sp == L'n') {
                        sp++;
                        c = L'\n';
                }
                if((rp[c] = *tsp++) == L'\\' && *tsp == L'n') {
                        rp[c] = L'\n';
                        tsp++;
                }
                if(rp[c] == seof || rp[c] == L'\0') {
                        free(r->re1);
                        r->re1 = nil;
                        return 0;
                }
        }
        if(*tsp != seof) {
                free(r->re1);
                r->re1 = nil;
                return 0;
        }
        cp = tsp+1;
        return 1;
}

void
execute(void)
{
        SedCom  *ipc;

        while (spend = gline(linebuf)){
                for(ipc = pspace; ipc->command; ) {
                        if (!executable(ipc)) {
                                ipc++;
                                continue;
                        }
                        command(ipc);

                        if(delflag)
                                break;
                        if(jflag) {
                                jflag = 0;
                                if((ipc = ipc->lb1) == 0)
                                        break;
                        } else
                                ipc++;
                }
                if(!nflag && !delflag)
                        putline(&fout, linebuf, spend - linebuf);
                if(aptr > abuf)
                        arout();
                delflag = 0;
        }
}

/* determine if a statement should be applied to an input line */
int
executable(SedCom *ipc)
{
        if (ipc->active) {      /* Addr1 satisfied - accept until Addr2 */
                if (ipc->active == 1)           /* Second line */
                        ipc->active = 2;
                switch(ipc->ad2.type) {
                case A_NONE:            /* No second addr; use first */
                        ipc->active = 0;
                        break;
                case A_DOL:             /* Accept everything */
                        return !ipc->negfl;
                case A_LINE:            /* Line at end of range? */
                        if (lnum <= ipc->ad2.line) {
                                if (ipc->ad2.line == lnum)
                                        ipc->active = 0;
                                return !ipc->negfl;
                        }
                        ipc->active = 0;        /* out of range */
                        return ipc->negfl;
                case A_RE:              /* Check for matching R.E. */
                        if (match(ipc->ad2.rp, linebuf))
                                ipc->active = 0;
                        return !ipc->negfl;
                default:
                        quit("Internal error");
                }
        }
        switch (ipc->ad1.type) {        /* Check first address */
        case A_NONE:                    /* Everything matches */
                return !ipc->negfl;
        case A_DOL:                     /* Only last line */
                if (dolflag)
                        return !ipc->negfl;
                break;
        case A_LINE:                    /* Check line number */
                if (ipc->ad1.line == lnum) {
                        ipc->active = 1;        /* In range */
                        return !ipc->negfl;
                }
                break;
        case A_RE:                      /* Check R.E. */
                if (match(ipc->ad1.rp, linebuf)) {
                        ipc->active = 1;        /* In range */
                        return !ipc->negfl;
                }
                break;
        default:
                quit("Internal error");
        }
        return ipc->negfl;
}

int
match(Reprog *pattern, Rune *buf)
{
        if (!pattern)
                return 0;
        subexp[0].rsp = buf;
        subexp[0].ep = 0;
        if (rregexec(pattern, linebuf, subexp, MAXSUB) > 0) {
                loc1 = subexp[0].rsp;
                loc2 = subexp[0].rep;
                return 1;
        }
        loc1 = loc2 = 0;
        return 0;
}

int
substitute(SedCom *ipc)
{
        int len;

        if(!match(ipc->re1, linebuf))
                return 0;

        /*
         * we have at least one match.  some patterns, e.g. '$' or '^', can
         * produce 0-length matches, so during a global substitute we must
         * bump to the character after a 0-length match to keep from looping.
         */
        sflag = 1;
        if(ipc->gfl == 0)                       /* single substitution */
                dosub(ipc->rhs);
        else
                do{                             /* global substitution */
                        len = loc2 - loc1;      /* length of match */
                        dosub(ipc->rhs);        /* dosub moves loc2 */
                        if(*loc2 == 0)          /* end of string */
                                break;
                        if(len == 0)            /* zero-length R.E. match */
                                loc2++;         /* bump over 0-length match */
                        if(*loc2 == 0)          /* end of string */
                                break;
                } while(match(ipc->re1, loc2));
        return 1;
}

void
dosub(Rune *rhsbuf)
{
        int c, n;
        Rune *lp, *sp, *rp;

        lp = linebuf;
        sp = genbuf;
        rp = rhsbuf;
        while (lp < loc1)
                *sp++ = *lp++;
        while(c = *rp++) {
                if (c == '&') {
                        sp = place(sp, loc1, loc2);
                        continue;
                }
                if (c == Runemax && (c = *rp++) >= '1' && c < MAXSUB + '0') {
                        n = c-'0';
                        if (subexp[n].rsp && subexp[n].rep) {
                                sp = place(sp, subexp[n].rsp, subexp[n].rep);
                                continue;
                        }
                        else {
                                fprint(2, "sed: Invalid back reference \\%d\n",n);
                                errexit();
                        }
                }
                *sp++ = c;
                if (sp >= &genbuf[LBSIZE])
                        fprint(2, "sed: Output line too long.\n");
        }
        lp = loc2;
        loc2 = sp - genbuf + linebuf;
        while (*sp++ = *lp++)
                if (sp >= &genbuf[LBSIZE])
                        fprint(2, "sed: Output line too long.\n");
        lp = linebuf;
        sp = genbuf;
        while (*lp++ = *sp++)
                ;
        spend = lp - 1;
}

Rune *
place(Rune *sp, Rune *l1, Rune *l2)
{
        while (l1 < l2) {
                *sp++ = *l1++;
                if (sp >= &genbuf[LBSIZE])
                        fprint(2, "sed: Output line too long.\n");
        }
        return sp;
}

char *
trans(int c)
{
        static char buf[] = "\\x0000";
        static char hex[] = "0123456789abcdef";

        switch(c) {
        case '\b':
                return "\\b";
        case '\n':
                return "\\n";
        case '\r':
                return "\\r";
        case '\t':
                return "\\t";
        case '\\':
                return "\\\\";
        }
        buf[2] = hex[(c>>12)&0xF];
        buf[3] = hex[(c>>8)&0xF];
        buf[4] = hex[(c>>4)&0xF];
        buf[5] = hex[c&0xF];
        return buf;
}

void
command(SedCom *ipc)
{
        int i, c;
        char *ucp;
        Rune *execp, *p1, *p2, *rp;

        switch(ipc->command) {
        case ACOM:
                *aptr++ = ipc;
                if(aptr >= abuf+MAXADDS)
                        quit("sed: Too many appends after line %ld\n",
                                (char *)lnum);
                *aptr = 0;
                break;
        case CCOM:
                delflag = 1;
                if(ipc->active == 1) {
                        for(rp = ipc->text; *rp; rp++)
                                Bputrune(&fout, *rp);
                        Bputc(&fout, '\n');
                }
                break;
        case DCOM:
                delflag++;
                break;
        case CDCOM:
                p1 = p2 = linebuf;
                while(*p1 != '\n') {
                        if(*p1++ == 0) {
                                delflag++;
                                return;
                        }
                }
                p1++;
                while(*p2++ = *p1++)
                        ;
                spend = p2 - 1;
                jflag++;
                break;
        case EQCOM:
                Bprint(&fout, "%ld\n", lnum);
                break;
        case GCOM:
                p1 = linebuf;
                p2 = holdsp;
                while(*p1++ = *p2++)
                        ;
                spend = p1 - 1;
                break;
        case CGCOM:
                *spend++ = '\n';
                p1 = spend;
                p2 = holdsp;
                while(*p1++ = *p2++)
                        if(p1 >= lbend)
                                break;
                spend = p1 - 1;
                break;
        case HCOM:
                p1 = holdsp;
                p2 = linebuf;
                while(*p1++ = *p2++);
                hspend = p1 - 1;
                break;
        case CHCOM:
                *hspend++ = '\n';
                p1 = hspend;
                p2 = linebuf;
                while(*p1++ = *p2++)
                        if(p1 >= hend)
                                break;
                hspend = p1 - 1;
                break;
        case ICOM:
                for(rp = ipc->text; *rp; rp++)
                        Bputrune(&fout, *rp);
                Bputc(&fout, '\n');
                break;
        case BCOM:
                jflag = 1;
                break;
        case LCOM:
                c = 0;
                for (i = 0, rp = linebuf; *rp; rp++) {
                        c = *rp;
                        if(c >= 0x20 && c < 0x7F && c != '\\') {
                                Bputc(&fout, c);
                                if(i++ > 71) {
                                        Bprint(&fout, "\\\n");
                                        i = 0;
                                }
                        } else {
                                for (ucp = trans(*rp); *ucp; ucp++){
                                        c = *ucp;
                                        Bputc(&fout, c);
                                        if(i++ > 71) {
                                                Bprint(&fout, "\\\n");
                                                i = 0;
                                        }
                                }
                        }
                }
                if(c == ' ')
                        Bprint(&fout, "\\n");
                Bputc(&fout, '\n');
                break;
        case NCOM:
                if(!nflag)
                        putline(&fout, linebuf, spend-linebuf);

                if(aptr > abuf)
                        arout();
                if((execp = gline(linebuf)) == 0) {
                        delflag = 1;
                        break;
                }
                spend = execp;
                break;
        case CNCOM:
                if(aptr > abuf)
                        arout();
                *spend++ = '\n';
                if((execp = gline(spend)) == 0) {
                        delflag = 1;
                        break;
                }
                spend = execp;
                break;
        case PCOM:
                putline(&fout, linebuf, spend-linebuf);
                break;
        case CPCOM:
cpcom:
                for(rp = linebuf; *rp && *rp != '\n'; rp++)
                        Bputc(&fout, *rp);
                Bputc(&fout, '\n');
                break;
        case QCOM:
                if(!nflag)
                        putline(&fout, linebuf, spend-linebuf);
                if(aptr > abuf)
                        arout();
                exits(0);
        case RCOM:
                *aptr++ = ipc;
                if(aptr >= &abuf[MAXADDS])
                        quit("sed: Too many reads after line %ld\n",
                                (char *)lnum);
                *aptr = 0;
                break;
        case SCOM:
                i = substitute(ipc);
                if(i && ipc->pfl)
                        if(ipc->pfl == 1)
                                putline(&fout, linebuf, spend-linebuf);
                        else
                                goto cpcom;
                if(i && ipc->fcode)
                        goto wcom;
                break;

        case TCOM:
                if(sflag) {
                        sflag = 0;
                        jflag = 1;
                }
                break;

        case WCOM:
wcom:
                putline(ipc->fcode,linebuf, spend - linebuf);
                break;
        case XCOM:
                p1 = linebuf;
                p2 = genbuf;
                while(*p2++ = *p1++)
                        ;
                p1 = holdsp;
                p2 = linebuf;
                while(*p2++ = *p1++)
                        ;
                spend = p2 - 1;
                p1 = genbuf;
                p2 = holdsp;
                while(*p2++ = *p1++)
                        ;
                hspend = p2 - 1;
                break;
        case YCOM:
                p1 = linebuf;
                p2 = ipc->text;
                for (i = *p2++; *p1; p1++)
                        if (*p1 <= i)
                                *p1 = p2[*p1];
                break;
        }
}

void
putline(Biobuf *bp, Rune *buf, int n)
{
        while (n--)
                Bputrune(bp, *buf++);
        Bputc(bp, '\n');
}
ecmp(Rune *a, Rune *b, int count)
{
        while(count--)
                if(*a++ != *b++)
                        return 0;
        return 1;
}

void
arout(void)
{
        int     c;
        char    *s;
        char    buf[128];
        Rune    *p1;
        Biobuf  *fi;

        for (aptr = abuf; *aptr; aptr++) {
                if((*aptr)->command == ACOM) {
                        for(p1 = (*aptr)->text; *p1; p1++ )
                                Bputrune(&fout, *p1);
                        Bputc(&fout, '\n');
                } else {
                        for(s = buf, p1 = (*aptr)->text; *p1; p1++)
                                s += runetochar(s, p1);
                        *s = '\0';
                        if((fi = Bopen(buf, OREAD)) == 0)
                                continue;
                        while((c = Bgetc(fi)) >= 0)
                                Bputc(&fout, c);
                        Bterm(fi);
                }
        }
        aptr = abuf;
        *aptr = 0;
}

void
errexit(void)
{
        exits("error");
}

void
quit(char *fmt, ...)
{
        char *p, *ep;
        char msg[256];
        va_list arg;

        ep = msg + sizeof msg;
        p = seprint(msg, ep, "sed: ");
        va_start(arg, fmt);
        p = vseprint(p, ep, fmt, arg);
        va_end(arg);
        p = seprint(p, ep, "\n");
        write(2, msg, p - msg);
        errexit();
}

Rune *
gline(Rune *addr)
{
        long c;
        Rune *p;
        static long peekc = 0;

        if (f == 0 && opendata() < 0)
                return 0;
        sflag = 0;
        lnum++;
/*      Bflush(&fout);********* dumped 4/30/92 - bobf****/
        do {
                p = addr;
                for (c = (peekc? peekc: Bgetrune(f)); c >= 0; c = Bgetrune(f)) {
                        if (c == '\n') {
                                if ((peekc = Bgetrune(f)) < 0 && fhead == 0)
                                        dolflag = 1;
                                *p = '\0';
                                return p;
                        }
                        if (c && p < lbend)
                                *p++ = c;
                }
                /* return partial final line, adding implicit newline */
                if(p != addr) {
                        *p = '\0';
                        peekc = -1;
                        if (fhead == 0)
                                dolflag = 1;
                        return p;
                }
                peekc = 0;
                Bterm(f);
        } while (opendata() > 0);               /* Switch to next stream */
        f = 0;
        return 0;
}

/*
 * Data file input section - the intent is to transparently
 *      catenate all data input streams.
 */
void
enroll(char *filename)          /* Add a file to the input file cache */
{
        FileCache *fp;

        if ((fp = (FileCache *)malloc(sizeof (FileCache))) == nil)
                quit("Out of memory");
        if (ftail == nil)
                fhead = fp;
        else
                ftail->next = fp;
        ftail = fp;
        fp->next = nil;
        fp->name = filename;            /* 0 => stdin */
}

int
opendata(void)
{
        if (fhead == nil)
                return -1;
        if (fhead->name) {
                if ((f = Bopen(fhead->name, OREAD)) == nil)
                        quit("Can't open %s", fhead->name);
        } else {
                Binit(&stdin, 0, OREAD);
                f = &stdin;
        }
        fhead = fhead->next;
        return 1;
}