ref: 147d51bf7c6cd8798ddb26e33d857be13a66c548
dir: /appl/acme/regx.b/
implement Regx; include "common.m"; sys : Sys; utils : Utils; textm : Textm; FALSE, TRUE, XXX : import Dat; NRange : import Dat; Range, Rangeset : import Dat; error, warning, tgetc, rgetc : import utils; Text : import textm; init(mods : ref Dat->Mods) { sys = mods.sys; utils = mods.utils; textm = mods.textm; } None : con 0; Fore : con '+'; Back : con '-'; Char : con 0; Line : con 1; isaddrc(r : int) : int { if (utils->strchr("0123456789+-/$.#", r) >= 0) return TRUE; return FALSE; } # # quite hard: could be almost anything but white space, but we are a little conservative, # aiming for regular expressions of alphanumerics and no white space # isregexc(r : int) : int { if(r == 0) return FALSE; if(utils->isalnum(r)) return TRUE; if(utils->strchr("^+-.*?#,;[]()$", r)>=0) return TRUE; return FALSE; } number(md: ref Dat->Mntdir, t : ref Text, r : Range, line : int, dir : int, size : int) : (int, Range) { q0, q1 : int; { if(size == Char){ if(dir == Fore) line = r.q1+line; # was t.file.buf.nc+line; else if(dir == Back){ if(r.q0==0 && line > 0) r.q0 = t.file.buf.nc; line = r.q0-line; # was t.file.buf.nc - line; } if(line<0 || line>t.file.buf.nc) raise "e"; return (TRUE, (line, line)); } (q0, q1) = r; case(dir){ None => q0 = 0; q1 = 0; while(line>0 && q1<t.file.buf.nc) if(t.readc(q1++) == '\n') if(--line > 0) q0 = q1; if(line==1 && t.readc(q1-1)!='\n') # no newline at end - count it ; else if(line > 0) raise "e"; Fore => if(q1 > 0) while(t.readc(q1-1) != '\n') q1++; q0 = q1; while(line>0 && q1<t.file.buf.nc) if(t.readc(q1++) == '\n') if(--line > 0) q0 = q1; if(line > 0) raise "e"; Back => if(q0 < t.file.buf.nc) while(q0>0 && t.readc(q0-1)!='\n') q0--; q1 = q0; while(line>0 && q0>0){ if(t.readc(q0-1) == '\n'){ if(--line >= 0) q1 = q0; } --q0; } if(line > 0) raise "e"; while(q0>0 && t.readc(q0-1)!='\n') --q0; } return (TRUE, (q0, q1)); } exception{ * => if(md != nil) warning(nil, "address out of range\n"); return (FALSE, r); } return (FALSE, r); } regexp(md: ref Dat->Mntdir, t : ref Text, lim : Range, r : Range, pat : string, dir : int) : (int, Range) { found : int; sel : Rangeset; q : int; if(pat == nil && rxnull()){ warning(md, "no previous regular expression"); return (FALSE, r); } if(pat == nil || !rxcompile(pat)) return (FALSE, r); if(dir == Back) (found, sel) = rxbexecute(t, r.q0); else{ if(lim.q0 < 0) q = Dat->Infinity; else q = lim.q1; (found, sel) = rxexecute(t, nil, r.q1, q); } if(!found && md == nil) warning(nil, "no match for regexp\n"); return (found, sel[0]); } xgetc(a0 : ref Text, a1 : string, n : int) : int { if (a0 == nil) return rgetc(a1, n); return tgetc(a0, n); } address(md: ref Dat->Mntdir, t : ref Text, lim : Range, ar : Range, a0 : ref Text, a1 : string, q0 : int, q1 : int, eval : int) : (int, int, Range) { dir, size : int; prevc, c, n : int; q : int; pat : string; r, nr : Range; r = ar; q = q0; dir = None; size = Line; c = 0; while(q < q1){ prevc = c; c = xgetc(a0, a1, q++); case(c){ ';' => ar = r; if(prevc == 0) # lhs defaults to 0 r.q0 = 0; if(q>=q1 && t!=nil && t.file!=nil) # rhs defaults to $ r.q1 = t.file.buf.nc; else{ (eval, q, nr) = address(md, t, lim, ar, a0, a1, q, q1, eval); r.q1 = nr.q1; } return (eval, q, r); ',' => if(prevc == 0) # lhs defaults to 0 r.q0 = 0; if(q>=q1 && t!=nil && t.file!=nil) # rhs defaults to $ r.q1 = t.file.buf.nc; else{ (eval, q, nr) = address(md, t, lim, ar, a0, a1, q, q1, eval); r.q1 = nr.q1; } return (eval, q, r); '+' or '-' => if(eval && (prevc=='+' || prevc=='-')){ if((nc := xgetc(a0, a1, q)) != '#' && nc != '/' && nc != '?') (eval, r) = number(md, t, r, 1, prevc, Line); # do previous one } dir = c; '.' or '$' => if(q != q0+1) return (eval, q-1, r); if(eval) if(c == '.') r = ar; else r = (t.file.buf.nc, t.file.buf.nc); if(q < q1) dir = Fore; else dir = None; '#' => if(q==q1 || (c=xgetc(a0, a1, q++))<'0' || '9'<c) return (eval, q-1, r); size = Char; n = c -'0'; while(q<q1){ c = xgetc(a0, a1, q++); if(c<'0' || '9'<c){ q--; break; } n = n*10+(c-'0'); } if(eval) (eval, r) = number(md, t, r, n, dir, size); dir = None; size = Line; '0' to '9' => n = c -'0'; while(q<q1){ c = xgetc(a0, a1, q++); if(c<'0' || '9'<c){ q--; break; } n = n*10+(c-'0'); } if(eval) (eval, r) = number(md, t, r, n, dir, size); dir = None; size = Line; '/' => pat = nil; break2 := 0; # Ow ! while(q<q1){ c = xgetc(a0, a1, q++); case(c){ '\n' => --q; break2 = 1; '\\' => pat[len pat] = c; if(q == q1) break2 = 1; else c = xgetc(a0, a1, q++); '/' => break2 = 1; } if (break2) break; pat[len pat] = c; } if(eval) (eval, r) = regexp(md, t, lim, r, pat, dir); pat = nil; dir = None; size = Line; * => return (eval, q-1, r); } } if(eval && dir != None) (eval, r) = number(md, t, r, 1, dir, Line); # do previous one return (eval, q, r); } sel : Rangeset = array[NRange] of Range; lastregexp : string; # Machine Information Inst : adt { typex : int; # < 16r10000 ==> literal, otherwise action # sid : int; subid : int; class : int; # other : cyclic ref Inst; right : cyclic ref Inst; # left : cyclic ref Inst; next : cyclic ref Inst; }; NPROG : con 1024; program := array[NPROG] of ref Inst; progp : int; startinst : ref Inst; # First inst. of program; might not be program[0] bstartinst : ref Inst; # same for backwards machine Ilist : adt { inst : ref Inst; # Instruction of the thread se : Rangeset; startp : int; # first char of match }; NLIST : con 128; thl, nl : array of Ilist; # This list, next list listx := array[2] of array of Ilist; sempty : Rangeset = array[NRange] of Range; # # Actions and Tokens # # 0x100xx are operators, value == precedence # 0x200xx are tokens, i.e. operands for operators # OPERATOR : con 16r10000; # Bitmask of all operators START : con 16r10000; # Start, used for marker on stack RBRA : con 16r10001; # Right bracket, ) LBRA : con 16r10002; # Left bracket, ( OR : con 16r10003; # Alternation, | CAT : con 16r10004; # Concatentation, implicit operator STAR : con 16r10005; # Closure, * PLUS : con 16r10006; # a+ == aa* QUEST : con 16r10007; # a? == a|nothing, i.e. 0 or 1 a's ANY : con 16r20000; # Any character but newline, . NOP : con 16r20001; # No operation, internal use only BOL : con 16r20002; # Beginning of line, ^ EOL : con 16r20003; # End of line, $ CCLASS : con 16r20004; # Character class, [] NCCLASS : con 16r20005; # Negated character class, [^] END : con 16r20077; # Terminate: match found ISATOR : con 16r10000; ISAND : con 16r20000; # Parser Information Node : adt { first : ref Inst; last : ref Inst; }; NSTACK : con 20; andstack := array[NSTACK] of ref Node; andp : int; atorstack := array[NSTACK] of int; atorp : int; lastwasand : int; # Last token was operand cursubid : int; subidstack := array[NSTACK] of int; subidp : int; backwards : int; nbra : int; exprs : string; exprp : int; # pointer to next character in source expression DCLASS : con 10; # allocation increment nclass : int; # number active Nclass : int = 0; # high water mark class : array of string; negateclass : int; nilnode : Node; nilinst : Inst; rxinit() { lastregexp = nil; for (k := 0; k < NPROG; k++) program[k] = ref nilinst; for (k = 0; k < NSTACK; k++) andstack[k] = ref nilnode; for (k = 0; k < 2; k++) { listx[k] = array[NLIST] of Ilist; for (i := 0; i < NLIST; i++) { listx[k][i].inst = nil; listx[k][i].startp = 0; listx[k][i].se = array[NRange] of Range; for (j := 0; j < NRange; j++) listx[k][i].se[j].q0 = listx[k][i].se[j].q1 = 0; } } } regerror(e : string) { lastregexp = nil; buf := sys->sprint("regexp: %s\n", e); warning(nil, buf); raise "regerror"; } newinst(t : int) : ref Inst { if(progp >= NPROG) regerror("expression too long"); program[progp].typex = t; program[progp].next = nil; # next was left program[progp].right = nil; return program[progp++]; } realcompile(s : string) : ref Inst { token : int; { startlex(s); atorp = 0; andp = 0; subidp = 0; cursubid = 0; lastwasand = FALSE; # Start with a low priority operator to prime parser pushator(START-1); while((token=lex()) != END){ if((token&ISATOR) == OPERATOR) operator(token); else operand(token); } # Close with a low priority operator evaluntil(START); # Force END operand(END); evaluntil(START); if(nbra) regerror("unmatched `('"); --andp; # points to first and only operand return andstack[andp].first; } exception{ "regerror" => return nil; } return nil; } rxcompile(r : string) : int { oprogp : int; if(lastregexp == r) return TRUE; lastregexp = nil; for (i := 0; i < nclass; i++) class[i] = nil; nclass = 0; progp = 0; backwards = FALSE; bstartinst = nil; startinst = realcompile(r); if(startinst == nil) return FALSE; optimize(0); oprogp = progp; backwards = TRUE; bstartinst = realcompile(r); if(bstartinst == nil) return FALSE; optimize(oprogp); lastregexp = r; return TRUE; } operand(t : int) { i : ref Inst; if(lastwasand) operator(CAT); # catenate is implicit i = newinst(t); if(t == CCLASS){ if(negateclass) i.typex = NCCLASS; # UGH i.class = nclass-1; # UGH } pushand(i, i); lastwasand = TRUE; } operator(t : int) { if(t==RBRA && --nbra<0) regerror("unmatched `)'"); if(t==LBRA){ cursubid++; # silently ignored nbra++; if(lastwasand) operator(CAT); }else evaluntil(t); if(t!=RBRA) pushator(t); lastwasand = FALSE; if(t==STAR || t==QUEST || t==PLUS || t==RBRA) lastwasand = TRUE; # these look like operands } pushand(f : ref Inst, l : ref Inst) { if(andp >= NSTACK) error("operand stack overflow"); andstack[andp].first = f; andstack[andp].last = l; andp++; } pushator(t : int) { if(atorp >= NSTACK) error("operator stack overflow"); atorstack[atorp++]=t; if(cursubid >= NRange) subidstack[subidp++]= -1; else subidstack[subidp++]=cursubid; } popand(op : int) : ref Node { if(andp <= 0) if(op){ buf := sys->sprint("missing operand for %c", op); regerror(buf); }else regerror("malformed regexp"); return andstack[--andp]; } popator() : int { if(atorp <= 0) error("operator stack underflow"); --subidp; return atorstack[--atorp]; } evaluntil(pri : int) { op1, op2 : ref Node; inst1, inst2 : ref Inst; while(pri==RBRA || atorstack[atorp-1]>=pri){ case(popator()){ LBRA => op1 = popand('('); inst2 = newinst(RBRA); inst2.subid = subidstack[subidp]; op1.last.next = inst2; inst1 = newinst(LBRA); inst1.subid = subidstack[subidp]; inst1.next = op1.first; pushand(inst1, inst2); return; # must have been RBRA OR => op2 = popand('|'); op1 = popand('|'); inst2 = newinst(NOP); op2.last.next = inst2; op1.last.next = inst2; inst1 = newinst(OR); inst1.right = op1.first; inst1.next = op2.first; # next was left pushand(inst1, inst2); CAT => op2 = popand(0); op1 = popand(0); if(backwards && op2.first.typex!=END) (op1, op2) = (op2, op1); op1.last.next = op2.first; pushand(op1.first, op2.last); STAR => op2 = popand('*'); inst1 = newinst(OR); op2.last.next = inst1; inst1.right = op2.first; pushand(inst1, inst1); PLUS => op2 = popand('+'); inst1 = newinst(OR); op2.last.next = inst1; inst1.right = op2.first; pushand(op2.first, inst1); QUEST => op2 = popand('?'); inst1 = newinst(OR); inst2 = newinst(NOP); inst1.next = inst2; # next was left inst1.right = op2.first; op2.last.next = inst2; pushand(inst1, inst2); * => error("unknown regexp operator"); } } } optimize(start : int) { inst : int; target : ref Inst; for(inst=start; program[inst].typex!=END; inst++){ target = program[inst].next; while(target.typex == NOP) target = target.next; program[inst].next = target; } } startlex(s : string) { exprs = s; exprp = 0; nbra = 0; } lex() : int { c : int; if (exprp == len exprs) return END; c = exprs[exprp++]; case(c){ '\\' => if(exprp < len exprs) if((c= exprs[exprp++])=='n') c='\n'; '*' => c = STAR; '?' => c = QUEST; '+' => c = PLUS; '|' => c = OR; '.' => c = ANY; '(' => c = LBRA; ')' => c = RBRA; '^' => c = BOL; '$' => c = EOL; '[' => c = CCLASS; bldcclass(); } return c; } nextrec() : int { if(exprp == len exprs || (exprp == len exprs-1 && exprs[exprp]=='\\')) regerror("malformed `[]'"); if(exprs[exprp] == '\\'){ exprp++; if(exprs[exprp]=='n'){ exprp++; return '\n'; } return exprs[exprp++] | 16r10000; } return exprs[exprp++]; } bldcclass() { c1, c2 : int; classp : string; # we have already seen the '[' if(exprp < len exprs && exprs[exprp] == '^'){ classp[len classp] = '\n'; # don't match newline in negate case negateclass = TRUE; exprp++; }else negateclass = FALSE; while((c1 = nextrec()) != ']'){ if(c1 == '-'){ classp = nil; regerror("malformed `[]'"); } if(exprp < len exprs && exprs[exprp] == '-'){ exprp++; # eat '-' if((c2 = nextrec()) == ']') { classp = nil; regerror("malformed '[]'"); } classp[len classp] = 16rFFFF; classp[len classp] = c1; classp[len classp] = c2; }else classp[len classp] = c1; } if(nclass == Nclass){ Nclass += DCLASS; oc := class; class = array[Nclass] of string; if (oc != nil) { class[0:] = oc[0:Nclass-DCLASS]; oc = nil; } } class[nclass++] = classp; } classmatch(classno : int, c : int, negate : int) : int { p : string; p = class[classno]; for (i := 0; i < len p; ) { if(p[i] == 16rFFFF){ if(p[i+1]<=c && c<=p[i+2]) return !negate; i += 3; }else if(p[i++] == c) return !negate; } return negate; } # # Note optimization in addinst: # *l must be pending when addinst called; if *l has been looked # at already, the optimization is a bug. # addinst(l : array of Ilist, inst : ref Inst, sep : Rangeset) { p : int; for(p = 0; l[p].inst != nil; p++){ if(l[p].inst==inst){ if(sep[0].q0 < l[p].se[0].q0) l[p].se[0:] = sep[0:NRange]; # this would be bug return; # It's already there } } l[p].inst = inst; l[p].se[0:]= sep[0:NRange]; l[p+1].inst = nil; } rxnull() : int { return startinst==nil || bstartinst==nil; } OVERFLOW : con "overflow"; # either t!=nil or r!=nil, and we match the string in the appropriate place rxexecute(t : ref Text, r: string, startp : int, eof : int) : (int, Rangeset) { flag : int; inst : ref Inst; tlp : int; p : int; nnl, ntl : int; nc, c : int; wrapped : int; startchar : int; flag = 0; p = startp; startchar = 0; wrapped = 0; nnl = 0; if(startinst.typex<OPERATOR) startchar = startinst.typex; listx[0][0].inst = listx[1][0].inst = nil; sel[0].q0 = -1; { if(t != nil) nc = t.file.buf.nc; else nc = len r; # Execute machine once for each character for(;;p++){ if(p>=eof || p>=nc){ case(wrapped++){ 0 or 2 => # let loop run one more click ; 1 => # expired; wrap to beginning if(sel[0].q0>=0 || eof!=Dat->Infinity) return (sel[0].q0>=0, sel); listx[0][0].inst = listx[1][0].inst = nil; p = -1; continue; * => return (sel[0].q0>=0, sel); } c = 0; }else{ if(((wrapped && p>=startp) || sel[0].q0>0) && nnl==0) break; if(t != nil) c = t.readc(p); else c = r[p]; } # fast check for first char if(startchar && nnl==0 && c!=startchar) continue; thl = listx[flag]; nl = listx[flag^=1]; nl[0].inst = nil; ntl = nnl; nnl = 0; if(sel[0].q0<0 && (!wrapped || p<startp || startp==eof)){ # Add first instruction to this list if(++ntl >= NLIST) raise OVERFLOW; sempty[0].q0 = p; addinst(thl, startinst, sempty); } # Execute machine until this list is empty tlp = 0; inst = thl[0].inst; while(inst != nil){ # assignment = case(inst.typex){ LBRA => if(inst.subid>=0) thl[tlp].se[inst.subid].q0 = p; inst = inst.next; continue; RBRA => if(inst.subid>=0) thl[tlp].se[inst.subid].q1 = p; inst = inst.next; continue; ANY => if(c!='\n') { if(++nnl >= NLIST) raise OVERFLOW; addinst(nl, inst.next, thl[tlp].se); } BOL => if(p==0 || (t != nil && t.readc(p-1)=='\n') || (r != nil && r[p-1] == '\n')){ inst = inst.next; continue; } EOL => if(c == '\n') { inst = inst.next; continue; } CCLASS => if(c>=0 && classmatch(inst.class, c, 0)) { if(++nnl >= NLIST) raise OVERFLOW; addinst(nl, inst.next, thl[tlp].se); } NCCLASS => if(c>=0 && classmatch(inst.class, c, 1)) { if(++nnl >= NLIST) raise OVERFLOW; addinst(nl, inst.next, thl[tlp].se); } OR => # evaluate right choice later if(++ntl >= NLIST) raise OVERFLOW; addinst(thl[tlp:], inst.right, thl[tlp].se); # efficiency: advance and re-evaluate inst = inst.next; # next was left continue; END => # Match! thl[tlp].se[0].q1 = p; newmatch(thl[tlp].se); * => # regular character if(inst.typex==c){ if(++nnl >= NLIST) raise OVERFLOW; addinst(nl, inst.next, thl[tlp].se); } } tlp++; inst = thl[tlp].inst; } } return (sel[0].q0>=0, sel); } exception{ OVERFLOW => error("regexp list overflow"); sel[0].q0 = -1; return (0, sel); } return (0, sel); } newmatch(sp : Rangeset) { if(sel[0].q0<0 || sp[0].q0<sel[0].q0 || (sp[0].q0==sel[0].q0 && sp[0].q1>sel[0].q1)) sel[0:] = sp[0:NRange]; } rxbexecute(t : ref Text, startp : int) : (int, Rangeset) { flag : int; inst : ref Inst; tlp : int; p : int; nnl, ntl : int; c : int; wrapped : int; startchar : int; flag = 0; nnl = 0; wrapped = 0; p = startp; startchar = 0; if(bstartinst.typex<OPERATOR) startchar = bstartinst.typex; listx[0][0].inst = listx[1][0].inst = nil; sel[0].q0= -1; { # Execute machine once for each character, including terminal NUL for(;;--p){ if(p <= 0){ case(wrapped++){ 0 or 2 => # let loop run one more click ; 1 => # expired; wrap to end if(sel[0].q0>=0) return (sel[0].q0>=0, sel); listx[0][0].inst = listx[1][0].inst = nil; p = t.file.buf.nc+1; continue; 3 or * => return (sel[0].q0>=0, sel); } c = 0; }else{ if(((wrapped && p<=startp) || sel[0].q0>0) && nnl==0) break; c = t.readc(p-1); } # fast check for first char if(startchar && nnl==0 && c!=startchar) continue; thl = listx[flag]; nl = listx[flag^=1]; nl[0].inst = nil; ntl = nnl; nnl = 0; if(sel[0].q0<0 && (!wrapped || p>startp)){ # Add first instruction to this list if(++ntl >= NLIST) raise OVERFLOW; # the minus is so the optimizations in addinst work sempty[0].q0 = -p; addinst(thl, bstartinst, sempty); } # Execute machine until this list is empty tlp = 0; inst = thl[0].inst; while(inst != nil){ # assignment = case(inst.typex){ LBRA => if(inst.subid>=0) thl[tlp].se[inst.subid].q0 = p; inst = inst.next; continue; RBRA => if(inst.subid >= 0) thl[tlp].se[inst.subid].q1 = p; inst = inst.next; continue; ANY => if(c != '\n') { if(++nnl >= NLIST) raise OVERFLOW; addinst(nl, inst.next, thl[tlp].se); } BOL => if(c=='\n' || p==0){ inst = inst.next; continue; } EOL => if(p<t.file.buf.nc && t.readc(p)=='\n') { inst = inst.next; continue; } CCLASS => if(c>0 && classmatch(inst.class, c, 0)) { if(++nnl >= NLIST) raise OVERFLOW; addinst(nl, inst.next, thl[tlp].se); } NCCLASS => if(c>0 && classmatch(inst.class, c, 1)) { if(++nnl >= NLIST) raise OVERFLOW; addinst(nl, inst.next, thl[tlp].se); } OR => # evaluate right choice later if(++ntl >= NLIST) raise OVERFLOW; addinst(thl[tlp:], inst.right, thl[tlp].se); # efficiency: advance and re-evaluate inst = inst.next; # next was left continue; END => # Match! thl[tlp].se[0].q0 = -thl[tlp].se[0].q0; # minus sign thl[tlp].se[0].q1 = p; bnewmatch(thl[tlp].se); * => # regular character if(inst.typex == c){ if(++nnl >= NLIST) raise OVERFLOW; addinst(nl, inst.next, thl[tlp].se); } } tlp++; inst = thl[tlp].inst; } } return (sel[0].q0>=0, sel); } exception{ OVERFLOW => error("regexp list overflow"); sel[0].q0 = -1; return (0, sel); } return (0, sel); } bnewmatch(sp : Rangeset) { i : int; if(sel[0].q0<0 || sp[0].q0>sel[0].q1 || (sp[0].q0==sel[0].q1 && sp[0].q1<sel[0].q0)) for(i = 0; i<NRange; i++){ # note the reversal; q0<=q1 sel[i].q0 = sp[i].q1; sel[i].q1 = sp[i].q0; } }