# Sam language taken from acme. # This file was /appl/acme/ecmd.b, changed for o/x. # # Sam commands are implemented here. # The main loop at edit.b, after parsing, calls # cmdexec() to do the actual work. # implement Samcmd; include "mods.m"; elogterm, eloginsert, elogreplace, elogdelete, elogapply: import samlog; Addr, Address, BUFSIZE, Cmd: import Sam; aNo, aDot, aAll, editerror, cmdlookup, cmdtab, warnc, C_nl, C_a, C_b, C_c, C_d, C_B, C_D, C_e, C_f, C_g, C_i, C_k, C_m, C_n, C_p, C_s, C_u, C_w, C_x, C_X, C_pipe, C_eq: import sam; NRange, Range, Rangeset: import Regx; rxcompile, rxexecute, rxbexecute: import regx; Edit, Tree, Etext, seled, trees, Esel: import oxedit; Xcmd, newedit, deledit, putedit, pipein, pipeout, findedit: import oxex; none: Address; init(d: Oxdat) { initmods(d->mods); none.r.q0 = none.r.q1 = 0; none.f = nil; } skipbl(r : string) : string { for(i := 0; i < len r && (r[i]==' ' || r[i]=='\t' || r[i]=='\n'); i++) ; if (i == len r) return ""; else return r[i:]; } # executes a sam command (compound or not). # builds the address and applies the command to it. cmdexec(t: ref Edit, cp: ref Cmd): int { i: int; ap: ref Addr; f: ref Edit; dot: Address; w: ref Panel; if(t == nil) w = nil; else w = t.body; if(w==nil && (cp.addr==nil || cp.addr.typex!='"') && strchr("bBnqUXY!", cp.cmdc) < 0&& !(cp.cmdc=='D' && cp.text!=nil)) editerror("no current panel"); i = cmdlookup(cp.cmdc); # will be -1 for '{' f = nil; if(t!=nil && t.body!=nil){ f = t; f.getedits(); } if(i>=0 && cmdtab[i].defaddr != aNo){ if((ap=cp.addr)==nil && cp.cmdc!='\n'){ cp.addr = ap = ref Addr; ap.typex = '.'; if(cmdtab[i].defaddr == aAll) ap.typex = '*'; }else if(ap!=nil && ap.typex=='"' && ap.next==nil && cp.cmdc!='\n'){ ap.next = ref Addr; ap.next.typex = '.'; if(cmdtab[i].defaddr == aAll) ap.next.typex = '*'; } if(cp.addr!=nil){ # may be false for '\n' (only) if(f!=nil){ dot = mkaddr(f); addr = cmdaddress(ap, dot, 0); }else # a " addr = cmdaddress(ap, none, 0); f = addr.f; f.getedits(); } } case(cp.cmdc){ '{' => dot = mkaddr(f); if(cp.addr != nil) dot = cmdaddress(cp.addr, dot, 0); for(cp = cp.cmd; cp!=nil; cp = cp.next){ f.q0 = dot.r.q0; f.q1 = dot.r.q1; cmdexec(f, cp); } break; * => if(i < 0) editerror(sprint("unknown command %c in cmdexec", cp.cmdc)); i = cmdtabexec(i, f, cp); return i; } return 1; } # Executes a command given its cmdtab index cmdtabexec(i: int, t: ref Edit, cp: ref Cmd): int { case (cmdtab[i].fnc){ C_nl => i = nl_cmd(t, cp); C_a => i = a_cmd(t, cp); C_c => i = c_cmd(t, cp); C_d => i = d_cmd(t, cp); C_g => i = g_cmd(t, cp); C_i => i = i_cmd(t, cp); C_m => i = m_cmd(t, cp); C_p => i = p_cmd(t, cp); C_s => i = s_cmd(t, cp); C_w => i = w_cmd(t, cp); C_x => i = x_cmd(t, cp); C_eq => i = eq_cmd(t, cp); C_B => i = B_cmd(t, cp); C_D => i = D_cmd(t, cp); C_X => i = X_cmd(t, cp); C_pipe => i = pipe_cmd(t, cp); * => error("bad case in cmdtabexec"); } return i; } Glooping: int; nest: int; Enoname := "no file name given"; addr: Address; menu: ref Edit; sel: Rangeset; collection: string; resetxec() { Glooping = nest = 0; collection = nil; } mkaddr(f: ref Edit): Address { a: Address; a.r.q0 = f.q0; a.r.q1 = f.q1; a.f = f; return a; } edittext(f: ref Edit, q: int, r: string): string { case(editing){ Inactive => return "permission denied"; Inserting => eloginsert(f, q, r); return nil; Collecting => collection += r; return nil; * => return "unknown state in edittext"; } } filelist(t: ref Edit, r: string): list of string { if(len r == 0) return nil; r = skipbl(r); l: list of string; if(len r == 0 || r[0] != '<') (nil, l) = tokenize(r, " \t\n"); else { # use < command to collect text collection = ""; runpipe(t, '<', r[1:], Collecting); (nil, l) = tokenize(collection, " \t\n"); } return l; } a_cmd(t: ref Edit, cp: ref Cmd): int { return append(t, cp, addr.r.q1); } B_cmd(t: ref Edit, cp: ref Cmd): int { tr := Tree.find(t.tid); l := filelist(t, cp.text); if(l == nil) editerror(Enoname); for(; l != nil; l = tl l) newedit(tr, hd l, 0, 0); collection = ""; return 1; } c_cmd(t: ref Edit, cp: ref Cmd): int { elogreplace(t, addr.r.q0, addr.r.q1, cp.text); return 1; } d_cmd(t: ref Edit, nil: ref Cmd): int { if(addr.r.q1 > addr.r.q0) elogdelete(t, addr.r.q0, addr.r.q1); return 1; } D_cmd(t: ref Edit, cp: ref Cmd): int { listx := filelist(t, cp.text); if(listx == nil){ deledit(t); return 1; } for(; listx != nil; listx = tl listx){ nm := names->rooted(hd listx, t.dir); nm = names->cleanname(nm); w := findedit(t, nm); deledit(w); } collection = ""; return 1; } readloader(f: ref Edit, q0: int, r: string): int { if(len r > 0) eloginsert(f, q0, r); return 0; } g_cmd(t: ref Edit, cp: ref Cmd): int { ok: int; if(t != addr.f){ error("internal error: g_cmd f!=addr.f\n"); return 0; } if(rxcompile(cp.re) == 0) editerror(sprint("bad regexp '%s' in g command",cp.re)); (ok, sel) = rxexecute(t.buf, nil, addr.r.q0, addr.r.q1); if(ok ^ cp.cmdc=='v'){ t.q0 = addr.r.q0; t.q1 = addr.r.q1; return cmdexec(t, cp.cmd); } return 1; } i_cmd(t: ref Edit, cp: ref Cmd): int { return append(t, cp, addr.r.q0); } copy(f: ref Edit, addr2: Address) { n := addr.r.q1 - addr.r.q0; s := f.buf.gets(addr.r.q0, n); eloginsert(addr2.f, addr2.r.q1, s); } move(f: ref Edit, addr2: Address) { if(addr.f!=addr2.f || addr.r.q1<=addr2.r.q0){ elogdelete(f, addr.r.q0, addr.r.q1); copy(f, addr2); }else if(addr.r.q0 >= addr2.r.q1){ copy(f, addr2); elogdelete(f, addr.r.q0, addr.r.q1); }else error("move overlaps itself"); } m_cmd(t: ref Edit, cp: ref Cmd): int { dot := mkaddr(t); addr2 := cmdaddress(cp.mtaddr, dot, 0); if(cp.cmdc == 'm') move(t, addr2); else copy(t, addr2); return 1; } p_cmd(t: ref Edit, nil: ref Cmd): int { return pdisplay(t); } s_cmd(t: ref Edit, cp: ref Cmd): int { n := cp.num; op:= -1; if(rxcompile(cp.re) == 0) editerror("bad regexp in s command"); nrp := 0; rp: array of Rangeset; rp = nil; delta := 0; didsub := 0; ok := 0; for(p1 := addr.r.q0; p1<=addr.r.q1; ){ (ok, sel) = rxexecute(t.buf, nil, p1, addr.r.q1); if(!ok) break; if(sel[0].q0 == sel[0].q1){ # empty match? if(sel[0].q0 == op){ p1++; continue; } p1 = sel[0].q1+1; }else p1 = sel[0].q1; op = sel[0].q1; if(--n>0) continue; nrp++; orp := rp; rp = array[nrp] of Rangeset; rp[0: ] = orp[0:nrp-1]; rp[nrp-1] = copysel(sel); orp = nil; } buf := ""; c: int; for(m:=0; mBUFSIZE){ editerror("replacement string too long"); return 0; } buf += t.buf.gets(sel[j].q0, sel[j].q1-sel[j].q0); }else buf[len buf] = c; }else if(c!='&') buf[len buf] = c; else{ if(sel[0].q1-sel[0].q0>BUFSIZE){ editerror("right hand side too long in substitution"); return 0; } buf += t.buf.gets(sel[0].q0, sel[0].q1-sel[0].q0); } elogreplace(t, sel[0].q0, sel[0].q1, buf); delta -= sel[0].q1-sel[0].q0; delta += len buf; didsub = 1; if(!cp.flag) break; } if(!didsub && nest==0) editerror("no substitution"); t.edited |= Esel; t.q0 = addr.r.q0; t.q1 = addr.r.q1+delta; return 1; } w_cmd(t: ref Edit, cp: ref Cmd): int { r := cmdname(t, cp.text); if(r == nil) editerror("no name specified for 'w' command"); putedit(t, r); return 1; } x_cmd(t: ref Edit, cp: ref Cmd): int { if(cp.re!=nil) looper(t, cp, cp.cmdc=='x'); else linelooper(t, cp); return 1; } X_cmd(nil: ref Edit, cp: ref Cmd): int { filelooper(cp, cp.cmdc=='X'); return 1; } runpipe(t: ref Edit, cmd: int, cr: string, state: int) { r := skipbl(cr); if(len r == 0) editerror("no command specified for >"); if(state == Inserting){ t.q0 = addr.r.q0; t.q1 = addr.r.q1; if(cmd == '<' || cmd=='|') elogdelete(t, t.q0, t.q1); } dir := "."; if(t != nil) dir = t.dir; if(dir == ".") dir = nil; editing = state; spawn run(t, cmd, cr, dir); editing = Inactive; } run(t: ref Edit, cmd: int, cr: string, dir: string) { ifd, ofd: ref FD; ifd = ofd = nil; oc: chan of string; oc = nil; if (cmd == '<' || cmd == '|'){ s := t.buf.gets(t.q0, t.q1-t.q0); ifd = pipein(s); } if (cmd== '>' || cmd == '|') (ofd, oc) = pipeout(); Xcmd.new(cr, dir, ifd, ofd, t.tid); ifd = ofd = nil; if (oc != nil){ out := <-oc; if (len out > 0) eloginsert(t, t.q0, out); } t.edited |= Etext|Esel; } pipe_cmd(t: ref Edit, cp: ref Cmd): int { runpipe(t, cp.cmdc, cp.text, Inserting); return 1; } nlcount(t: ref Edit, q0: int, q1: int): int { nl := 0; l := t.buf.blen(); for (i := q0; i < q1 && i < l; i++) if (t.buf.getc(i) == '\n') nl++; return nl; } printposn(t: ref Edit, charsonly: int) { s := t.path + ": "; if(!charsonly){ l1 := 1+nlcount(t, 0, addr.r.q0); l2 := l1+nlcount(t, addr.r.q0, addr.r.q1); # check if addr ends with '\n' if(addr.r.q1>0 && addr.r.q1>addr.r.q0 && t.buf.getc(addr.r.q1-1)=='\n') --l2; s += sprint("%ud", l1); if(l2 != l1) s += sprint(",%ud", l2); warnc <-= s + "\n"; return; } s += sprint("#%d", addr.r.q0); if(addr.r.q1 != addr.r.q0) s += sprint(",#%d", addr.r.q1); warnc <-= s + "\n"; } eq_cmd(t: ref Edit, cp: ref Cmd): int { charsonly := 0; case(len cp.text){ 0 => break; 1 => if(cp.text[0] == '#'){ charsonly = 1; break; } * => charsonly = 1; editerror("newline expected"); } printposn(t, charsonly); return 1; } nl_cmd(t: ref Edit, cp: ref Cmd): int { if(cp.addr == nil){ # First put it on newline boundaries a := mkaddr(t); addr = lineaddr(0, a, -1); a = lineaddr(0, a, 1); addr.r.q1 = a.r.q1; if(addr.r.q0==t.q0 && addr.r.q1==t.q1){ a = mkaddr(t); addr = lineaddr(1, a, 1); } } t.q0 = addr.r.q0; t.q1 = addr.r.q1; t.edited |= Esel; return 1; } append(f: ref Edit, cp: ref Cmd, p: int): int { if(len cp.text > 0) eloginsert(f, p, cp.text); return 1; } pdisplay(f: ref Edit): int { p1 := addr.r.q0; p2 := addr.r.q1; if(p2 > f.buf.blen()) p2 = f.buf.blen(); if(p1 < p2) warnc <-= f.buf.gets(p1, p2 - p1); f.q0 = addr.r.q0; f.q1 = addr.r.q1; f.edited |= Esel; return 1; } filename(f: ref Edit): string { dirt := cur := ' '; if (f.dirty) dirt = '\''; if (seled == f) cur = '.'; return sprint("%c+%c %s\n", dirt, cur, f.path); } loopcmd(f: ref Edit, cp: ref Cmd, rp: array of Range) { for(i:=0; ir.q1) break; tr.q0 = op; tr.q1 = r.q1; p = r.q1+1; # exit next loop }else{ if(sel[0].q0==sel[0].q1){ # empty match? if(sel[0].q0==op){ p++; continue; } p = sel[0].q1+1; }else p = sel[0].q1; if(xy) tr = sel[0]; else{ tr.q0 = op; tr.q1 = sel[0].q0; } } op = sel[0].q1; nrp++; orp := rp; rp = array[nrp] of Range; rp[0: ] = orp[0: nrp-1]; rp[nrp-1] = tr; orp = nil; } loopcmd(f, cp.cmd, rp); rp = nil; --nest; } linelooper(f: ref Edit, cp: ref Cmd) { nrp, p: int; r, linesel: Range; a, a3: Address; rp: array of Range; nest++; nrp = 0; rp = nil; r = addr.r; a3.f = f; a3.r.q0 = a3.r.q1 = r.q0; a = lineaddr(0, a3, 1); linesel = a.r; for(p = r.q0; p= r.q1) break; if(linesel.q1 >= r.q1) linesel.q1 = r.q1; if(linesel.q1 > linesel.q0) if(linesel.q0>=a3.r.q1 && linesel.q1>a3.r.q1){ a3.r = linesel; nrp++; orp := rp; rp = array[nrp] of Range; rp[0: ] = orp[0: nrp-1]; rp[nrp-1] = linesel; orp = nil; continue; } break; } loopcmd(f, cp.cmd, rp); rp = nil; --nest; } matchfiles(re: string): list of ref Edit { edl: list of ref Edit; edl = nil; for(trl := trees; trl != nil; trl = tl trl) for (eds := (hd trl).eds; eds != nil; eds = tl eds) if (filematch(hd eds, re)) edl = hd eds::edl; return edl; } filelooper(cp: ref Cmd, XY: int) { if(Glooping++) editerror(sprint("can't nest %c command", "YX"[XY])); nest++; for(edl := matchfiles(cp.re); edl != nil; edl = tl edl) cmdexec(hd edl, cp.cmd); --Glooping; --nest; } nextmatch(f: ref Edit, r: string, p: int, sign: int) { ok: int; if(rxcompile(r) == 0) editerror("bad regexp in command address"); if(sign >= 0){ (ok, sel) = rxexecute(f.buf, nil, p, 16r7FFFFFFF); if(!ok) editerror("no match for regexp"); if(sel[0].q0==sel[0].q1 && sel[0].q0==p){ if(++p>f.buf.blen()) p = 0; (ok, sel) = rxexecute(f.buf, nil, p, 16r7FFFFFFF); if(!ok) editerror("address"); } }else{ (ok, sel) = rxbexecute(f.buf, p); if(!ok) editerror("no match for regexp"); if(sel[0].q0==sel[0].q1 && sel[0].q1==p){ if(--p<0) p = f.buf.blen(); (ok, sel) = rxbexecute(f.buf, p); if(!ok) editerror("address"); } } } cmdaddress(ap: ref Addr, a: Address, sign: int): Address { f := a.f; a1, a2: Address; do{ case(ap.typex){ 'l' or '#' => if(ap.typex == '#') a = charaddr(ap.num, a, sign); else a = lineaddr(ap.num, a, sign); break; '.' => a = mkaddr(f); break; '$' => a.r.q0 = a.r.q1 = f.buf.blen(); break; '\'' => editerror("can't handle '"); # a.r = f.mark; break; '?' => sign = -sign; if(sign == 0) sign = -1; if(sign >= 0) v := a.r.q1; else v = a.r.q0; nextmatch(f, ap.re, v, sign); a.r = sel[0]; break; '/' => if(sign >= 0) v := a.r.q1; else v = a.r.q0; nextmatch(f, ap.re, v, sign); a.r = sel[0]; break; '"' => eds := matchfiles(ap.re); if (eds == nil) editerror("no file matches"); f = hd eds; a = mkaddr(f); break; '*' => a.r.q0 = 0; a.r.q1 = f.buf.blen(); return a; ',' or ';' => if(ap.left!=nil) a1 = cmdaddress(ap.left, a, 0); else{ a1.f = a.f; a1.r.q0 = a1.r.q1 = 0; } if(ap.typex == ';'){ f = a1.f; a = a1; f.q0 = a1.r.q0; f.q1 = a1.r.q1; } if(ap.next!=nil) a2 = cmdaddress(ap.next, a, 0); else{ a2.f = a.f; a2.r.q0 = a2.r.q1 = f.buf.blen(); } if(a1.f != a2.f) editerror("addresses in different files"); a.f = a1.f; a.r.q0 = a1.r.q0; a.r.q1 = a2.r.q1; if(a.r.q1 < a.r.q0) editerror("addresses out of order"); return a; '+' or '-' => sign = 1; if(ap.typex == '-') sign = -1; if(ap.next==nil || ap.next.typex=='+' || ap.next.typex=='-') a = lineaddr(1, a, sign); break; * => error("cmdaddress"); return a; } }while((ap = ap.next)!=nil); # assign = return a; } filematch(f: ref Edit, r: string): int { if(rxcompile(r) == 0) editerror("bad regexp in file match"); buf := filename(f); (match, nil) := rxexecute(nil, buf, 0, len buf); return match; } charaddr(l: int, addr: Address, sign: int): Address { if(sign == 0) addr.r.q0 = addr.r.q1 = l; else if(sign < 0) addr.r.q1 = addr.r.q0 -= l; else if(sign > 0) addr.r.q0 = addr.r.q1 += l; if(addr.r.q0<0 || addr.r.q1>addr.f.buf.blen()) editerror("address out of range"); return addr; } lineaddr(l: int, addr: Address, sign: int): Address { n: int; c: int; f := addr.f; a: Address; p: int; a.f = f; if(sign >= 0){ bl := f.buf.blen(); if(l == 0){ if(sign==0 || addr.r.q1==0){ a.r.q0 = a.r.q1 = 0; return a; } a.r.q0 = addr.r.q1; p = addr.r.q1-1; }else{ if(sign==0 || addr.r.q1==0){ p = 0; n = 1; }else{ p = addr.r.q1-1; n = f.buf.getc(p++)=='\n'; } while(n < l){ if(p >= bl) editerror("address out of range"); if(f.buf.getc(p++) == '\n') n++; } a.r.q0 = p; } while(p < bl && f.buf.getc(p++)!='\n') ; a.r.q1 = p; }else{ p = addr.r.q0; if(l == 0) a.r.q1 = addr.r.q0; else{ for(n = 0; n 0) p--; } while(p > 0 && f.buf.getc(p-1)!='\n') # lines start after a newline p--; a.r.q0 = p; } return a; } cmdname(f: ref Edit, str: string): string { r := string nil; if(len str == 0){ # no name; use existing return f.path; } str = skipbl(str); if(len str > 0){ if(str[0] == '/') r = str; else r = f.dir; } return r; } copysel(rs: Rangeset): Rangeset { nrs := array[NRange] of Range; for(i := 0; i < NRange; i++) nrs[i] = rs[i]; return nrs; }