#include #include #include #include #include #include #include #include "libframe/frame.h" #include #include #include #include "dat.h" #include "edit.h" #include "fns.h" static char linex[] = "\n"; static char wordx[] = " \t\n"; struct cmdtab cmdtab[] = { /* cmdc text regexp addr defcmd defaddr count token fn */ '\n', 0, 0, 0, 0, aDot, 0, 0, nl_cmd, 'a', 1, 0, 0, 0, aDot, 0, 0, a_cmd, 'b', 0, 0, 0, 0, aNo, 0, linex, b_cmd, 'c', 1, 0, 0, 0, aDot, 0, 0, c_cmd, 'd', 0, 0, 0, 0, aDot, 0, 0, d_cmd, 'e', 0, 0, 0, 0, aNo, 0, wordx, e_cmd, 'f', 0, 0, 0, 0, aNo, 0, wordx, f_cmd, 'g', 0, 1, 0, 'p', aDot, 0, 0, g_cmd, 'i', 1, 0, 0, 0, aDot, 0, 0, i_cmd, 'm', 0, 0, 1, 0, aDot, 0, 0, m_cmd, 'p', 0, 0, 0, 0, aDot, 0, 0, p_cmd, 'r', 0, 0, 0, 0, aDot, 0, wordx, e_cmd, 's', 0, 1, 0, 0, aDot, 1, 0, s_cmd, 't', 0, 0, 1, 0, aDot, 0, 0, m_cmd, 'u', 0, 0, 0, 0, aNo, 2, 0, u_cmd, 'v', 0, 1, 0, 'p', aDot, 0, 0, g_cmd, 'w', 0, 0, 0, 0, aAll, 0, wordx, w_cmd, 'x', 0, 1, 0, 'p', aDot, 0, 0, x_cmd, 'y', 0, 1, 0, 'p', aDot, 0, 0, x_cmd, '=', 0, 0, 0, 0, aDot, 0, linex, eq_cmd, 'B', 0, 0, 0, 0, aNo, 0, linex, B_cmd, 'D', 0, 0, 0, 0, aNo, 0, linex, D_cmd, 'X', 0, 1, 0, 'f', aNo, 0, 0, X_cmd, 'Y', 0, 1, 0, 'f', aNo, 0, 0, X_cmd, '<', 0, 0, 0, 0, aDot, 0, linex, pipe_cmd, '|', 0, 0, 0, 0, aDot, 0, linex, pipe_cmd, '>', 0, 0, 0, 0, aDot, 0, linex, pipe_cmd, /* deliberately unimplemented: 'k', 0, 0, 0, 0, aDot, 0, 0, k_cmd, 'n', 0, 0, 0, 0, aNo, 0, 0, n_cmd, 'q', 0, 0, 0, 0, aNo, 0, 0, q_cmd, '!', 0, 0, 0, 0, aNo, 0, linex, plan9_cmd, */ 0, 0, 0, 0, 0, 0, 0, 0}; Cmd* parsecmd(int); Addr* compoundaddr(void); Addr* simpleaddr(void); void freecmd(void); void okdelim(int); Rune* cmdstartp; Rune* cmdendp; Rune* cmdp; Channel* editerrc; String* lastpat; int patset; List cmdlist; List addrlist; List stringlist; Text* curtext; int editing = Inactive; String* newstring(int); void editthread(void* v) { Cmd* cmdp; USED(v); threadsetname("editthread"); while ((cmdp = parsecmd(0)) != 0) { if (cmdexec(curtext, cmdp) == 0) break; freecmd(); } sendp(editerrc, nil); } void allelogterm(Window* w, void* x) { USED(x); elogterm(w->body.file); } void alleditinit(Window* w, void* x) { USED(x); textcommit(&w->tag, TRUE); textcommit(&w->body, TRUE); w->body.file->editclean = FALSE; } void allupdate(Window* w, void* x) { Text* t; int i; File* f; USED(x); t = &w->body; f = t->file; if (f->curtext != t) /* do curtext only */ return; if (f->elog.type == Null) elogterm(f); else if (f->elog.type != Empty) { elogapply(f); if (f->editclean) { f->mod = FALSE; for (i = 0; i < f->ntext; i++) f->text[i]->w->dirty = FALSE; } } textsetselect(t, t->q0, t->q1); textscrdraw(t); winsettag(w); } void editerror(char* fmt, ...) { va_list arg; char* s; va_start(arg, fmt); s = vsmprint(fmt, arg); va_end(arg); freecmd(); allwindows(allelogterm, nil); /* truncate the edit logs */ sendp(editerrc, s); threadexits(nil); } void editcmd(Text* ct, Rune* r, uint n) { char* err; if (n == 0) return; if (2 * n > RBUFSIZE) { warning(nil, "string too long\n"); return; } allwindows(alleditinit, nil); if (cmdstartp) free(cmdstartp); cmdstartp = runemalloc(n + 2); runemove(cmdstartp, r, n); if (r[n - 1] != '\n') cmdstartp[n++] = '\n'; cmdstartp[n] = '\0'; cmdendp = cmdstartp + n; cmdp = cmdstartp; if (ct->w == nil) curtext = nil; else curtext = &ct->w->body; resetxec(); if (editerrc == nil) { editerrc = chancreate(sizeof(char*), 0); chansetname(editerrc, "editerrc"); lastpat = allocstring(0); } threadcreate(editthread, nil, STACK); err = recvp(editerrc); editing = Inactive; if (err != nil) { if (err[0] != '\0') warning(nil, "Edit: %s\n", err); free(err); } /* update everyone whose edit log has data */ allwindows(allupdate, nil); } int getch(void) { if (cmdp == cmdendp) return -1; return *cmdp++; } int nextc(void) { if (cmdp == cmdendp) return -1; return *cmdp; } void ungetch(void) { if (--cmdp < cmdstartp) error("ungetch"); } long getnum(int signok) { long n; int c, sign; n = 0; sign = 1; if (signok > 1 && nextc() == '-') { sign = -1; getch(); } if ((c = nextc()) < '0' || '9' < c) /* no number defaults to 1 */ return sign; while ('0' <= (c = getch()) && c <= '9') n = n * 10 + (c - '0'); ungetch(); return sign * n; } int cmdskipbl(void) { int c; do c = getch(); while (c == ' ' || c == '\t'); if (c >= 0) ungetch(); return c; } /* * Check that list has room for one more element. */ void growlist(List* l) { if (l->u.listptr == 0 || l->nalloc == 0) { l->nalloc = INCR; l->u.listptr = emalloc(INCR * sizeof(void*)); l->nused = 0; } else if (l->nused == l->nalloc) { l->u.listptr = erealloc(l->u.listptr, (l->nalloc + INCR) * sizeof(void*)); memset(l->u.ptr + l->nalloc, 0, INCR * sizeof(void*)); l->nalloc += INCR; } } /* * Remove the ith element from the list */ void dellist(List* l, int i) { memmove(&l->u.ptr[i], &l->u.ptr[i + 1], (l->nused - (i + 1)) * sizeof(void*)); l->nused--; } /* * Add a new element, whose position is i, to the list */ void inslist(List* l, int i, void* v) { growlist(l); memmove(&l->u.ptr[i + 1], &l->u.ptr[i], (l->nused - i) * sizeof(void*)); l->u.ptr[i] = v; l->nused++; } void listfree(List* l) { free(l->u.listptr); free(l); } String* allocstring(int n) { String* s; s = emalloc(sizeof(String)); s->n = n; s->nalloc = n + 10; s->r = emalloc(s->nalloc * sizeof(Rune)); s->r[n] = '\0'; return s; } void freestring(String* s) { free(s->r); free(s); } Cmd* newcmd(void) { Cmd* p; p = emalloc(sizeof(Cmd)); inslist(&cmdlist, cmdlist.nused, p); return p; } String* newstring(int n) { String* p; p = allocstring(n); inslist(&stringlist, stringlist.nused, p); return p; } Addr* newaddr(void) { Addr* p; p = emalloc(sizeof(Addr)); inslist(&addrlist, addrlist.nused, p); return p; } void freecmd(void) { int i; while (cmdlist.nused > 0) free(cmdlist.u.ucharptr[--cmdlist.nused]); while (addrlist.nused > 0) free(addrlist.u.ucharptr[--addrlist.nused]); while (stringlist.nused > 0) { i = --stringlist.nused; freestring(stringlist.u.stringptr[i]); } } void okdelim(int c) { if ( c == '\\' || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || ('0' <= c && c <= '9')) editerror("bad delimiter %c\n", c); } void atnl(void) { int c; cmdskipbl(); c = getch(); if (c != '\n') editerror("newline expected (saw %C)", c); } void Straddc(String* s, int c) { if (s->n + 1 >= s->nalloc) { s->nalloc += 10; s->r = erealloc(s->r, s->nalloc * sizeof(Rune)); } s->r[s->n++] = c; s->r[s->n] = '\0'; } void getrhs(String* s, int delim, int cmd) { int c; while ((c = getch()) > 0 && c != delim && c != '\n') { if (c == '\\') { if ((c = getch()) <= 0) error("bad right hand side"); if (c == '\n') { ungetch(); c = '\\'; } else if (c == 'n') c = '\n'; else if (c != delim && (cmd == 's' || c != '\\')) /* s does its own */ Straddc(s, '\\'); } Straddc(s, c); } ungetch(); /* let client read whether delimiter, '\n' or whatever */ } String* collecttoken(char* end) { String* s = newstring(0); int c; while ((c = nextc()) == ' ' || c == '\t') Straddc(s, getch()); /* blanks significant for getname() */ while ((c = getch()) > 0 && utfrune(end, c) == 0) Straddc(s, c); if (c != '\n') atnl(); return s; } String* collecttext(void) { String* s; int begline, i, c, delim; s = newstring(0); if (cmdskipbl() == '\n') { getch(); i = 0; do { begline = i; while ((c = getch()) > 0 && c != '\n') i++, Straddc(s, c); i++, Straddc(s, '\n'); if (c < 0) goto Return; } while (s->r[begline] != '.' || s->r[begline + 1] != '\n'); s->r[s->n - 2] = '\0'; s->n -= 2; } else { okdelim(delim = getch()); getrhs(s, delim, 'a'); if (nextc() == delim) getch(); atnl(); } Return: return s; } int cmdlookup(int c) { int i; for (i = 0; cmdtab[i].cmdc; i++) if (cmdtab[i].cmdc == c) return i; return -1; } Cmd* parsecmd(int nest) { int i, c; struct cmdtab* ct; Cmd *cp, *ncp; Cmd cmd; cmd.next = cmd.u.cmd = 0; cmd.re = 0; cmd.flag = cmd.num = 0; cmd.addr = compoundaddr(); if (cmdskipbl() == -1) return 0; if ((c = getch()) == -1) return 0; cmd.cmdc = c; if (cmd.cmdc == 'c' && nextc() == 'd') { /* sleazy two-character case */ getch(); /* the 'd' */ cmd.cmdc = 'c' | 0x100; } i = cmdlookup(cmd.cmdc); if (i >= 0) { if (cmd.cmdc == '\n') goto Return; /* let nl_cmd work it all out */ ct = &cmdtab[i]; if (ct->defaddr == aNo && cmd.addr) editerror("command takes no address"); if (ct->count) cmd.num = getnum(ct->count); if (ct->regexp) { /* x without pattern -> .*\n, indicated by cmd.re==0 */ /* X without pattern is all files */ if ( (ct->cmdc != 'x' && ct->cmdc != 'X') || ((c = nextc()) != ' ' && c != '\t' && c != '\n')) { cmdskipbl(); if ((c = getch()) == '\n' || c < 0) editerror("no address"); okdelim(c); cmd.re = getregexp(c); if (ct->cmdc == 's') { cmd.u.text = newstring(0); getrhs(cmd.u.text, c, 's'); if (nextc() == c) { getch(); if (nextc() == 'g') cmd.flag = getch(); } } } } if (ct->addr && (cmd.u.mtaddr = simpleaddr()) == 0) editerror("bad address"); if (ct->defcmd) { if (cmdskipbl() == '\n') { getch(); cmd.u.cmd = newcmd(); cmd.u.cmd->cmdc = ct->defcmd; } else if ((cmd.u.cmd = parsecmd(nest)) == 0) error("defcmd"); } else if (ct->text) cmd.u.text = collecttext(); else if (ct->token) cmd.u.text = collecttoken(ct->token); else atnl(); } else switch (cmd.cmdc) { case '{': cp = 0; do { if (cmdskipbl() == '\n') getch(); ncp = parsecmd(nest + 1); if (cp) cp->next = ncp; else cmd.u.cmd = ncp; } while (cp = ncp); break; case '}': atnl(); if (nest == 0) editerror("right brace with no left brace"); return 0; default: editerror("unknown command %c", cmd.cmdc); } Return: cp = newcmd(); *cp = cmd; return cp; } String* getregexp(int delim) { String *buf, *r; int i, c; buf = allocstring(0); for (i = 0;; i++) { if ((c = getch()) == '\\') { if (nextc() == delim) c = getch(); else if (nextc() == '\\') { Straddc(buf, c); c = getch(); } } else if (c == delim || c == '\n') break; if (i >= RBUFSIZE) editerror("regular expression too long"); Straddc(buf, c); } if (c != delim && c) ungetch(); if (buf->n > 0) { patset = TRUE; freestring(lastpat); lastpat = buf; } else freestring(buf); if (lastpat->n == 0) editerror("no regular expression defined"); r = newstring(lastpat->n); runemove(r->r, lastpat->r, lastpat->n); /* newstring put \0 at end */ return r; } Addr* simpleaddr(void) { Addr addr; Addr *ap, *nap; addr.num = 0; addr.next = 0; addr.u.left = 0; switch (cmdskipbl()) { case '#': addr.type = getch(); addr.num = getnum(1); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': addr.num = getnum(1); addr.type = 'l'; break; case '/': case '?': case '"': addr.u.re = getregexp(addr.type = getch()); break; case '.': case '$': case '+': case '-': case '\'': addr.type = getch(); break; default: return 0; } if (addr.next = simpleaddr()) switch (addr.next->type) { case '.': case '$': case '\'': if (addr.type != '"') case '"': editerror("bad address syntax"); break; case 'l': case '#': if (addr.type == '"') break; /* fall through */ case '/': case '?': if (addr.type != '+' && addr.type != '-') { /* insert the missing '+' */ nap = newaddr(); nap->type = '+'; nap->next = addr.next; addr.next = nap; } break; case '+': case '-': break; default: error("simpleaddr"); } ap = newaddr(); *ap = addr; return ap; } Addr* compoundaddr(void) { Addr addr; Addr *ap, *next; addr.u.left = simpleaddr(); if ((addr.type = cmdskipbl()) != ',' && addr.type != ';') return addr.u.left; getch(); next = addr.next = compoundaddr(); if (next && (next->type == ',' || next->type == ';') && next->u.left == 0) editerror("bad address syntax"); ap = newaddr(); *ap = addr; return ap; }