kirkenda@eecs.cs.pdx.edu (Steve Kirkendall) (08/25/90)
Archive-name: elvis1.3/part5 #! /bin/sh # This is a shell archive, meaning: # 1. Remove everything above the #! /bin/sh line. # 2. Save the resulting text in a file. # 3. Execute the file with /bin/sh (not csh) to create: # regexp.c # regexp.h # regsub.c # sysdos.c # system.c # tinytcap.c # tio.c # tmp.c # This archive created: Fri Aug 24 10:29:59 1990 export PATH; PATH=/bin:/usr/bin:$PATH if test -f 'regexp.c' then echo shar: "will not over-write existing file 'regexp.c'" else cat << \SHAR_EOF > 'regexp.c' /* ALTERED VERSION */ /* * regcomp and regexec -- regsub and regerror are elsewhere * * Copyright (c) 1986 by University of Toronto. * Written by Henry Spencer. Not derived from licensed software. * * Permission is granted to anyone to use this software for any * purpose on any computer system, and to redistribute it freely, * subject to the following restrictions: * * 1. The author is not responsible for the consequences of use of * this software, no matter how awful, even if they arise * from defects in it. * * 2. The origin of this software must not be misrepresented, either * by explicit claim or by omission. * * 3. Altered versions must be plainly marked as such, and must not * be misrepresented as being the original software. * * Beware that some of this code is subtly aware of the way operator * precedence is structured in regular expressions. Serious changes in * regular-expression syntax might require a total rethink. * * The third parameter to regexec was added by Martin C. Atkins. * Andy Tanenbaum also made some changes. * Steve Kirkendall changed the syntax and added o_magic, o_ignorecase */ #include <ctype.h> #include "config.h" #include "vi.h" #include "regexp.h" #define NULL (char *)0 extern char *ustrchr(); /* version of strchr which uses o_ignorecase */ extern int ustrncmp(); /* version of strcmp which uses o_ignorecase */ /* * The first byte of the regexp internal "program" is actually this magic * number; the start node begins in the second byte. */ #define MAGIC 0234 #ifdef NO_MAGIC # include "nomagic.c" #else /* * The "internal use only" fields in regexp.h are present to pass info from * compile to execute that permits the execute phase to run lots faster on * simple cases. They are: * * regstart char that must begin a match; '\0' if none obvious * reganch is the match anchored (at beginning-of-line only)? * regmust string (pointer into program) that match must include, or NULL * regmlen length of regmust string * * Regstart and reganch permit very fast decisions on suitable starting points * for a match, cutting down the work a lot. Regmust permits fast rejection * of lines that cannot possibly match. The regmust tests are costly enough * that regcomp() supplies a regmust only if the r.e. contains something * potentially expensive (at present, the only such thing detected is * or + * at the start of the r.e., which can involve a lot of backup). Regmlen is * supplied because the test in regexec() needs it and regcomp() is computing * it anyway. */ /* * Structure for regexp "program". This is essentially a linear encoding * of a nondeterministic finite-state machine (aka syntax charts or * "railroad normal form" in parsing technology). Each node is an opcode * plus a "next" pointer, possibly plus an operand. "Next" pointers of * all nodes except BRANCH implement concatenation; a "next" pointer with * a BRANCH on both ends of it is connecting two alternatives. (Here we * have one of the subtle syntax dependencies: an individual BRANCH (as * opposed to a collection of them) is never concatenated with anything * because of operator precedence.) The operand of some types of node is * a literal string; for others, it is a node leading into a sub-FSM. In * particular, the operand of a BRANCH node is the first node of the branch. * (NB this is *not* a tree structure: the tail of the branch connects * to the thing following the set of BRANCHes.) The opcodes are: */ /* definition number opnd? meaning */ #define END 0 /* no End of program. */ #define BOL 1 /* no Match "" at beginning of line. */ #define EOL 2 /* no Match "" at end of line. */ #define ANY 3 /* no Match any one character. */ #define ANYOF 4 /* str Match any character in this string. */ #define ANYBUT 5 /* str Match any character not in this string. */ #define BRANCH 6 /* node Match this alternative, or the next... */ #define BACK 7 /* no Match "", "next" ptr points backward. */ #define EXACTLY 8 /* str Match this string. */ #define NOTHING 9 /* no Match empty string. */ #define STAR 10 /* node Match this (simple) thing 0 or more times. */ #define PLUS 11 /* node Match this (simple) thing 1 or more times. */ #define BOW 12 /* no Match "" at front of word */ #define EOW 13 /* no Match "" at rear of word */ #define OPEN 20 /* no Mark this point in input as start of #n. */ /* OPEN+1 is number 1, etc. */ #define CLOSE 30 /* no Analogous to OPEN. */ /* * Opcode notes: * * BRANCH The set of branches constituting a single choice are hooked * together with their "next" pointers, since precedence prevents * anything being concatenated to any individual branch. The * "next" pointer of the last BRANCH in a choice points to the * thing following the whole choice. This is also where the * final "next" pointer of each individual branch points; each * branch starts with the operand node of a BRANCH node. * * BACK Normal "next" pointers all implicitly point forward; BACK * exists to make loop structures possible. * * STAR,PLUS '?', and complex '*' and '+', are implemented as circular * BRANCH structures using BACK. Simple cases (one character * per match) are implemented with STAR and PLUS for speed * and to minimize recursive plunges. * * OPEN,CLOSE ...are numbered at compile time. */ /* * A node is one char of opcode followed by two chars of "next" pointer. * "Next" pointers are stored as two 8-bit pieces, high order first. The * value is a positive offset from the opcode of the node containing it. * An operand, if any, simply follows the node. (Note that much of the * code generation knows about this implicit relationship.) * * Using two bytes for the "next" pointer is vast overkill for most things, * but allows patterns to get big without disasters. */ #define OP(p) (*(p)) #define NEXT(p) (((*((p)+1)&0377)<<8) + (*((p)+2)&0377)) #define OPERAND(p) ((p) + 3) /* * Utility definitions. */ #define UCHARAT(p) UCHAR(*p) #define FAIL(m) { regerror(m); return 0; } #define ISMULT(c) ((c) == '*' || (c) == '+' || (c) == '?') #define META "^$.[()|?+*\\" /* * Flags to be passed up and down. */ #define HASWIDTH 01 /* Known never to match null string. */ #define SIMPLE 02 /* Simple enough to be STAR/PLUS operand. */ #define SPSTART 04 /* Starts with * or +. */ #define WORST 0 /* Worst case. */ /* * Global work variables for regcomp(). */ static char *regstr; /* the RE being compiled */ static char *regparse; /* Input-scan pointer. */ static int regnpar; /* () count. */ static char regdummy; static char *regcode; /* Code-emit pointer; ®dummy = don't. */ static long regsize; /* Code size. */ /* * Forward declarations for regcomp()'s friends. */ #ifndef STATIC #define STATIC static #endif STATIC char *reg(); STATIC char *regbranch(); STATIC char *regpiece(); STATIC char *regatom(); STATIC char *regnode(); STATIC char *regnext(); STATIC void regc(); STATIC void reginsert(); STATIC void regtail(); STATIC void regoptail(); /* - regcomp - compile a regular expression into internal code * * We can't allocate space until we know how big the compiled form will be, * but we can't compile it (and thus know how big it is) until we've got a * place to put the code. So we cheat: we compile it twice, once with code * generation turned off and size counting turned on, and once "for real". * This also means that we don't allocate space until we are sure that the * thing really will compile successfully, and we never have to move the * code and thus invalidate pointers into it. (Note that it has to be in * one piece because free() must be able to free it all.) * * Beware that the optimization-preparation code in here knows about some * of the structure of the compiled regexp. */ regexp * regcomp(exp) char *exp; { register regexp *r; register char *scan; register char *longest; register int len; int flags; if (exp == NULL) FAIL("NULL argument"); /* Make the start address of this RE available to everybody */ regstr = exp; /* First pass: determine size, legality. */ regparse = exp; regnpar = 1; regsize = 0L; regcode = ®dummy; regc(MAGIC); if (reg(0, &flags) == NULL) return (regexp *)0; /* Small enough for pointer-storage convention? */ if (regsize >= 32767L) /* Probably could be 65535L. */ FAIL("regexp too big"); /* Allocate space. */ r = (regexp *)malloc(sizeof(regexp) + (unsigned)regsize); if (!r) FAIL("out of space"); /* Second pass: emit code. */ regparse = exp; regnpar = 1; regcode = r->program; regc(MAGIC); if (reg(0, &flags) == NULL) return (regexp *)0; /* Dig out information for optimizations. */ r->regstart = '\0'; /* Worst-case defaults. */ r->reganch = 0; r->regmust = NULL; r->regmlen = 0; scan = r->program+1; /* First BRANCH. */ if (OP(regnext(scan)) == END) { /* Only one top-level choice. */ scan = OPERAND(scan); /* Starting-point info. */ if (OP(scan) == EXACTLY) r->regstart = *OPERAND(scan); else if (OP(scan) == BOL) r->reganch++; /* * If there's something expensive in the r.e., find the * longest literal string that must appear and make it the * regmust. Resolve ties in favor of later strings, since * the regstart check works with the beginning of the r.e. * and avoiding duplication strengthens checking. Not a * strong reason, but sufficient in the absence of others. */ if (flags&SPSTART) { longest = NULL; len = 0; for (; scan != NULL; scan = regnext(scan)) if (OP(scan) == EXACTLY && strlen(OPERAND(scan)) >= len) { longest = OPERAND(scan); len = strlen(OPERAND(scan)); } r->regmust = longest; r->regmlen = len; } } return(r); } /* - reg - regular expression, i.e. main body or parenthesized thing * * Caller must absorb opening parenthesis. * * Combining parenthesis handling with the base level of regular expression * is a trifle forced, but the need to tie the tails of the branches to what * follows makes it hard to avoid. */ static char * reg(paren, flagp) int paren; /* Parenthesized? */ int *flagp; { register char *ret; register char *br; register char *ender; register int parno; int flags; *flagp = HASWIDTH; /* Tentatively. */ /* Make an OPEN node, if parenthesized. */ if (paren) { if (regnpar >= NSUBEXP) FAIL("too many ()"); parno = regnpar; regnpar++; ret = regnode(OPEN+parno); } else ret = NULL; /* Pick up the branches, linking them together. */ br = regbranch(&flags); if (br == NULL) return(NULL); if (ret != NULL) regtail(ret, br); /* OPEN -> first. */ else ret = br; if (!(flags&HASWIDTH)) *flagp &= ~HASWIDTH; *flagp |= flags&SPSTART; while (regparse[0] == '\\' && regparse[1] == '|') { regparse += 2; br = regbranch(&flags); if (br == NULL) return(NULL); regtail(ret, br); /* BRANCH -> BRANCH. */ if (!(flags&HASWIDTH)) *flagp &= ~HASWIDTH; *flagp |= flags&SPSTART; } /* Make a closing node, and hook it on the end. */ ender = regnode((paren) ? CLOSE+parno : END); regtail(ret, ender); /* Hook the tails of the branches to the closing node. */ for (br = ret; br != NULL; br = regnext(br)) regoptail(br, ender); /* Check for proper termination. */ if (paren && (*regparse++ != '\\' || *regparse++ != ')')) { FAIL("unmatched \\(\\)"); } else if (!paren && *regparse != '\0') { if (regparse[0] == '\\' && regparse[1] == ')') { FAIL("unmatched \\(\\)"); } else FAIL("junk on end"); /* "Can't happen". */ /* NOTREACHED */ } return(ret); } /* - regbranch - one alternative of an | operator * * Implements the concatenation operator. */ static char * regbranch(flagp) int *flagp; { register char *ret; register char *chain; register char *latest; int flags; *flagp = WORST; /* Tentatively. */ ret = regnode(BRANCH); chain = NULL; while (*regparse != '\0' && (regparse[0] != '\\' || regparse[1] != '|' && regparse[1] != ')')) { latest = regpiece(&flags); if (latest == NULL) return(NULL); *flagp |= flags&HASWIDTH; if (chain == NULL) /* First piece. */ *flagp |= flags&SPSTART; else regtail(chain, latest); chain = latest; } if (chain == NULL) /* Loop ran zero times. */ regnode(NOTHING); return(ret); } /* - regpiece - something followed by possible [*+?] * * Note that the branching code sequences used for ? and the general cases * of * and + are somewhat optimized: they use the same NOTHING node as * both the endmarker for their branch list and the body of the last branch. * It might seem that this node could be dispensed with entirely, but the * endmarker role is not redundant. */ static char * regpiece(flagp) int *flagp; { register char *ret; register char op; register char *next; int flags; ret = regatom(&flags); if (ret == NULL) return(NULL); op = *regparse; if (!ISMULT(op)) { *flagp = flags; return(ret); } if (!(flags&HASWIDTH) && op != '?') FAIL("*+ operand could be empty"); *flagp = (op != '+') ? (WORST|SPSTART) : (WORST|HASWIDTH); if (op == '*' && (flags&SIMPLE)) reginsert(STAR, ret); else if (op == '*') { /* Emit x* as (x&|), where & means "self". */ reginsert(BRANCH, ret); /* Either x */ regoptail(ret, regnode(BACK)); /* and loop */ regoptail(ret, ret); /* back */ regtail(ret, regnode(BRANCH)); /* or */ regtail(ret, regnode(NOTHING)); /* null. */ } else if (op == '+' && (flags&SIMPLE)) reginsert(PLUS, ret); else if (op == '+') { /* Emit x+ as x(&|), where & means "self". */ next = regnode(BRANCH); /* Either */ regtail(ret, next); regtail(regnode(BACK), ret); /* loop back */ regtail(next, regnode(BRANCH)); /* or */ regtail(ret, regnode(NOTHING)); /* null. */ } else if (op == '?') { /* Emit x? as (x|) */ reginsert(BRANCH, ret); /* Either x */ regtail(ret, regnode(BRANCH)); /* or */ next = regnode(NOTHING); /* null. */ regtail(ret, next); regoptail(ret, next); } regparse++; if (ISMULT(*regparse)) FAIL("nested *?+"); return(ret); } /* - regatom - the lowest level * * Optimization: gobbles an entire sequence of ordinary characters so that * it can turn them into a single node, which is smaller to store and * faster to run. Backslashed characters are exceptions, each becoming a * separate node; the code is simpler that way and it's not worth fixing. * * !sk! Most of my changes are located here. I fixed the backslash bug, and * modified things so that ()<>| are only special when preceded by a backslash * Since I'm shoehorning this into code which didn't particularly care about * backslashes, interfaces are a bit rough. This routine expects to be called * with regparse pointing to a backslash, if there is one; but it exits with * regparse pointing to the ()| AFTER the backslash, except for \< and \>. * Yeah, really messy. */ static char * regatom(flagp) int *flagp; { register char *ret; int flags; register int len; register char ender; int more; *flagp = WORST; /* Tentatively. */ /* The first character may be special */ switch (*regparse++) { case '^': return regnode(BOL); case '$': return regnode(EOL); case '.': if (*o_magic) { ret = regnode(ANY); *flagp |= HASWIDTH|SIMPLE; return ret; } break; case '[': if (*o_magic) { register int class; register int classend; if (*regparse == '^') { /* Complement of range. */ ret = regnode(ANYBUT); regparse++; } else ret = regnode(ANYOF); if (*regparse == ']' || *regparse == '-') regc(*regparse++); while (*regparse != '\0' && *regparse != ']') { if (*regparse == '-') { regparse++; if (*regparse == ']' || *regparse == '\0') regc('-'); else { class = UCHARAT(regparse-2)+1; classend = UCHARAT(regparse); if (class > classend+1) FAIL("invalid [] range"); for (; class <= classend; class++) regc(class); regparse++; } } else regc(*regparse++); } regc('\0'); if (*regparse != ']') FAIL("unmatched []"); regparse++; *flagp |= HASWIDTH|SIMPLE; return ret; } break; case '\\': if (*o_magic) { switch (*regparse++) { case '(': ret = reg(1, &flags); if (ret == NULL) return(NULL); *flagp |= flags&(HASWIDTH|SPSTART); return ret; case '\0': case '|': case ')': FAIL("internal urp"); /* Supposed to be caught earlier. */ case '<': return regnode(BOW); case '>': return regnode(EOW); } regparse--; } break; case '?': case '+': case '*': if (*o_magic) { FAIL("?+* follows nothing"); } break; } /* The first char wasn't special, so start building an EXACTLY string */ regparse--; for (len = 0, more = 1; regparse[len] && more; len++) { switch (regparse[len]) { case '^': case '$': len--; more = 0; break; case '+': case '*': case '?': case '.': case '[': if (*o_magic) { len--; more = 0; } break; case '\\': switch (regparse[++len]) { case '<': case '>': case '(': case ')': case '|': len -= 2; /* put the whole \< or \> back in string */ more = 0; break; case '\0': FAIL("Trailing \\"); } break; } } if (len <= 0) FAIL("internal disaster"); ender = *(regparse+len); if (len > 1 && ISMULT(ender)) len--; /* Back off clear of ?+* operand. */ *flagp |= HASWIDTH; if (len == 1) *flagp |= SIMPLE; ret = regnode(EXACTLY); while (len > 0) { if (*regparse == '\\') { regparse++; len--; if (len == 0) break; } regc(*regparse++); len--; } regc('\0'); return(ret); } /* - regnode - emit a node */ static char * /* Location. */ regnode(op) char op; { register char *ret; register char *ptr; ret = regcode; if (ret == ®dummy) { regsize += 3; return(ret); } ptr = ret; *ptr++ = op; *ptr++ = '\0'; /* Null "next" pointer. */ *ptr++ = '\0'; regcode = ptr; return(ret); } /* - regc - emit (if appropriate) a byte of code */ static void regc(b) char b; { if (regcode != ®dummy) *regcode++ = b; else regsize++; } /* - reginsert - insert an operator in front of already-emitted operand * * Means relocating the operand. */ static void reginsert(op, opnd) char op; char *opnd; { register char *src; register char *dst; register char *place; if (regcode == ®dummy) { regsize += 3; return; } src = regcode; regcode += 3; dst = regcode; while (src > opnd) *--dst = *--src; place = opnd; /* Op node, where operand used to be. */ *place++ = op; *place++ = '\0'; *place++ = '\0'; } /* - regtail - set the next-pointer at the end of a node chain */ static void regtail(p, val) char *p; char *val; { register char *scan; register char *temp; register int offset; if (p == ®dummy) return; /* Find last node. */ scan = p; for (;;) { temp = regnext(scan); if (temp == NULL) break; scan = temp; } if (OP(scan) == BACK) offset = scan - val; else offset = val - scan; *(scan+1) = (offset>>8)&0377; *(scan+2) = offset&0377; } /* - regoptail - regtail on operand of first argument; nop if operandless */ static void regoptail(p, val) char *p; char *val; { /* "Operandless" and "op != BRANCH" are synonymous in practice. */ if (p == NULL || p == ®dummy || OP(p) != BRANCH) return; regtail(OPERAND(p), val); } /* * regexec and friends */ /* * Global work variables for regexec(). */ static char *reginput; /* String-input pointer. */ static char *regbol; /* Beginning of input, for ^ check. */ static char **regstartp; /* Pointer to startp array. */ static char **regendp; /* Ditto for endp. */ /* * Forwards. */ STATIC int regtry(); STATIC int regmatch(); STATIC int regrepeat(); #ifdef DEBUG int regnarrate = 0; void regdump(); STATIC char *regprop(); #endif /* - regexec - match a regexp against a string */ int regexec(prog, string, bolflag) register regexp *prog; register char *string; int bolflag; { register char *s; extern char *strchr(); /* Be paranoid... */ if (!prog || !string) { regerror("NULL parameter"); return(0); } /* Check validity of program. */ if (UCHARAT(prog->program) != MAGIC) { regerror("corrupted program"); return(0); } /* If there is a "must appear" string, look for it. */ if (prog->regmust != NULL) { s = string; while ((s = ustrchr(s, prog->regmust[0])) != NULL) { if (ustrncmp(s, prog->regmust, prog->regmlen) == 0) break; /* Found it. */ s++; } if (s == NULL) /* Not present. */ return(0); } /* Mark beginning of line for ^ . */ if(bolflag) regbol = string; else regbol = NULL; /* Simplest case: anchored match need be tried only once. */ if (prog->reganch) return(regtry(prog, string)); /* Messy cases: unanchored match. */ s = string; if (prog->regstart != '\0') /* We know what char it must start with. */ while ((s = ustrchr(s, prog->regstart)) != NULL) { if (regtry(prog, s)) return(1); s++; } else /* We don't -- general case. */ do { if (regtry(prog, s)) return(1); } while (*s++ != '\0'); /* Failure. */ return(0); } /* - regtry - try match at specific point */ static int /* 0 failure, 1 success */ regtry(prog, string) regexp *prog; char *string; { register int i; register char **sp; register char **ep; reginput = string; regstartp = prog->startp; regendp = prog->endp; sp = prog->startp; ep = prog->endp; for (i = NSUBEXP; i > 0; i--) { *sp++ = NULL; *ep++ = NULL; } if (regmatch(prog->program + 1)) { prog->startp[0] = string; prog->endp[0] = reginput; return(1); } else return(0); } /* - regmatch - main matching routine * * Conceptually the strategy is simple: check to see whether the current * node matches, call self recursively to see whether the rest matches, * and then act accordingly. In practice we make some effort to avoid * recursion, in particular by going through "ordinary" nodes (that don't * need to know whether the rest of the match failed) by a loop instead of * by recursion. */ static int /* 0 failure, 1 success */ regmatch(prog) char *prog; { register char *scan; /* Current node. */ char *next; /* Next node. */ extern char *strchr(); scan = prog; #ifdef DEBUG if (scan != NULL && regnarrate) wprintw(stdscr, "%s(\n", regprop(scan)); #endif while (scan != NULL) { #ifdef DEBUG if (regnarrate) wprintw(stdscr, "%s...\n", regprop(scan)); #endif next = regnext(scan); switch (OP(scan)) { case BOL: if (reginput != regbol) return(0); break; case EOL: if (*reginput != '\0' && *reginput != '\n') return(0); break; case BOW: if (reginput != regbol && (isalnum(reginput[-1]) || reginput[-1] == '_')) return(0); break; case EOW: if (isalnum(*reginput) || *reginput == '_') return(0); break; case ANY: if (*reginput == '\0' || *reginput == '\n') return(0); reginput++; break; case EXACTLY: { register int len; register char *opnd; opnd = OPERAND(scan); /* Inline the first character, for speed. */ if (!*o_ignorecase && *opnd != *reginput) return(0); len = strlen(opnd); if (len > 1 && ustrncmp(opnd, reginput, len) != 0) return(0); reginput += len; } break; case ANYOF: if (*reginput == '\0' || *reginput == '\n' || strchr(OPERAND(scan), *reginput) == NULL) return(0); reginput++; break; case ANYBUT: if (*reginput == '\0' || *reginput == '\n' || strchr(OPERAND(scan), *reginput) != NULL) return(0); reginput++; break; case NOTHING: break; case BACK: break; case OPEN+1: case OPEN+2: case OPEN+3: case OPEN+4: case OPEN+5: case OPEN+6: case OPEN+7: case OPEN+8: case OPEN+9: { register int no; register char *save; no = OP(scan) - OPEN; save = reginput; if (regmatch(next)) { /* * Don't set startp if some later * invocation of the same parentheses * already has. */ if (regstartp[no] == NULL) regstartp[no] = save; return(1); } else return(0); } case CLOSE+1: case CLOSE+2: case CLOSE+3: case CLOSE+4: case CLOSE+5: case CLOSE+6: case CLOSE+7: case CLOSE+8: case CLOSE+9: { register int no; register char *save; no = OP(scan) - CLOSE; save = reginput; if (regmatch(next)) { /* * Don't set endp if some later * invocation of the same parentheses * already has. */ if (regendp[no] == NULL) regendp[no] = save; return(1); } else return(0); } case BRANCH: { register char *save; if (OP(next) != BRANCH) /* No choice. */ next = OPERAND(scan); /* Avoid recursion. */ else { do { save = reginput; if (regmatch(OPERAND(scan))) return(1); reginput = save; scan = regnext(scan); } while (scan != NULL && OP(scan) == BRANCH); return(0); /* NOTREACHED */ } } break; case STAR: case PLUS: { register char nextch; register int no; register char *save; register int min; /* * Lookahead to avoid useless match attempts * when we know what character comes next. */ nextch = '\0'; if (OP(next) == EXACTLY) nextch = *OPERAND(next); min = (OP(scan) == STAR) ? 0 : 1; save = reginput; no = regrepeat(OPERAND(scan)); while (no >= min) { /* If it could work, try it. */ if (nextch == '\0' || *reginput == nextch) if (regmatch(next)) return(1); /* Couldn't or didn't -- back up. */ no--; reginput = save + no; } return(0); } case END: return(1); /* Success! */ default: regerror("memory corruption"); return(0); } scan = next; } /* * We get here only if there's trouble -- normally "case END" is * the terminating point. */ /*NOTREACHED*/ regerror("corrupted pointers"); return(0); } /* - regrepeat - repeatedly match something simple, report how many */ static int regrepeat(p) char *p; { register int count = 0; register char *scan; register char *opnd; scan = reginput; opnd = OPERAND(p); switch (OP(p)) { case ANY: while (*scan && *scan != '\n') { count++; scan++; } break; case EXACTLY: while (*opnd == *scan) { count++; scan++; } break; case ANYOF: while (*scan && *scan != '\n' && strchr(opnd, *scan) != NULL) { count++; scan++; } break; case ANYBUT: while (*scan && *scan != '\n' && strchr(opnd, *scan) == NULL) { count++; scan++; } break; default: /* Oh dear. Called inappropriately. */ regerror("internal foulup"); count = 0; /* Best compromise. */ break; } reginput = scan; return(count); } /* - regnext - dig the "next" pointer out of a node */ static char * regnext(p) register char *p; { register int offset; if (p == ®dummy) return(NULL); offset = NEXT(p); if (offset == 0) return(NULL); if (OP(p) == BACK) return(p-offset); else return(p+offset); } #ifdef DEBUG STATIC char *regprop(); /* - regdump - dump a regexp onto stdout in vaguely comprehensible form */ void regdump(r) regexp *r; { register char *s; register char op = EXACTLY; /* Arbitrary non-END op. */ register char *next; extern char *strchr(); s = r->program + 1; while (op != END) { /* While that wasn't END last time... */ op = OP(s); wprintw(stdscr, "%2d%s", (int)(s-r->program), regprop(s)); /* Where, what. */ next = regnext(s); if (next == NULL) /* Next ptr. */ wprintw(stdscr, "(0)"); else wprintw(stdscr, "(%d)", (int)(s-r->program)+(int)(next-s)); s += 3; if (op == ANYOF || op == ANYBUT || op == EXACTLY) { /* Literal string, where present. */ while (*s != '\0') { addch(*s); s++; } s++; } addch('\n'); } /* Header fields of interest. */ if (r->regstart != '\0') wprintw(stdscr, "start `%c' ", r->regstart); if (r->reganch) wprintw(stdscr, "anchored "); if (r->regmust != NULL) wprintw(stdscr, "must have \"%s\"", r->regmust); wprintw(stdscr, "\n"); } /* - regprop - printable representation of opcode */ static char * regprop(op) char *op; { register char *p; static char buf[50]; (void) strcpy(buf, ":"); switch (OP(op)) { case BOL: p = "BOL"; break; case EOL: p = "EOL"; break; case ANY: p = "ANY"; break; case ANYOF: p = "ANYOF"; break; case ANYBUT: p = "ANYBUT"; break; case BRANCH: p = "BRANCH"; break; case EXACTLY: p = "EXACTLY"; break; case NOTHING: p = "NOTHING"; break; case BACK: p = "BACK"; break; case END: p = "END"; break; case BOW: p = "BOW"; break; case EOW: p = "EOW"; break; case OPEN+1: case OPEN+2: case OPEN+3: case OPEN+4: case OPEN+5: case OPEN+6: case OPEN+7: case OPEN+8: case OPEN+9: sprintf(buf+strlen(buf), "OPEN%d", OP(op)-OPEN); p = NULL; break; case CLOSE+1: case CLOSE+2: case CLOSE+3: case CLOSE+4: case CLOSE+5: case CLOSE+6: case CLOSE+7: case CLOSE+8: case CLOSE+9: sprintf(buf+strlen(buf), "CLOSE%d", OP(op)-CLOSE); p = NULL; break; case STAR: p = "STAR"; break; case PLUS: p = "PLUS"; break; default: regerror("corrupted opcode"); break; } if (p != NULL) (void) strcat(buf, p); return(buf); } #endif /* Here is a function which performs string comparisons. Uses o_ignorecase */ int ustrncmp(str1, str2, len) register char *str1, *str2; /* the strings to compare */ register int len; /* max # of chars we care about */ { if (*o_ignorecase) { while (--len >= 0) { if (tolower(*str1) != tolower(*str2)) { return tolower(*str2) - tolower(*str1); } str1++; str2++; } return 0; } else { while (--len >= 0 && *str1++ == *str2++) { } if (len < 0) { return 0; } str1--; str2--; return *str2 - *str1; } } /* Here is a function which looks for a character in a string. */ char *ustrchr(str, ch) register char *str; /* the string to look in */ register char ch; /* the character to look for */ { if (*o_ignorecase) { for (ch = tolower(ch); *str && *str != '\n'; str++) { if (tolower(*str) == ch) { return str; } } } else { for (; *str && *str != '\n'; str++) { if (*str == ch) { return str; } } } return (char *)0; } #endif SHAR_EOF fi if test -f 'regexp.h' then echo shar: "will not over-write existing file 'regexp.h'" else cat << \SHAR_EOF > 'regexp.h' /* * Definitions etc. for regexp(3) routines. * * Caveat: this is V8 regexp(3) [actually, a reimplementation thereof], * not the System V one. */ #define NSUBEXP 10 typedef struct regexp { char *startp[NSUBEXP]; char *endp[NSUBEXP]; char regstart; /* Internal use only. */ char reganch; /* Internal use only. */ char *regmust; /* Internal use only. */ int regmlen; /* Internal use only. */ char program[1]; /* Unwarranted chumminess with compiler. */ } regexp; extern regexp *regcomp(); extern int regexec(); extern void regsub(); extern void regerror(); SHAR_EOF fi if test -f 'regsub.c' then echo shar: "will not over-write existing file 'regsub.c'" else cat << \SHAR_EOF > 'regsub.c' /* * regsub * * Copyright (c) 1986 by University of Toronto. * Written by Henry Spencer. Not derived from licensed software. * * Permission is granted to anyone to use this software for any * purpose on any computer system, and to redistribute it freely, * subject to the following restrictions: * * 1. The author is not responsible for the consequences of use of * this software, no matter how awful, even if they arise * from defects in it. * * 2. The origin of this software must not be misrepresented, either * by explicit claim or by omission. * * 3. Altered versions must be plainly marked as such, and must not * be misrepresented as being the original software. */ #include "regexp.h" #define NULL (char *)0 /* * The first byte of the regexp internal "program" is actually this magic * number; the start node begins in the second byte. */ #define MAGIC 0234 #define CHARBITS 0377 #ifndef CHARBITS #define UCHARAT(p) ((int)*(unsigned char *)(p)) #else #define UCHARAT(p) ((int)*(p)&CHARBITS) #endif /* - regsub - perform substitutions after a regexp match */ void regsub(prog, source, dest) regexp *prog; char *source; char *dest; { register char *src; register char *dst; register char c; register int no; register int len; extern char *strncpy(); if (!prog || !source || !dest) { regerror("NULL parm to regsub"); return; } if (UCHARAT(prog->program) != MAGIC) { regerror("damaged regexp fed to regsub"); return; } src = source; dst = dest; while ((c = *src++) != '\0') { if (c == '&') no = 0; else if (c == '\\' && '0' <= *src && *src <= '9') no = *src++ - '0'; else no = -1; if (no < 0) { /* Ordinary character. */ if (c == '\\' && *src != '\0') c = *src++; *dst++ = c; } else if (prog->startp[no] != NULL && prog->endp[no] != NULL) { len = (int)(prog->endp[no] - prog->startp[no]); strncpy(dst, prog->startp[no], len); dst += len; if (len !=0 && *(dst-1) == '\0') { /* strncpy hit NUL. */ regerror("damaged match string"); return; } } } *dst++ = '\0'; } SHAR_EOF fi if test -f 'sysdos.c' then echo shar: "will not over-write existing file 'sysdos.c'" else cat << \SHAR_EOF > 'sysdos.c' /* sysdos.c -- DOS version of system.c */ /* Author: * Guntram Blohm * Buchenstrasse 19 * 7904 Erbach, West Germany * Tel. ++49-7305-6997 * sorry - no regular network connection */ /* This file is derived from Steve Kirkendall's system.c. * * Entry points are: * system(cmd) - run a single shell command * wildcard(names) - expand wildcard characters in filanames * * This file is for use with DOS and TOS. For OS/2, slight modifications * might be sufficient. For UNIX, use system.c. For Amiga, completely * rewrite this stuff. * * Another system function, filter, is the same on DOS and UNIX and thus * can be found in the original system.c. */ #include "config.h" #include "vi.h" extern char **environ; #if MSDOS #include <process.h> extern unsigned char _osmajor; #endif #if TOS #include <osbind.h> #endif #if MSDOS || TOS #include <string.h> /* * Calling command is a bit nasty because of the undocumented yet sometimes * used feature to change the option char to something else than /. * Versions 2.x and 3.x support this, 4.x doesn't. * * For Atari, some shells define a shortcut entry which is faster than * shell -c. Also, Mark Williams uses a special ARGV environment variable * to pass more than 128 chars to a called command. * We try to support all of these features here. */ int system(cmd) const char *cmd; { #if MSDOS char *cmdswitch="/c"; if (_osmajor<4) cmdswitch[0]=switchar(); return spawnle(P_WAIT, o_shell, o_shell, cmdswitch, cmd, (char *)0, environ); #else long ssp; int (*shell)(); char line[130]; char env[4096], *ep=env; int i; /* does our shell have a shortcut, that we can use? */ ssp = Super(0L); shell = *((int (**)())0x4F6); Super(ssp); if (shell) return (*shell)(cmd); /* else we'll have to call a shell ... */ for (i=0; environ[i] && strncmp(environ[i], "ARGV=", 5); i++) { strcpy(ep, environ[i]); ep+=strlen(ep)+1; } if (environ[i]) { strcpy(ep, environ[i]); ep+=strlen(ep)+1; strcpy(ep, o_shell); ep+=strlen(ep)+1; strcpy(ep, "-c"); ep+=3; strcpy(ep, cmd); ep+=strlen(ep)+1; } *ep='\0'; strcpy(line+1, "-c "); strncat(line+1, cmd, 126); line[0]=strlen(line+1); return Pexec(0, o_shell, line, env); #endif } /* This private function opens a pipe from a filter. It is similar to the * system() function above, and to popen(cmd, "r"). * sorry - i cant use cmdstate until rpclose, but get it from spawnle. */ static int cmdstate; static char output[80]; int rpipe(cmd, in) char *cmd; /* the filter command to use */ int in; /* the fd to use for stdin */ { int fd, old0, old1, old2; /* create the file that will collect the filter's output */ strcpy(output, o_directory); if ((fd=strlen(output)) && !strchr("/\\:", output[fd-1])) output[fd++]=SLASH; strcpy(output+fd, SCRATCHIN+3); mktemp(output); close(creat(output, 0666)); if ((fd=open(output, O_RDWR))==-1) { unlink(output); return -1; } /* save and redirect stdin, stdout, and stderr */ old0=dup(0); old1=dup(1); if (in) { dup2(in, 0); close(in); } dup2(fd, 1); /* call command */ cmdstate=system(cmd); /* restore old std... */ dup2(old0, 0); close(old0); dup2(old1, 1); close(old1); /* rewind command output */ lseek(fd, 0L, 0); return fd; } /* This function closes the pipe opened by rpipe(), and returns 0 for success */ int rpclose(fd) int fd; { int status; close(fd); unlink(output); return cmdstate; } #endif SHAR_EOF fi if test -f 'system.c' then echo shar: "will not over-write existing file 'system.c'" else cat << \SHAR_EOF > 'system.c' /* system.c -- UNIX version */ /* Author: * Steve Kirkendall * 16820 SW Tallac Way * Beaverton, OR 97006 * kirkenda@jove.cs.pdx.edu, or ...uunet!tektronix!psueea!jove!kirkenda */ /* This file contains a new version of the system() function and related stuff. * * Entry points are: * system(cmd) - run a single shell command * wildcard(names) - expand wildcard characters in filanames * filter(m,n,cmd) - run text lines through a filter program * * This is probably the single least portable file in the program. The code * shown here should work correctly if it links at all; it will work on UNIX * and any O.S./Compiler combination which adheres to UNIX forking conventions. */ #include "config.h" #include "vi.h" #include <signal.h> extern char **environ; #if ANY_UNIX /* This is a new version of the system() function. The only difference * between this one and the library one is: this one uses the o_shell option. */ int system(cmd) char *cmd; /* a command to run */ { int status; /* exit status of the command */ /* warn the user if the file hasn't been saved yet */ if (*o_warn && tstflag(file, MODIFIED)) { msg("Warning: \"%s\" has been modified but not yet saved", origname); } switch (fork()) { case -1: /* error */ status = -1; break; case 0: /* child */ execle(o_shell, o_shell, "-c", cmd, (char *)0, environ); exit(1); /* if we get here, the exec failed */ default: /* parent */ signal(SIGINT, SIG_IGN); wait(&status); signal(SIGINT, trapint); } return status; } /* This private function opens a pipe from a filter. It is similar to the * system() function above, and to popen(cmd, "r"). */ static int rpipe(cmd, in) char *cmd; /* the filter command to use */ int in; /* the fd to use for stdin */ { int r0w1[2];/* the pipe fd's */ /* make the pipe */ if (pipe(r0w1) < 0) { return -1; /* pipe failed */ } switch (fork()) { case -1: /* error */ return -1; case 0: /* child */ /* close the "read" end of the pipe */ close(r0w1[0]); /* redirect stdout to go to the "write" end of the pipe */ close(1); dup(r0w1[1]); close(2); dup(r0w1[1]); close(r0w1[1]); /* redirect stdin */ if (in != 0) { close(0); dup(in); close(in); } /* exec the shell to run the command */ execle(o_shell, o_shell, "-c", cmd, (char *)0, environ); exit(1); /* if we get here, exec failed */ default: /* parent */ signal(SIGINT, SIG_IGN); /* <- reset after the wait() */ /* close the "write" end of the pipe */ close(r0w1[1]); return r0w1[0]; } } /* This function closes the pipe opened by rpipe(), and returns 0 for success */ static int rpclose(fd) int fd; { int status; close(fd); wait(&status); signal(SIGINT, trapint); return status; } #endif /* non-DOS */ /* This function expands wildcards in a filename or filenames. It does this * by running the "echo" command on the filenames via the shell; it is assumed * that the shell will expand the names for you. If for any reason it can't * run echo, then it returns the names unmodified. */ #if MSDOS || TOS #define PROG "wildcard " #define PROGLEN 9 #include <string.h> #else #define PROG "echo " #define PROGLEN 5 #endif char *wildcard(names) char *names; { int i, j, fd; /* build the echo command */ if (names != tmpblk.c) { /* the names aren't in tmpblk.c, so we can do it the easy way */ strcpy(tmpblk.c, PROG); strcat(tmpblk.c, names); } else { register char *s, *d; /* the names are already in tmpblk.c, so shift them to make * room for the word "echo " */ for (s = names + strlen(names) + 1, d = s + PROGLEN; s > names; ) { *--d = *--s; } strncpy(names, PROG, PROGLEN); } /* run the command & read the resulting names */ fd = rpipe(tmpblk.c, 0); if (fd < 0) return names; i = 0; do { j = tread(fd, tmpblk.c + i, BLKSIZE - i); i += j; } while (j > 0); /* successful? */ if (rpclose(fd) == 0 && j == 0 && i < BLKSIZE && i > 0) { tmpblk.c[i-1] = '\0'; /* "i-1" so we clip off the newline */ return tmpblk.c; } else { return names; } } /* This function runs a range of lines through a filter program, and replaces * the original text with the filtered version. As a special case, if "to" * is MARK_UNSET, then it runs the filter program with stdin coming from * /dev/null, and inserts any output lines. */ int filter(from, to, cmd) MARK from, to; /* the range of lines to filter */ char *cmd; /* the filter command */ { int scratch; /* fd of the scratch file */ int fd; /* fd of the pipe from the filter */ char scrout[50]; /* name of the scratch out file */ int i; /* write the lines (if specified) to a temp file */ if (to) { /* we have lines */ #if MSDOS || TOS strcpy(scrout, o_directory); if ((i=strlen(scrout)) && strchr("\\/:", scrout[i-1])) scrout[i++]=SLASH; strcpy(scrout+i, SCRATCHOUT+3); #else sprintf(scrout, SCRATCHOUT, o_directory); #endif mktemp(scrout); cmd_write(from, to, CMD_BANG, 0, scrout); /* use those lines as stdin */ scratch = open(scrout, O_RDONLY); if (scratch < 0) { unlink(scrout); return -1; } } else { scratch = 0; } /* start the filter program */ fd = rpipe(cmd, scratch); if (fd < 0) { if (to) { close(scratch); unlink(scrout); } return -1; } ChangeText { /* delete the original lines, if any. Lines! */ if (to) { from &= ~(BLKSIZE - 1); to &= ~(BLKSIZE - 1); to += BLKSIZE; delete(from, to); } /* repeatedly read in new text and add it */ while ((i = tread(fd, tmpblk.c, BLKSIZE)) > 0) { tmpblk.c[i] = '\0'; add(from, tmpblk.c); for (i = 0; tmpblk.c[i]; i++) { if (tmpblk.c[i] == '\n') { from = (from & ~(BLKSIZE - 1)) + BLKSIZE; } else { from++; } } } } /* cleanup */ rpclose(fd); if (to) { close(scratch); unlink(scrout); } return 0; } SHAR_EOF fi if test -f 'tinytcap.c' then echo shar: "will not over-write existing file 'tinytcap.c'" else cat << \SHAR_EOF > 'tinytcap.c' /* tinytcap.c */ /* This file contains functions which simulate the termcap functions, but which * can only describe the capabilities of the ANSI.SYS and NANSI.SYS drivers on * an MS-DOS system or the VT-52 emulator of an Atari-ST. These functions * do *NOT* access a "termcap" database file. */ #include "config.h" #if MSDOS || TOS #define CAP(str) CAP2((str)[0], (str)[1]) #define CAP2(a,b) (((a) << 8) + ((b) & 0xff)) #if MSDOS # define VAL2(v,a) (a) # define VAL3(v,a,n) (nansi ? (n) : (a)) static int nansi = 0; #endif #if TOS # define VAL2(v,a) (v) # define VAL3(v,a,n) (v) #endif /*ARGSUSED*/ int tgetent(bp, name) char *bp; /* buffer for storing the entry -- ignored */ char *name; /* name of the entry */ { #if MSDOS if (!strcmp(name, "ansi") || !strcmp(name, "nansi")) { nansi = (name[0] == 'n'); return 1; } #endif #if TOS if (!strcmp(name, "vt52")) { return 1; } #endif return 0; } int tgetnum(id) char *id; { switch (CAP(id)) { case CAP2('l','i'): return 25; case CAP2('c','o'): return 80; case CAP2('s','g'): return 0; case CAP2('u','g'): return 0; default: return -1; } } int tgetflag(id) char *id; { switch (CAP(id)) { case CAP2('a','m'): return 1; case CAP2('b','s'): return 1; case CAP2('m','i'): return 1; default: return 0; } } /*ARGSUSED*/ char *tgetstr(id, bp) char *id; char **bp; /* pointer to pointer to buffer - ignored */ { switch (CAP(id)) { case CAP2('c','e'): return VAL2("\033K", "\033[K"); case CAP2('c','l'): return VAL2("\033E", "\033[2J"); case CAP2('a','l'): return VAL3("\033L", (char *)0, "\033[L"); case CAP2('d','l'): return VAL3("\033M", (char *)0, "\033[M"); case CAP2('c','m'): return VAL2("\033Y%i%+ %+ ", "\033[%i%d;%dH"); case CAP2('d','o'): return VAL2("\033B", "\033[B"); case CAP2('n','d'): return VAL2("\033C", "\033[C"); case CAP2('u','p'): return VAL2("\033A", "\033[A"); case CAP2('t','i'): return VAL2("\033e", ""); case CAP2('t','e'): return VAL2("", ""); case CAP2('s','o'): return VAL2("\033p", "\033[7m"); case CAP2('s','e'): return VAL2("\033q", "\033[m"); case CAP2('u','s'): return VAL2((char *)0, "\033[4m"); case CAP2('u','e'): return VAL2((char *)0, "\033[m"); case CAP2('V','B'): return VAL2((char *)0, "\033[1m"); case CAP2('V','b'): return VAL2((char *)0, "\033[m"); case CAP2('k','u'): return "#H"; case CAP2('k','d'): return "#P"; case CAP2('k','l'): return "#K"; case CAP2('k','r'): return "#M"; case CAP2('H','M'): return "#G"; case CAP2('E','N'): return "#O"; case CAP2('P','U'): return "#I"; case CAP2('P','D'): return "#Q"; default: return (char *)0; } } /*ARGSUSED*/ char *tgoto(cm, destcol, destrow) char *cm; /* cursor movement string -- ignored */ int destcol;/* destination column, 0 - 79 */ int destrow;/* destination row, 0 - 24 */ { static char buf[30]; #if MSDOS sprintf(buf, "\033[%d;%dH", destrow + 1, destcol + 1); #endif #if TOS sprintf(buf, "\033Y%c%c", ' ' + destrow, ' ' + destcol); #endif return buf; } /*ARGSUSED*/ void tputs(cp, affcnt, outfn) char *cp; /* the string to output */ int affcnt; /* number of affected lines -- ignored */ int (*outfn)(); /* the output function */ { while (*cp) { (*outfn)(*cp); cp++; } } #endif SHAR_EOF fi if test -f 'tio.c' then echo shar: "will not over-write existing file 'tio.c'" else cat << \SHAR_EOF > 'tio.c' /* tio.c */ /* Author: * Steve Kirkendall * 16820 SW Tallac Way * Beaverton, OR 97006 * kirkenda@jove.cs.pdx.edu, or ...uunet!tektronix!psueea!jove!kirkenda */ /* This file contains terminal I/O functions */ #include "config.h" #include <signal.h> #include "vi.h" /* This function reads in a line from the terminal. */ int vgets(prompt, buf, bsize) char prompt; /* the prompt character, or '\0' for none */ char *buf; /* buffer into which the string is read */ int bsize; /* size of the buffer */ { int len; /* how much we've read so far */ int ch; /* a character from the user */ int quoted; /* is the next char quoted? */ int tab; /* column position of cursor */ char widths[132]; /* widths of characters */ /* show the prompt */ move(LINES - 1, 0); tab = 0; if (prompt) { addch(prompt); tab = 1; } clrtoeol(); refresh(); /* read in the line */ quoted = len = 0; for (;;) { ch = getkey(quoted ? 0 : WHEN_EX); /* some special conversions */ if (ch == ctrl('D') && len == 0) ch = ctrl('['); /* inhibit detection of special chars (except ^J) after a ^V */ if (quoted && ch != '\n') { ch |= 256; } /* process the character */ switch(ch) { case ctrl('V'): qaddch('^'); qaddch('\b'); quoted = TRUE; break; case ctrl('['): return -1; case '\n': case '\r': clrtoeol(); goto BreakBreak; case '\b': if (len > 0) { len--; addstr("\b\b\b\b\b\b\b\b" + 8 - widths[len]); if (mode == MODE_EX) { clrtoeol(); } tab -= widths[len]; } else { return -1; } break; default: /* strip off quotation bit */ if (ch & 256) { ch &= ~256; quoted = FALSE; qaddch(' '); qaddch('\b'); } /* add & echo the char */ if (len < bsize - 1) { if (ch == '\t') { widths[len] = *o_tabstop - (tab % *o_tabstop); addstr(" " + 8 - widths[len]); tab += widths[len]; } else if (ch > 0 && ch < ' ') /* > 0 by GB */ { addch('^'); addch(ch + '@'); widths[len] = 2; tab += 2; } else if (ch == '\177') { addch('^'); addch('?'); widths[len] = 2; tab += 2; } else { addch(ch); widths[len] = 1; tab++; } buf[len++] = ch; } else { beep(); } } } BreakBreak: refresh(); buf[len] = '\0'; return len; } /* ring the terminal's bell */ beep() { if (*o_vbell) { do_VB(); refresh(); } else if (*o_errorbells) { ttywrite("\007", 1); } } static manymsgs; /* This variable keeps msgs from overwriting each other */ /* Write a message in an appropriate way. This should really be a varargs * function, but there is no such thing as vwprintw. Hack!!! Also uses a * little sleaze in the way it saves messages for repetition later. * * msg((char *)0) - repeats the previous message * msg("") - clears the message line * msg("%s %d", ...) - does a printf onto the message line */ /*VARARGS1*/ msg(fmt, arg1, arg2, arg3, arg4, arg5, arg6, arg7) char *fmt; long arg1, arg2, arg3, arg4, arg5, arg6, arg7; { static char pmsg[80]; /* previous message */ char *start; /* start of current message */ if (mode != MODE_VI) { sprintf(pmsg, fmt, arg1, arg2, arg3, arg4, arg5, arg6, arg7); qaddstr(pmsg); addch('\n'); exrefresh(); } else { /* redrawing previous message? */ if (!fmt) { move(LINES - 1, 0); standout(); qaddch(' '); addstr(pmsg); qaddch(' '); standend(); clrtoeol(); return; } /* just blanking out message line? */ if (!*fmt) { if (!*pmsg) return; *pmsg = '\0'; move(LINES - 1, 0); clrtoeol(); return; } /* wait for keypress between consecutive msgs */ if (manymsgs) { qaddstr("[More...]"); wqrefresh(stdscr); getkey(0); } /* real message */ move(LINES - 1, 0); standout(); qaddch(' '); sprintf(pmsg, fmt, arg1, arg2, arg3, arg4, arg5, arg6, arg7); qaddstr(pmsg); qaddch(' '); standend(); clrtoeol(); refresh(); } manymsgs = TRUE; } /* This function calls refresh() if the option exrefresh is set */ exrefresh() { char *scan; /* If this ex command wrote ANYTHING set exwrote so vi's : command * can tell that it must wait for a user keystroke before redrawing. */ for (scan=kbuf; scan<stdscr; scan++) if (*scan == '\n') exwrote = TRUE; #if MICROSOFT /* avoid compiler bug */ scan = stdscr; #define stdscr scan #endif /* now we do the refresh thing */ if (*o_exrefresh) { refresh(); } else { wqrefresh(stdscr); } #if MICROSOFT #undef stdscr stdscr = scan; #endif manymsgs = FALSE; } /* This variable holds a single ungotten key, or 0 for no key */ static int ungotten; ungetkey(key) int key; { ungotten = key; } /* This array describes mapped key sequences */ static struct _keymap { char *name; /* name of the key, or NULL */ char rawin[LONGKEY]; /* the unmapped version of input */ char cooked[80]; /* the mapped version of input */ int len; /* length of the unmapped version */ int when; /* when is this key mapped? */ } mapped[MAXMAPS]; #if !MSDOS && !TOS static int dummy(){} /* for timeout */ #endif /* This function reads in a keystroke for VI mode. It automatically handles * key mapping. */ int getkey(when) int when; /* which bits must be ON? */ { static char keybuf[100]; /* array of already-read keys */ static int nkeys; /* total number of keys in keybuf */ static int next; /* index of next key to return */ static char *cooked; /* rawin, or pointer to converted key */ static int oldwhen; /* "when" from last time */ static int oldleft; static long oldtop; static long oldnlines; static char *cshape; /* current cursor shape */ register char *kptr; /* &keybuf[next] */ register struct _keymap *km; /* used to count through keymap */ register int i, j, k; /* if this key is needed for delay between multiple error messages, * then reset the manymsgs flag and abort any mapped key sequence. */ if (manymsgs) { manymsgs = FALSE; cooked = (char *)0; ungotten = 0; } /* if we have an ungotten key, use it */ if (ungotten != 0) { k = ungotten; ungotten = 0; return k; } /* if we're doing a mapped key, get the next char */ if (cooked && *cooked) { return *cooked++; } /* if keybuf is empty, fill it */ if (next == nkeys) { #ifndef NO_CURSORSHAPE /* make sure the cursor is the right shape */ if (has_CQ) { cooked = cshape; switch (when) { case WHEN_EX: cooked = CX; break; case WHEN_VICMD: cooked = CV; break; case WHEN_VIINP: cooked = CI; break; case WHEN_VIREP: cooked = CR; break; } if (cooked != cshape) { cshape = cooked; switch (when) { case WHEN_EX: do_CX(); break; case WHEN_VICMD: do_CV(); break; case WHEN_VIINP: do_CI(); break; case WHEN_VIREP: do_CR(); break; } } cooked = (char *)0; } #endif #ifndef NO_SHOWMODE /* if "showmode" then say which mode we're in */ if (*o_showmode && mode == MODE_VI && (when != oldwhen || topline != oldtop || leftcol != oldleft || nlines != oldnlines)) { oldwhen = when; oldtop = topline; oldleft = leftcol; oldnlines = nlines; if (when & WHEN_VICMD) { redraw(cursor, FALSE); move(LINES - 1, COLS - 10); standout(); addstr("Command"); standend(); redraw(cursor, FALSE); } else if (when & WHEN_VIINP) { redraw(cursor, TRUE); move(LINES - 1, COLS - 10); standout(); addstr(" Input "); standend(); redraw(cursor, TRUE); } else if (when & WHEN_VIREP) { redraw(cursor, TRUE); move(LINES - 1, COLS - 10); standout(); addstr("Replace"); standend(); redraw(cursor, TRUE); } } else #endif /* redraw if getting a VI command */ if (when & WHEN_VICMD) { redraw(cursor, FALSE); } /* read the rawin keystrokes */ refresh(); while ((nkeys = ttyread(keybuf, sizeof keybuf)) <= 0) { /* terminal was probably resized */ *o_lines = LINES; *o_columns = COLS; if (when & (WHEN_VICMD|WHEN_VIINP|WHEN_VIREP)) { redraw(MARK_UNSET, FALSE); redraw(cursor, (when & WHEN_VICMD) == 0); refresh(); } } next = 0; } /* see how many mapped keys this might be */ kptr = &keybuf[next]; for (i = j = 0, k = -1, km = mapped; i < MAXMAPS; i++, km++) { if ((km->when & when) && km->len > 0 && *km->rawin == *kptr) { if (km->len > nkeys - next) { if (!strncmp(km->rawin, kptr, nkeys - next)) { j++; } } else { if (!strncmp(km->rawin, kptr, km->len)) { j++; k = i; } } } } /* if more than one, try to read some more */ while (j > 1) { #if ANY_UNIX signal(SIGALRM, dummy); #endif alarm((unsigned)*o_keytime); i = nkeys; if ((k = ttyread(keybuf + nkeys, sizeof keybuf - nkeys)) >= 0) { nkeys += k; } alarm(0); /* if we couldn't read any more, pretend 0 mapped keys */ if (i == nkeys) { j = 0; } else /* else we got some more - try again */ { for (i = j = 0, k = -1, km = mapped; i < MAXMAPS; i++, km++) { if ((km->when & when) && km->len > 0 && *km->rawin == *kptr) { if (km->len > nkeys - next) { if (!strncmp(km->rawin, kptr, nkeys - next)) { j++; } } else { if (!strncmp(km->rawin, kptr, km->len)) { j++; k = i; } } } } } } /* if unambiguously mapped key, use it! */ if (j == 1 && k >= 0) { next += mapped[k].len; cooked = mapped[k].cooked; #ifndef NO_EXTENSIONS if ((when & (WHEN_VIINP|WHEN_VIREP)) && (mapped[k].when & WHEN_INMV)) { return 0; /* special case, means "a movement char follows" */ } else #endif { return *cooked++; } } else /* assume key is unmapped, but still translate weird erase key to '\b' */ if (keybuf[next] == ERASEKEY && when != 0) { next++; return '\b'; } else { return keybuf[next++]; } } /* This function maps or unmaps a key */ mapkey(rawin, cooked, when, name) char *rawin; /* the input key sequence, before mapping */ char *cooked;/* after mapping */ short when; /* bitmap of when mapping should happen */ char *name; /* name of the key, if any */ { int i, j; /* if the mapped version starts with the word "visual" then set WHEN_INMV */ if (!strncmp(cooked, "visual ", 7)) { when |= WHEN_INMV; cooked += 7; } /* if WHEN_INMV is set, then WHEN_VIINP and WHEN_VIREP must be set */ if (when & WHEN_INMV) { when |= (WHEN_VIINP | WHEN_VIREP); } /* see if the key sequence was mapped before */ j = strlen(rawin); for (i = 0; i < MAXMAPS; i++) { if (mapped[i].len == j && !strncmp(mapped[i].rawin, rawin, j) && (mapped[i].when & when)) { break; } } /* if not already mapped, then try to find a new slot to use */ if (i == MAXMAPS) { for (i = 0; i < MAXMAPS && mapped[i].len > 0; i++) { } } /* no room for the new key? */ if (i == MAXMAPS) { msg("No room left in the key map table"); return; } /* map the key */ if (cooked && *cooked) { /* Map the key */ mapped[i].len = j; strncpy(mapped[i].rawin, rawin, j); strcpy(mapped[i].cooked, cooked); mapped[i].when = when; mapped[i].name = name; } else /* unmap the key */ { mapped[i].len = 0; } } /* Dump keys of a given type - WHEN_VICMD dumps the ":map" keys, and * WHEN_VIINP|WHEN_VIREP dumps the ":map!" keys */ dumpkey(when) { int i, len, mlen; char *scan; char *mraw; for (i = 0; i < MAXMAPS; i++) { /* skip unused entries, or entries that don't match "when" */ if (mapped[i].len <= 0 || !(mapped[i].when & when)) { continue; } /* dump the key label, if any */ len = 8; if (mapped[i].name) { qaddstr(mapped[i].name); len -= strlen(mapped[i].name); } do { qaddch(' '); } while (len-- > 0); /* dump the raw version */ len = 0; mlen = mapped[i].len; mraw = mapped[i].rawin; for (scan = mraw; scan < mraw + mlen; scan++) { if (UCHAR(*scan) < ' ' || *scan == '\177') { qaddch('^'); qaddch(*scan ^ '@'); len += 2; } else { qaddch(*scan); len++; } } do { qaddch(' '); } while (++len < 8); /* dump the mapped version */ if ((mapped[i].when & WHEN_INMV) && (when & (WHEN_VIINP|WHEN_VIREP))) { qaddstr("visual "); } for (scan = mapped[i].cooked; *scan; scan++) { if (UCHAR(*scan) < ' ' || *scan == '\177') { qaddch('^'); qaddch(*scan ^ '@'); } else { qaddch(*scan); } } addch('\n'); exrefresh(); } } /* This function saves the current configuration of mapped keys to a file */ savekeys(fd) int fd; /* file descriptor to save them to */ { int i; char buf[80]; /* now write a map command for each key other than the arrows */ for (i = 0; i < MAXMAPS; i++) { /* ignore keys that came from termcap */ if (mapped[i].name) { continue; } /* If this isn't used, ignore it */ if (mapped[i].len <= 0) { continue; } /* write the map command */ if (mapped[i].when & WHEN_INMV) { sprintf(buf, "map%s %.*s visual %s\n", (mapped[i].when & WHEN_VICMD) ? "" : "!", mapped[i].len, mapped[i].rawin, mapped[i].cooked); twrite(fd, buf, strlen(buf)); } else { if (mapped[i].when & WHEN_VICMD) { sprintf(buf, "map %.*s %s\n", mapped[i].len, mapped[i].rawin, mapped[i].cooked); twrite(fd, buf, strlen(buf)); } if (mapped[i].when & (WHEN_VIINP | WHEN_VIREP)) { sprintf(buf, "map! %.*s %s\n", mapped[i].len, mapped[i].rawin, mapped[i].cooked); twrite(fd, buf, strlen(buf)); } } } } SHAR_EOF fi if test -f 'tmp.c' then echo shar: "will not over-write existing file 'tmp.c'" else cat << \SHAR_EOF > 'tmp.c' /* tmpfile.c */ /* Author: * Steve Kirkendall * 16820 SW Tallac Way * Beaverton, OR 97006 * kirkenda@jove.cs.pdx.edu, or ...uunet!tektronix!psueea!jove!kirkenda */ /* This file contains functions which create & readback a TMPFILE */ #include "config.h" #include "vi.h" #if TOS #include <stat.h> #else #include <sys/stat.h> #endif /* The FAIL() macro prints an error message and then exits. */ #define FAIL(why,arg) mode = MODE_EX; msg(why, arg); endwin(); exit(9) /* This is the name of the temp file */ static char tmpname[80]; /* This function creates the temp file and copies the original file into it. * Returns if successful, or stops execution if it fails. */ int tmpstart(filename) char *filename; /* name of the original file */ { int origfd; /* fd used for reading the original file */ struct stat statb; /* stat buffer, used to examine inode */ register BLK *this; /* pointer to the current block buffer */ register BLK *next; /* pointer to the next block buffer */ int inbuf; /* number of characters in a buffer */ int nread; /* number of bytes read */ #ifdef FASTLOAD int kin; /* number of bytes in kbuf */ int kout; /* index into kbuf of where to take bytes */ #endif register int j, k; int i; /* switching to a different file certainly counts as a change */ changes++; redraw(MARK_UNSET, FALSE); /* open the original file for reading */ *origname = '\0'; if (filename && *filename) { strcpy(origname, filename); origfd = open(origname, O_RDONLY); if (origfd < 0 && errno != ENOENT) { FAIL("Can't open \"%s\"", origname); } if (origfd >= 0) { #if TURBOC || TOS if (stat(origname, &statb) < 0) #else if (fstat(origfd, &statb) < 0) #endif { FAIL("Can't stat \"%s\"", origname); } #if TOS if (origfd >= 0 && (statb.st_mode & S_IJDIR)) #else if (origfd >= 0 && (statb.st_mode & S_IFMT) != S_IFREG) #endif { FAIL("\"%s\" is not a regular file", origname); } } else { stat(".", &statb); } if (origfd >= 0) { origtime = statb.st_mtime; #if MSDOS if (*o_readonly || !(statb.st_mode & S_IWRITE)) #endif #if TOS if (*o_readonly || (statb.st_mode & S_IJRON)) #endif #if OS9 if we don't have write permission... #endif #if ANY_UNIX if (*o_readonly || !(statb.st_mode & (statb.st_uid != geteuid() ? 0022 : 0200))) #endif { setflag(file, READONLY); } } else { origtime = 0L; } } else { setflag(file, NOFILE); origfd = -1; origtime = 0L; stat(".", &statb); } /* make a name for the tmp file */ #if MSDOS || TOS /* MS-Dos doesn't allow multiple slashes, but supports drives * with current directories. * This relies on TMPNAME beginning with "%s\\"!!!! */ strcpy(tmpname, o_directory); if ((i = strlen(tmpname)) && !strchr(":/\\", tmpname[i-1])) tmpname[i++]=SLASH; sprintf(tmpname+i, TMPNAME+3, statb.st_ino, statb.st_dev); #else sprintf(tmpname, TMPNAME, o_directory, statb.st_ino, statb.st_dev); #endif /* make sure nobody else is editing the same file */ if (access(tmpname, 0) == 0) { FAIL("\"%s\" is busy", filename); } /* create the temp file */ close(creat(tmpname, 0600)); tmpfd = open(tmpname, O_RDWR | O_BINARY); if (tmpfd < 0) { FAIL("Can't create temporary file, errno=%d", errno); return 1; } /* allocate space for the header in the file */ write(tmpfd, hdr.c, BLKSIZE); #ifndef NO_RECYCLE /* initialize the block allocator */ /* This must already be done here, before the first attempt * to write to the new file! GB */ garbage(); #endif /* initialize lnum[] */ for (i = 1; i < MAXBLKS; i++) { lnum[i] = INFINITY; } lnum[0] = 0; /* if there is no original file, then create a 1-line file */ if (origfd < 0) { hdr.n[0] = 0; /* invalid inode# denotes new file */ this = blkget(1); /* get the new text block */ strcpy(this->c, "\n"); /* put a line in it */ lnum[1] = 1; /* block 1 ends with line 1 */ nlines = 1; /* there is 1 line in the file */ if (*origname) { msg("\"%s\" [NEW FILE] 1 line", origname); } else { msg("\"[NO FILE]\" 1 line"); } } else /* there is an original file -- read it in */ { hdr.n[0] = statb.st_ino; nlines = 0; /* preallocate 1 "next" buffer */ i = 1; next = blkget(i); inbuf = 0; /* loop, moving blocks from orig to tmp */ #ifdef FASTLOAD kin = kout = 0; #endif for (;;) { /* "next" buffer becomes "this" buffer */ this = next; /* read [more] text into this block */ do { #ifdef FASTLOAD if (kout >= kin) { kout = 0; kin = tread(origfd, kbuf, KBSIZ); } nread = kin - kout; if (nread > BLKSIZE - 1 - inbuf) nread = BLKSIZE - 1 - inbuf; if (nread > 0) { memcpy(&this->c[inbuf], &kbuf[kout], nread); kout += nread; } #else nread = tread(origfd, &this->c[inbuf], BLKSIZE - 1 - inbuf); #endif if (nread < 0) { close(origfd); close(tmpfd); tmpfd = -1; unlink(tmpname); FAIL("Error reading \"%s\"", origname); } /* convert NUL characters to something else */ for (k = inbuf; k < inbuf + nread; k++) { if (!this->c[k]) { setflag(file, HADNUL); this->c[k] = 0x80; } } inbuf += nread; /* if the buffer is empty, quit */ if (inbuf == 0) { goto FoundEOF; } } while (0 /* nread > 0 && inbuf < BLKSIZE - 2 */ ); #if MSDOS || TOS /* BAH! MS text mode read fills inbuf, then compresses eliminating \r but leaving garbage at end of buf. The same is true for TURBOC. GB. */ memset(this->c + inbuf, '\0', BLKSIZE - inbuf); #endif /* search backward for last newline */ for (k = inbuf; --k >= 0 && this->c[k] != '\n';) { } if (k++ < 0) { if (inbuf >= BLKSIZE - 1) { k = 80; } else { k = inbuf; } } /* allocate next buffer */ next = blkget(++i); /* move fragmentary last line to next buffer */ inbuf -= k; for (j = 0; k < BLKSIZE; j++, k++) { next->c[j] = this->c[k]; this->c[k] = 0; } /* if necessary, add a newline to this buf */ for (k = BLKSIZE - inbuf; --k >= 0 && !this->c[k]; ) { } if (this->c[k] != '\n') { setflag(file, ADDEDNL); this->c[k + 1] = '\n'; } /* count the lines in this block */ for (k = 0; k < BLKSIZE && this->c[k]; k++) { if (this->c[k] == '\n') { nlines++; } } lnum[i - 1] = nlines; } FoundEOF: /* if this is a zero-length file, add 1 line */ if (nlines == 0) { this = blkget(1); /* get the new text block */ strcpy(this->c, "\n"); /* put a line in it */ lnum[1] = 1; /* block 1 ends with line 1 */ nlines = 1; /* there is 1 line in the file */ } /* report the number of lines in the file */ msg("\"%s\" %s %ld line%s", origname, (tstflag(file, READONLY) ? "[READONLY]" : ""), nlines, nlines == 1 ? "" : "s"); } /* initialize the cursor to start of line 1 */ cursor = MARK_FIRST; /* close the original file */ close(origfd); return 0; } /* This function copies the temp file back onto an original file. * Returns TRUE if successful, or FALSE if the file could NOT be saved. */ tmpsave(filename, bang) char *filename; /* the name to save it to */ int bang; /* forced write? */ { int fd; /* fd of the file we're writing to */ register int len; /* length of a text block */ register BLK *this; /* a text block */ long bytes; /* byte counter */ register int i; /* if no filename is given, assume the original file name */ if (!filename || !*filename) { filename = origname; } /* if still no file name, then fail */ if (!*filename) { msg("Don't know a name for this file -- NOT WRITTEN"); return FALSE; } /* open the file */ if (*filename == '>' && filename[1] == '>') { filename += 2; while (*filename == ' ' || *filename == '\t') { filename++; } #ifdef O_APPEND fd = open(filename, O_WRONLY|O_APPEND); #else fd = open(filename, O_WRONLY); lseek(fd, 0L, 2); #endif } else { /* either the file must not exist, or it must be the original * file, or we must have a bang */ if (strcmp(filename, origname) && access(filename, 0) == 0 && !bang) { msg("File already exists - Use :w! to overwrite"); return FALSE; } fd = creat(filename, 0666); } if (fd < 0) { msg("Can't write to \"%s\" -- NOT WRITTEN", filename); return FALSE; } /* write each text block to the file */ bytes = 0L; for (i = 1; i < MAXBLKS && (this = blkget(i)) && this->c[0]; i++) { for (len = 0; len < BLKSIZE && this->c[len]; len++) { } twrite(fd, this->c, len); bytes += len; } /* reset the "modified" flag */ clrflag(file, MODIFIED); /* report lines & characters */ #if MSDOS || TOS bytes += nlines; /* for the inserted carriage returns */ #endif if (strncmp(filename, o_directory, strlen(o_directory))) { msg("Wrote \"%s\" %ld lines, %ld characters", filename, nlines, bytes); } /* close the file */ close(fd); return TRUE; } /* This function deletes the temporary file. If the file has been modified * and "bang" is FALSE, then it returns FALSE without doing anything; else * it returns TRUE. * * If the "autowrite" option is set, then instead of returning FALSE when * the file has been modified and "bang" is false, it will call tmpend(). */ tmpabort(bang) int bang; { /* if there is no file, return successfully */ if (tmpfd < 0) { return TRUE; } /* see if we must return FALSE -- can't quit */ if (!bang && tstflag(file, MODIFIED)) { /* if "autowrite" is set, then act like tmpend() */ if (*o_autowrite) return tmpend(bang); else return FALSE; } /* delete the tmp file */ cutswitch(tmpname); close(tmpfd); tmpfd = -1; unlink(tmpname); strcpy(prevorig, origname); prevline = markline(cursor); *origname = '\0'; origtime = 0L; blkinit(); nlines = 0; initflags(); return TRUE; } /* This function saves the file if it has been modified, and then deletes * the temporary file. Returns TRUE if successful, or FALSE if the file * needs to be saved but can't be. When it returns FALSE, it will not have * deleted the tmp file, either. */ tmpend(bang) int bang; { /* save the file if it has been modified */ if (tstflag(file, MODIFIED) && !tmpsave((char *)0, FALSE) && !bang) { return FALSE; } /* delete the tmp file */ tmpabort(TRUE); return TRUE; } /* If the tmp file has been changed, then this function will force those * changes to be written to the disk, so that the tmp file will survive a * system crash or power failure. */ #if MSDOS || TOS || OS9 sync() { # if OS9 /* OS-9 doesn't need an explicit sync operation, but the linker * demands something called sync(), so this is a dummy function. */ #else /* MS-DOS and TOS don't flush their buffers until the file is closed, * so here we close the tmp file and then immediately reopen it. */ close(tmpfd); tmpfd = open(tmpname, O_RDWR | O_BINARY); #endif } #endif SHAR_EOF fi exit 0 # End of shell archive ------------------------------------------------------------------------------- Steve Kirkendall kirkenda@cs.pdx.edu uunet!tektronix!psueea!eecs!kirkenda