strhas(s: string, c: int): ref Val{ for(i := 0; i < len s; i++) if(s[i] == c) return true; return false;} rsplit(r: string): (string, string){ esc := 0; i := 1; # skip '/' for(;;){ c := r[i++]; if(!esc && c == '/') break; esc = !esc && c == '\\'; } return (r[1: i-1], r[i: ]);} badflags(f: string): int{ g := i := m := 0; for(j := 0; j < len f; j++){ case(f[j]){ 'g' => g++; 'i' => i++; 'm' => m++; * => return 1; } } return g > 1 || i > 1 || m > 1;} regexpvals(ex: ref Exec, v: ref Val, o: ref Ecmascript->Obj): (string, string, int){ if(v != nil){ if(v.ty == TRegExp) return (v.rev.p, v.rev.f, v.rev.i); o = v.obj; } p := toString(ex, esget(ex, o, "source", 0)); f := ""; if(toBoolean(ex, esget(ex, o, "global", 0)) == true) f += "g"; if(toBoolean(ex, esget(ex, o, "ignoreCase", 0)) == true) f += "i"; if(toBoolean(ex, esget(ex, o, "multiline", 0)) == true) f += "m"; i := toInt32(ex, esget(ex, o, "lastIndex", 0)); return (p, f, i);} nregexp(ex: ref Exec, nil: ref Ecmascript->Obj, args: array of ref Val): ref Ecmascript->Obj{ pat := biarg(args, 0); flags := biarg(args, 1); (p, f) := ("", ""); if(isregexp(pat)){ if(flags == undefined) (p, f, nil) = regexpvals(ex, pat, nil); else runtime(ex, TypeError, "flags defined"); } else{ if(pat == undefined) p = ""; else p = toString(ex, pat); if(flags == undefined) f = ""; else f = toString(ex, flags); } o := nobj(ex, nil, array[] of { regexpval(p, f, 0) }); if(badflags(f)) runtime(ex, SyntaxError, "bad regexp flags"); regex = ex; (re, err) := compile(p, 1); if(re == nil || err != nil) runtime(ex, SyntaxError, "bad regexp pattern"); o.re = re; return o;} cregexp(ex: ref Exec, f, nil: ref Ecmascript->Obj, args: array of ref Val): ref Val{ pat := biarg(args, 0); flags := biarg(args, 1); if(isregexp(pat) && flags == undefined) return pat; return objval(nregexp(ex, f, args));} cregexpprotoexec(ex: ref Exec, f, this: ref Ecmascript->Obj, args: array of ref Val): ref Val{ m: array of (int, int); regexpcheck(ex, this, f); s := toString(ex, biarg(args, 0)); l := len s; i := toInt32(ex, esget(ex, this, "lastIndex", 0)); e := 0; glob := esget(ex, this, "global", 0); multiline := esget(ex, this, "multiline", 0); ignorecase := esget(ex, this, "ignoreCase", 0); if(glob == false) i = 0; for(;;){ if(i < 0 || i >= l){ esput(ex, this, "lastIndex", numval(real 0), 0); return null; } regex = ex; m = executese(this.re, s, (i, len s), i == 0, 1, multiline == true, ignorecase == true); if(m != nil) break; i++; i = -1; # no need to loop with executese } (i, e) = m[0]; if(glob == true) esput(ex, this, "lastIndex", numval(real e), 0); n := len m; av := array[n] of ref Val; for(j := 0; j < n; j++){ (a, b) := m[j]; if(a < 0) av[j] = undefined; else av[j] = strval(s[a: b]); } a := narray(ex, nil, av); esput(ex, a, "index", numval(real i), 0); esput(ex, a, "input", strval(s), 0); return objval(a);} cregexpprototest(ex: ref Exec, f, this: ref Ecmascript->Obj, args: array of ref Val): ref Val{ regexpcheck(ex, this, f); v := cregexpprotoexec(ex, f, this, args); if(!isnull(v)) return true; return false;} cregexpprototoString(ex: ref Exec, f, this: ref Ecmascript->Obj, nil: array of ref Val): ref Val{ regexpcheck(ex, this, f); (p, fl, nil) := regexpvals(ex, nil, this); return strval("/" + p + "/" + fl);} regexpcheck(ex: ref Exec, o: ref Ecmascript->Obj, f: ref Obj){ if(f == nil) s := "exec"; else s = f.val.str; if(!isregexpobj(o)) runtime(ex, TypeError, "RegExp.prototype." + s + " called on non-RegExp object");} cstrprotomatch(ex: ref Exec, nil, this: ref Ecmascript->Obj, args: array of ref Val): ref Val{ v := biarg(args, 0); if(!isregexp(v)) re := nregexp(ex, nil, args); else if(v.ty == TObj) re = v.obj; else re = nobj(ex, nil, args); s := toString(ex, this.val); glob := esget(ex, re, "global", 0); av := array[1] of ref Val; av[0] = strval(s); if(glob == false) return cregexpprotoexec(ex, nil, re, av); li := 0; esput(ex, re, "lastIndex", numval(real li), 0); ms: list of ref Val; for(;;){ v = cregexpprotoexec(ex, nil, re, av); if(isnull(v)) break; ms = esget(ex, v.obj, "0", 0) :: ms; ni := int toUint32(ex, esget(ex, re, "lastIndex", 0)); if(ni == li) esput(ex, re, "lastIndex", numval(real ++li), 0); else li = ni; } n := len ms; av = array[n] of ref Val; for(j := n-1; j >= 0; j--){ av[j] = hd ms; ms = tl ms; } return objval(narray(ex, nil, av));} cstrprotoreplace(ex: ref Exec, nil, this: ref Ecmascript->Obj, args: array of ref Val): ref Val{ re: ref Ecmascript->Obj; v := biarg(args, 0); rege := isregexp(v); if(!rege){ if(args == nil) re = nregexp(ex, nil, args); else re = nregexp(ex, nil, args[0:1]); } else if(v.ty == TObj) re = v.obj; else re = nobj(ex, nil, args); s := toString(ex, this.val); if(rege) glob := esget(ex, re, "global", 0); else glob = false; av := array[1] of ref Val; av[0] = strval(s); ms: list of ref Val; li := 0; if(glob == true) esput(ex, re, "lastIndex", numval(real li), 0); for(;;){ v = cregexpprotoexec(ex, nil, re, av); if(!isnull(v)) ms = v :: ms; if(isnull(v) || glob == false) break; ni := int toUint32(ex, esget(ex, re, "lastIndex", 0)); if(ni == li) esput(ex, re, "lastIndex", numval(real ++li), 0); else li = ni; } if(ms == nil) return strval(s); ms = rev(ms); if(rege) lcp := int toUint32(ex, esget(ex, (hd ms).obj, "length", 0))-1; else lcp = 0; v = biarg(args, 1); if(isobj(v) && isfuncobj(v.obj)){ ns := s; n := len ms; args = array[lcp+3] of ref Val; o := inc := 0; for(i := 0; i < n; i++){ a := (hd ms).obj; ms = tl ms; for(j := 0; j <= lcp; j++) args[j] = esget(ex, a, string j, 0); ss := toString(ex, args[0]); o = offset(ss, s, o); args[lcp+1] = numval(real o); args[lcp+2] = strval(s); rs := toString(ex, getValue(ex, escall(ex, v.obj, nil, args, 0))); ns = repl(ns, o+inc, o+inc+len ss, rs); o += len ss; inc += len rs - len ss; } return strval(ns); } else{ ps := toString(ex, v); lps := len ps; ns := s; n := len ms; o := inc := 0; for(i := 0; i < n; i++){ a := (hd ms).obj; ms = tl ms; ss := toString(ex, esget(ex, a, "0", 0)); o = offset(ss, s, o); rs := ""; for(j := 0; j < lps; j++){ if(ps[j] == '$' && j < lps-1){ j++; case(c := ps[j]){ '$' => rs += "$"; '&' => rs += ss; '`' => rs += s[0: o]; ''' => rs += s[o+len ss: ]; '0' to '9' => if(j < lps-1 && isdigit(ps[j+1])) c = 10*(c-'0')+ps[++j]-'0'; else c = c-'0'; if(c >= 1 && c <= lcp) rs += toString(ex, esget(ex, a, string c, 0)); } } else rs += ps[j: j+1]; } ns = repl(ns, o+inc, o+inc+len ss, rs); o += len ss; inc += len rs - len ss; } return strval(ns); }} cstrprotosearch(ex: ref Exec, nil, this: ref Ecmascript->Obj, args: array of ref Val): ref Val{ v := biarg(args, 0); if(!isregexp(v)) re := nregexp(ex, nil, args); else if(v.ty == TObj) re = v.obj; else re = nobj(ex, nil, args); s := toString(ex, this.val); glob := esget(ex, re, "global", 0); esput(ex, re, "global", false, 0); av := array[1] of ref Val; av[0] = strval(s); v = cregexpprotoexec(ex, nil, re, av); if(isnull(v)) r := -1; else{ ss := toString(ex, esget(ex, v.obj, "0", 0)); r = offset(ss, s, 0); } esput(ex, re, "global", glob, 0); return numval(real r);} offset(ss: string, s: string, m: int): int{ nn := len ss; n := len s; for(i := m; i <= n-nn; i++){ if(s[i: i+nn] == ss) return i; } return -1;} repl(s: string, a: int, b: int, ns: string): string{ return s[0: a] + ns + s[b: ];} rev(ls: list of ref Val): list of ref Val{ ns: list of ref Val; for( ; ls != nil; ls = tl ls) ns = hd ls :: ns; return ns;} ########################################################################## regex.b originally # normally imported identifiers # internal identifiers, not normally imported ALT, CAT, DOT, SET, HAT, DOL, NUL, PCLO, CLO, OPT, LPN, RPN, LPN0, RPN0, LPN1, RPN1, LPN2, RPN2, BEET, BEEF, MNCLO, LCP, IDLE: con (1<<16)+iota; # syntax # RE ALT regular expression# NUL# ALT CAT alternation# CAT | ALT## CAT DUP catenation# DUP CAT## DUP PRIM possibly duplicated primary# PCLO# CLO# OPT## PCLO PRIM + 1 or more# CLO PRIM * 0 or more# OPT PRIM ? 0 or 1## PRIM ( RE )# ()# DOT any character# CHAR a single character# ESC escape sequence# [ SET ] character set# NUL null string# HAT beginning of string# DOL end of string# regex: ref Exec; NIL : con -1; # a refRex constantNONE: con -2; # ditto, for an un-set valueBAD: con 1<<16; # a non-character HUGE: con (1<<31) - 1; # the data structures of re.m would like to be ref-linked, but are# circular (see fn walk), thus instead of pointers we use indexes# into an array (arena) of nodes of the syntax tree of a regular expression.# from a storage-allocation standpoint, this replaces many small# allocations of one size with one big one of variable size. ReStr: adt { s : string; i : int; # cursor postion n : int; # number of chars left; -1 on error peek : fn(s: self ref ReStr): int; next : fn(s: self ref ReStr): int; unput: fn(s: self ref ReStr);}; ReStr.peek(s: self ref ReStr): int{ if(s.n <= 0) return BAD; return s.s[s.i];} ReStr.next(s: self ref ReStr): int{ if(s.n <= 0) syntax("bad regular expression"); s.n--; return s.s[s.i++];} ReStr.unput(s: self ref ReStr){ s.n++; s.i--;} newRe(kind: int, left, right: refRex, set: ref Set, ar: ref Arena, pno: int, greedy: int): refRex{ ar.rex[ar.ptr] = Rex(kind, left, right, set, pno, greedy, nil); return ar.ptr++;} # parse a regex by recursive descent to get a syntax tree re(s: ref ReStr, ar: ref Arena): refRex{ left := cat(s, ar); if(left==NIL || s.peek()!='|') return left; s.next(); right := re(s, ar); if(right == NIL) return NIL; return newRe(ALT, left, right, nil, ar, 0, 0);} cat(s: ref ReStr, ar: ref Arena): refRex{ left := dup(s, ar); if(left == NIL) return left; right := cat(s, ar); if(right == NIL) return left; return newRe(CAT, left, right, nil, ar, 0, 0);} dup(s: ref ReStr, ar: ref Arena): refRex{ n1, n2: int; case s.peek() { BAD or ')' or ']' or '|' or '?' or '*' or '+' => return NIL; } prim: refRex; case kind:=s.next() { '(' => if(ar.pno < 0) { if(s.peek() == ')') { s.next(); prim = newRe(NUL, NONE, NONE, nil, ar, 0, 0); } else { prim = re(s, ar); if(prim==NIL || s.next()!=')') syntax("( with no )");Lines 1–500 of 1286. Load more
strhas(s: string, c: int): ref Val { for(i := 0; i < len s; i++) if(s[i] == c) return true; return false; } rsplit(r: string): (string, string) { esc := 0; i := 1; # skip '/' for(;;){ c := r[i++]; if(!esc && c == '/') break; esc = !esc && c == '\\'; } return (r[1: i-1], r[i: ]); } badflags(f: string): int { g := i := m := 0; for(j := 0; j < len f; j++){ case(f[j]){ 'g' => g++; 'i' => i++; 'm' => m++; * => return 1; } } return g > 1 || i > 1 || m > 1; } regexpvals(ex: ref Exec, v: ref Val, o: ref Ecmascript->Obj): (string, string, int) { if(v != nil){ if(v.ty == TRegExp) return (v.rev.p, v.rev.f, v.rev.i); o = v.obj; } p := toString(ex, esget(ex, o, "source", 0)); f := ""; if(toBoolean(ex, esget(ex, o, "global", 0)) == true) f += "g"; if(toBoolean(ex, esget(ex, o, "ignoreCase", 0)) == true) f += "i"; if(toBoolean(ex, esget(ex, o, "multiline", 0)) == true) f += "m"; i := toInt32(ex, esget(ex, o, "lastIndex", 0)); return (p, f, i); } nregexp(ex: ref Exec, nil: ref Ecmascript->Obj, args: array of ref Val): ref Ecmascript->Obj { pat := biarg(args, 0); flags := biarg(args, 1); (p, f) := ("", ""); if(isregexp(pat)){ if(flags == undefined) (p, f, nil) = regexpvals(ex, pat, nil); else runtime(ex, TypeError, "flags defined"); } else{ if(pat == undefined) p = ""; else p = toString(ex, pat); if(flags == undefined) f = ""; else f = toString(ex, flags); } o := nobj(ex, nil, array[] of { regexpval(p, f, 0) }); if(badflags(f)) runtime(ex, SyntaxError, "bad regexp flags"); regex = ex; (re, err) := compile(p, 1); if(re == nil || err != nil) runtime(ex, SyntaxError, "bad regexp pattern"); o.re = re; return o; } cregexp(ex: ref Exec, f, nil: ref Ecmascript->Obj, args: array of ref Val): ref Val { pat := biarg(args, 0); flags := biarg(args, 1); if(isregexp(pat) && flags == undefined) return pat; return objval(nregexp(ex, f, args)); } cregexpprotoexec(ex: ref Exec, f, this: ref Ecmascript->Obj, args: array of ref Val): ref Val { m: array of (int, int); regexpcheck(ex, this, f); s := toString(ex, biarg(args, 0)); l := len s; i := toInt32(ex, esget(ex, this, "lastIndex", 0)); e := 0; glob := esget(ex, this, "global", 0); multiline := esget(ex, this, "multiline", 0); ignorecase := esget(ex, this, "ignoreCase", 0); if(glob == false) i = 0; for(;;){ if(i < 0 || i >= l){ esput(ex, this, "lastIndex", numval(real 0), 0); return null; } regex = ex; m = executese(this.re, s, (i, len s), i == 0, 1, multiline == true, ignorecase == true); if(m != nil) break; i++; i = -1; # no need to loop with executese } (i, e) = m[0]; if(glob == true) esput(ex, this, "lastIndex", numval(real e), 0); n := len m; av := array[n] of ref Val; for(j := 0; j < n; j++){ (a, b) := m[j]; if(a < 0) av[j] = undefined; else av[j] = strval(s[a: b]); } a := narray(ex, nil, av); esput(ex, a, "index", numval(real i), 0); esput(ex, a, "input", strval(s), 0); return objval(a); } cregexpprototest(ex: ref Exec, f, this: ref Ecmascript->Obj, args: array of ref Val): ref Val { regexpcheck(ex, this, f); v := cregexpprotoexec(ex, f, this, args); if(!isnull(v)) return true; return false; } cregexpprototoString(ex: ref Exec, f, this: ref Ecmascript->Obj, nil: array of ref Val): ref Val { regexpcheck(ex, this, f); (p, fl, nil) := regexpvals(ex, nil, this); return strval("/" + p + "/" + fl); } regexpcheck(ex: ref Exec, o: ref Ecmascript->Obj, f: ref Obj) { if(f == nil) s := "exec"; else s = f.val.str; if(!isregexpobj(o)) runtime(ex, TypeError, "RegExp.prototype." + s + " called on non-RegExp object"); } cstrprotomatch(ex: ref Exec, nil, this: ref Ecmascript->Obj, args: array of ref Val): ref Val { v := biarg(args, 0); if(!isregexp(v)) re := nregexp(ex, nil, args); else if(v.ty == TObj) re = v.obj; else re = nobj(ex, nil, args); s := toString(ex, this.val); glob := esget(ex, re, "global", 0); av := array[1] of ref Val; av[0] = strval(s); if(glob == false) return cregexpprotoexec(ex, nil, re, av); li := 0; esput(ex, re, "lastIndex", numval(real li), 0); ms: list of ref Val; for(;;){ v = cregexpprotoexec(ex, nil, re, av); if(isnull(v)) break; ms = esget(ex, v.obj, "0", 0) :: ms; ni := int toUint32(ex, esget(ex, re, "lastIndex", 0)); if(ni == li) esput(ex, re, "lastIndex", numval(real ++li), 0); else li = ni; } n := len ms; av = array[n] of ref Val; for(j := n-1; j >= 0; j--){ av[j] = hd ms; ms = tl ms; } return objval(narray(ex, nil, av)); } cstrprotoreplace(ex: ref Exec, nil, this: ref Ecmascript->Obj, args: array of ref Val): ref Val { re: ref Ecmascript->Obj; v := biarg(args, 0); rege := isregexp(v); if(!rege){ if(args == nil) re = nregexp(ex, nil, args); else re = nregexp(ex, nil, args[0:1]); } else if(v.ty == TObj) re = v.obj; else re = nobj(ex, nil, args); s := toString(ex, this.val); if(rege) glob := esget(ex, re, "global", 0); else glob = false; av := array[1] of ref Val; av[0] = strval(s); ms: list of ref Val; li := 0; if(glob == true) esput(ex, re, "lastIndex", numval(real li), 0); for(;;){ v = cregexpprotoexec(ex, nil, re, av); if(!isnull(v)) ms = v :: ms; if(isnull(v) || glob == false) break; ni := int toUint32(ex, esget(ex, re, "lastIndex", 0)); if(ni == li) esput(ex, re, "lastIndex", numval(real ++li), 0); else li = ni; } if(ms == nil) return strval(s); ms = rev(ms); if(rege) lcp := int toUint32(ex, esget(ex, (hd ms).obj, "length", 0))-1; else lcp = 0; v = biarg(args, 1); if(isobj(v) && isfuncobj(v.obj)){ ns := s; n := len ms; args = array[lcp+3] of ref Val; o := inc := 0; for(i := 0; i < n; i++){ a := (hd ms).obj; ms = tl ms; for(j := 0; j <= lcp; j++) args[j] = esget(ex, a, string j, 0); ss := toString(ex, args[0]); o = offset(ss, s, o); args[lcp+1] = numval(real o); args[lcp+2] = strval(s); rs := toString(ex, getValue(ex, escall(ex, v.obj, nil, args, 0))); ns = repl(ns, o+inc, o+inc+len ss, rs); o += len ss; inc += len rs - len ss; } return strval(ns); } else{ ps := toString(ex, v); lps := len ps; ns := s; n := len ms; o := inc := 0; for(i := 0; i < n; i++){ a := (hd ms).obj; ms = tl ms; ss := toString(ex, esget(ex, a, "0", 0)); o = offset(ss, s, o); rs := ""; for(j := 0; j < lps; j++){ if(ps[j] == '$' && j < lps-1){ j++; case(c := ps[j]){ '$' => rs += "$"; '&' => rs += ss; '`' => rs += s[0: o]; ''' => rs += s[o+len ss: ]; '0' to '9' => if(j < lps-1 && isdigit(ps[j+1])) c = 10*(c-'0')+ps[++j]-'0'; else c = c-'0'; if(c >= 1 && c <= lcp) rs += toString(ex, esget(ex, a, string c, 0)); } } else rs += ps[j: j+1]; } ns = repl(ns, o+inc, o+inc+len ss, rs); o += len ss; inc += len rs - len ss; } return strval(ns); } } cstrprotosearch(ex: ref Exec, nil, this: ref Ecmascript->Obj, args: array of ref Val): ref Val { v := biarg(args, 0); if(!isregexp(v)) re := nregexp(ex, nil, args); else if(v.ty == TObj) re = v.obj; else re = nobj(ex, nil, args); s := toString(ex, this.val); glob := esget(ex, re, "global", 0); esput(ex, re, "global", false, 0); av := array[1] of ref Val; av[0] = strval(s); v = cregexpprotoexec(ex, nil, re, av); if(isnull(v)) r := -1; else{ ss := toString(ex, esget(ex, v.obj, "0", 0)); r = offset(ss, s, 0); } esput(ex, re, "global", glob, 0); return numval(real r); } offset(ss: string, s: string, m: int): int { nn := len ss; n := len s; for(i := m; i <= n-nn; i++){ if(s[i: i+nn] == ss) return i; } return -1; } repl(s: string, a: int, b: int, ns: string): string { return s[0: a] + ns + s[b: ]; } rev(ls: list of ref Val): list of ref Val { ns: list of ref Val; for( ; ls != nil; ls = tl ls) ns = hd ls :: ns; return ns; } ######################################################################### # regex.b originally # normally imported identifiers # internal identifiers, not normally imported ALT, CAT, DOT, SET, HAT, DOL, NUL, PCLO, CLO, OPT, LPN, RPN, LPN0, RPN0, LPN1, RPN1, LPN2, RPN2, BEET, BEEF, MNCLO, LCP, IDLE: con (1<<16)+iota; # syntax # RE ALT regular expression # NUL # ALT CAT alternation # CAT | ALT # # CAT DUP catenation # DUP CAT # # DUP PRIM possibly duplicated primary # PCLO # CLO # OPT # # PCLO PRIM + 1 or more # CLO PRIM * 0 or more # OPT PRIM ? 0 or 1 # # PRIM ( RE ) # () # DOT any character # CHAR a single character # ESC escape sequence # [ SET ] character set # NUL null string # HAT beginning of string # DOL end of string # regex: ref Exec; NIL : con -1; # a refRex constant NONE: con -2; # ditto, for an un-set value BAD: con 1<<16; # a non-character HUGE: con (1<<31) - 1; # the data structures of re.m would like to be ref-linked, but are # circular (see fn walk), thus instead of pointers we use indexes # into an array (arena) of nodes of the syntax tree of a regular expression. # from a storage-allocation standpoint, this replaces many small # allocations of one size with one big one of variable size. ReStr: adt { s : string; i : int; # cursor postion n : int; # number of chars left; -1 on error peek : fn(s: self ref ReStr): int; next : fn(s: self ref ReStr): int; unput: fn(s: self ref ReStr); }; ReStr.peek(s: self ref ReStr): int { if(s.n <= 0) return BAD; return s.s[s.i]; } ReStr.next(s: self ref ReStr): int { if(s.n <= 0) syntax("bad regular expression"); s.n--; return s.s[s.i++]; } ReStr.unput(s: self ref ReStr) { s.n++; s.i--; } newRe(kind: int, left, right: refRex, set: ref Set, ar: ref Arena, pno: int, greedy: int): refRex { ar.rex[ar.ptr] = Rex(kind, left, right, set, pno, greedy, nil); return ar.ptr++; } # parse a regex by recursive descent to get a syntax tree re(s: ref ReStr, ar: ref Arena): refRex { left := cat(s, ar); if(left==NIL || s.peek()!='|') return left; s.next(); right := re(s, ar); if(right == NIL) return NIL; return newRe(ALT, left, right, nil, ar, 0, 0); } cat(s: ref ReStr, ar: ref Arena): refRex { left := dup(s, ar); if(left == NIL) return left; right := cat(s, ar); if(right == NIL) return left; return newRe(CAT, left, right, nil, ar, 0, 0); } dup(s: ref ReStr, ar: ref Arena): refRex { n1, n2: int; case s.peek() { BAD or ')' or ']' or '|' or '?' or '*' or '+' => return NIL; } prim: refRex; case kind:=s.next() { '(' => if(ar.pno < 0) { if(s.peek() == ')') { s.next(); prim = newRe(NUL, NONE, NONE, nil, ar, 0, 0); } else { prim = re(s, ar); if(prim==NIL || s.next()!=')') syntax("( with no )");