summaryrefslogtreecommitdiff
path: root/appl/lib/ecmascript
diff options
context:
space:
mode:
authorCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
committerCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
commit37da2899f40661e3e9631e497da8dc59b971cbd0 (patch)
treecbc6d4680e347d906f5fa7fca73214418741df72 /appl/lib/ecmascript
parent54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff)
20060303a
Diffstat (limited to 'appl/lib/ecmascript')
-rw-r--r--appl/lib/ecmascript/builtin.b1480
-rw-r--r--appl/lib/ecmascript/date.b495
-rw-r--r--appl/lib/ecmascript/ecmascript.b2626
-rw-r--r--appl/lib/ecmascript/exec.b863
-rw-r--r--appl/lib/ecmascript/mkfile23
-rw-r--r--appl/lib/ecmascript/obj.b836
-rw-r--r--appl/lib/ecmascript/pprint.b378
-rw-r--r--appl/lib/ecmascript/regexp.b1286
-rw-r--r--appl/lib/ecmascript/uri.b140
9 files changed, 8127 insertions, 0 deletions
diff --git a/appl/lib/ecmascript/builtin.b b/appl/lib/ecmascript/builtin.b
new file mode 100644
index 00000000..c8374f35
--- /dev/null
+++ b/appl/lib/ecmascript/builtin.b
@@ -0,0 +1,1480 @@
+#
+# utility functions
+#
+biinst(o: ref Obj, bi: Builtin, p: ref Obj, h: ESHostobj): ref Obj
+{
+ bo := mkobj(p, "Function");
+ bo.call = mkcall(nil, bi.params);
+ bo.val = strval(bi.val);
+ bo.host = h;
+ varinstant(bo, DontEnum|DontDelete|ReadOnly, "length", ref RefVal(numval(real bi.length)));
+ varinstant(o, DontEnum, bi.name, ref RefVal(objval(bo)));
+ return bo;
+}
+
+biminst(o: ref Obj, bis: array of Builtin, p: ref Obj, h: ESHostobj)
+{
+ for(i := 0; i < len bis; i++)
+ biinst(o, bis[i], p, h);
+}
+
+biarg(args: array of ref Val, i: int): ref Val
+{
+ if(i < len args)
+ return args[i];
+ return undefined;
+}
+
+#
+# interface to builtin objects
+#
+get(ex: ref Ecmascript->Exec, o: ref Ecmascript->Obj, property: string): ref Ecmascript->Val
+{
+ return esget(ex, o, property, 1);
+}
+
+put(ex: ref Ecmascript->Exec, o: ref Ecmascript->Obj, property: string, val: ref Ecmascript->Val)
+{
+ return esput(ex, o, property, val, 1);
+}
+
+canput(ex: ref Ecmascript->Exec, o: ref Ecmascript->Obj, property: string): ref Ecmascript->Val
+{
+ return escanput(ex, o, property, 1);
+}
+
+hasproperty(ex: ref Ecmascript->Exec, o: ref Ecmascript->Obj, property: string): ref Ecmascript->Val
+{
+ return eshasproperty(ex, o, property, 1);
+}
+
+delete(ex: ref Ecmascript->Exec, o: ref Ecmascript->Obj, property: string)
+{
+ return esdelete(ex, o, property, 1);
+}
+
+defaultval(ex: ref Ecmascript->Exec, o: ref Ecmascript->Obj, tyhint: int): ref Ecmascript->Val
+{
+ return esdefaultval(ex, o, tyhint, 1);
+}
+
+call(ex: ref Ecmascript->Exec, f, this: ref Ecmascript->Obj, args: array of ref Ecmascript->Val, eval: int): ref Ecmascript->Ref
+{
+ x, y: real;
+ v: ref Val;
+
+ if(this == nil)
+ this = ex.global;
+ if(f.host != me)
+ return escall(ex, f, this, args, eval);
+ case f.val.str{
+ "eval" =>
+ v = ceval(ex, f, this, args);
+ "parseInt" =>
+ v = cparseInt(ex, f, this, args);
+ "parseFloat" =>
+ v = cparseFloat(ex, f, this, args);
+ "escape" =>
+ v = cescape(ex, f, this, args);
+ "unescape" =>
+ v = cunescape(ex, f, this, args);
+ "isNaN" =>
+ v = cisNaN(ex, f, this, args);
+ "isFinite" =>
+ v = cisFinite(ex, f, this, args);
+ "decodeURI" =>
+ v = cdecodeuri(ex, f, this, args);
+ "encodeURI" =>
+ v = cencodeuri(ex, f, this, args);
+ "decodeURIComponent" =>
+ v = cdecodeuric(ex, f, this, args);
+ "encodeURIComponent" =>
+ v = cencodeuric(ex, f, this, args);
+ "Object" =>
+ v = cobj(ex, f, this, args);
+ "Object.prototype.toString" or
+ "Object.prototype.toLocaleString" =>
+ v = cobjprototoString(ex, f, this, args);
+ "Object.prototype.valueOf" =>
+ v = cobjprotovalueOf(ex, f, this, args);
+ "Object.prototype.hasOwnProperty" =>
+ v = cobjprotohasownprop(ex, f, this, args);
+ "Object.prototype.isPrototypeOf" =>
+ v = cobjprotoisprotoof(ex, f, this, args);
+ "Object.prototype.propertyisEnumerable" =>
+ v = cobjprotopropisenum(ex, f, this, args);
+ "Function" =>
+ v = objval(nfunc(ex, f, args));
+ "Function.Prototype" =>
+ v = undefined;
+ "Function.prototype.toString" =>
+ v = cfuncprototoString(ex, f, this, args);
+ "Function.prototype.apply" =>
+ v = cfuncprotoapply(ex, f, this, args);
+ "Function.prototype.call" =>
+ v = cfuncprotocall(ex, f, this, args);
+ "Error" =>
+ v = objval(nerr(ex, f, args, ex.errproto));
+ "Error.prototype.toString" =>
+ v = cerrprototoString(ex, f, this, args);
+ "EvalError" =>
+ v = objval(nerr(ex, f, args, ex.evlerrproto));
+ "EvalError.prototype.toString" =>
+ v = cerrprototoString(ex, f, this, args);
+ "RangeError" =>
+ v = objval(nerr(ex, f, args, ex.ranerrproto));
+ "RangeError.prototype.toString" =>
+ v = cerrprototoString(ex, f, this, args);
+ "ReferenceError" =>
+ v = objval(nerr(ex, f, args, ex.referrproto));
+ "ReferenceError.prototype.toString" =>
+ v = cerrprototoString(ex, f, this, args);
+ "SyntaxError" =>
+ v = objval(nerr(ex, f, args, ex.synerrproto));
+ "SyntaxError.prototype.toString" =>
+ v = cerrprototoString(ex, f, this, args);
+ "TypeError" =>
+ v = objval(nerr(ex, f, args, ex.typerrproto));
+ "TypeError.prototype.toString" =>
+ v = cerrprototoString(ex, f, this, args);
+ "URIError" =>
+ v = objval(nerr(ex, f, args, ex.urierrproto));
+ "URIError.prototype.toString" =>
+ v = cerrprototoString(ex, f, this, args);
+ "InternalError" =>
+ v = objval(nerr(ex, f, args, ex.interrproto));
+ "InternalError.prototype.toString" =>
+ v = cerrprototoString(ex, f, this, args);
+ "Array" =>
+ v = objval(narray(ex, f, args));
+ "Array.prototype.toString" or "Array.prototype.toLocaleString" =>
+ v = carrayprototoString(ex, f, this, args);
+ "Array.prototype.concat" =>
+ v = carrayprotoconcat(ex, f, this, args);
+ "Array.prototype.join" =>
+ v = carrayprotojoin(ex, f, this, args);
+ "Array.prototype.pop" =>
+ v = carrayprotopop(ex, f, this, args);
+ "Array.prototype.push" =>
+ v = carrayprotopush(ex, f, this, args);
+ "Array.prototype.reverse" =>
+ v = carrayprotoreverse(ex, f, this, args);
+ "Array.prototype.shift" =>
+ v = carrayprotoshift(ex, f, this, args);
+ "Array.prototype.slice" =>
+ v = carrayprotoslice(ex, f, this, args);
+ "Array.prototype.splice" =>
+ v = carrayprotosplice(ex, f, this, args);
+ "Array.prototype.sort" =>
+ v = carrayprotosort(ex, f, this, args);
+ "Array.prototype.unshift" =>
+ v = carrayprotounshift(ex, f, this, args);
+ "String" =>
+ v = cstr(ex, f, this, args);
+ "String.fromCharCode" =>
+ v = cstrfromCharCode(ex, f, this, args);
+ "String.prototype.toString" =>
+ v = cstrprototoString(ex, f, this, args);
+ "String.prototype.valueOf" =>
+ v = cstrprototoString(ex, f, this, args);
+ "String.prototype.charAt" =>
+ v = cstrprotocharAt(ex, f, this, args);
+ "String.prototype.charCodeAt" =>
+ v = cstrprotocharCodeAt(ex, f, this, args);
+ "String.prototype.concat" =>
+ v = cstrprotoconcat(ex, f, this, args);
+ "String.prototype.indexOf" =>
+ v = cstrprotoindexOf(ex, f, this, args);
+ "String.prototype.lastIndexOf" =>
+ v = cstrprotolastindexOf(ex, f, this, args);
+ "String.prototype.localeCompare" =>
+ v = cstrprotocmp(ex, f, this, args);
+ "String.prototype.slice" =>
+ v = cstrprotoslice(ex, f, this, args);
+ "String.prototype.split" =>
+ v = cstrprotosplit(ex, f, this, args);
+ "String.prototype.substr" =>
+ v = cstrprotosubstr(ex, f, this, args);
+ "String.prototype.substring" =>
+ v = cstrprotosubstring(ex, f, this, args);
+ "String.prototype.toLowerCase" or "String.prototype.toLocaleLowerCase" =>
+ v = cstrprototoLowerCase(ex, f, this, args);
+ "String.prototype.toUpperCase" or "String.prototype.toLocaleUpperCase" =>
+ v = cstrprototoUpperCase(ex, f, this, args);
+ "String.prototype.match" =>
+ v = cstrprotomatch(ex, f, this, args);
+ "String.prototype.replace" =>
+ v = cstrprotoreplace(ex, f, this, args);
+ "String.prototype.search" =>
+ v = cstrprotosearch(ex, f, this, args);
+# JavaScript 1.0
+ "String.prototype.anchor" or
+ "String.prototype.big" or
+ "String.prototype.blink" or
+ "String.prototype.bold" or
+ "String.prototype.fixed" or
+ "String.prototype.fontcolor" or
+ "String.prototype.fontsize" or
+ "String.prototype.italics" or
+ "String.prototype.link" or
+ "String.prototype.small" or
+ "String.prototype.strike" or
+ "String.prototype.sub" or
+ "String.prototype.sup" =>
+ s := toString(ex, objval(this));
+ arg := toString(ex, biarg(args, 0));
+ tag, endtag: string;
+ case f.val.str{
+ "String.prototype.anchor" =>
+ tag = "<A NAME=\"" + arg + "\">";
+ endtag = "</A>";
+ "String.prototype.big" =>
+ tag = "<BIG>";
+ endtag = "</BIG>";
+ "String.prototype.blink" =>
+ tag = "<BLINK>";
+ endtag = "</BLINK>";
+ "String.prototype.bold" =>
+ tag = "<B>";
+ endtag = "</B>";
+ "String.prototype.fixed" =>
+ tag = "<TT>";
+ endtag = "</TT>";
+ "String.prototype.fontcolor" =>
+ tag = "<FONT COLOR=\"" + arg + "\">";
+ endtag = "</FONT>";
+ "String.prototype.fontsize" =>
+ tag = "<FONT SIZE=\"" + arg + "\">";
+ endtag = "</FONT>";
+ "String.prototype.italics" =>
+ tag = "<I>";
+ endtag = "</I>";
+ "String.prototype.link" =>
+ tag = "<A HREF=\"" + arg + "\">";
+ endtag = "</A>";
+ "String.prototype.small" =>
+ tag = "<SMALL>";
+ endtag = "</SMALL>";
+ "String.prototype.strike" =>
+ tag = "<STRIKE>";
+ endtag = "</STRIKE>";
+ "String.prototype.sub" =>
+ tag = "<SUB>";
+ endtag = "</SUB>";
+ "String.prototype.sup" =>
+ tag = "<SUP>";
+ endtag = "</SUP>";
+ }
+ v = strval(tag + s + endtag);
+ "Boolean" =>
+ v = cbool(ex, f, this, args);
+ "Boolean.prototype.toString" =>
+ v = cboolprototoString(ex, f, this, args);
+ "Boolean.prototype.valueOf" =>
+ v = cboolprotovalueOf(ex, f, this, args);
+ "Number" =>
+ v = cnum(ex, f, this, args);
+ "Number.prototype.toString" or "Number.prototype.toLocaleString" =>
+ v = cnumprototoString(ex, f, this, args);
+ "Number.prototype.valueOf" =>
+ v = cnumprotovalueOf(ex, f, this, args);
+ "Number.prototype.toFixed" =>
+ v = cnumprotofix(ex, f, this, args);
+ "Number.prototype.toExponential" =>
+ v = cnumprotoexp(ex, f, this, args);
+ "Number.prototype.toPrecision" =>
+ v = cnumprotoprec(ex, f, this, args);
+ "RegExp" =>
+ v = cregexp(ex, f, this, args);
+ "RegExp.prototype.exec" =>
+ v = cregexpprotoexec(ex, f, this, args);
+ "RegExp.prototype.test" =>
+ v = cregexpprototest(ex, f, this, args);
+ "RegExp.prototype.toString" =>
+ v = cregexpprototoString(ex, f, this, args);
+ "Math.abs" or
+ "Math.acos" or
+ "Math.asin" or
+ "Math.atan" or
+ "Math.ceil" or
+ "Math.cos" or
+ "Math.exp" or
+ "Math.floor" or
+ "Math.log" or
+ "Math.round" or
+ "Math.sin" or
+ "Math.sqrt" or
+ "Math.tan" =>
+ x = toNumber(ex, biarg(args, 0));
+ case f.val.str{
+ "Math.abs" =>
+ if(x < 0.)
+ x = -x;
+ else if(x == 0.)
+ x = 0.;
+ "Math.acos" => x = math->acos(x);
+ "Math.asin" => x = math->asin(x);
+ "Math.atan" => x = math->atan(x);
+ "Math.ceil" => x = math->ceil(x);
+ "Math.cos" => x = math->cos(x);
+ "Math.exp" => x = math->exp(x);
+ "Math.floor" => x = math->floor(x);
+ "Math.log" => x = math->log(x);
+ "Math.round" => if((x == .0 && copysign(1., x) == -1.)
+ || (x < .0 && x >= -0.5))
+ x = -0.;
+ else
+ x = math->floor(x+.5);
+ "Math.sin" => x = math->sin(x);
+ "Math.sqrt" => x = math->sqrt(x);
+ "Math.tan" => x = math->tan(x);
+ }
+ v = numval(x);
+ "Math.random" =>
+# range := big 16r7fffffffffffffff;
+ range := big 1000000000;
+ v = numval(real bigrand(range)/ real range);
+ "Math.atan2" or
+ "Math.max" or
+ "Math.min" or
+ "Math.pow" =>
+ x = toNumber(ex, biarg(args, 0));
+ y = toNumber(ex, biarg(args, 1));
+ case f.val.str{
+ "Math.atan2" =>
+ x = math->atan2(x, y);
+ "Math.max" =>
+ if(x > y)
+ ;
+ else if(x < y)
+ x = y;
+ else if(x == y){
+ if(x == 0. && copysign(1., x) == -1. && copysign(1., y) == 1.)
+ x = y;
+ }else
+ x = Math->NaN;
+ "Math.min" =>
+ if(x < y)
+ ;
+ else if(x > y)
+ x = y;
+ else if(x == y){
+ if(x == 0. && copysign(1., x) == 1. && copysign(1., y) == -1.)
+ x = y;
+ }else
+ x = Math->NaN;
+ "Math.pow" =>
+ x = math->pow(x, y);
+ }
+ v = numval(x);
+ "Date" =>
+ v = cdate(ex, f, this, args);
+ "Date.parse" =>
+ v = cdateparse(ex, f, this, args);
+ "Date.UTC" =>
+ v = cdateUTC(ex, f, this, args);
+ "Date.prototype.toString" or
+ "Date.prototype.toLocaleString" =>
+ v = cdateprototoString(ex, f, this, args);
+ "Date.prototype.toDateString" or
+ "Date.prototype.toLocaleDateString" =>
+ v = cdateprototoDateString(ex, f, this, args);
+ "Date.prototype.toTimeString" or
+ "Date.prototype.toLocaleTimeString" =>
+ v = cdateprototoTimeString(ex, f, this, args);
+ "Date.prototype.valueOf" or
+ "Date.prototype.getTime" =>
+ v = cdateprotovalueOf(ex, f, this, args);
+ "Date.prototype.getYear" or
+ "Date.prototype.getFullYear" or
+ "Date.prototype.getMonth" or
+ "Date.prototype.getDate" or
+ "Date.prototype.getDay" or
+ "Date.prototype.getHours" or
+ "Date.prototype.getMinutes" or
+ "Date.prototype.getSeconds" =>
+ v = cdateprotoget(ex, f, this, args, !UTC);
+ "Date.prototype.getUTCFullYear" or
+ "Date.prototype.getUTCMonth" or
+ "Date.prototype.getUTCDate" or
+ "Date.prototype.getUTCDay" or
+ "Date.prototype.getUTCHours" or
+ "Date.prototype.getUTCMinutes" or
+ "Date.prototype.getUTCSeconds" =>
+ v = cdateprotoget(ex, f, this, args, UTC);
+ "Date.prototype.getMilliseconds" or
+ "Date.prototype.getUTCMilliseconds" =>
+ v = cdateprotogetMilliseconds(ex, f, this, args);
+ "Date.prototype.getTimezoneOffset" =>
+ v = cdateprotogetTimezoneOffset(ex, f, this, args);
+ "Date.prototype.setTime" =>
+ v = cdateprotosetTime(ex, f, this, args);
+ "Date.prototype.setMilliseconds" =>
+ v = cdateprotosetMilliseconds(ex, f, this, args, !UTC);
+ "Date.prototype.setUTCMilliseconds" =>
+ v = cdateprotosetMilliseconds(ex, f, this, args, UTC);
+ "Date.prototype.setSeconds" =>
+ v = cdateprotosetSeconds(ex, f, this, args, !UTC);
+ "Date.prototype.setUTCSeconds" =>
+ v = cdateprotosetSeconds(ex, f, this, args, UTC);
+ "Date.prototype.setMinutes" =>
+ v = cdateprotosetMinutes(ex, f, this, args, !UTC);
+ "Date.prototype.setUTCMinutes" =>
+ v = cdateprotosetMinutes(ex, f, this, args, UTC);
+ "Date.prototype.setHours" =>
+ v = cdateprotosetHours(ex, f, this, args, !UTC);
+ "Date.prototype.setUTCHours" =>
+ v = cdateprotosetHours(ex, f, this, args, UTC);
+ "Date.prototype.setDate" =>
+ v = cdateprotosetDate(ex, f, this, args, !UTC);
+ "Date.prototype.setUTCDate" =>
+ v = cdateprotosetDate(ex, f, this, args, UTC);
+ "Date.prototype.setMonth" =>
+ v = cdateprotosetMonth(ex, f, this, args, !UTC);
+ "Date.prototype.setUTCMonth" =>
+ v = cdateprotosetMonth(ex, f, this, args, UTC);
+ "Date.prototype.setFullYear" =>
+ v = cdateprotosetFullYear(ex, f, this, args, !UTC);
+ "Date.prototype.setUTCFullYear" =>
+ v = cdateprotosetFullYear(ex, f, this, args, UTC);
+ "Date.prototype.setYear" =>
+ v = cdateprotosetYear(ex, f, this, args);
+ "Date.prototype.toUTCString" or
+ "Date.prototype.toGMTString" =>
+ v = cdateprototoUTCString(ex, f, this, args);
+ * =>
+ v = nil;
+ }
+ if(v == nil)
+ runtime(ex, ReferenceError, "unknown function "+f.val.str+" in builtin call");
+ return valref(v);
+}
+
+rsalt := big 12345678;
+
+randinit(seed: big)
+{
+ rsalt = big seed;
+ bigrand(big 1);
+ bigrand(big 1);
+}
+
+RANDMASK: con (big 1<<63)-(big 1);
+
+bigrand(modulus: big): big
+{
+ rsalt = rsalt * big 1103515245 + big 12345;
+ if(modulus <= big 0)
+ return big 0;
+ return ((rsalt&RANDMASK)>>10) % modulus;
+}
+
+construct(ex: ref Ecmascript->Exec, f: ref Ecmascript->Obj, args: array of ref Ecmascript->Val): ref Ecmascript->Obj
+{
+ if(f.host != me)
+ runtime(ex, TypeError, "ecmascript builtin called incorrectly");
+ case f.val.str{
+ "Object" =>
+ return nobj(ex, f, args);
+ "Function" =>
+ return nfunc(ex, f, args);
+ "Array" =>
+ return narray(ex, f, args);
+ "Error" =>
+ return nerr(ex, f, args, ex.errproto);
+ "EvalError" =>
+ return nerr(ex, f, args, ex.evlerrproto);
+ "RangeError" =>
+ return nerr(ex, f, args, ex.ranerrproto);
+ "ReferenceError" =>
+ return nerr(ex, f, args, ex.referrproto);
+ "SyntaxError" =>
+ return nerr(ex, f, args, ex.synerrproto);
+ "TypeError" =>
+ return nerr(ex, f, args, ex.typerrproto);
+ "URIError" =>
+ return nerr(ex, f, args, ex.urierrproto);
+ "InternalError" =>
+ return nerr(ex, f, args, ex.interrproto);
+ "String" or
+ "Boolean" or
+ "Number" =>
+ return coerceToObj(ex, call(ex, f, nil, args, 0).val).obj;
+ "Date" =>
+ return ndate(ex, f, args);
+ "RegExp" =>
+ return nregexp(ex, f, args);
+ }
+ runtime(ex, ReferenceError, "unknown constructor "+f.val.str+" in builtin construct");
+ return nil;
+}
+
+ceval(ex: ref Exec, nil, nil: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ if(len args < 1)
+ return undefined;
+ vs := coerceToVal(args[0]);
+ if(!isstr(vs))
+ return args[0];
+ (k, v, nil) := eval(ex, vs.str);
+ if(k != CNormal || v == nil)
+ v = undefined;
+ return v;
+}
+
+cparseInt(ex: ref Exec, nil, nil: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ sv := biarg(args, 0);
+ s := toString(ex, sv);
+ neg := 0;
+ i := 0;
+ if(len s > i){
+ if(s[i] == '-'){
+ neg = 1;
+ i++;
+ }else if(s[i] == '+')
+ i++;
+ }
+ rv := biarg(args, 1);
+ if(rv == undefined)
+ r := big 0;
+ else
+ r = big toInt32(ex, rv);
+ if(r == big 0){
+ if(len s > i && s[i] == '0'){
+ r = big 8;
+ if(len s >= i+2 && (s[i+1] == 'x' || s[i+1] == 'X'))
+ r = big 16;
+ }else
+ r = big 10;
+ }else if(r < big 0 || r > big 36)
+ return numval(Math->NaN);
+ if(r == big 16 && len s >= i+2 && s[i] == '0' && (s[i+1] == 'x' || s[i+1] == 'X'))
+ i += 2;
+ ok := 0;
+ n := big 0;
+ for(; i < len s; i++) {
+ c := s[i];
+ v := r;
+ case c {
+ 'a' to 'z' =>
+ v = big(c - 'a' + 10);
+ 'A' to 'Z' =>
+ v = big(c - 'A' + 10);
+ '0' to '9' =>
+ v = big(c - '0');
+ }
+ if(v >= r)
+ break;
+ ok = 1;
+ n = n * r + v;
+ }
+ if(!ok)
+ return numval(Math->NaN);
+ if(neg)
+ n = -n;
+ return numval(real n);
+}
+
+cparseFloat(ex: ref Exec, nil, nil: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ s := toString(ex, biarg(args, 0));
+ (nil, r) := parsenum(ex, s, 0, ParseReal|ParseTrim);
+ return numval(r);
+}
+
+cescape(ex: ref Exec, nil, nil: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ s := toString(ex, biarg(args, 0));
+ t := "";
+ for(i := 0; i < len s; i++){
+ c := s[i];
+ case c{
+ 'A' to 'Z' or
+ 'a' to 'z' or
+ '0' to '9' or
+ '@' or '*' or '_' or '+' or '-' or '.' or '/' =>
+ t[len t] = s[i];
+ * =>
+ e := "";
+ do{
+ d := c & 16rf;
+ e = "0123456789abcdef"[d:d+1] + e;
+ c >>= 4;
+ }while(c);
+ if(len e & 1)
+ e = "0" + e;
+ if(len e == 4)
+ e = "u" + e;
+ t += "%" + e;
+ }
+ }
+ return strval(t);
+}
+
+cunescape(ex: ref Exec, nil, nil: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ s := toString(ex, biarg(args, 0));
+ t := "";
+ for(i := 0; i < len s; i++){
+ c := s[i];
+ if(c == '%'){
+ if(i + 5 < len s && s[i+1] == 'u'){
+ (v, e) := str->toint(s[i+2:i+6], 16);
+ if(e == ""){
+ c = v;
+ i += 5;
+ }
+ }else if(i + 2 < len s){
+ (v, e) := str->toint(s[i+1:i+3], 16);
+ if(e == ""){
+ c = v;
+ i += 2;
+ }
+ }
+ }
+ t[len t] = c;
+ }
+ return strval(t);
+}
+
+cisNaN(ex: ref Exec, nil, nil: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ if(math->isnan(toNumber(ex, biarg(args, 0))))
+ return true;
+ return false;
+}
+
+cisFinite(ex: ref Exec, nil, nil: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ r := toNumber(ex, biarg(args, 0));
+ if(math->isnan(r) || r == +Infinity || r == -Infinity)
+ return false;
+ return true;
+}
+
+cobj(ex: ref Exec, f, nil: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ o: ref Obj;
+
+ v := biarg(args, 0);
+
+ if(isnull(v) || isundefined(v))
+ o = nobj(ex, f, args);
+ else
+ o = toObject(ex, v);
+ return objval(o);
+}
+
+nobj(ex: ref Exec, nil: ref Ecmascript->Obj, args: array of ref Val): ref Ecmascript->Obj
+{
+ o: ref Obj;
+
+ v := biarg(args, 0);
+
+ case v.ty{
+ TNull or TUndef =>
+ o = mkobj(ex.objproto, "Object");
+ TBool =>
+ o = mkobj(ex.boolproto, "Boolean");
+ o.val = v;
+ TStr =>
+ o = mkobj(ex.strproto, "String");
+ o.val = v;
+ varinstant(o, DontEnum|DontDelete|ReadOnly, "length", ref RefVal(numval(real len v.str)));
+ TNum =>
+ o = mkobj(ex.numproto, "Number");
+ o.val = v;
+ TObj =>
+ o = v.obj;
+ TRegExp =>
+ o = mkobj(ex.regexpproto, "RegExp");
+ o.val = v;
+ varinstant(o, DontEnum|DontDelete|ReadOnly, "length", ref RefVal(numval(real len v.rev.p)));
+ varinstant(o, DontEnum|DontDelete|ReadOnly, "source", ref RefVal(strval(v.rev.p)));
+ varinstant(o, DontEnum|DontDelete|ReadOnly, "global", ref RefVal(strhas(v.rev.f, 'g')));
+ varinstant(o, DontEnum|DontDelete|ReadOnly, "ignoreCase", ref RefVal(strhas(v.rev.f, 'i')));
+ varinstant(o, DontEnum|DontDelete|ReadOnly, "multiline", ref RefVal(strhas(v.rev.f, 'm')));
+ varinstant(o, DontEnum|DontDelete, "lastIndex", ref RefVal(numval(real v.rev.i)));
+
+ * =>
+ runtime(ex, ReferenceError, "unknown type in Object constructor");
+ }
+ return o;
+}
+
+cobjprototoString(nil: ref Exec, nil, this: ref Ecmascript->Obj, nil: array of ref Val): ref Val
+{
+ return strval("[object " + this.class + "]");
+}
+
+cobjprotovalueOf(nil: ref Exec, nil, this: ref Ecmascript->Obj, nil: array of ref Val): ref Val
+{
+ return objval(this);
+}
+
+cobjprotohasownprop(ex: ref Exec, nil, this: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ o := this;
+ s := toString(ex, biarg(args, 0));
+ p := o.prototype;
+ o.prototype = nil;
+ v := eshasproperty(ex, o, s, 0);
+ o.prototype = p;
+ return v;
+}
+
+cobjprotoisprotoof(nil: ref Exec, nil, this: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ o := this;
+ v := biarg(args, 0);
+ if(!isobj(v))
+ return false;
+ for(p := v.obj.prototype; p != nil; p = p.prototype)
+ if(p == o)
+ return true;
+ return false;
+}
+
+cobjprotopropisenum(ex: ref Exec, nil, this: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ return eshasenumprop(this, toString(ex, biarg(args, 0)));
+}
+
+nfunc(ex: ref Exec, nil: ref Ecmascript->Obj, args: array of ref Val): ref Ecmascript->Obj
+{
+ params := "";
+ body := "";
+ sep := "";
+ for(i := 0; i < len args - 1; i++){
+ params += sep + toString(ex, args[i]);
+ sep = ",";
+ }
+ if(i < len args)
+ body = toString(ex, args[i]);
+
+ p := mkparser(ex, "function anonymous("+params+"){"+body+"}");
+ fundecl(ex, p, 0);
+ if(p.errors)
+ runtime(ex, SyntaxError, ex.error);
+ if(p.code.vars[0].name != "anonymous")
+ runtime(ex, SyntaxError, "parse failure");
+ return p.code.vars[0].val.val.obj;
+}
+
+cfuncprototoString(ex: ref Exec, nil, this: ref Ecmascript->Obj, nil: array of ref Val): ref Val
+{
+ if(this.call == nil)
+ runtime(ex, TypeError, "Function.prototype.toString called for a non-Function object");
+ return strval(funcprint(ex, this));
+}
+
+nerr(ex: ref Exec, f: ref Ecmascript->Obj, args: array of ref Val, proto: ref Obj): ref Ecmascript->Obj
+{
+ msg := biarg(args, 0);
+ if(msg == undefined)
+ s := "";
+ else
+ s = toString(ex, msg);
+ o := mkobj(proto, f.val.str);
+ varinstant(o, DontEnum|DontDelete|ReadOnly, "length", ref RefVal(numval(real 1)));
+ varinstant(o, DontEnum|DontDelete, "name", ref RefVal(strval(f.val.str)));
+ varinstant(o, DontEnum|DontDelete, "message", ref RefVal(strval(s)));
+ return o;
+}
+
+cfuncprotoapply(ex: ref Exec, nil, this: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ ar: ref Obj;
+
+ if(this.call == nil || !isfuncobj(this))
+ runtime(ex, TypeError, "Function.prototype.apply called for a non-Function object");
+ v := biarg(args, 0);
+ if(v == null || v == undefined)
+ th := ex.global;
+ else
+ th = coerceToObj(ex, v).obj;
+ v = biarg(args, 1);
+ if(v == null || v == undefined)
+ l := 0;
+ else{
+ if(!isobj(v))
+ runtime(ex, TypeError, "Function.prototype.apply non-array argument");
+ ar = v.obj;
+ v = esget(ex, ar, "length", 0);
+ if(v == undefined)
+ runtime(ex, TypeError, "Function.prototype.apply non-array argument");
+ l = int toUint32(ex, v);
+ }
+ args = array[l] of ref Val;
+ for(i := 0; i < l; i++)
+ args[i] = esget(ex, ar, string i, 0);
+ return getValue(ex, escall(ex, this, th, args, 0));
+}
+
+cfuncprotocall(ex: ref Exec, nil, this: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ if(this.call == nil || !isfuncobj(this))
+ runtime(ex, TypeError, "Function.prototype.call called for a non-Function object");
+ v := biarg(args, 0);
+ if(v == null || v == undefined)
+ th := ex.global;
+ else
+ th = coerceToObj(ex, v).obj;
+ return getValue(ex, escall(ex, this, th, args[1: ], 0));
+}
+
+cerrprototoString(ex: ref Exec, nil, this: ref Ecmascript->Obj, nil: array of ref Val): ref Val
+{
+ return esget(ex, this, "message", 0);
+}
+
+narray(ex: ref Exec, nil: ref Ecmascript->Obj, args: array of ref Val): ref Ecmascript->Obj
+{
+ o := mkobj(ex.arrayproto, "Array");
+ length := big len args;
+ if(length == big 1 && isnum(coerceToVal(args[0]))){
+ length = toUint32(ex, args[0]);
+ varinstant(o, DontEnum|DontDelete, "length", ref RefVal(numval(real length)));
+ }else{
+ varinstant(o, DontEnum|DontDelete, "length", ref RefVal(numval(real length)));
+ for(i := 0; i < len args; i++)
+ esput(ex, o, string i, args[i], 0);
+ }
+
+ return o;
+}
+
+carrayprototoString(ex: ref Exec, nil, this: ref Ecmascript->Obj, nil: array of ref Val): ref Val
+{
+ return carrayprotojoin(ex, nil, this, nil);
+}
+
+carrayprotoconcat(ex: ref Exec, nil, this: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ v: ref Val;
+ e: ref Obj;
+
+ a := narray(ex, nil, nil);
+ n := 0;
+ nargs := len args;
+ for(i := -1; i < nargs; i++){
+ if(i < 0){
+ e = this;
+ v = objval(e);
+ }
+ else{
+ v = biarg(args, i);
+ if(isobj(v))
+ e = v.obj;
+ else
+ e = nil;
+ }
+ if(e != nil && isarray(e)){
+ leng := int toUint32(ex, esget(ex, e, "length", 0));
+ for(k := 0; k < leng; k++){
+ av := esget(ex, e, string k, 0);
+ if(v != undefined)
+ esput(ex, a, string n, av, 0);
+ n++;
+ }
+ }
+ else{
+ esput(ex, a, string n, v, 0);
+ n++;
+ }
+ }
+ esput(ex, a, "length", numval(real n), 0);
+ return objval(a);
+}
+
+carrayprotojoin(ex: ref Exec, nil, this: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ length := toUint32(ex, esget(ex, this, "length", 0));
+ sepv := biarg(args, 0);
+ sep := ",";
+ if(sepv != undefined)
+ sep = toString(ex, sepv);
+ s := "";
+ ss := "";
+ for(i := big 0; i < length; i++){
+ tv := esget(ex, this, string i, 0);
+ t := "";
+ if(tv != undefined && !isnull(tv))
+ t = toString(ex, tv);
+ s += ss + t;
+ ss = sep;
+ }
+ return strval(s);
+}
+
+carrayprotoreverse(ex: ref Exec, nil, this: ref Ecmascript->Obj, nil: array of ref Val): ref Val
+{
+ length := toUint32(ex, esget(ex, this, "length", 0));
+ mid := length / big 2;
+ for(i := big 0; i < mid; i++){
+ i1 := string i;
+ v1 := esget(ex, this, i1, 0);
+ i2 := string(length - i - big 1);
+ v2 := esget(ex, this, i2, 0);
+ if(v2 == undefined)
+ esdelete(ex, this, i1, 0);
+ else
+ esput(ex, this, i1, v2, 0);
+ if(v1 == undefined)
+ esdelete(ex, this, i2, 0);
+ else
+ esput(ex, this, i2, v1, 0);
+ }
+ return objval(this);
+}
+
+carrayprotopop(ex: ref Exec, nil, this: ref Ecmascript->Obj, nil: array of ref Val): ref Val
+{
+ leng := toUint32(ex, esget(ex, this, "length", 0));
+ if(leng == big 0){
+ esput(ex, this, "length", numval(0.), 0);
+ return undefined;
+ }
+ ind := string (leng-big 1);
+ v := esget(ex, this, ind, 0);
+ esdelete(ex, this, ind, 0);
+ esput(ex, this, "length", numval(real (leng-big 1)), 0);
+ return v;
+}
+
+carrayprotopush(ex: ref Exec, nil, this: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ leng := toUint32(ex, esget(ex, this, "length", 0));
+ nargs := len args;
+ for(i := 0; i < nargs; i++)
+ esput(ex, this, string (leng+big i), biarg(args, i), 0);
+ nv := numval(real (leng+big nargs));
+ esput(ex, this, "length", nv, 0);
+ return nv;
+}
+
+carrayprotoshift(ex: ref Exec, nil, this: ref Ecmascript->Obj, nil: array of ref Val): ref Val
+{
+ leng := int toUint32(ex, esget(ex, this, "length", 0));
+ if(leng == 0){
+ esput(ex, this, "length", numval(0.), 0);
+ return undefined;
+ }
+ v0 := esget(ex, this, "0", 0);
+ for(k := 1; k < leng; k++){
+ v := esget(ex, this, string k, 0);
+ if(v == undefined)
+ esdelete(ex, this, string (k-1), 0);
+ else
+ esput(ex, this, string (k-1), v, 0);
+ }
+ esdelete(ex, this, string (leng-1), 0);
+ esput(ex, this, "length", numval(real (leng-1)), 0);
+ return v0;
+}
+
+carrayprotounshift(ex: ref Exec, nil, this: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ leng := int toUint32(ex, esget(ex, this, "length", 0));
+ nargs := len args;
+ for(i := leng-1; i >= 0; i--){
+ v := esget(ex, this, string i, 0);
+ if(v == undefined)
+ esdelete(ex, this, string (i+nargs), 0);
+ else
+ esput(ex, this, string (i+nargs), v, 0);
+ }
+ for(i = 0; i < nargs; i++)
+ esput(ex, this, string i, biarg(args, i), 0);
+ nv := numval(real (leng+nargs));
+ esput(ex, this, "length", nv, 0);
+ return nv;
+}
+
+carrayprotoslice(ex: ref Exec, nil, this: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ a := narray(ex, nil, nil);
+ leng := int toUint32(ex, esget(ex, this, "length", 0));
+ start := toInt32(ex, biarg(args, 0));
+ if(start < 0) start += leng;
+ if(start < 0) start = 0;
+ if(start > leng) start = leng;
+ if(biarg(args, 1) == undefined)
+ end := leng;
+ else
+ end = toInt32(ex, biarg(args, 1));
+ if(end < 0) end += leng;
+ if(end < 0) end = 0;
+ if(end > leng) end = leng;
+ n := 0;
+ for(k := start; k < end; k++){
+ v := esget(ex, this, string k, 0);
+ if(v != undefined)
+ esput(ex, a, string n, v, 0);
+ n++;
+ }
+ esput(ex, a, "length", numval(real n), 0);
+ return objval(a);
+}
+
+carrayprotosplice(ex: ref Exec, nil, this: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ a := narray(ex, nil, nil);
+ leng := int toUint32(ex, esget(ex, this, "length", 0));
+ start := toInt32(ex, biarg(args, 0));
+ if(start < 0) start += leng;
+ if(start < 0) start = 0;
+ if(start > leng) start = leng;
+ delc := toInt32(ex, biarg(args, 1));
+ if(delc < 0) delc = 0;
+ if(start+delc > leng) delc = leng-start;
+ for(k := 0; k < delc; k++){
+ v := esget(ex, this, string (k+start), 0);
+ if(v != undefined)
+ esput(ex, a, string k, v, 0);
+ }
+ esput(ex, a, "length", numval(real delc), 0);
+ nargs := len args - 2;
+ if(nargs < delc){
+ for(k = start; k < leng-delc; k++){
+ v := esget(ex, this, string (k+delc), 0);
+ if(v == undefined)
+ esdelete(ex, this, string (k+nargs), 0);
+ else
+ esput(ex, this, string (k+nargs), v, 0);
+ }
+ for(k = leng; k > leng-delc+nargs; k--)
+ esdelete(ex, this, string (k-1), 0);
+ }
+ else if(nargs > delc){
+ for(k = leng-delc; k > start; k--){
+ v := esget(ex, this, string (k+delc-1), 0);
+ if(v == undefined)
+ esdelete(ex, this, string (k+nargs-1), 0);
+ else
+ esput(ex, this, string (k+nargs-1), v, 0);
+ }
+ }
+ for(k = start; k < start+nargs; k++)
+ esput(ex, this, string k, biarg(args, k-start+2), 0);
+ esput(ex, this, "length", numval(real (leng-delc+nargs)), 0);
+ return objval(a);
+}
+
+carrayprotosort(ex: ref Exec, nil, this: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ length := toUint32(ex, esget(ex, this, "length", 0));
+ cmp := biarg(args, 0);
+ if(cmp == undefined)
+ cmp = nil;
+ else if(!isobj(cmp) || cmp.obj.call == nil)
+ runtime(ex, TypeError, "Array.prototype.sort argument is not a function");
+
+ #
+ # shell sort
+ #
+ for(m := (length+big 3)/big 5; m > big 0; m = (m+big 1)/big 3){
+ for(i := length-m; i-- != big 0;){
+ v1, v2 : ref Val = nil;
+ ji := big -1;
+ for(j := i+m; j < length; j += m){
+ if(v1 == nil)
+ v1 = esget(ex, this, string(j-m), 0);
+ v2 = esget(ex, this, string(j), 0);
+ cr : real;
+ if(v1 == undefined && v2 == undefined)
+ cr = 0.;
+ else if(v1 == undefined)
+ cr = 1.;
+ else if(v2 == undefined)
+ cr = -1.;
+ else if(cmp == nil){
+ s1 := toString(ex, v1);
+ s2 := toString(ex, v2);
+ if(s1 < s2)
+ cr = -1.;
+ else if(s1 > s2)
+ cr = 1.;
+ else
+ cr = 0.;
+ }else{
+ #
+ # this value not specified by docs
+ #
+ cr = toNumber(ex, getValue(ex, escall(ex, cmp.obj, this, array[] of {v1, v2}, 0)));
+ }
+ if(cr <= 0.)
+ break;
+ if(v2 == undefined)
+ esdelete(ex, this, string(j-m), 0);
+ else
+ esput(ex, this, string(j-m), v2, 0);
+ ji = j;
+ }
+ if(ji != big -1){
+ if(v1 == undefined)
+ esdelete(ex, this, string(ji), 0);
+ else
+ esput(ex, this, string(ji), v1, 0);
+ }
+ }
+ }
+ return objval(this);
+}
+
+cstr(ex: ref Exec, nil, nil: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ s := "";
+ if(len args > 0)
+ s = toString(ex, biarg(args, 0));
+ return strval(s);
+}
+
+cstrfromCharCode(ex: ref Exec, nil, nil: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ s := "";
+ for(i := 0; i < len args; i++)
+ s[i] = toUint16(ex, args[i]);
+ return strval(s);
+}
+
+cstrprototoString(ex: ref Exec, nil, this: ref Ecmascript->Obj, nil: array of ref Val): ref Val
+{
+ if(!isstrobj(this))
+ runtime(ex, TypeError, "String.prototype.toString called on non-String object");
+ return this.val;
+}
+
+cstrprotocharAt(ex: ref Exec, nil, this: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ s := toString(ex, objval(this));
+ rpos := toInteger(ex, biarg(args, 0));
+ if(rpos < 0. || rpos >= real len s)
+ s = "";
+ else{
+ pos := int rpos;
+ s = s[pos: pos+1];
+ }
+ return strval(s);
+}
+
+cstrprotocharCodeAt(ex: ref Exec, nil, this: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ s := toString(ex, objval(this));
+ rpos := toInteger(ex, biarg(args, 0));
+ if(rpos < 0. || rpos >= real len s)
+ c := Math->NaN;
+ else
+ c = real s[int rpos];
+ return numval(c);
+}
+
+cstrprotoindexOf(ex: ref Exec, nil, this: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ s := toString(ex, objval(this));
+ t := toString(ex, biarg(args, 0));
+ rpos := toInteger(ex, biarg(args, 1));
+ if(rpos < 0.)
+ rpos = 0.;
+ else if(rpos > real len s)
+ rpos = real len s;
+ lent := len t;
+ stop := len s - lent;
+ for(i := int rpos; i <= stop; i++)
+ if(s[i:i+lent] == t)
+ break;
+ if(i > stop)
+ i = -1;
+ return numval(real i);
+}
+
+cstrprotolastindexOf(ex: ref Exec, nil, this: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ s := toString(ex, objval(this));
+ t := toString(ex, biarg(args, 0));
+ v := biarg(args, 1);
+ rpos := toNumber(ex, v);
+ if(math->isnan(rpos))
+ rpos = Math->Infinity;
+ else
+ rpos = toInteger(ex, v);
+ if(rpos < 0.)
+ rpos = 0.;
+ else if(rpos > real len s)
+ rpos = real len s;
+ lent := len t;
+ i := len s - lent;
+ if(i > int rpos)
+ i = int rpos;
+ for(; i >= 0; i--)
+ if(s[i:i+lent] == t)
+ break;
+ return numval(real i);
+}
+
+cstrprotosplit(ex: ref Exec, nil, this: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ s := toString(ex, objval(this));
+ a := narray(ex, nil, nil);
+ tv := biarg(args, 0);
+ ai := 0;
+ if(tv == undefined)
+ esput(ex, a, string ai, strval(s), 0);
+ else{
+ t := toString(ex, tv);
+ lent := len t;
+ stop := len s - lent;
+ pos := 0;
+ if(lent == 0){
+ for(; pos < stop; pos++)
+ esput(ex, a, string ai++, strval(s[pos:pos+1]), 0);
+ }else{
+ for(k := pos; k <= stop; k++){
+ if(s[k:k+lent] == t){
+ esput(ex, a, string ai++, strval(s[pos:k]), 0);
+ pos = k + lent;
+ k = pos - 1;
+ }
+ }
+ esput(ex, a, string ai, strval(s[pos:k]), 0);
+ }
+ }
+ return objval(a);
+}
+
+cstrprotosubstring(ex: ref Exec, nil, this: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ s := toString(ex, objval(this));
+ rstart := toInteger(ex, biarg(args, 0));
+ lens := real len s;
+ rend := lens;
+ if(len args >= 2)
+ rend = toInteger(ex, biarg(args, 1));
+ if(rstart < 0.)
+ rstart = 0.;
+ else if(rstart > lens)
+ rstart = lens;
+ if(rend < 0.)
+ rend = 0.;
+ else if(rend > lens)
+ rend = lens;
+ if(rstart > rend){
+ lens = rstart;
+ rstart = rend;
+ rend = lens;
+ }
+ return strval(s[int rstart: int rend]);
+}
+
+cstrprotosubstr(ex: ref Exec, nil, this: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ s := toString(ex, objval(this));
+ ls := len s;
+ start := toInt32(ex, biarg(args, 0));
+ if(biarg(args, 1) == undefined)
+ leng := ls;
+ else
+ leng = toInt32(ex, biarg(args, 1));
+ if(start < 0)
+ start += ls;
+ if(start < 0)
+ start = 0;
+ if(leng < 0)
+ leng = 0;
+ if(start+leng > ls)
+ leng = ls-start;
+ if(leng <= 0)
+ s = "";
+ else
+ s = s[start: start+leng];
+ return strval(s);
+}
+
+cstrprotoslice(ex: ref Exec, nil, this: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ s := toString(ex, objval(this));
+ ls := len s;
+ start := toInt32(ex, biarg(args, 0));
+ if(biarg(args, 1) == undefined)
+ end := ls;
+ else
+ end = toInt32(ex, biarg(args, 1));
+ if(start < 0)
+ start += ls;
+ if(start < 0)
+ start = 0;
+ if(start > ls)
+ start = ls;
+ if(end < 0)
+ end += ls;
+ if(end < 0)
+ end = 0;
+ if(end > ls)
+ end = ls;
+ leng := end-start;
+ if(leng < 0)
+ leng = 0;
+ return strval(s[start: start+leng]);
+}
+
+cstrprotocmp(ex: ref Exec, nil, this: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ s := toString(ex, objval(this));
+ t := toString(ex, biarg(args, 0));
+ r := 0;
+ if(s < t)
+ r = -1;
+ else if(s > t)
+ r = 1;
+ return numval(real r);
+}
+
+cstrprotoconcat(ex: ref Exec, nil, this: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ s := toString(ex, objval(this));
+ n := len args;
+ for(i := 0; i < n; i++)
+ s += toString(ex, biarg(args, i));
+ return strval(s);
+}
+
+# this doesn't use unicode tolower
+cstrprototoLowerCase(ex: ref Exec, nil, this: ref Ecmascript->Obj, nil: array of ref Val): ref Val
+{
+ s := toString(ex, objval(this));
+ for(i := 0; i < len s; i++)
+ s[i] = tolower(s[i]);
+ return strval(s);
+}
+
+#this doesn't use unicode toupper
+cstrprototoUpperCase(ex: ref Exec, nil, this: ref Ecmascript->Obj, nil: array of ref Val): ref Val
+{
+ s := toString(ex, objval(this));
+ for(i := 0; i < len s; i++)
+ s[i] = toupper(s[i]);
+ return strval(s);
+}
+
+cbool(ex: ref Exec, nil, nil: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ return toBoolean(ex, biarg(args, 0));
+}
+
+tolower(c: int): int
+{
+ if(c >= 'A' && c <= 'Z')
+ return c - 'A' + 'a';
+ return c;
+}
+
+toupper(c: int): int
+{
+ if(c >= 'a' && c <= 'a')
+ return c - 'a' + 'A';
+ return c;
+}
+
+cboolprototoString(ex: ref Exec, nil, this: ref Ecmascript->Obj, nil: array of ref Val): ref Val
+{
+ if(!isboolobj(this))
+ runtime(ex, TypeError, "Boolean.prototype.toString called on non-Boolean object");
+ return strval(toString(ex, this.val));
+}
+
+cboolprotovalueOf(ex: ref Exec, nil, this: ref Ecmascript->Obj, nil: array of ref Val): ref Val
+{
+ if(!isboolobj(this))
+ runtime(ex, TypeError, "Boolean.prototype.valueOf called on non-Boolean object");
+ return this.val;
+}
+
+cnum(ex: ref Exec, nil, nil: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ r := 0.;
+ if(len args > 0)
+ r = toNumber(ex, biarg(args, 0));
+ return numval(r);
+}
+
+cnumprototoString(ex: ref Exec, nil, this: ref Ecmascript->Obj, nil: array of ref Val): ref Val
+{
+ if(!isnumobj(this))
+ runtime(ex, TypeError, "Number.prototype.toString called on non-Number object");
+ return this.val;
+}
+
+cnumprotovalueOf(ex: ref Exec, nil, this: ref Ecmascript->Obj, nil: array of ref Val): ref Val
+{
+ if(!isnumobj(this))
+ runtime(ex, TypeError, "Number.prototype.valueOf called on non-Number object");
+ return strval(toString(ex, this.val));
+}
+
+cnumprotofix(ex: ref Exec, nil, this: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ if(!isnumobj(this))
+ runtime(ex, TypeError, "Number.prototype.toFixed called on non-Number object");
+ v := biarg(args, 0);
+ if(v == undefined)
+ f := 0;
+ else
+ f = toInt32(ex, v);
+ if(f < 0 || f > 20)
+ runtime(ex, RangeError, "fraction digits out of range");
+ x := toNumber(ex, this.val);
+ if(isnan(x) || x == Infinity || x == -Infinity)
+ s := toString(ex, this.val);
+ else
+ s = sys->sprint("%.*f", f, x);
+ return strval(s);
+}
+
+cnumprotoexp(ex: ref Exec, nil, this: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ if(!isnumobj(this))
+ runtime(ex, TypeError, "Number.prototype.toExponential called on non-Number object");
+ v := biarg(args, 0);
+ if(v == undefined)
+ f := 6;
+ else
+ f = toInt32(ex, v);
+ if(f < 0 || f > 20)
+ runtime(ex, RangeError, "fraction digits out of range");
+ x := toNumber(ex, this.val);
+ if(isnan(x) || x == Infinity || x == -Infinity)
+ s := toString(ex, this.val);
+ else
+ s = sys->sprint("%.*e", f, x);
+ return strval(s);
+}
+
+cnumprotoprec(ex: ref Exec, nil, this: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ if(!isnumobj(this))
+ runtime(ex, TypeError, "Number.prototype.toPrecision called on non-Number object");
+ v := biarg(args, 0);
+ if(v == undefined)
+ return strval(toString(ex, this.val));
+ p := toInt32(ex, v);
+ if(p < 1 || p > 21)
+ runtime(ex, RangeError, "fraction digits out of range");
+ x := toNumber(ex, this.val);
+ if(isnan(x) || x == Infinity || x == -Infinity)
+ s := toString(ex, this.val);
+ else{
+ y := x;
+ if(y < 0.0)
+ y = -y;
+ er := math->log10(y);
+ (e, ef) := math->modf(er);
+ if(ef < 0.0)
+ e--;
+ if(e < -6 || e >=p)
+ s = sys->sprint("%.*e", p-1, x);
+ else
+ s = sys->sprint("%.*f", p-1, x);
+ }
+ return strval(s);
+}
diff --git a/appl/lib/ecmascript/date.b b/appl/lib/ecmascript/date.b
new file mode 100644
index 00000000..e84d00b6
--- /dev/null
+++ b/appl/lib/ecmascript/date.b
@@ -0,0 +1,495 @@
+# the Date object is founded on the Daytime module
+
+UTC: con 1;
+msPerSec: con big 1000;
+
+# based on Daytime->Tm with big fields
+bigTm: adt {
+ ms: big;
+ sec: big;
+ min: big;
+ hour: big;
+ mday: big;
+ mon: big;
+ year: big;
+ tzoff: int;
+};
+
+isfinite(r: real): int
+{
+ if(math->isnan(r) || r == +Infinity || r == -Infinity)
+ return 0;
+ return 1;
+}
+
+time2Tm(t: real, utc: int): ref Daytime->Tm
+{
+ secs := int(big t / msPerSec);
+ if(big t % msPerSec < big 0) # t<0?
+ secs -= 1;
+ if(utc)
+ tm := daytime->gmt(secs);
+ else
+ tm = daytime->local(secs);
+ return tm;
+}
+
+time2bigTm(t: real, utc: int): ref bigTm
+{
+ tm := time2Tm(t, utc);
+ btm := ref bigTm;
+ btm.ms = big t % msPerSec;
+ if(btm.ms < big 0)
+ btm.ms += msPerSec;
+ btm.sec = big tm.sec;
+ btm.min = big tm.min;
+ btm.hour = big tm.hour;
+ btm.mday = big tm.mday;
+ btm.mon = big tm.mon;
+ btm.year = big tm.year;
+ btm.tzoff = tm.tzoff;
+ return btm;
+}
+
+bigTm2time(btm: ref bigTm): real
+{
+ # normalize
+ if(btm.mon / big 12 != big 0){
+ btm.year += btm.mon / big 12;
+ btm.mon %= big 12;
+ }
+ if(btm.ms / msPerSec != big 0){
+ btm.sec += btm.ms / msPerSec;
+ btm.ms %= msPerSec;
+ }
+ if(btm.sec / big 60 != big 0){
+ btm.min += btm.sec / big 60;
+ btm.sec %= big 60;
+ }
+ if(btm.min / big 60 != big 0){
+ btm.hour += btm.hour / big 60;
+ btm.min %= big 60;
+ }
+ if(btm.hour / big 24 != big 0){
+ btm.mday += btm.mday / big 24;
+ btm.hour %= big 24;
+ }
+
+ tm := ref Tm;
+ tm.sec = int btm.sec;
+ tm.min = int btm.min;
+ tm.hour = int btm.hour;
+ tm.mday = int btm.mday;
+ tm.mon = int btm.mon;
+ tm.year = int btm.year;
+ tm.tzoff = btm.tzoff;
+ secs := daytime->tm2epoch(tm);
+ # check for out-of-band times
+ if(secs == daytime->tm2epoch(daytime->gmt(secs)))
+ r := real(big secs * msPerSec + btm.ms);
+ else
+ r = Math->NaN;
+ return r;
+}
+
+str2time(s: string): real
+{
+ tm := daytime->string2tm(s);
+ if(tm == nil)
+ r := Math->NaN;
+ else
+ r = real (big daytime->tm2epoch(tm) * msPerSec);
+ return r;
+}
+
+cdate(nil: ref Exec, nil, nil: ref Ecmascript->Obj, nil: array of ref Val): ref Val
+{
+ return strval(daytime->time());
+}
+
+# process arguments of Date() [called as constructor] and Date.UTC()
+datectorargs(ex: ref Exec, args: array of ref Val): (int, ref bigTm)
+{
+ x := array[7] of { * => big 0 };
+ ok := 1;
+ for(i := 0; i < 7 && i < len args; i++){
+ if(!isfinite(toNumber(ex, biarg(args, i))))
+ ok = 0;
+ else
+ x[i] = big toInteger(ex, biarg(args, i));
+ }
+ btm := ref bigTm;
+ yr := x[0];
+ if(yr >= big 0 && yr <= big 99)
+ btm.year = yr;
+ else
+ btm.year = yr - big 1900;
+ btm.mon = x[1];
+ btm.mday = x[2];
+ btm.hour = x[3];
+ btm.min = x[4];
+ btm.sec = x[5];
+ btm.ms = x[6];
+ return (ok, btm);
+}
+
+ndate(ex: ref Exec, nil: ref Ecmascript->Obj, args: array of ref Val): ref Ecmascript->Obj
+{
+ o := mkobj(ex.dateproto, "Date");
+ r := Math->NaN;
+ case len args{
+ 0 =>
+ r = real(big daytime->now() * msPerSec);
+ 1 =>
+ v := toPrimitive(ex, biarg(args, 0), NoHint);
+ if(isstr(v))
+ r = str2time(v.str);
+ else if(isfinite(toNumber(ex, v))){
+ t := big toInteger(ex, v);
+ secs := t / msPerSec;
+ if(big t % msPerSec < big 0)
+ secs -= big 1;
+ if(secs == big int secs)
+ r = real t;
+ }
+ * =>
+ (ok, btm) := datectorargs(ex, args);
+ if(ok){
+ tm := daytime->local(daytime->now());
+ btm.tzoff = tm.tzoff;
+ r = bigTm2time(btm);
+ }
+ }
+ o.val = numval(r);
+ return o;
+}
+
+cdateparse(ex: ref Exec, nil, nil: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ s := toString(ex, biarg(args, 0));
+ return numval(str2time(s));
+}
+
+cdateUTC(ex: ref Exec, nil, nil: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ r := Math->NaN;
+ if(len args == 0)
+ r = real(big daytime->now() * msPerSec);
+ else{
+ (ok, btm) := datectorargs(ex, args);
+ if(ok){
+ btm.tzoff = 0;
+ r = bigTm2time(btm);
+ }
+ }
+ return numval(r);
+}
+
+datecheck(ex: ref Exec, o: ref Ecmascript->Obj, f: string)
+{
+ if(!isdateobj(o))
+ runtime(ex, TypeError, "Date.prototype." + f + " called on non-Date object");
+}
+
+cdateprototoString(ex: ref Exec, f, this: ref Ecmascript->Obj, nil: array of ref Val): ref Val
+{
+ datecheck(ex, this, f.val.str);
+ secs := 0;
+ t := this.val.num;
+ if(!math->isnan(t)){
+ secs = int(big t / msPerSec);
+ if(big t % msPerSec < big 0)
+ secs -= 1;
+ }
+ return strval(daytime->text(daytime->local(secs)));
+}
+
+cdateprototoDateString(ex: ref Exec, f, this: ref Ecmascript->Obj, nil: array of ref Val): ref Val
+{
+ datecheck(ex, this, f.val.str);
+ secs := 0;
+ t := this.val.num;
+ if(!math->isnan(t)){
+ secs = int(big t / msPerSec);
+ if(big t % msPerSec < big 0)
+ secs -= 1;
+ }
+ s := daytime->text(daytime->local(secs));
+ (n, ls) := sys->tokenize(s, " ");
+ if(n < 3)
+ return strval("");
+ return strval(hd ls + " " + hd tl ls + " " + hd tl tl ls);
+}
+
+cdateprototoTimeString(ex: ref Exec, f, this: ref Ecmascript->Obj, nil: array of ref Val): ref Val
+{
+ datecheck(ex, this, f.val.str);
+ secs := 0;
+ t := this.val.num;
+ if(!math->isnan(t)){
+ secs = int(big t / msPerSec);
+ if(big t % msPerSec < big 0)
+ secs -= 1;
+ }
+ s := daytime->text(daytime->local(secs));
+ (n, ls) := sys->tokenize(s, " ");
+ if(n < 4)
+ return strval("");
+ return strval(hd tl tl tl ls);
+}
+
+cdateprotovalueOf(ex: ref Exec, f, this: ref Ecmascript->Obj, nil: array of ref Val): ref Val
+{
+ datecheck(ex, this, f.val.str);
+ return this.val;
+}
+
+cdateprotoget(ex: ref Exec, f, this: ref Ecmascript->Obj, nil: array of ref Val, utc: int): ref Val
+{
+ datecheck(ex, this, f.val.str);
+ t := this.val.num;
+ if(!math->isnan(t)){
+ tm := time2Tm(t, utc);
+ case f.val.str{
+ "Date.prototype.getYear" =>
+ if (tm.year < 0 || tm.year > 99)
+ t = real(tm.year + 1900);
+ else
+ t = real tm.year;
+ "Date.prototype.getFullYear" or
+ "Date.prototype.getUTCFullYear" =>
+ t = real(tm.year + 1900);
+ "Date.prototype.getMonth" or
+ "Date.prototype.getUTCMonth" =>
+ t = real tm.mon;
+ "Date.prototype.getDate" or
+ "Date.prototype.getUTCDate" =>
+ t = real tm.mday;
+ "Date.prototype.getDay" or
+ "Date.prototype.getUTCDay" =>
+ t = real tm.wday;
+ "Date.prototype.getHours" or
+ "Date.prototype.getUTCHours" =>
+ t = real tm.hour;
+ "Date.prototype.getMinutes" or
+ "Date.prototype.getUTCMinutes" =>
+ t = real tm.min;
+ "Date.prototype.getSeconds" or
+ "Date.prototype.getUTCSeconds" =>
+ t = real tm.sec;
+ }
+ }
+ return numval(t);
+}
+
+cdateprotogetMilliseconds(ex: ref Exec, f, this: ref Ecmascript->Obj, nil: array of ref Val): ref Val
+{
+ datecheck(ex, this, f.val.str);
+ t := this.val.num;
+ if(!math->isnan(t)){
+ ms := big t % msPerSec;
+ if(ms < big 0)
+ ms += msPerSec;
+ t = real ms;
+ }
+ return numval(t);
+}
+
+cdateprotogetTimezoneOffset(ex: ref Exec, f, this: ref Ecmascript->Obj, nil: array of ref Val): ref Val
+{
+ datecheck(ex, this, f.val.str);
+ t := this.val.num;
+ if(!math->isnan(t)){
+ tm := time2Tm(t, !UTC);
+ t = real(tm.tzoff / 60);
+ }
+ return numval(t);
+}
+
+# process arguments of Date.prototype.set*() functions
+dateprotosetargs(ex: ref Exec, this: ref Ecmascript->Obj, args: array of ref Val, n: int): (int, big, big, big, big)
+{
+ x := array[4] of { * => big 0 };
+ ok := 1;
+ if(this != nil && !isfinite(this.val.num))
+ ok = 0;
+ for(i := 0; i < n && i < len args; i++){
+ if(!isfinite(toNumber(ex, biarg(args, i))))
+ ok = 0;
+ else
+ x[i] = big toInteger(ex, biarg(args, i));
+ }
+ return (ok, x[0], x[1], x[2], x[3]);
+}
+
+cdateprotosetTime(ex: ref Exec, f, this: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ datecheck(ex, this, f.val.str);
+ r := Math->NaN;
+ (ok, t, nil, nil, nil) := dateprotosetargs(ex, nil, args, 1);
+ if(ok){
+ secs := t / msPerSec;
+ if(big t % msPerSec < big 0)
+ secs -= big 1;
+ if(secs == big int secs)
+ r = real t;
+ }
+ this.val.num = r;
+ return numval(r);
+}
+
+cdateprotosetMilliseconds(ex: ref Exec, f, this: ref Ecmascript->Obj, args: array of ref Val, utc: int): ref Val
+{
+ datecheck(ex, this, f.val.str);
+ r := Math->NaN;
+ (ok, ms, nil, nil, nil) := dateprotosetargs(ex, this, args, 1);
+ if(ok){
+ btm := time2bigTm(this.val.num, utc);
+ btm.ms = ms;
+ r = bigTm2time(btm);
+ }
+ this.val.num = r;
+ return numval(r);
+}
+
+cdateprotosetSeconds(ex: ref Exec, f, this: ref Ecmascript->Obj, args: array of ref Val, utc: int): ref Val
+{
+ datecheck(ex, this, f.val.str);
+ r := Math->NaN;
+ (ok, sec, ms, nil, nil) := dateprotosetargs(ex, this, args, 2);
+ if(ok){
+ btm := time2bigTm(this.val.num, utc);
+ btm.sec = sec;
+ if(len args > 1)
+ btm.ms = ms;
+ r = bigTm2time(btm);
+ }
+ this.val.num = r;
+ return numval(r);
+}
+
+cdateprotosetMinutes(ex: ref Exec, f, this: ref Ecmascript->Obj, args: array of ref Val, utc: int): ref Val
+{
+ datecheck(ex, this, f.val.str);
+ r := Math->NaN;
+ (ok, min, sec, ms, nil) := dateprotosetargs(ex, this, args, 3);
+ if(ok){
+ btm := time2bigTm(this.val.num, utc);
+ btm.min = min;
+ if(len args > 1){
+ btm.sec = sec;
+ if(len args > 2)
+ btm.ms = ms;
+ }
+ r = bigTm2time(btm);
+ }
+ this.val.num = r;
+ return numval(r);
+}
+
+cdateprotosetHours(ex: ref Exec, f, this: ref Ecmascript->Obj, args: array of ref Val, utc: int): ref Val
+{
+ datecheck(ex, this, f.val.str);
+ r := Math->NaN;
+ (ok, hour, min, sec, ms) := dateprotosetargs(ex, this, args, 4);
+ if(ok){
+ btm := time2bigTm(this.val.num, utc);
+ btm.hour = hour;
+ if(len args > 1){
+ btm.min = min;
+ if(len args > 2){
+ btm.sec = sec;
+ if(len args > 3)
+ btm.ms = ms;
+ }
+ }
+ r = bigTm2time(btm);
+ }
+ this.val.num = r;
+ return numval(r);
+}
+
+cdateprotosetDate(ex: ref Exec, f, this: ref Ecmascript->Obj, args: array of ref Val, utc: int): ref Val
+{
+ datecheck(ex, this, f.val.str);
+ r := Math->NaN;
+ (ok, day, nil, nil, nil) := dateprotosetargs(ex, this, args, 1);
+ if(ok){
+ btm := time2bigTm(this.val.num, utc);
+ btm.mday = day;
+ r = bigTm2time(btm);
+ }
+ this.val.num = r;
+ return numval(r);
+}
+
+cdateprotosetMonth(ex: ref Exec, f, this: ref Ecmascript->Obj, args: array of ref Val, utc: int): ref Val
+{
+ datecheck(ex, this, f.val.str);
+ r := Math->NaN;
+ (ok, mon, day, nil, nil) := dateprotosetargs(ex, this, args, 2);
+ if(ok){
+ btm := time2bigTm(this.val.num, utc);
+ btm.mon = mon;
+ if(len args > 1)
+ btm.mday = day;
+ r = bigTm2time(btm);
+ }
+ this.val.num = r;
+ return numval(r);
+}
+
+cdateprotosetFullYear(ex: ref Exec, f, this: ref Ecmascript->Obj, args: array of ref Val, utc: int): ref Val
+{
+ datecheck(ex, this, f.val.str);
+ r := Math->NaN;
+ (ok, year, mon, day, nil) := dateprotosetargs(ex, nil, args, 3);
+ if(ok){
+ t := this.val.num;
+ if(!isfinite(t))
+ t = 0.;
+ btm := time2bigTm(t, utc);
+ btm.year = (year - big 1900);
+ if(len args > 1){
+ btm.mon = mon;
+ if(len args > 2)
+ btm.mday = day;
+ }
+ r = bigTm2time(btm);
+ }
+ this.val.num = r;
+ return numval(r);
+}
+
+cdateprotosetYear(ex: ref Exec, f, this: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ datecheck(ex, this, f.val.str);
+ r := Math->NaN;
+ (ok, year, nil, nil, nil) := dateprotosetargs(ex, nil, args, 1);
+ if(ok){
+ t := this.val.num;
+ if(!isfinite(t))
+ t = 0.;
+ btm := time2bigTm(t, !UTC);
+ if(year >= big 0 && year <= big 99)
+ btm.year = year;
+ else
+ btm.year = year - big 1900;
+ r = bigTm2time(btm);
+ }
+ this.val.num = r;
+ return numval(r);
+}
+
+cdateprototoUTCString(ex: ref Exec, f, this: ref Ecmascript->Obj, nil: array of ref Val): ref Val
+{
+ datecheck(ex, this, f.val.str);
+ secs := 0;
+ t := this.val.num;
+ if(!math->isnan(t)){
+ secs = int(big t / msPerSec);
+ if(big t % msPerSec < big 0)
+ secs -= 1;
+ }
+ return strval(daytime->text(daytime->gmt(secs)));
+}
diff --git a/appl/lib/ecmascript/ecmascript.b b/appl/lib/ecmascript/ecmascript.b
new file mode 100644
index 00000000..ccbce016
--- /dev/null
+++ b/appl/lib/ecmascript/ecmascript.b
@@ -0,0 +1,2626 @@
+implement Ecmascript;
+
+include "sys.m";
+include "math.m";
+include "string.m";
+include "daytime.m";
+include "ecmascript.m";
+
+include "pprint.b";
+include "obj.b";
+include "exec.b";
+include "date.b";
+include "builtin.b";
+include "regexp.b";
+include "uri.b";
+
+FF: con '\u000c';
+LS: con '\u2028';
+PS: con '\u2029';
+
+islt(c: int): int
+{
+ return c == '\n' || c == '\r' || c == LS || c == PS;
+}
+
+me: ESHostobj;
+
+sys: Sys;
+print, sprint: import sys;
+stdout: ref Sys->FD;
+
+math: Math;
+ isnan, floor, copysign, fabs, fmod, NaN, Infinity: import math;
+
+str: String;
+
+daytime: Daytime;
+ Tm: import daytime;
+
+labrec: adt{
+ s: string; # name
+ k: int; # kind
+};
+
+HashSize: con 1024;
+
+Parser: adt
+{
+ ex: ref Exec;
+
+ code: ref Code;
+
+ inloop: int; # parser state
+ incase: int;
+ infunc: int;
+ lastnl: int; # parser state for inserting ;
+ notin: int; # don't allow `in' in expression
+
+ token: int; # lexical token
+ token1: int; # lexical token
+ id: int; # associated value
+ lineno: int;
+
+ src: string; # lexical input state
+ esrc: int;
+ srci: int;
+
+ errors: int;
+ labs: list of ref labrec;
+};
+
+Keywd: adt
+{
+ name: string;
+ token: int;
+};
+
+#
+# lexical tokens and ops
+#
+ Lbase: con 128;
+
+ Leos,
+
+ Landas,
+ Loras,
+ Lxoras,
+ Llshas,
+ Lrshas,
+ Lrshuas,
+ Laddas,
+ Lsubas,
+ Lmulas,
+ Ldivas,
+ Lmodas,
+ Loror,
+ Landand,
+ Leq,
+ Lneq,
+ Lleq,
+ Lgeq,
+ Llsh,
+ Lrsh,
+ Lrshu,
+ Linc,
+ Ldec,
+ Lnum,
+ Lid,
+ Lstr,
+ Lthis,
+ Ltypeof,
+ Ldelete,
+ Lvoid,
+ Lwhile,
+ Lfor,
+ Lbreak,
+ Lcontinue,
+ Lwith,
+ Lreturn,
+ Lfunction,
+ Lvar,
+ Lif,
+ Lelse,
+ Lin,
+ Lnew,
+ Lcase,
+ Ldefault,
+ Lswitch,
+ Ldo,
+ Linstanceof,
+ Lcatch,
+ Lfinally,
+ Lthrow,
+ Ltry,
+ Lregexp,
+ Lseq,
+ Lsne,
+ Lprint,
+
+ Lpostinc, # ops that aren't lexical tokens
+ Lpostdec,
+ Lpresub,
+ Lpreadd,
+ Lcall,
+ Lnewcall,
+ Lgetval,
+ Las,
+ Lasop,
+ Lforin,
+ Lforvar,
+ Lforvarin,
+ Larrinit,
+ Lobjinit,
+ Lnoval,
+ Llabel,
+ Lbreaklab,
+ Lcontinuelab,
+
+ #
+ # reserved words
+ #
+ Labstract,
+ Lboolean,
+ Lbyte,
+ Lchar,
+ Lclass,
+ Lconst,
+ Ldebugger,
+ Ldouble,
+ Lenum,
+ Lexport,
+ Lextends,
+ Lfinal,
+ Lfloat,
+ Lgoto,
+ Limplements,
+ Limport,
+ Lint,
+ Linterface,
+ Llong,
+ Lnative,
+ Lpackage,
+ Lprivate,
+ Lprotected,
+ Lpublic,
+ Lshort,
+ Lstatic,
+ Lsuper,
+ Lsynchronized,
+ Lthrows,
+ Ltransient,
+ Lvolatile: con Lbase + iota;
+
+
+#
+# internals
+#
+
+Mlower, Mupper, Munder, Mdigit, Msign, Mexp, Mhex, Moct: con byte 1 << iota;
+Malpha: con Mupper|Mlower|Munder;
+map := array[256] of
+{
+ '_' or '$' => Munder,
+ '-' or '+' => Msign,
+ 'a' to 'd' or 'f' => Mlower | Mhex,
+ 'e' => Mlower | Mhex | Mexp,
+ 'g' to 'z' => Mlower,
+ 'A' to 'D' or 'F' => Mupper | Mhex,
+ 'E' => Mupper | Mhex | Mexp,
+ 'G' to 'Z' => Mupper,
+ '0' to '7' => Mdigit | Mhex | Moct,
+ '8' to '9' => Mdigit | Mhex,
+ * => byte 0
+};
+
+maxerr: int;
+toterrors: int;
+fabort: int;
+
+escmap := array[] of
+{
+ '\'' => byte '\'',
+ '"' => byte '"',
+ '\\' => byte '\\',
+ 'b' => byte '\b',
+ 'f' => byte FF,
+ 'n' => byte '\n',
+ 'r' => byte '\r',
+ 't' => byte '\t',
+ 'v' => byte FF,
+
+ * => byte 255
+};
+
+#
+# must be sorted
+#
+keywords := array [] of
+{
+ Keywd("abstract", Labstract),
+ Keywd("boolean", Lboolean),
+ Keywd("byte", Lbyte),
+ Keywd("break", Lbreak),
+ Keywd("case", Lcase),
+ Keywd("catch", Lcatch),
+ Keywd("char", Lchar),
+ Keywd("class", Lclass),
+ Keywd("const", Lconst),
+ Keywd("continue", Lcontinue),
+ Keywd("debugger", Ldebugger),
+ Keywd("default", Ldefault),
+ Keywd("delete", Ldelete),
+ Keywd("do", Ldo),
+ Keywd("double", Ldouble),
+ Keywd("else", Lelse),
+ Keywd("enum", Lenum),
+ Keywd("export", Lexport),
+ Keywd("extends ", Lextends),
+ Keywd("final", Lfinal),
+ Keywd("finally", Lfinally),
+ Keywd("float", Lfloat),
+ Keywd("for", Lfor),
+ Keywd("function", Lfunction),
+ Keywd("goto", Lgoto),
+ Keywd("if", Lif),
+ Keywd("implements", Limplements),
+ Keywd("import", Limport),
+ Keywd("in", Lin),
+ Keywd("instanceof", Linstanceof),
+ Keywd("int", Lint),
+ Keywd("interface", Linterface),
+ Keywd("long", Llong),
+ Keywd("native", Lnative),
+ Keywd("new", Lnew),
+ Keywd("package", Lpackage),
+ # Keywd("print", Lprint),
+ Keywd("private", Lprivate),
+ Keywd("protected", Lprotected),
+ Keywd("public", Lpublic),
+ Keywd("return", Lreturn),
+ Keywd("short", Lshort),
+ Keywd("static", Lstatic),
+ Keywd("super", Lsuper),
+ Keywd("switch", Lswitch),
+ Keywd("synchronized", Lsynchronized),
+ Keywd("this", Lthis),
+ Keywd("throw", Lthrow),
+ Keywd("throws", Lthrows),
+ Keywd("transient", Ltransient),
+ Keywd("try", Ltry),
+ Keywd("typeof", Ltypeof),
+ Keywd("var", Lvar),
+ Keywd("void", Lvoid),
+ Keywd("volatile", Lvolatile),
+ Keywd("while", Lwhile),
+ Keywd("with", Lwith),
+};
+
+debug = array[256] of {* => 0};
+
+glbuiltins := array[] of
+{
+ Builtin("eval", "eval", array[] of {"src"}, 1),
+ Builtin("parseInt", "parseInt", array[] of {"string", "radix"}, 2),
+ Builtin("parseFloat", "parseFloat", array[] of {"string"}, 1),
+ Builtin("escape", "escape", array[] of {"string"}, 1),
+ Builtin("unescape", "unescape", array[] of {"string"}, 1),
+ Builtin("isNaN", "isNaN", array[] of {"number"}, 1),
+ Builtin("isFinite", "isFinite", array[] of {"number"}, 1),
+ Builtin("decodeURI", "decodeURI", array[] of {"string"}, 1),
+ Builtin("decodeURIComponent", "decodeURIComponent", array[] of {"string"}, 1),
+ Builtin("encodeURI", "encodeURI", array[] of {"string"}, 1),
+ Builtin("encodeURIComponent", "encodeURIComponent", array[] of {"string"}, 1),
+};
+
+biobj := Builtin("Object", "Object", array[] of {"value"}, 1);
+biobjproto := array[] of
+{
+ Builtin("toString", "Object.prototype.toString", nil, 0),
+ Builtin("toLocaleString", "Object.prototype.toLocaleString", nil, 0),
+ Builtin("valueOf", "Object.prototype.valueOf", nil, 0),
+ Builtin("hasOwnProperty", "Object.prototype.hasOwnProperty", array[] of {"V"}, 1),
+ Builtin("isPrototypeOf", "Object.prototype.isPrototypeOf", array[] of {"V"}, 1),
+ Builtin("propertyisEnumerable", "Object.prototype.propertyisEnumerable", array[] of {"V"}, 1),
+};
+
+bifunc := Builtin("Function", "Function", array[] of {"body"}, 1);
+bifuncproto := array[] of
+{
+ Builtin("toString", "Function.prototype.toString", nil, 0),
+ Builtin("apply", "Function.prototype.apply", array[] of {"this", "array"}, 2),
+ Builtin("call", "Function.prototype.call", array[] of {"this", "arg"}, 1),
+};
+
+bierr := Builtin("Error", "Error", array[] of {"message"}, 1);
+bierrproto := array[] of
+{
+ Builtin("toString", "Error.prototype.toString", nil , 0),
+};
+
+biarray := Builtin("Array", "Array", array[] of {"length"}, 1);
+biarrayproto := array[] of
+{
+ Builtin("toString", "Array.prototype.toString", nil, 0),
+ Builtin("toLocaleString", "Array.prototype.toLocaleString", nil, 0),
+ Builtin("concat", "Array.prototype.concat", array[] of {"item"}, 1),
+ Builtin("join", "Array.prototype.join", array[] of {"separator"}, 1),
+ Builtin("pop", "Array.prototype.pop", nil, 0),
+ Builtin("push", "Array.prototype.push", array[] of {"item"} , 1),
+ Builtin("reverse", "Array.prototype.reverse", nil, 0),
+ Builtin("shift", "Array.prototype.shift", nil, 0),
+ Builtin("slice", "Array.prototype.slice", array[] of {"start", "end"}, 2),
+ Builtin("splice", "Array.prototype.splice", array[] of {"start", "delcnt", "item"}, 2),
+ Builtin("sort", "Array.prototype.sort", array[] of {"comparefunc"}, 1),
+ Builtin("unshift", "Array.prototype.unshift", array[] of {"item"}, 1),
+};
+
+bistr := Builtin("String", "String", array[] of {"value"}, 1);
+bistrproto := array[] of
+{
+ Builtin("toString", "String.prototype.toString", nil, 0),
+ Builtin("valueOf", "String.prototype.valueOf", nil, 0),
+ Builtin("charAt", "String.prototype.charAt", array[] of {"pos"}, 1),
+ Builtin("charCodeAt", "String.prototype.charCodeAt", array[] of {"pos"}, 1),
+ Builtin("concat", "String.prototype.concat", array[] of {"string"}, 1),
+ Builtin("indexOf", "String.prototype.indexOf", array[] of {"string", "pos"}, 2),
+ Builtin("lastIndexOf", "String.prototype.lastIndexOf", array[] of {"string", "pos"}, 2),
+ Builtin("localeCompare", "String.prototype.localeCompare", array[] of {"that"}, 1),
+ Builtin("slice", "String.prototype.slice", array[] of {"start", "end"}, 2),
+ Builtin("split", "String.prototype.split", array[] of {"separator"}, 2),
+ Builtin("substr", "String.prototype.substr", array[] of {"start", "length"}, 2),
+ Builtin("substring", "String.prototype.substring", array[] of {"start", "end"}, 2),
+ Builtin("toLowerCase", "String.prototype.toLowerCase", nil, 0),
+ Builtin("toUpperCase", "String.prototype.toUpperCase", nil, 0),
+ Builtin("toLocaleLowerCase", "String.prototype.toLocaleLowerCase", nil, 0),
+ Builtin("toLocaleUpperCase", "String.prototype.toLocaleUpperCase", nil, 0),
+# JavaScript 1.0
+ Builtin("anchor", "String.prototype.anchor", array[] of {"name"}, 1),
+ Builtin("big", "String.prototype.big", nil, 0),
+ Builtin("blink", "String.prototype.blink", nil, 0),
+ Builtin("bold", "String.prototype.bold", nil, 0),
+ Builtin("fixed", "String.prototype.fixed", nil, 0),
+ Builtin("fontcolor", "String.prototype.fontcolor", array[] of {"color"}, 1),
+ Builtin("fontsize", "String.prototype.fontsize", array[] of {"size"}, 1),
+ Builtin("italics", "String.prototype.italics", nil, 0),
+ Builtin("link", "String.prototype.link", array[] of {"href"}, 1),
+ Builtin("small", "String.prototype.small", nil, 0),
+ Builtin("strike", "String.prototype.strike", nil, 0),
+ Builtin("sub", "String.prototype.sub", nil, 0),
+ Builtin("sup", "String.prototype.sup", nil, 0),
+ Builtin("match", "String.prototype.match", array[] of {"regexp"}, 1),
+ Builtin("replace", "String.prototype.replace", array[] of {"searchval", "replaceval"}, 2),
+ Builtin("search", "String.prototype.search", array[] of {"regexp"}, 1),
+};
+bistrctor := Builtin("fromCharCode", "String.fromCharCode", array[] of {"characters"}, 1);
+
+bibool := Builtin("Boolean", "Boolean", array[] of {"value"}, 1);
+biboolproto := array[] of
+{
+ Builtin("toString", "Boolean.prototype.toString", nil, 0),
+ Builtin("valueOf", "Boolean.prototype.valueOf", nil, 0),
+};
+
+binum := Builtin("Number", "Number", array[] of {"value"}, 1);
+binumproto := array[] of
+{
+ Builtin("toString", "Number.prototype.toString", nil, 0),
+ Builtin("toLocaleString", "Number.prototype.toLocaleString", nil, 0),
+ Builtin("valueOf", "Number.prototype.valueOf", nil, 0),
+ Builtin("toFixed", "Number.prototype.toFixed", array[] of {"digits"}, 1),
+ Builtin("toExponential", "Number.prototype.toExponential", array[] of {"digits"}, 1),
+ Builtin("toPrecision", "Number.prototype.toPrecision", array[] of {"digits"}, 1),
+};
+
+biregexp := Builtin("RegExp", "RegExp", array[] of {"pattern", "flags"}, 2);
+biregexpproto := array[] of
+{
+ Builtin("exec", "RegExp.prototype.exec", array[] of {"string"}, 1),
+ Builtin("test", "RegExp.prototype.test", array[] of {"string"}, 1),
+ Builtin("toString", "RegExp.prototype.toString", nil, 0),
+};
+
+bidate := Builtin("Date", "Date", array[] of {"value"}, 1);
+bidateproto := array[] of
+{
+ Builtin("toString", "Date.prototype.toString", nil, 0),
+ Builtin("toDateString", "Date.prototype.toDateString", nil, 0),
+ Builtin("toTimeString", "Date.prototype.toTimeString", nil, 0),
+ Builtin("toLocaleString", "Date.prototype.toLocalString", nil, 0),
+ Builtin("toLocaleDateString", "Date.prototype.toLocaleDateString", nil, 0),
+ Builtin("toLocaleTimeString", "Date.prototype.toLocaleTimeString", nil, 0),
+ Builtin("valueOf", "Date.prototype.valueOf", nil, 0),
+ Builtin("getTime", "Date.prototype.getTime", nil, 0),
+ Builtin("getYear", "Date.prototype.getYear", nil, 0),
+ Builtin("getFullYear", "Date.prototype.getFullYear", nil, 0),
+ Builtin("getUTCFullYear", "Date.prototype.getUTCFullYear", nil, 0),
+ Builtin("getMonth", "Date.prototype.getMonth", nil, 0),
+ Builtin("getUTCMonth", "Date.prototype.getUTCMonth", nil, 0),
+ Builtin("getDate", "Date.prototype.getDate", nil, 0),
+ Builtin("getUTCDate", "Date.prototype.getUTCDate", nil, 0),
+ Builtin("getDay", "Date.prototype.getDay", nil, 0),
+ Builtin("getUTCDay", "Date.prototype.getUTCDay", nil, 0),
+ Builtin("getHours", "Date.prototype.getHours", nil, 0),
+ Builtin("getUTCHours", "Date.prototype.getUTCHours", nil, 0),
+ Builtin("getMinutes", "Date.prototype.getMinutes", nil, 0),
+ Builtin("getUTCMinutes", "Date.prototype.getUTCMinutes", nil, 0),
+ Builtin("getSeconds", "Date.prototype.getSeconds", nil, 0),
+ Builtin("getUTCSeconds", "Date.prototype.getUTCSeconds", nil, 0),
+ Builtin("getMilliseconds", "Date.prototype.getMilliseconds", nil, 0),
+ Builtin("getUTCMilliseconds", "Date.prototype.getUTCMilliseconds", nil, 0),
+ Builtin("getTimezoneOffset", "Date.prototype.getTimezoneOffset", nil, 0),
+ Builtin("setTime", "Date.prototype.setTime", array[] of {"time"}, 1),
+ Builtin("setMilliseconds", "Date.prototype.setMilliseconds", array[] of {"ms"}, 1),
+ Builtin("setUTCMilliseconds", "Date.prototype.setUTCMilliseconds", array[] of {"ms"}, 1),
+ Builtin("setSeconds", "Date.prototype.setSeconds", array[] of {"sec", "ms"}, 2),
+ Builtin("setUTCSeconds", "Date.prototype.setUTCSeconds", array[] of {"sec", "ms"}, 2),
+ Builtin("setMinutes", "Date.prototype.setMinutes", array[] of {"min", "sec", "ms"}, 3),
+ Builtin("setUTCMinutes", "Date.prototype.setUTCMinutes", array[] of {"min", "sec", "ms"}, 3),
+ Builtin("setHours", "Date.prototype.setHours", array[] of {"hour", "min", "sec", "ms"}, 4),
+ Builtin("setUTCHours", "Date.prototype.setUTCHours", array[] of {"hour", "min", "sec", "ms"}, 4),
+ Builtin("setDate", "Date.prototype.setDate", array[] of {"date"}, 1),
+ Builtin("setUTCDate", "Date.prototype.setUTCDate", array[] of {"date"}, 1),
+ Builtin("setMonth", "Date.prototype.setMonth", array[] of {"mon", "date"}, 2),
+ Builtin("setUTCMonth", "Date.prototype.setUTCMonth", array[] of {"mon", "date"}, 2),
+ Builtin("setFullYear", "Date.prototype.setFullYear", array[] of {"year", "mon", "date"}, 3),
+ Builtin("setUTCFullYear", "Date.prototype.setUTCFullYear", array[] of {"year", "mon", "date"}, 3),
+ Builtin("setYear", "Date.prototype.setYear", array[] of {"year"}, 1),
+ Builtin("toLocaleString", "Date.prototype.toLocaleString", nil, 0),
+ Builtin("toUTCString", "Date.prototype.toUTCString", nil, 0),
+ Builtin("toGMTString", "Date.prototype.toGMTString", nil, 0),
+};
+bidatector := array[] of
+{
+ Builtin("parse", "Date.parse", array[] of {"string"}, 1),
+ Builtin("UTC", "Date.UTC", array[] of {"year", "month", "date", "hours", "minutes", "seconds", "ms"}, 7),
+};
+
+bimath := array[] of
+{
+ Builtin("abs", "Math.abs", array[] of {"x"}, 1),
+ Builtin("acos", "Math.acos", array[] of {"x"}, 1),
+ Builtin("asin", "Math.asin", array[] of {"x"}, 1),
+ Builtin("atan", "Math.atan", array[] of {"x"}, 1),
+ Builtin("atan2", "Math.atan2", array[] of {"y", "x"}, 2),
+ Builtin("ceil", "Math.ceil", array[] of {"x"}, 1),
+ Builtin("cos", "Math.cos", array[] of {"x"}, 1),
+ Builtin("exp", "Math.exp", array[] of {"x"}, 1),
+ Builtin("floor", "Math.floor", array[] of {"x"}, 1),
+ Builtin("log", "Math.log", array[] of {"x"}, 1),
+ Builtin("max", "Math.max", array[] of {"x", "y"}, 2),
+ Builtin("min", "Math.min", array[] of {"x", "y"}, 2),
+ Builtin("pow", "Math.pow", array[] of {"x", "y"}, 2),
+ Builtin("random", "Math.random", nil, 0),
+ Builtin("round", "Math.round", array[] of {"x"}, 1),
+ Builtin("sin", "Math.sin", array[] of {"x"}, 1),
+ Builtin("sqrt", "Math.sqrt", array[] of {"x"}, 1),
+ Builtin("tan", "Math.tan", array[] of {"x"}, 1),
+};
+
+init(): string
+{
+ sys = load Sys Sys->PATH;
+ math = load Math Math->PATH;
+ if(math == nil)
+ return sys->sprint("can't load module %s: %r", Math->PATH);
+
+ str = load String String->PATH;
+ if(str == nil)
+ return sys->sprint("can't load module %s: %r", String->PATH);
+
+ daytime = load Daytime Daytime->PATH;
+ if(daytime == nil)
+ return sys->sprint("can't load module %s: %r", Daytime->PATH);
+
+ me = load ESHostobj SELF;
+ if(me == nil)
+ return "can't load builtin functions";
+
+ randinit(big sys->millisec());
+ stdout = sys->fildes(1);
+ #
+ # maximum number of syntax errors reported
+ #
+ maxerr = 1;
+
+ undefined = ref Val(TUndef, 0., nil, nil, nil);
+ null = ref Val(TNull, 0., nil, nil, nil);
+ true = ref Val(TBool, 1., nil, nil, nil);
+ false = ref Val(TBool, 0., nil, nil, nil);
+ return "";
+}
+
+mkcall(ex : ref Exec, p: array of string): ref Call
+{
+ return ref Call(p, nil, ex);
+}
+
+mkbiobj(ex: ref Exec, meth: Builtin, proto: ref Obj): ref Obj
+{
+ o := biinst(ex.global, meth, ex.funcproto, me);
+ o.construct = o.call;
+ valinstant(o, DontEnum|DontDelete|ReadOnly, "prototype", objval(proto));
+ valinstant(proto, DontEnum, "constructor", objval(o));
+ return o;
+}
+
+mkexec(go: ref Obj): ref Exec
+{
+ o: ref Obj;
+ if(go == nil)
+ go = mkobj(nil, "global");
+ ex := ref Exec;
+ ex.this = go;
+ ex.scopechain = go :: nil;
+ ex.stack = array[4] of ref Ref;
+ ex.sp = 0;
+ ex.global = go;
+
+ #
+ # builtin object prototypes
+ #
+ ex.objproto = mkobj(nil, "Object");
+ ex.funcproto = mkobj(ex.objproto, "Function");
+ ex.arrayproto = mkobj(ex.objproto, "Array");
+ ex.strproto = mkobj(ex.objproto, "String");
+ ex.numproto = mkobj(ex.objproto, "Number");
+ ex.boolproto = mkobj(ex.objproto, "Boolean");
+ ex.dateproto = mkobj(ex.objproto, "Date");
+ ex.regexpproto = mkobj(ex.objproto, "RegExp");
+ ex.errproto = mkobj(ex.objproto, "Error");
+
+ biminst(ex.objproto, biobjproto, ex.funcproto, me);
+
+ biminst(ex.funcproto, bifuncproto, ex.funcproto, me);
+ ex.funcproto.call = mkcall(ex, nil);
+ ex.funcproto.val = strval("Function.prototype");
+ valinstant(ex.funcproto, DontEnum|DontDelete|ReadOnly, "length", numval(real 0));
+
+ biminst(ex.arrayproto, biarrayproto, ex.funcproto, me);
+ valinstant(ex.arrayproto, DontEnum|DontDelete, "length", numval(real 0));
+
+ biminst(ex.errproto, bierrproto, ex.funcproto, me);
+ ex.errproto.val = strval("");
+ valinstant(ex.errproto, DontEnum|DontDelete, "length", numval(real 0));
+ valinstant(ex.errproto, DontEnum|DontDelete, "name", strval(""));
+ valinstant(ex.errproto, DontEnum|DontDelete, "message", strval("Error"));
+
+ biminst(ex.strproto, bistrproto, ex.funcproto, me);
+ ex.strproto.val = strval("");
+ valinstant(ex.strproto, DontEnum|DontDelete|ReadOnly, "length", numval(real 0));
+
+ biminst(ex.boolproto, biboolproto, ex.funcproto, me);
+ ex.boolproto.val = false;
+
+ biminst(ex.numproto, binumproto, ex.funcproto, me);
+ ex.numproto.val = numval(real +0);
+
+ biminst(ex.regexpproto, biregexpproto, ex.funcproto, me);
+ ex.regexpproto.val = strval("");
+ valinstant(ex.regexpproto, DontEnum|DontDelete|ReadOnly, "length", numval(real 2));
+ valinstant(ex.regexpproto, DontEnum|DontDelete|ReadOnly, "source", strval(""));
+ valinstant(ex.regexpproto, DontEnum|DontDelete|ReadOnly, "global", false);
+ valinstant(ex.regexpproto, DontEnum|DontDelete|ReadOnly, "ignoreCase", false);
+ valinstant(ex.regexpproto, DontEnum|DontDelete|ReadOnly, "multiline", false);
+ valinstant(ex.regexpproto, DontEnum|DontDelete, "lastIndex", numval(real 0));
+
+ biminst(ex.dateproto, bidateproto, ex.funcproto, me);
+ ex.dateproto.val = numval(Math->NaN);
+ valinstant(ex.dateproto, DontEnum|DontDelete|ReadOnly, "length", numval(real 7));
+
+ #
+ # simple builtin functions and values
+ #
+ valinstant(go, DontEnum, "NaN", numval(Math->NaN));
+ valinstant(go, DontEnum, "Infinity", numval(Math->Infinity));
+
+ biminst(go, glbuiltins, ex.funcproto, me);
+
+ #
+ # builtin objects, and cross-link them to their prototypes
+ #
+ mkbiobj(ex, biobj, ex.objproto);
+ mkbiobj(ex, bifunc, ex.funcproto);
+ mkbiobj(ex, biarray, ex.arrayproto);
+ o = mkbiobj(ex, bistr, ex.strproto);
+ biinst(o, bistrctor, ex.funcproto, me);
+ mkbiobj(ex, bibool, ex.boolproto);
+ o = mkbiobj(ex, binum, ex.numproto);
+ mkbiobj(ex, biregexp, ex.regexpproto);
+ mkbiobj(ex, bierr, ex.errproto);
+
+ math->FPcontrol(0, Math->INVAL|Math->ZDIV|Math->OVFL|Math->UNFL|Math->INEX);
+
+ valinstant(o, DontEnum|DontDelete|ReadOnly, "MAX_VALUE", numval(math->nextafter(Math->Infinity, 0.)));
+ valinstant(o, DontEnum|DontDelete|ReadOnly, "MIN_VALUE", numval(math->nextafter(0., 1.)));
+ valinstant(o, DontEnum|DontDelete|ReadOnly, "NaN", numval(Math->NaN));
+ valinstant(o, DontEnum|DontDelete|ReadOnly, "NEGATIVE_INFINITY", numval(-Math->Infinity));
+ valinstant(o, DontEnum|DontDelete|ReadOnly, "POSITIVE_INFINITY", numval(+Math->Infinity));
+ o = mkbiobj(ex, bidate, ex.dateproto);
+ biminst(o, bidatector, ex.funcproto, me);
+
+ #
+ # the math object is a little different
+ #
+ o = mkobj(ex.objproto, "Object");
+ valinstant(go, DontEnum, "Math", objval(o));
+ biminst(o, bimath, ex.funcproto, me);
+
+ #
+ # these are computed so they are consistent with numbers ecma might calculate
+ #
+ mathe := math->exp(1.);
+ valinstant(o, DontEnum|DontDelete|ReadOnly, "E", numval(mathe));
+ valinstant(o, DontEnum|DontDelete|ReadOnly, "LN10", numval(math->log(10.)));
+ valinstant(o, DontEnum|DontDelete|ReadOnly, "LN2", numval(math->log(2.)));
+ valinstant(o, DontEnum|DontDelete|ReadOnly, "LOG2E", numval(1./math->log(2.)));
+ valinstant(o, DontEnum|DontDelete|ReadOnly, "LOG10E", numval(1./math->log(10.)));
+ valinstant(o, DontEnum|DontDelete|ReadOnly, "PI", numval(Math->Pi));
+ valinstant(o, DontEnum|DontDelete|ReadOnly, "SQRT1_2", numval(math->sqrt(1./2.)));
+ valinstant(o, DontEnum|DontDelete|ReadOnly, "SQRT2", numval(math->sqrt(2.)));
+
+ (EvalError, ex.evlerrproto) = mkerr(ex, "EvalError");
+ (RangeError, ex.ranerrproto) = mkerr(ex, "RangeError");
+ (ReferenceError, ex.referrproto) = mkerr(ex, "ReferenceError");
+ (SyntaxError, ex.synerrproto) = mkerr(ex, "SyntaxError");
+ (TypeError, ex.typerrproto) = mkerr(ex, "TypeError");
+ (URIError, ex.urierrproto) = mkerr(ex, "URIError");
+ (InternalError, ex.interrproto) = mkerr(ex, "InternalError");
+
+ return ex;
+}
+
+mkerr(ex: ref Exec, e: string): (ref Obj, ref Obj)
+{
+ errproto := mkobj(ex.objproto, e);
+ biminst(errproto, array[] of { Builtin("toString", e+".prototype.toString", nil, 0) }, ex.funcproto, me);
+ errproto.val = strval("");
+ valinstant(errproto, DontEnum|DontDelete, "length", numval(real 0));
+ valinstant(errproto, DontEnum|DontDelete, "name", strval(e));
+ valinstant(errproto, DontEnum|DontDelete, "message", strval(e));
+ eo := mkbiobj(ex, Builtin(e, e, array[] of {"message"}, 1), errproto);
+ # return (eo, errproto);
+ return (nerr(ex, eo, array[] of {strval(e)}, errproto), errproto);
+}
+
+mkparser(ex: ref Exec, src: string): ref Parser
+{
+ p := ref Parser;
+ p.ex = ex;
+ p.src = src;
+ p.esrc = len src;
+ p.srci = 0;
+ p.errors = 0;
+ p.lineno = 1;
+ p.token = -1;
+ p.token1 = -1;
+ p.lastnl = 0;
+ p.inloop = 0;
+ p.incase = 0;
+ p.infunc = 0;
+ p.notin = 0;
+ p.code = mkcode();
+ return p;
+}
+
+eval(ex: ref Exec, src: string): Completion
+{
+ {
+ p := mkparser(ex, src);
+
+ if(debug['t'])
+ parset := sys->millisec();
+
+ prog(ex, p);
+
+ toterrors += p.errors;
+
+ if(p.errors)
+ runtime(ex, SyntaxError, ex.error);
+ if(debug['p']){
+ s := array of byte pprint(ex, p.code, "");
+ if(len s)
+ sys->write(stdout, s, len s);
+ }
+
+ if(debug['t'])
+ xect := sys->millisec();
+
+ globalinstant(hd ex.scopechain, p.code.vars);
+ c := exec(ex, p.code);
+
+ if(debug['t'])
+ print("parse time %d exec time %d\n", xect - parset, sys->millisec() - xect);
+
+ return c;
+ }exception{
+ "throw" =>
+ return (CThrow, ex.errval, nil);
+ }
+}
+
+#prog : selems
+# ;
+#
+#selems : selem
+# | selems selem
+# ;
+#selem : stmt
+# | fundecl
+# ;
+prog(ex: ref Exec, p: ref Parser)
+{
+ while(look(p) != Leos)
+ if(look(p) == Lfunction)
+ fundecl(ex, p, 0);
+ else
+ stmt(p);
+}
+
+#fundecl : Lfunction Lid '(' zplist ')' '{' stmts '}'
+# ;
+#zplist :
+# | plist
+# ;
+#
+#plist : Lid
+# | plist ',' Lid
+# ;
+fundecl(ex: ref Exec, p: ref Parser, expr: int): ref Obj
+{
+ jp: ref Prop;
+
+ c := p.code;
+ labs := p.labs;
+ p.labs = nil;
+ mustbe(p, Lfunction);
+ if(!expr || look(p) == Lid){
+ mustbe(p, Lid);
+ jp = codevar(p, expr);
+ }
+ p.code = mkcode();
+ mustbe(p, '(');
+ if(look(p) != ')'){
+ for(;;){
+ mustbe(p, Lid);
+ codevar(p, 1);
+ if(look(p) == ')')
+ break;
+ mustbe(p, ',');
+ }
+ }
+ params := p.code.vars;
+ p.code.vars = nil;
+ mustbe(p, ')');
+ mustbe(p, '{');
+ p.infunc++;
+ stmts(p);
+ p.infunc--;
+ mustbe(p, '}');
+
+ #
+ # override any existing value,
+ # as per sec. 10.1.3 Variable instantiation
+ #
+ sparams := array[len params] of string;
+ for(i := 0; i < len sparams; i++)
+ sparams[i] = params[i].name;
+
+ #
+ # construct a function object;
+ # see section 15.3.21
+ o := mkobj(ex.funcproto, "Function");
+ o.call = ref Call(sparams, p.code, ex);
+ o.construct = o.call;
+ if(jp != nil)
+ o.val = strval(jp.name);
+ else
+ o.val = strval("");
+ valinstant(o, DontDelete|DontEnum|ReadOnly, "length", numval(real len sparams));
+ po := nobj(ex, nil, nil);
+ valinstant(o, DontEnum, "prototype", objval(po));
+ valinstant(po, DontEnum, "constructor", objval(o));
+ valinstant(o, DontDelete|DontEnum|ReadOnly, "arguments", null);
+ if(jp != nil)
+ jp.val.val = objval(o);
+
+ if(debug['p']){
+ s := array of byte (funcprint(ex, o) + "\n");
+ sys->write(stdout, s, len s);
+ }
+
+ p.code = c;
+ p.labs = labs;
+ if(expr && jp != nil)
+ popvar(p);
+ return o;
+}
+
+#
+# install a variable for the id just lexed
+#
+codevar(p: ref Parser, forcenew: int): ref Prop
+{
+ name := p.code.strs[p.id];
+ vs := p.code.vars;
+ i : int;
+ if(!forcenew){
+ for(i = 0; i < len vs; i++)
+ if(vs[i].name == name)
+ return vs[i];
+ }else{
+ i = len vs;
+ }
+ vs = array[i+1] of ref Prop;
+ vs[:] = p.code.vars;
+ p.code.vars = vs;
+ vs[i] = ref Prop(0, name, ref RefVal(undefined));
+ return vs[i];
+}
+
+popvar(p: ref Parser)
+{
+ vs := p.code.vars;
+ p.code.vars = vs[0: len vs - 1];
+}
+
+#stmts :
+# | stmts stmt
+# ;
+stmts(p: ref Parser)
+{
+ while((op := look(p)) != '}' && op != Leos)
+ stmt(p);
+}
+
+#stmt : '{' stmts '}'
+# | Lvar varlist ';'
+# | exp ';'
+# | ';'
+# | Lif '(' exp ')' stmt
+# | Lif '(' exp ')' stmt Lelse stmt
+# | Lwhile '(' exp ')' stmt
+# | Ldo stmt Lwhile '(' exp ')'
+# | Lfor '(' zexp-notin ';' zexp ';' zexp ')' stmt
+# | Lfor '(' Lvar varlist-notin ';' zexp ';' zexp ')' stmt
+# | Lfor '(' lhsexp Lin exp ')' stmt
+# | Lfor '(' Lvar Lid [init] Lin exp ')' stmt
+# | Lcontinue ';'
+# | Lbreak ';'
+# | Lreturn zexp ';' # no line term after return
+# | Lwith '(' exp ')' stmt
+# | Lswitch '(' exp ')' '{' caseblk '}'
+# | Lthrow exp ';'
+# | Ltry block Lcatch '(' Lid ')' block
+# | Ltry block finally block
+# | Ltry block Lcatch '(' Lid ')' block finally block
+# ;
+stmt(p: ref Parser)
+{
+ pc: int;
+
+ seenlabs := 0;
+ while(look(p) == Lid && look2(p) == ':'){
+ pushlab(p, p.code.strs[p.id]);
+ emitconst(p, Llabel, p.id);
+ lex(p);
+ lex(p);
+ seenlabs++;
+ }
+
+ op := look(p);
+ if(seenlabs)
+ setkindlab(p, op, seenlabs);
+ case op{
+ ';' =>
+ lexemit(p);
+ '{' =>
+ if(seenlabs == 0){
+ lex(p);
+ stmts(p);
+ }
+ else{
+ lexemit(p);
+ pc = epatch(p);
+ stmts(p);
+ patch(p, pc);
+ }
+ mustbe(p, '}');
+ Lvar =>
+ lexemit(p);
+ pc = epatch(p);
+ varlist(p);
+ semi(p);
+ patch(p, pc);
+ * =>
+ exp(p);
+ semi(p);
+ emit(p, ';');
+ Lif =>
+ lexemit(p);
+ pc = epatch(p);
+ mustbe(p, '(');
+ exp(p);
+ mustbe(p, ')');
+ patch(p, pc);
+ pc = epatch(p);
+ stmt(p);
+ patch(p, pc);
+ pc = epatch(p);
+ if(look(p) == Lelse){
+ lex(p);
+ stmt(p);
+ }
+ patch(p, pc);
+ Lwhile or
+ Lwith =>
+ lexemit(p);
+ pc = epatch(p);
+ mustbe(p, '(');
+ exp(p);
+ mustbe(p, ')');
+ patch(p, pc);
+ if(op == Lwhile)
+ p.inloop++;
+ pc = epatch(p);
+ stmt(p);
+ patch(p, pc);
+ if(op == Lwhile)
+ p.inloop--;
+ Ldo =>
+ p.inloop++;
+ lexemit(p);
+ pc = epatch(p);
+ stmt(p);
+ patch(p, pc);
+ mustbe(p, Lwhile);
+ mustbe(p, '(');
+ pc = epatch(p);
+ exp(p);
+ patch(p, pc);
+ mustbe(p, ')');
+ mustbe(p, ';');
+ p.inloop--;
+ Lfor =>
+ fpc := p.code.npc;
+ lexemit(p);
+ mustbe(p, '(');
+ p.notin++;
+ if(look(p) == Lvar){
+ lex(p);
+ pc = epatch(p);
+ varlist(p);
+ patch(p, pc);
+ p.notin--;
+ if(look(p) == Lin){
+ check1var(p);
+ p.code.ops[fpc] = byte Lforvarin;
+ lex(p);
+ pc = epatch(p);
+ exp(p);
+ patch(p, pc);
+ }else{
+ p.code.ops[fpc] = byte Lforvar;
+ mustbe(p, ';');
+ pc = epatch(p);
+ zexp(p);
+ patch(p, pc);
+ mustbe(p, ';');
+ pc = epatch(p);
+ zexp(p);
+ patch(p, pc);
+ }
+ }else{
+ pc = epatch(p);
+ lhspc := p.code.npc;
+ zexp(p);
+ patch(p, pc);
+ p.notin--;
+ if(look(p) == Lin){
+ p.code.ops[fpc] = byte Lforin;
+ checklhsexp(p, lhspc);
+ lex(p);
+ pc = epatch(p);
+ exp(p);
+ patch(p, pc);
+ }else{
+ mustbe(p, ';');
+ pc = epatch(p);
+ zexp(p);
+ patch(p, pc);
+ mustbe(p, ';');
+ pc = epatch(p);
+ zexp(p);
+ patch(p, pc);
+ }
+ }
+ mustbe(p, ')');
+ p.inloop++;
+ pc = epatch(p);
+ stmt(p);
+ patch(p, pc);
+ p.inloop--;
+ Lcontinue or
+ Lbreak =>
+ lex(p);
+ lab := 0;
+ if(look(p) == Lid){
+ if((lr := findlab(p, p.code.strs[p.id])) == nil)
+ error(p, "missing label");
+ if(op == Lcontinue && !itstmt(lr.k))
+ error(p, "continue label not on iteration statement");
+ if(op == Lbreak)
+ nop := Lbreaklab;
+ else
+ nop = Lcontinuelab;
+ if(!inlocallabs(p, lr, seenlabs)) # otherwise noop
+ emitconst(p, nop, p.id);
+ lex(p);
+ lab = 1;
+ }
+ else
+ emit(p, op);
+ semi(p);
+ if(op == Lbreak && !lab && !p.inloop && !p.incase)
+ error(p, "break not in a do or for or while or case");
+ if(op == Lcontinue && !p.inloop)
+ error(p, "continue not in a do or for or while");
+ Lreturn =>
+ lexemit(p);
+ nextop := look(p);
+ if(nextop != ';' && nextop != '}' && !p.lastnl)
+ exp(p);
+ semi(p);
+ emit(p, ';');
+ if(!p.infunc)
+ error(p, tokname(op)+" not in a function");
+ Lswitch =>
+ lexemit(p);
+ mustbe(p, '(');
+ pc = epatch(p);
+ exp(p);
+ patch(p, pc);
+ mustbe(p, ')');
+ mustbe(p, '{');
+ pc = epatch(p);
+ caseblk(p);
+ patch(p, pc);
+ mustbe(p, '}');
+ Lthrow =>
+ lexemit(p);
+ nextop := look(p);
+ if(!p.lastnl)
+ exp(p);
+ mustbe(p, ';');
+ emit(p, ';');
+ Lprint =>
+ lexemit(p);
+ nextop := look(p);
+ if(!p.lastnl)
+ exp(p);
+ mustbe(p, ';');
+ emit(p, ';');
+ Ltry =>
+ lexemit(p);
+ pc = epatch(p);
+ block(p);
+ patch(p, pc);
+ pc = epatch(p);
+ if(look(p) == Lcatch){
+ lex(p);
+ mustbe(p, '(');
+ mustbe(p, Lid);
+ emitconst(p, Lid, p.id);
+ mustbe(p, ')');
+ block(p);
+ }
+ patch(p, pc);
+ pc = epatch(p);
+ if(look(p) == Lfinally){
+ lex(p);
+ block(p);
+ }
+ patch(p, pc);
+ }
+ while(--seenlabs >= 0)
+ poplab(p);
+}
+
+block(p : ref Parser)
+{
+ mustbe(p, '{');
+ stmts(p);
+ mustbe(p, '}');
+}
+
+caseblk(p : ref Parser)
+{
+ pc, defaultpc, clausepc : int;
+ gotdef := 0;
+ p.incase++;
+
+ defaultpc = epatch(p);
+ while((op := look(p)) != '}' && op != Leos) {
+ if (op != Lcase && op != Ldefault) {
+ err := "expected " + tokname(Lcase)
+ + " or " + tokname(Ldefault)
+ + " found " + tokname(op);
+ error(p, err);
+ }
+ if (op == Ldefault) {
+ if (gotdef)
+ error(p, "default case already defined");
+ gotdef = 1;
+
+ patch(p, defaultpc);
+ }
+ lex(p);
+ clausepc = epatch(p);
+ if (op == Lcase) {
+ pc = epatch(p);
+ exp(p);
+ patch(p, pc);
+ }
+ mustbe(p, ':');
+ casestmts(p);
+ patch(p, clausepc);
+ }
+ clausepc = epatch(p);
+ patch(p, clausepc);
+ if (!gotdef)
+ patch(p, defaultpc);
+ p.incase--;
+}
+
+casestmts(p : ref Parser)
+{
+ while((op := look(p)) != '}' && op != Lcase && op != Ldefault && op != Leos)
+ stmt(p);
+}
+
+semi(p: ref Parser)
+{
+ op := look(p);
+ if(op == ';'){
+ lex(p);
+ return;
+ }
+ if(op == '}' || op == Leos || p.lastnl)
+ return;
+ mustbe(p, ';');
+}
+
+#varlist : vardecl
+# | varlist ',' vardecl
+# ;
+#
+#vardecl : Lid init
+# ;
+#
+#init :
+# | '=' asexp
+# ;
+varlist(p: ref Parser)
+{
+ #
+ # these declaration aren't supposed
+ # to override current definitions
+ #
+ mustbe(p, Lid);
+ codevar(p, 0);
+ emitconst(p, Lid, p.id);
+ if(look(p) == '='){
+ lex(p);
+ asexp(p);
+ emit(p, '=');
+ }
+ if(look(p) != ',')
+ return;
+ emit(p, Lgetval);
+ lex(p);
+ varlist(p);
+ emit(p, ',');
+}
+
+#
+# check that only 1 id is declared in the var list
+#
+check1var(p: ref Parser)
+{
+ if(p.code.ops[p.code.npc-1] == byte ',')
+ error(p, "only one identifier allowed");
+}
+
+#zexp :
+# | exp
+# ;
+zexp(p: ref Parser)
+{
+ op := look(p);
+ if(op == ';' || op == ')')
+ return;
+ exp(p);
+}
+
+#exp : asexp
+# | exp ',' asexp
+# ;
+exp(p: ref Parser)
+{
+ asexp(p);
+ while(look(p) == ','){
+ lex(p);
+ emit(p, Lgetval);
+ asexp(p);
+ emit(p, ',');
+ }
+}
+
+#asexp : condexp
+# | lhsexp asop asexp
+# ;
+#
+#asop : '=' | Lmulas | Ldivas | Lmodas | Laddas | Lsubas
+# | Llshas | Lrshas | Lrshuas | Landas | Lxoras | Loras
+# ;
+asops := array[] of { '=', Lmulas, Ldivas, Lmodas, Laddas, Lsubas,
+ Llshas, Lrshas, Lrshuas, Landas, Lxoras, Loras };
+asbaseops := array[] of { '=', '*', '/', '%', '+', '-',
+ Llsh, Lrsh, Lrshu, '&', '^', '|' };
+asexp(p: ref Parser)
+{
+ lhspc := p.code.npc;
+ condexp(p);
+ i := inops(look(p), asops);
+ if(i >= 0){
+ op := lex(p);
+ checklhsexp(p, lhspc);
+ if(op != '=')
+ emit(p, Lasop);
+ asexp(p);
+ emit(p, asbaseops[i]);
+ if(op != '=')
+ emit(p, Las);
+ }
+}
+
+#condexp : ororexp
+# | ororexp '?' asexp ':' asexp
+# ;
+condexp(p: ref Parser)
+{
+ ororexp(p);
+ if(look(p) == '?'){
+ lexemit(p);
+ pc := epatch(p);
+ asexp(p);
+ mustbe(p, ':');
+ patch(p, pc);
+ pc = epatch(p);
+ asexp(p);
+ patch(p, pc);
+ }
+}
+
+#ororexp : andandexp
+# | ororexp op andandexp
+# ;
+ororexp(p: ref Parser)
+{
+ andandexp(p);
+ while(look(p) == Loror){
+ lexemit(p);
+ pc := epatch(p);
+ andandexp(p);
+ patch(p, pc);
+ }
+}
+
+#andandexp : laexp
+# | andandexp op laexp
+# ;
+andandexp(p: ref Parser)
+{
+ laexp(p, 0);
+ while(look(p) == Landand){
+ lexemit(p);
+ pc := epatch(p);
+ laexp(p, 0);
+ patch(p, pc);
+ }
+}
+
+#laexp : unexp
+# | laexp op laexp
+# ;
+prectab := array[] of
+{
+ array[] of { '|' },
+ array[] of { '^' },
+ array[] of { '&' },
+ array[] of { Leq, Lneq, Lseq, Lsne },
+ array[] of { '<', '>', Lleq, Lgeq, Lin, Linstanceof },
+ array[] of { Llsh, Lrsh, Lrshu },
+ array[] of { '+', '-' },
+ array[] of { '*', '/', '%' },
+};
+laexp(p: ref Parser, prec: int)
+{
+ unexp(p);
+ for(pr := len prectab - 1; pr >= prec; pr--){
+ while(inops(look(p), prectab[pr]) >= 0){
+ emit(p, Lgetval);
+ op := lex(p);
+ laexp(p, pr + 1);
+ emit(p, op);
+ }
+ }
+}
+
+#unexp : postexp
+# | Ldelete unexp
+# | Lvoid unexp
+# | Ltypeof unexp
+# | Linc unexp
+# | Ldec unexp
+# | '+' unexp
+# | '-' unexp
+# | '~' unexp
+# | '!' unexp
+# ;
+preops := array[] of { Ldelete, Lvoid, Ltypeof, Linc, Ldec, '+', '-', '~', '!' };
+unexp(p: ref Parser)
+{
+ if(inops(look(p), preops) >= 0){
+ op := lex(p);
+ unexp(p);
+ if(op == '-')
+ op = Lpresub;
+ else if(op == '+')
+ op = Lpreadd;
+ emit(p, op);
+ return;
+ }
+ postexp(p);
+}
+
+#postexp : lhsexp
+# | lhsexp Linc # no line terminators before Linc or Ldec
+# | lhsexp Ldec
+# ;
+postexp(p: ref Parser)
+{
+ lhsexp(p, 0);
+ if(p.lastnl)
+ return;
+ op := look(p);
+ if(op == Linc || op == Ldec){
+ if(op == Linc)
+ op = Lpostinc;
+ else
+ op = Lpostdec;
+ lex(p);
+ emit(p, op);
+ }
+}
+
+#
+# verify that the last expression is actually a lhsexp
+#
+checklhsexp(p: ref Parser, pc: int)
+{
+
+ case int p.code.ops[p.code.npc-1]{
+ Lthis or
+ ')' or
+ '.' or
+ '[' or
+ Lcall or
+ Lnew or
+ Lnewcall =>
+ return;
+ }
+
+ case int p.code.ops[pc]{
+ Lid or
+ Lnum or
+ Lstr or
+ Lregexp =>
+ npc := pc + 1;
+ (npc, nil) = getconst(p.code.ops, npc);
+ if(npc == p.code.npc)
+ return;
+ }
+
+ (nil, e) := pexp(mkpprint(p.ex, p.code), pc, p.code.npc);
+ error(p, "only left-hand-side expressions allowed: "+e);
+}
+
+#lhsexp : newexp
+# | callexp
+# ;
+#callexp: memexp args
+# | callexp args
+# | callexp '[' exp ']'
+# | callexp '.' Lid
+# ;
+#newexp : memexp
+# | Lnew newexp
+# ;
+#memexp : primexp
+# | Lfunction id(opt) '(' zplist ')' '{' stmts '}'
+# | memexp '[' exp ']'
+# | memexp '.' Lid
+# | Lnew memexp args
+# ;
+lhsexp(p: ref Parser, hasnew: int): int
+{
+ a: int;
+ if(look(p) == Lnew){
+ lex(p);
+ hasnew = lhsexp(p, hasnew + 1);
+ if(hasnew){
+ emit(p, Lnew);
+ hasnew--;
+ }
+ return hasnew;
+ }
+ if(look(p) == Lfunction){
+ o := fundecl(p.ex, p, 1);
+ emitconst(p, Lfunction, fexplook(p, o));
+ return 0;
+ }
+ primexp(p);
+ for(;;){
+ op := look(p);
+ if(op == '('){
+ op = Lcall;
+ if(hasnew){
+ hasnew--;
+ #
+ # stupid different order of evaluation
+ #
+ emit(p, Lgetval);
+ op = Lnewcall;
+ }
+ a = args(p);
+ emitconst(p, op, a);
+ }else if(op == '['){
+ emit(p, Lgetval);
+ lex(p);
+ exp(p);
+ mustbe(p, ']');
+ emit(p, '[');
+ }else if(op == '.'){
+ lex(p);
+ mustbe(p, Lid);
+ emitconst(p, Lid, p.id);
+ emit(p, '.');
+ }else
+ return hasnew;
+ }
+}
+
+#primexp : Lthis
+# | Lid
+# | Lnum
+# | Lstr
+# | Lregexp
+# | '(' exp ')'
+# | '[' array initializer ']'
+# | '{' propandval '}'
+# ;
+primexp(p: ref Parser)
+{
+ case t := lex(p){
+ Lthis =>
+ emit(p, t);
+ Lid or
+ Lnum or
+ Lstr =>
+ emitconst(p, t, p.id);
+ '/' =>
+ lexregexp(p);
+ emitconst(p, Lregexp, p.id);
+ '(' =>
+ emit(p, '(');
+ exp(p);
+ mustbe(p, ')');
+ emit(p, ')');
+ '[' =>
+ a := 0;
+ if(look(p) == ']')
+ lex(p);
+ else{
+ for(;;){
+ if(look(p) == ']'){
+ lex(p);
+ break;
+ }
+ if(look(p) == ',')
+ emit(p, Lnoval);
+ else
+ asexp(p);
+ emit(p, Lgetval);
+ a++;
+ if(look(p) == ']'){
+ lex(p);
+ break;
+ }
+ mustbe(p, ',');
+ }
+ }
+ emitconst(p, Larrinit, a);
+ '{' =>
+ a := 0;
+ if(look(p) == '}')
+ lex(p);
+ else{
+ for(;;){
+ case(tt := lex(p)){
+ Lid =>
+ emitconst(p, Lstr, p.id);
+ Lnum or
+ Lstr =>
+ emitconst(p, tt, p.id);
+ * =>
+ error(p, "expected identifier, number or string");
+ }
+ mustbe(p, ':');
+ asexp(p);
+ emit(p, Lgetval);
+ a++;
+ if(look(p) == '}'){
+ lex(p);
+ break;
+ }
+ mustbe(p, ',');
+ }
+ }
+ emitconst(p, Lobjinit, a);
+ * =>
+ error(p, "expected an expression");
+ }
+}
+
+#args : '(' ')'
+# | '(' arglist ')'
+# ;
+#
+#arglist : asexp
+# | arglist ',' asexp
+# ;
+args(p: ref Parser): int
+{
+ mustbe(p, '(');
+ if(look(p) == ')'){
+ lex(p);
+ return 0;
+ }
+ a := 0;
+ for(;;){
+ asexp(p);
+ emit(p, Lgetval);
+ a++;
+ if(look(p) == ')'){
+ lex(p);
+ return a;
+ }
+ mustbe(p, ',');
+ }
+}
+
+inops(tok: int, ops: array of int): int
+{
+ for(i := 0; i < len ops; i++)
+ if(tok == ops[i])
+ return i;
+ return -1;
+}
+
+mustbe(p: ref Parser, t: int)
+{
+ tt := lex(p);
+ if(tt != t)
+ error(p, "expected "+tokname(t)+" found "+tokname(tt));
+}
+
+toknames := array[] of
+{
+ Leos-Lbase => "end of input",
+ Landas-Lbase => "&=",
+ Loras-Lbase => "|=",
+ Lxoras-Lbase => "^=",
+ Llshas-Lbase => "<<=",
+ Lrshas-Lbase => ">>=",
+ Lrshuas-Lbase => ">>>=",
+ Laddas-Lbase => "+=",
+ Lsubas-Lbase => "-=",
+ Lmulas-Lbase => "*=",
+ Ldivas-Lbase => "/=",
+ Lmodas-Lbase => "%=",
+ Loror-Lbase => "||",
+ Landand-Lbase => "&&",
+ Leq-Lbase => "==",
+ Lneq-Lbase => "!=",
+ Lleq-Lbase => "<=",
+ Lgeq-Lbase => ">=",
+ Llsh-Lbase => "<<",
+ Lrsh-Lbase => ">>",
+ Lrshu-Lbase => ">>>",
+ Linc-Lbase => "++",
+ Ldec-Lbase => "--",
+ Lnum-Lbase => "a number",
+ Lid-Lbase => "an identifier",
+ Lstr-Lbase => "a string",
+ Lthis-Lbase => "this",
+ Ltypeof-Lbase => "typeof",
+ Ldelete-Lbase => "delete",
+ Lvoid-Lbase => "void",
+ Lwhile-Lbase => "while",
+ Lfor-Lbase => "for",
+ Lbreak-Lbase => "break",
+ Lcontinue-Lbase => "continue",
+ Lwith-Lbase => "with",
+ Lreturn-Lbase => "return",
+ Lfunction-Lbase => "function",
+ Lvar-Lbase => "var",
+ Lif-Lbase => "if",
+ Lelse-Lbase => "else",
+ Lin-Lbase => "in",
+ Lnew-Lbase => "new",
+
+ Lpreadd-Lbase => "+",
+ Lpresub-Lbase => "-",
+ Lpostinc-Lbase => "++",
+ Lpostdec-Lbase => "--",
+ Lcall-Lbase => "call",
+ Lnewcall-Lbase => "newcall",
+ Lgetval-Lbase => "[[GetValue]]",
+ Las-Lbase => "[[as]]",
+ Lasop-Lbase => "[[asop]]",
+ Lforin-Lbase => "forin",
+ Lforvar-Lbase => "forvar",
+ Lforvarin-Lbase => "forvarin",
+ Lcase-Lbase => "case",
+ Labstract-Lbase => "abstract",
+ Lboolean-Lbase => "boolean",
+ Lbyte-Lbase => "byte",
+ Lcatch-Lbase => "catch",
+ Lchar-Lbase => "char",
+ Lclass-Lbase => "class",
+ Lconst-Lbase => "const",
+ Ldebugger-Lbase => "debugger",
+ Ldefault-Lbase => "default",
+ Ldo-Lbase => "do",
+ Ldouble-Lbase => "double",
+ Lenum-Lbase => "enum",
+ Lexport-Lbase => "export",
+ Lextends-Lbase => "extends",
+ Lfinal-Lbase => "final",
+ Lfinally-Lbase => "finally",
+ Lfloat-Lbase => "float",
+ Lgoto-Lbase => "goto",
+ Limplements-Lbase => "implements",
+ Limport-Lbase => "import",
+ Linstanceof-Lbase => "instanceof",
+ Lint-Lbase => "int",
+ Linterface-Lbase => "interface",
+ Llong-Lbase => "long",
+ Lnative-Lbase => "native",
+ Lpackage-Lbase => "package",
+ Lprint-Lbase => "print",
+ Lprivate-Lbase => "private",
+ Lprotected-Lbase => "protected",
+ Lpublic-Lbase => "public",
+ Lregexp-Lbase => "regexp",
+ Lseq-Lbase => "===",
+ Lsne-Lbase => "!==",
+ Lshort-Lbase => "short",
+ Lstatic-Lbase => "static",
+ Lsuper-Lbase => "super",
+ Lswitch-Lbase => "switch",
+ Lsynchronized-Lbase => "synchronized",
+ Lthrow-Lbase => "throw",
+ Lthrows-Lbase => "throws",
+ Ltransient-Lbase => "transient",
+ Ltry-Lbase=> "try",
+ Lvolatile-Lbase => "volatile",
+ Larrinit-Lbase => "arrayinit",
+ Lobjinit-Lbase => "objinit",
+ Lnoval-Lbase => "novalue",
+ Llabel-Lbase => "label",
+ Lbreaklab-Lbase => "break",
+ Lcontinuelab-Lbase => "continue",
+};
+
+tokname(t: int): string
+{
+ if(t < Lbase){
+ s := "";
+ s[0] = t;
+ return s;
+ }
+ if(t-Lbase >= len toknames || toknames[t-Lbase] == "")
+ return sprint("<%d>", t);
+ return toknames[t-Lbase];
+}
+
+lexemit(p: ref Parser)
+{
+ emit(p, lex(p));
+ if(debug['s'])
+ sys->print("%d: %s\n", p.code.npc-1, tokname(int p.code.ops[p.code.npc-1]));
+}
+
+emit(p: ref Parser, t: int)
+{
+ if(t > 255)
+ fatal(p.ex, sprint("emit too big: %d\n", t));
+ if(p.code.npc >= len p.code.ops){
+ ops := array[2 * len p.code.ops] of byte;
+ ops[:] = p.code.ops;
+ p.code.ops = ops;
+ }
+ p.code.ops[p.code.npc++] = byte t;
+}
+
+emitconst(p: ref Parser, op, c: int)
+{
+ emit(p, op);
+ if(c < 0)
+ fatal(p.ex, "emit negative constant");
+ if(c >= 255){
+ if(c >= 65536)
+ fatal(p.ex, "constant too large");
+ emit(p, 255);
+ emit(p, c & 16rff);
+ c >>= 8;
+ }
+ emit(p, c);
+}
+
+epatch(p: ref Parser): int
+{
+ pc := p.code.npc;
+ emit(p, 0);
+ emit(p, 0);
+ return pc;
+}
+
+patch(p: ref Parser, pc: int)
+{
+ val := p.code.npc - pc;
+ if(val >= 65536)
+ fatal(p.ex, "patch constant too large");
+ p.code.ops[pc] = byte val;
+ p.code.ops[pc+1] = byte(val >> 8);
+}
+
+getconst(ops: array of byte, pc: int): (int, int)
+{
+ c := int ops[pc++];
+ if(c == 255){
+ c = int ops[pc] + (int ops[pc+1] << 8);
+ pc += 2;
+ }
+ return (pc, c);
+}
+
+getjmp(ops: array of byte, pc: int): (int, int)
+{
+ c := int ops[pc] + (int ops[pc+1] << 8) + pc;
+ pc += 2;
+ return (pc, c);
+}
+
+mkcode(): ref Code
+{
+ return ref Code(array[16] of byte, 0, nil, nil, nil, nil, nil);
+}
+
+look(p: ref Parser): int
+{
+ if(p.token == -1)
+ p.token = lex(p);
+ if(p.notin && p.token == Lin)
+ return ~Lin;
+ return p.token;
+}
+
+look2(p: ref Parser): int
+{
+ look(p);
+ if(p.token1 == -1){
+ # fool lex()
+ t := p.token;
+ p.token = -1;
+ p.token1 = lex(p);
+ p.token = t;
+ }
+ return p.token1;
+}
+
+lex(p: ref Parser): int
+{
+ t := lex0(p);
+ if(0)
+ sys->print("tok=%d %s\n", t, tokname(t));
+ return t;
+}
+
+lex0(p: ref Parser): int
+{
+ t := p.token;
+ if(t != -1){
+ p.token = p.token1;
+ p.token1 = -1;
+ return t;
+ }
+
+ p.lastnl = 0;
+ while(p.srci < p.esrc){
+ c := p.src[p.srci++];
+ case c{
+ '\r' or LS or PS =>
+ p.lastnl = 1;
+ '\n' =>
+ p.lineno++;
+ p.lastnl = 1;
+ ' ' or
+ '\t' or
+ '\v' or
+ FF or # form feed
+ '\u00a0' => # no-break space
+ ;
+ '"' or
+ '\''=>
+ return lexstring(p, c);
+ '(' or
+ ')' or
+ '[' or
+ ']' or
+ '{' or
+ '}' or
+ ',' or
+ ';' or
+ '~' or
+ '?' or
+ ':' =>
+ return c;
+ '.' =>
+ if(p.srci < p.esrc && (map[p.src[p.srci]] & Mdigit) != byte 0){
+ p.srci--;
+ return lexnum(p);
+ }
+ return '.';
+ '^' =>
+ if(p.srci < p.esrc && p.src[p.srci] == '='){
+ p.srci++;
+ return Lxoras;
+ }
+ return '^';
+ '*' =>
+ if(p.srci < p.esrc && p.src[p.srci] == '='){
+ p.srci++;
+ return Lmulas;
+ }
+ return '*';
+ '%' =>
+ if(p.srci < p.esrc && p.src[p.srci] == '='){
+ p.srci++;
+ return Lmodas;
+ }
+ return '%';
+ '=' =>
+ if(p.srci < p.esrc && p.src[p.srci] == '='){
+ p.srci++;
+ if(p.srci < p.esrc && p.src[p.srci] == '='){
+ p.srci++;
+ return Lseq;
+ }
+ return Leq;
+ }
+ return '=';
+ '!' =>
+ if(p.srci < p.esrc && p.src[p.srci] == '='){
+ p.srci++;
+ if(p.srci < p.esrc && p.src[p.srci] == '='){
+ p.srci++;
+ return Lsne;
+ }
+ return Lneq;
+ }
+ return '!';
+ '+' =>
+ if(p.srci < p.esrc){
+ c = p.src[p.srci];
+ if(c == '='){
+ p.srci++;
+ return Laddas;
+ }
+ if(c == '+'){
+ p.srci++;
+ return Linc;
+ }
+ }
+ return '+';
+ '-' =>
+ if(p.srci < p.esrc){
+ c = p.src[p.srci];
+ if(c == '='){
+ p.srci++;
+ return Lsubas;
+ }
+ if(c == '-'){
+ p.srci++;
+ return Ldec;
+ }
+ }
+ return '-';
+ '|' =>
+ if(p.srci < p.esrc){
+ c = p.src[p.srci];
+ if(c == '='){
+ p.srci++;
+ return Loras;
+ }
+ if(c == '|'){
+ p.srci++;
+ return Loror;
+ }
+ }
+ return '|';
+ '&' =>
+ if(p.srci < p.esrc){
+ c = p.src[p.srci];
+ if(c == '='){
+ p.srci++;
+ return Landas;
+ }
+ if(c == '&'){
+ p.srci++;
+ return Landand;
+ }
+ }
+ return '&';
+ '/' =>
+ if(p.srci < p.esrc){
+ c = p.src[p.srci];
+ if(c == '='){
+ p.srci++;
+ return Ldivas;
+ }
+ if(c == '/'){
+ p.srci++;
+ if(lexcom(p) < 0)
+ return Leos;
+ break;
+ }
+ if(c == '*'){
+ p.srci++;
+ if(lexmcom(p) < 0)
+ return Leos;
+ break;
+ }
+ }
+ return '/';
+ '>' =>
+ if(p.srci < p.esrc){
+ c = p.src[p.srci];
+ if(c == '='){
+ p.srci++;
+ return Lgeq;
+ }
+ if(c == '>'){
+ p.srci++;
+ if (p.srci < p.esrc) {
+ c = p.src[p.srci];
+ if(c == '='){
+ p.srci++;
+ return Lrshas;
+ }
+ if(c == '>'){
+ p.srci++;
+ c = p.src[p.srci];
+ if(c == '='){
+ p.srci++;
+ return Lrshuas;
+ }
+ return Lrshu;
+ }
+ }
+ return Lrsh;
+ }
+ }
+ return '>';
+ '<' =>
+ if(p.srci < p.esrc){
+ c = p.src[p.srci];
+ case c {
+ '=' =>
+ p.srci++;
+ return Lleq;
+ '<' =>
+ p.srci++;
+ if (p.srci < p.esrc) {
+ c = p.src[p.srci];
+ if(c == '='){
+ p.srci++;
+ return Llshas;
+ }
+ }
+ return Llsh;
+ '!' =>
+ # HTML comment - consume to end of line or end of comment
+ # No way of having the HTML parser do this
+ if (p.srci+2 >= p.esrc)
+ return Leos;
+
+ if (p.src[p.srci+1] != '-' || p.src[p.srci+2] != '-')
+ # don't treat as a comment, let the parser report syntax error
+ return '<';
+ # consume "!--"
+ p.srci += 3;
+ if(lexhtmlcom(p) < 0)
+ return Leos;
+ continue;
+ }
+ }
+ return '<';
+ '0' to '9' =>
+ p.srci--;
+ return lexnum(p);
+ '\\' =>
+ return lexid(p);
+ * =>
+ if((map[c] & Malpha) != byte 0)
+ return lexid(p);
+ s := "";
+ s[0] = c;
+ error(p, "unknown character '"+s+"'");
+ }
+ }
+ return Leos;
+}
+
+#
+# single line comment
+#
+lexcom(p: ref Parser): int
+{
+ while(p.srci < p.esrc){
+ c := p.src[p.srci];
+ if(islt(c))
+ return 0;
+ p.srci++;
+ }
+ return -1;
+}
+
+#
+# multi-line comment
+#
+lexmcom(p: ref Parser): int
+{
+ star := 0;
+ while(p.srci < p.esrc){
+ c := p.src[p.srci++];
+ if(c == '/' && star)
+ return 0;
+ star = c == '*';
+ }
+ return -1;
+}
+
+# HTML comment
+# consume to end of line or end of comment (-->), whichever we see first.
+# [not strict HTML comment semantics because of
+# the way in which HTML comments are used in JavaScript]
+#
+lexhtmlcom(p: ref Parser): int
+{
+ nmin := 0;
+ for (;p.srci < p.esrc;) {
+ c := p.src[p.srci++];
+ if (c == '-') {
+ nmin++;
+ continue;
+ }
+ if (c == '>' && nmin >= 2)
+ return 0;
+ if (islt(c))
+ return 0;
+ nmin = 0;
+ }
+ return -1;
+}
+
+lexid(p: ref Parser): int
+{
+ p.srci--;
+ id := "";
+ ch := "Z";
+ while(p.srci < p.esrc){
+ c := p.src[p.srci];
+ if(c == '\\'){
+ p.srci++;
+ c = uniescchar(p);
+ if(c == -1)
+ error(p, "malformed unicode escape sequence in identifier");
+ else
+ ;
+ }
+ else{
+ if(c >= 0 && c < 256 && (map[c] & (Malpha|Mdigit)) == byte 0)
+ # if(c >= 256 || (map[c] & (Malpha|Mdigit)) == byte 0)
+ break;
+ p.srci++;
+ }
+ ch[0] = c;
+ id += ch;
+ }
+ # id := p.src[srci:p.srci];
+ t := keywdlook(id);
+ if(t != -1)
+ return t;
+ p.id = strlook(p, id);
+ return Lid;
+}
+
+ParseReal, ParseHex, ParseOct, ParseTrim, ParseEmpty: con 1 << iota;
+
+#
+# parse a numeric identifier
+# format [0-9]+(r[0-9A-Za-z]+)?
+# or ([0-9]+(\.[0-9]*)?|\.[0-9]+)([eE][+-]?[0-9]+)?
+#
+lexnum(p: ref Parser): int
+{
+ v: real;
+ (p.srci, v) = parsenum(p.ex, p.src, p.srci, ParseReal|ParseHex|ParseOct);
+ p.id = numlook(p, v);
+ return Lnum;
+}
+
+parsenum(ex: ref Exec, s: string, si, how: int): (int, real)
+{
+ Inf: con "Infinity";
+
+ osi := si;
+ lens := len s;
+ if (how & ParseTrim) {
+ while(si < lens && iswhite(s[si]))
+ si++;
+ }
+ if(si >= lens) {
+ if (how & ParseEmpty)
+ return (si, 0.);
+ return (osi, Math->NaN);
+ }
+ c := s[si];
+ neg := 0;
+ if(c == '+')
+ si++;
+ else if(c == '-'){
+ si++;
+ neg = 1;
+ }
+ v := 0.;
+ if((how & ParseReal) && si + len Inf <= lens && s[si:si+len Inf] == Inf){
+ si += len Inf;
+ v = Math->Infinity;
+ }else{
+ nsi := si;
+ (si, v) = parsenumval(ex, s, si, how);
+ if(si == nsi)
+ return (osi, Math->NaN);
+ }
+ if(neg)
+ v = -v;
+ if (how & ParseTrim) {
+ while(si < lens && iswhite(s[si]))
+ si++;
+ }
+ return (si, v);
+}
+
+#
+# parse a bunch of difference subsets of numbers
+#
+parsenumval(ex: ref Exec, s: string, si, how: int): (int, real)
+{
+ Int, Oct, Hex, FracSeen, Frac, ExpSeen, ExpSignSeen, Exp: con iota;
+
+ lens := len s;
+ if(si >= lens)
+ return (si, Math->NaN);
+ ssi := si;
+ c := s[si];
+ state := Int;
+ if(c == '.' && (how & ParseReal)){
+ state = FracSeen;
+ si++;
+ }else if(c == '0'){
+ if(si+1 >= lens)
+ return (si+1, 0.);
+ c = s[si+1];
+ if(c == '.' && (how & ParseReal)){
+ state = Frac;
+ si += 2;
+ }else if((c == 'x' || c == 'X') && (how & ParseHex)){
+ state = Hex;
+ ssi += 2;
+ si += 2;
+ }else if(how & ParseOct)
+ state = Oct;
+ }
+
+done: while(si < lens){
+ c = s[si];
+ case state{
+ Int =>
+ if((map[c] & Mdigit) != byte 0)
+ break;
+ if((map[c] & Mexp) != byte 0 && (how & ParseReal))
+ state = ExpSeen;
+ else if(c == '.' && (how & ParseReal))
+ state = Frac;
+ else
+ break done;
+ Hex =>
+ if((map[c] & Mhex) == byte 0)
+ break done;
+ Oct =>
+ if((map[c] & Moct) == byte 0)
+ break done;
+ FracSeen or
+ Frac =>
+ if((map[c] & Mdigit) != byte 0)
+ state = Frac;
+ else if((map[c] & Mexp) != byte 0)
+ state = ExpSeen;
+ else
+ break done;
+ ExpSeen =>
+ if((map[c] & Msign) != byte 0)
+ state = ExpSignSeen;
+ else if((map[c] & Mdigit) != byte 0)
+ state = Exp;
+ else
+ break done;
+ ExpSignSeen or
+ Exp =>
+ if((map[c] & Mdigit) != byte 0)
+ state = Exp;
+ else
+ break done;
+ }
+ si++;
+ }
+
+ esi := si;
+ if(state == FracSeen)
+ return (si - 1, Math->NaN);
+ if(state == ExpSeen){
+ state = Frac;
+ esi--;
+ }else if(state == ExpSignSeen){
+ state = Frac;
+ esi -= 2;
+ }
+ buf := s[ssi:esi];
+ v: real;
+ case state{
+ * =>
+ # only if the above lexing code is wrong
+ fatal(ex, "bad parse of numerical constant '"+buf+"'");
+ v = 0.;
+ Oct =>
+ v = strtoi(ex, buf, 8);
+ Hex =>
+ v = strtoi(ex, buf, 16);
+ Int or
+ Frac or
+ Exp =>
+ v = real buf;
+ }
+ return (si, v);
+}
+
+#
+# called only from parsenumval
+# can never fatal error if that routine works correctly
+#
+strtoi(ex: ref Exec, t: string, base: int): real
+{
+ if(len t == 0)
+ return Math->NaN;
+
+ v := 0.;
+ for(i := 0; i < len t; i++){
+ c := t[i];
+ if(c >= '0' && c <= '9')
+ c -= '0';
+ else if(c >= 'a' && c <= 'z')
+ c -= 'a' - 10;
+ else
+ c -= 'A' - 10;
+ if(c >= base){
+ fatal(ex, "digit '"+t[i:i+1]+"' is not radix "+string base);
+ return Math->NaN;
+ }
+ v = v * real base + real c;
+ }
+ return v;
+}
+
+lexstring(p: ref Parser, end: int): int
+{
+ s := "";
+ i := 0;
+ for(;;){
+ if(p.srci >= p.esrc){
+ error(p, "end of file in string constant");
+ break;
+ }
+ c := p.src[p.srci];
+ if(islt(c)){
+ error(p, "newline in string constant");
+ break;
+ }
+ p.srci++;
+ if(c == end)
+ break;
+ if(c == '\\'){
+ c = escchar(p);
+ if(c == Leos)
+ continue;
+ }
+ s[i++] = c;
+ }
+ p.id = strlook(p, s);
+ return Lstr;
+}
+
+lexregexp(p: ref Parser): int
+{
+ c := esc := 0;
+ s := "";
+ i := 0;
+ s[i++] = '/';
+ for(;;){
+ if(p.srci >= p.esrc){
+ error(p, "end of file in regexp constant");
+ break;
+ }
+ c = p.src[p.srci];
+ if(islt(c)){
+ error(p, "newline in regexp constant");
+ break;
+ }
+ p.srci++;
+ s[i++] = c;
+ if(!esc && c == '/')
+ break;
+ esc = !esc && c == '\\';
+ }
+ if(esc)
+ error(p, "missing escaped character");
+ if(i == 2)
+ error(p, "missing regexp");
+ while(p.srci < p.esrc){
+ c = p.src[p.srci];
+ if(c >= 256 || (map[c] & (Malpha|Mdigit)) == byte 0)
+ break;
+ p.srci++;
+ s[i++] = c;
+ }
+ p.id = strlook(p, s);
+ return Lregexp;
+}
+
+uniescchar(p: ref Parser): int
+{
+ if(p.srci >= p.esrc)
+ return -1;
+ c := p.src[p.srci++];
+ if(c != 'u')
+ return -1;
+ v := 0;
+ for(i := 0; i < 4; i++){
+ if(p.srci >= p.esrc || (map[c = p.src[p.srci]] & (Mdigit|Mhex)) == byte 0)
+ return -1;
+ p.srci++;
+ if((map[c] & Mdigit) != byte 0)
+ c -= '0';
+ else if((map[c] & Mlower) != byte 0)
+ c = c - 'a' + 10;
+ else if((map[c] & Mupper) != byte 0)
+ c = c - 'A' + 10;
+ v = v * 16 + c;
+ }
+ return v;
+}
+
+escchar(p: ref Parser): int
+{
+ v: int;
+ if(p.srci >= p.esrc)
+ return Leos;
+ c := p.src[p.srci++];
+ if(c == 'u' || c == 'x'){
+ d := 2;
+ if(c == 'u')
+ d = 4;
+ v = 0;
+ for(i := 0; i < d; i++){
+ if(p.srci >= p.esrc || (map[c = p.src[p.srci]] & (Mdigit|Mhex)) == byte 0){
+ error(p, "malformed hex escape sequence");
+ break;
+ }
+ p.srci++;
+ if((map[c] & Mdigit) != byte 0)
+ c -= '0';
+ else if((map[c] & Mlower) != byte 0)
+ c = c - 'a' + 10;
+ else if((map[c] & Mupper) != byte 0)
+ c = c - 'A' + 10;
+ v = v * 16 + c;
+ }
+ return v;
+ }
+ if(c >= '0' && c <= '7'){
+ v = c - '0';
+ if(p.srci < p.esrc && (c = p.src[p.srci]) >= '0' && c <= '7'){
+ p.srci++;
+ v = v * 8 + c - '0';
+ if(v <= 8r37 && p.srci < p.esrc && (c = p.src[p.srci]) >= '0' && c <= '7'){
+ p.srci++;
+ v = v * 8 + c - '0';
+ }
+ }
+ return v;
+ }
+
+ if(c < len escmap && (v = int escmap[c]) < 255)
+ return v;
+ return c;
+}
+
+keywdlook(s: string): int
+{
+ m: int;
+ l := 1;
+ r := len keywords - 1;
+ while(l <= r){
+ m = (r + l) >> 1;
+ if(keywords[m].name <= s)
+ l = m + 1;
+ else
+ r = m - 1;
+ }
+ m = l - 1;
+ if(keywords[m].name == s)
+ return keywords[m].token;
+ return -1;
+}
+
+strlook(p: ref Parser, s: string): int
+{
+ for(i := 0; i < len p.code.strs; i++)
+ if(p.code.strs[i] == s)
+ return i;
+ strs := array[i + 1] of string;
+ strs[:] = p.code.strs;
+ strs[i] = s;
+ p.code.strs = strs;
+ return i;
+}
+
+numlook(p: ref Parser, r: real): int
+{
+ for(i := 0; i < len p.code.nums; i++)
+ if(p.code.nums[i] == r)
+ return i;
+ nums := array[i + 1] of real;
+ nums[:] = p.code.nums;
+ nums[i] = r;
+ p.code.nums = nums;
+ return i;
+}
+
+fexplook(p: ref Parser, o: ref Obj): int
+{
+ i := len p.code.fexps;
+ fexps := array[i+1] of ref Obj;
+ fexps[:] = p.code.fexps;
+ fexps[i] = o;
+ p.code.fexps = fexps;
+ return i;
+}
+
+iswhite(c: int): int
+{
+ if(islt(c))
+ return 1;
+ case c {
+ ' ' or
+ '\t' or
+ '\v' or
+ FF or # form feed
+ '\u00a0' => # no-break space
+ return 1;
+ }
+ return 0;
+}
+
+error(p: ref Parser, s: string)
+{
+ p.errors++;
+ p.ex.error += sys->sprint("%d: syntax error: %s\n", p.lineno, s);
+ if(p.errors >= maxerr)
+ runtime(p.ex, SyntaxError, p.ex.error);
+}
+
+fatal(ex: ref Exec, msg: string)
+{
+ if(debug['f']){
+ print("fatal ecmascript error: %s\n", msg);
+ if(""[5] == -1); # abort
+ }
+ runtime(ex, InternalError, "unrecoverable internal ecmascript error: "+ msg);
+}
+
+# scanb(p: ref Parser, s: string): int
+# {
+# n := len s;
+# for(i := p.srci; i+n > p.esrc || p.src[i: i+n] != s; --i)
+# ;
+# return i;
+# }
+
+setkindlab(p: ref Parser, op: int, n: int)
+{
+ l := p.labs;
+ for(i := 0; i < n; i++){
+ (hd l).k = op;
+ l = tl l;
+ }
+}
+
+inlocallabs(p: ref Parser, lr: ref labrec, n: int): int
+{
+ l := p.labs;
+ for(i := 0; i < n; i++){
+ if(hd l == lr)
+ return 1;
+ l = tl l;
+ }
+ return 0;
+}
+
+findlab(p: ref Parser, s: string): ref labrec
+{
+ for(l := p.labs; l != nil; l = tl l)
+ if((hd l).s == s)
+ return hd l;
+ return nil;
+}
+
+pushlab(p: ref Parser, s: string)
+{
+ if(findlab(p, s) != nil)
+ error(p, "duplicate labels");
+ p.labs = ref labrec(s, 0) :: p.labs;
+}
+
+poplab(p: ref Parser)
+{
+ p.labs = tl p.labs;
+}
+
+itstmt(k: int): int
+{
+ return k == Lwhile || k == Ldo || k == Lfor;
+}
diff --git a/appl/lib/ecmascript/exec.b b/appl/lib/ecmascript/exec.b
new file mode 100644
index 00000000..d182df82
--- /dev/null
+++ b/appl/lib/ecmascript/exec.b
@@ -0,0 +1,863 @@
+exec(ex: ref Exec, code: ref Code): Completion
+{
+ ssp := ex.sp;
+
+ r := estmt(ex, code, 0, code.npc);
+
+ if(r.kind == CThrow)
+ ex.sp = ssp;
+
+ if(ssp != ex.sp)
+ runtime(ex, InternalError, "internal error: exec stack not balanced");
+
+ if(r.lab != nil)
+ runtime(ex, InternalError, "internal error: label out of stack");
+ return r;
+}
+
+estmt(ex: ref Exec, code: ref Code, pc, epc: int): Completion
+{
+ e: ref Ref;
+ ev: ref Val;
+ k, apc, pc2, apc2, pc3, apc3, c: int;
+ lab: string;
+ labs: list of string;
+
+ osp := ex.sp;
+
+{
+ v : ref Val = nil;
+ k1 := CNormal;
+ while(pc < epc){
+ v1 : ref Val = nil;
+
+ labs = nil;
+ op := int code.ops[pc++];
+ while(op == Llabel){
+ (pc, c) = getconst(code.ops, pc);
+ labs = code.strs[c] :: labs;
+ op = int code.ops[pc++];
+ }
+ if(debug['e'] > 1)
+ print("estmt(pc %d, sp %d) %s\n", pc-1, ex.sp, tokname(op));
+ case op {
+ Lbreak =>
+ return (CBreak, v, nil);
+ Lcontinue =>
+ return (CContinue, v, nil);
+ Lbreaklab =>
+ (pc, c) = getconst(code.ops, pc);
+ return (CBreak, v, code.strs[c]);
+ Lcontinuelab =>
+ (pc, c) = getconst(code.ops, pc);
+ return (CContinue, v, code.strs[c]);
+ Lreturn =>
+ (pc, v) = eexpval(ex, code, pc, code.npc);
+ return (CReturn, v, nil);
+ '{' =>
+ (pc, apc) = getjmp(code.ops, pc);
+ (k1, v1, lab) = estmt(ex, code, pc, apc);
+ pc = apc;
+ Lif =>
+ (pc, apc) = getjmp(code.ops, pc);
+ (pc, ev) = eexpval(ex, code, pc, apc);
+ (pc, apc) = getjmp(code.ops, pc);
+ (pc2, apc2) = getjmp(code.ops, apc);
+ if(toBoolean(ex, ev) != false)
+ (k1, v1, lab) = estmt(ex, code, pc, apc);
+ else if(pc2 != apc2)
+ (k1, v1, lab) = estmt(ex, code, pc2, apc2);
+ pc = apc2;
+ Lwhile =>
+ (pc, apc) = getjmp(code.ops, pc);
+ (pc2, apc2) = getjmp(code.ops, apc);
+ for(;;){
+ (nil, ev) = eexpval(ex, code, pc, apc);
+ if(toBoolean(ex, ev) == false)
+ break;
+ (k, v1, lab) = estmt(ex, code, pc2, apc2);
+ if(v1 != nil)
+ v = v1;
+ if(k == CBreak || k == CContinue){
+ if(initlabs(lab, labs)){
+ if(k == CBreak)
+ break;
+ else
+ continue;
+ }
+ else
+ return (k, v1, lab);
+ }
+ if(k == CReturn || k == CThrow)
+ return (k, v1, nil);
+ }
+ pc = apc2;
+ Ldo =>
+ (pc, apc) = getjmp(code.ops, pc);
+ (pc2, apc2) = getjmp(code.ops, apc);
+ for(;;){
+ (k, v1, lab) = estmt(ex, code, pc, apc);
+ if(v1 != nil)
+ v = v1;
+ if(k == CBreak || k == CContinue){
+ if(initlabs(lab, labs)){
+ if(k == CBreak)
+ break;
+ else
+ continue;
+ }
+ else
+ return (k, v1, lab);
+ }
+ if(k == CReturn || k == CThrow)
+ return (k, v1, nil);
+ (nil, ev) = eexpval(ex, code, pc2, apc2);
+ if(toBoolean(ex, ev) == false)
+ break;
+ }
+ pc = apc2;
+ Lfor or
+ Lforvar =>
+ (pc, apc) = getjmp(code.ops, pc);
+ (pc, nil) = eexpval(ex, code, pc, apc);
+ (pc, apc) = getjmp(code.ops, pc);
+ (pc2, apc2) = getjmp(code.ops, apc);
+ (pc3, apc3) = getjmp(code.ops, apc2);
+ for(;;){
+ (nil, e) = eexp(ex, code, pc, apc);
+ if(e != nil && toBoolean(ex, getValue(ex, e)) == false)
+ break;
+ (k, v1, lab) = estmt(ex, code, pc3, apc3);
+ if(v1 != nil)
+ v = v1;
+ if(k == CBreak || k == CContinue){
+ if(initlabs(lab, labs)){
+ if(k == CBreak)
+ break;
+ else
+ continue;
+ }
+ else
+ return (k, v1, lab);
+ }
+ if(k == CReturn || k == CThrow)
+ return (k, v1, nil);
+ eexpval(ex, code, pc2, apc2);
+ }
+ pc = apc3;
+ Lforin or
+ Lforvarin =>
+ (pc, apc) = getjmp(code.ops, pc);
+ (pc2, apc2) = getjmp(code.ops, apc);
+ (pc3, apc3) = getjmp(code.ops, apc2);
+ if(op == Lforvarin){
+ (nil, nil) = eexp(ex, code, pc, apc);
+ # during for only evaluate the id, not the initializer
+ apc = pc + 1;
+ }
+ (nil, ev) = eexpval(ex, code, pc2, apc2);
+ bo := toObject(ex, ev);
+
+ #
+ # note this won't enumerate host properties
+ #
+ enum:
+ for(o := bo; o != nil; o = o.prototype){
+ if(o.host != nil && o.host != me)
+ continue;
+ for(i := 0; i < len o.props; i++){
+ if(o.props[i] == nil
+ || (o.props[i].attr & DontEnum)
+ || propshadowed(bo, o, o.props[i].name))
+ continue;
+ (nil, e) = eexp(ex, code, pc, apc);
+ putValue(ex, e, strval(o.props[i].name));
+ (k, v1, lab) = estmt(ex, code, pc3, apc3);
+ if(v1 != nil)
+ v = v1;
+ if(k == CBreak || k == CContinue){
+ if(initlabs(lab, labs)){
+ if(k == CBreak)
+ break enum;
+ else
+ continue enum;
+ }
+ else
+ return (k, v1, lab);
+ }
+ if(k == CReturn || k == CThrow)
+ return (k, v1, nil);
+ }
+ }
+ pc = apc3;
+ Lwith =>
+ (pc, apc) = getjmp(code.ops, pc);
+ (pc, ev) = eexpval(ex, code, pc, apc);
+ pushscope(ex, toObject(ex, ev));
+ (pc, apc) = getjmp(code.ops, pc);
+ (k1, v1, lab) = estmt(ex, code, pc, apc);
+ popscope(ex);
+ pc = apc;
+ ';' =>
+ ;
+ Lvar =>
+ (pc, apc) = getjmp(code.ops, pc);
+ (pc, nil) = eexp(ex, code, pc, apc);
+ Lswitch =>
+ (pc, apc) = getjmp(code.ops, pc);
+ (pc, ev) = eexpval(ex, code, pc, apc);
+ (pc, apc) = getjmp(code.ops, pc);
+ (k1, v1, lab) = ecaseblk(ex, code, ev, pc, apc, labs);
+ pc = apc;
+ Lthrow =>
+ (pc, v) = eexpval(ex, code, pc, code.npc);
+ ex.error = toString(ex, v);
+ return (CThrow, v, nil);
+ Lprint =>
+ (pc, v1) = eexpval(ex, code, pc, code.npc);
+ print("%s\n", toString(ex, v1));
+ Ltry =>
+ (pc, apc) = getjmp(code.ops, pc);
+ (k1, v1, lab) = estmt(ex, code, pc, apc);
+ (kc, vc) := (k1, v1);
+ (pc, apc) = getjmp(code.ops, apc);
+ if(pc != apc){
+ (pc, c) = getconst(code.ops, ++pc);
+ if(k1 == CThrow){
+ o := mkobj(ex.objproto, "Object");
+ valinstant(o, DontDelete, code.strs[c], v1);
+ pushscope(ex, o);
+ (k1, v1, lab) = estmt(ex, code, pc, apc);
+ popscope(ex);
+ if(k1 != CNormal)
+ (kc, vc) = (k1, v1);
+ }
+ }
+ (pc, apc) = getjmp(code.ops, apc);
+ if(pc != apc){
+ (k, v, lab) = estmt(ex, code, pc, apc);
+ if(k == CNormal)
+ (k1, v1) = (kc, vc);
+ else
+ (k1, v1) = (k, v);
+ }
+ pc = apc;
+ * =>
+ (pc, e) = eexp(ex, code, pc-1, code.npc);
+ if(e != nil)
+ v1 = getValue(ex, e);
+ if(debug['v'])
+ print("%s\n", toString(ex, v1));
+ }
+
+ if(v1 != nil)
+ v = v1;
+ if(k1 == CBreak && lab != nil && inlabs(lab, labs))
+ (k1, lab) = (CNormal, nil);
+ if(k1 != CNormal)
+ return (k1, v, lab);
+ }
+ return (CNormal, v, nil);
+}
+exception{
+ "throw" =>
+ ex.sp = osp;
+ return (CThrow, ex.errval, nil);
+}
+}
+
+ecaseblk(ex : ref Exec, code : ref Code, sv : ref Val, pc, epc : int, labs: list of string) : Completion
+{ defpc, nextpc, clausepc, apc : int;
+ ev : ref Val;
+ lab: string;
+
+ k := CNormal;
+ v := undefined;
+ matched := 0;
+
+ (pc, defpc) = getjmp(code.ops, pc);
+ clausepc = pc;
+ (pc, nextpc) = getjmp(code.ops, pc);
+ for (; pc <= epc; (clausepc, (pc, nextpc)) = (nextpc, getjmp(code.ops, nextpc))) {
+ if (nextpc == epc) {
+ if (matched || defpc == epc)
+ break;
+ # do the default
+ matched = 1;
+ nextpc = defpc;
+ continue;
+ }
+ if (!matched && clausepc == defpc)
+ # skip default case - still scanning guards
+ continue;
+ if (clausepc != defpc) {
+ # only case clauses have guard exprs
+ (pc, apc) = getjmp(code.ops, pc);
+ if (matched)
+ pc = apc;
+ else {
+ (pc, ev) = eexpval(ex, code, pc, apc);
+ if (identical(sv, ev))
+ matched = 1;
+ else
+ continue;
+ }
+ }
+ (k, v, lab) = estmt(ex, code, pc, nextpc);
+ if(k == CBreak && initlabs(lab, labs))
+ return (CNormal, v, nil);
+ if(k == CBreak || k == CContinue || k == CReturn || k == CThrow)
+ return (k, v, lab);
+ }
+ return (k, v, lab);
+}
+
+identical(v1, v2 : ref Val) : int
+{
+ if (v1.ty != v2.ty)
+ return 0;
+ ret := 0;
+ case v1.ty{
+ TUndef or
+ TNull =>
+ ret = 1;
+ TNum =>
+ if(v1.num == v2.num)
+ ret = 1;
+ TBool =>
+ if(v1 == v2)
+ ret = 1;
+ TStr =>
+ if(v1.str == v2.str)
+ ret = 1;
+ TObj =>
+ if(v1.obj == v2.obj)
+ ret = 1;
+ TRegExp =>
+ if(v1.rev == v2.rev)
+ ret = 1;
+ }
+ return ret;
+}
+
+eexpval(ex: ref Exec, code: ref Code, pc, epc: int): (int, ref Val)
+{
+ e: ref Ref;
+
+ (pc, e) = eexp(ex, code, pc, epc);
+ if(e == nil)
+ v := undefined;
+ else
+ v = getValue(ex, e);
+ return (pc, v);
+}
+
+eexp(ex: ref Exec, code: ref Code, pc, epc: int): (int, ref Ref)
+{
+ o, th: ref Obj;
+ a1: ref Ref;
+ v, v1, v2: ref Val;
+ s: string;
+ r1, r2: real;
+ c, apc, i1, i2: int;
+
+ savesp := ex.sp;
+out: while(pc < epc){
+ op := int code.ops[pc++];
+ if(debug['e'] > 1){
+ case op{
+ Lid or
+ Lstr or
+ Lregexp =>
+ (nil, c) = getconst(code.ops, pc);
+ print("eexp(pc %d, sp %d) %s '%s'\n", pc-1, ex.sp, tokname(op), code.strs[c]);
+ Lnum =>
+ (nil, c) = getconst(code.ops, pc);
+ print("eexp(pc %d, sp %d) %s '%g'\n", pc-1, ex.sp, tokname(op), code.nums[c]);
+ * =>
+ print("eexp(pc %d, sp %d) %s\n", pc-1, ex.sp, tokname(op));
+ }
+ }
+ case op{
+ Lthis =>
+ v1 = objval(ex.this);
+ Lnum =>
+ (pc, c) = getconst(code.ops, pc);
+ v1 = numval(code.nums[c]);
+ Lstr =>
+ (pc, c) = getconst(code.ops, pc);
+ v1 = strval(code.strs[c]);
+ Lregexp =>
+ (pc, c) = getconst(code.ops, pc);
+ (p, f) := rsplit(code.strs[c]);
+ o = nregexp(ex, nil, array[] of { strval(p), strval(f) });
+ v1 = objval(o);
+ # v1 = regexpval(p, f, 0);
+ Lid =>
+ (pc, c) = getconst(code.ops, pc);
+ epush(ex, esprimid(ex, code.strs[c]));
+ continue;
+ Lnoval =>
+ v1 = undefined;
+ '.' =>
+ a1 = epop(ex);
+ v1 = epopval(ex);
+ epush(ex, ref Ref(1, nil, toObject(ex, v1), a1.name));
+ continue;
+ '[' =>
+ v2 = epopval(ex);
+ v1 = epopval(ex);
+ epush(ex, ref Ref(1, nil, toObject(ex, v1), toString(ex, v2)));
+ continue;
+ Lpostinc or
+ Lpostdec =>
+ a1 = epop(ex);
+ r1 = toNumber(ex, getValue(ex, a1));
+ v1 = numval(r1);
+ if(op == Lpostinc)
+ r1++;
+ else
+ r1--;
+ putValue(ex, a1, numval(r1));
+ Linc or
+ Ldec or
+ Lpreadd or
+ Lpresub =>
+ a1 = epop(ex);
+ r1 = toNumber(ex, getValue(ex, a1));
+ case op{
+ Linc =>
+ r1++;
+ Ldec =>
+ r1--;
+ Lpresub =>
+ r1 = -r1;
+ }
+ v1 = numval(r1);
+ if(op == Linc || op == Ldec)
+ putValue(ex, a1, v1);
+ '~' =>
+ v = epopval(ex);
+ i1 = toInt32(ex, v);
+ i1 = ~i1;
+ v1 = numval(real i1);
+ '!' =>
+ v = epopval(ex);
+ v1 = toBoolean(ex, v);
+ if(v1 == true)
+ v1 = false;
+ else
+ v1 = true;
+ Ltypeof =>
+ a1 = epop(ex);
+ if(a1.isref && getBase(ex, a1) == nil)
+ s = "undefined";
+ else case (v1 = getValue(ex, a1)).ty{
+ TUndef =>
+ s = "undefined";
+ TNull =>
+ s = "object";
+ TBool =>
+ s = "boolean";
+ TNum =>
+ s = "number";
+ TStr =>
+ s = "string";
+ TObj =>
+ if(v1.obj.call != nil)
+ s = "function";
+ else
+ s = "object";
+ TRegExp =>
+ s = "regexp";
+ }
+ v1 = strval(s);
+ Ldelete =>
+ a1 = epop(ex);
+ o = getBase(ex, a1);
+ s = getPropertyName(ex, a1);
+ if(o != nil)
+ esdelete(ex, o, s, 0);
+ v1 = undefined;
+ Lvoid =>
+ epopval(ex);
+ v = undefined;
+ '*' or
+ '/' or
+ '%' or
+ '-' =>
+ v2 = epopval(ex);
+ a1 = epop(ex);
+ r1 = toNumber(ex, getValue(ex, a1));
+ r2 = toNumber(ex, v2);
+ case op{
+ '*' =>
+ r1 = r1 * r2;
+ '/' =>
+ r1 = r1 / r2;
+ '%' =>
+ r1 = fmod(r1, r2);
+ '-' =>
+ r1 = r1 - r2;
+ }
+ v1 = numval(r1);
+ '+' =>
+ v2 = epopval(ex);
+ a1 = epop(ex);
+ v1 = toPrimitive(ex, getValue(ex, a1), NoHint);
+ v2 = toPrimitive(ex, v2, NoHint);
+ if(v1.ty == TStr || v2.ty == TStr)
+ v1 = strval(toString(ex, v1)+toString(ex, v2));
+ else
+ v1 = numval(toNumber(ex, v1)+toNumber(ex, v2));
+ Llsh or
+ Lrsh or
+ Lrshu or
+ '&' or
+ '^' or
+ '|' =>
+ v2 = epopval(ex);
+ a1 = epop(ex);
+ i1 = toInt32(ex, getValue(ex, a1));
+ i2 = toInt32(ex, v2);
+ case op{
+ Llsh =>
+ i1 <<= i2 & 16r1f;
+ Lrsh =>
+ i1 >>= i2 & 16r1f;
+ Lrshu =>
+ i1 = int (((big i1) & 16rffffffff) >> (i2 & 16r1f));
+ '&' =>
+ i1 &= i2;
+ '|' =>
+ i1 |= i2;
+ '^' =>
+ i1 ^= i2;
+ }
+ v1 = numval(real i1);
+ '=' or
+ Las =>
+ v1 = epopval(ex);
+ a1 = epop(ex);
+ putValue(ex, a1, v1);
+ '<' or
+ '>' or
+ Lleq or
+ Lgeq =>
+ v2 = epopval(ex);
+ v1 = epopval(ex);
+ if(op == '>' || op == Lleq){
+ v = v1;
+ v1 = v2;
+ v2 = v;
+ }
+ v1 = toPrimitive(ex, v1, TNum);
+ v2 = toPrimitive(ex, v2, TNum);
+ if(v1.ty == TStr && v2.ty == TStr){
+ if(v1.str < v2.str)
+ v1 = true;
+ else
+ v1 = false;
+ }else{
+ r1 = toNumber(ex, v1);
+ r2 = toNumber(ex, v2);
+ if(isnan(r1) || isnan(r2))
+ v1 = undefined;
+ else if(r1 < r2)
+ v1 = true;
+ else
+ v1 = false;
+ }
+ if(op == Lgeq || op == Lleq){
+ if(v1 == false)
+ v1 = true;
+ else
+ v1 = false;
+ }
+ Lin =>
+ v2 = epopval(ex);
+ v1 = epopval(ex);
+ if(v2.ty != TObj)
+ runtime(ex, TypeError, "rhs of 'in' not an object");
+ s = toString(ex, v1);
+ v1 = eshasproperty(ex, v2.obj, s, 0);
+ Linstanceof =>
+ v2 = epopval(ex);
+ v1 = epopval(ex);
+ if(v2.ty != TObj)
+ runtime(ex, TypeError, "rhs of 'instanceof' not an object");
+ if(!isfuncobj(v2.obj))
+ runtime(ex, TypeError, "rhs of 'instanceof' not a function");
+ if(v1.ty != TObj)
+ v1 = false;
+ else{
+ v2 = esget(ex, v2.obj, "prototype", 0);
+ if(v2.ty != TObj)
+ runtime(ex, TypeError, "prototype value not an object");
+ o = v2.obj;
+ for(p := v1.obj.prototype; p != nil; p = p.prototype){
+ if(p == o){
+ v1 = true;
+ break;
+ }
+ }
+ if(p == nil)
+ v1 = false;
+ }
+ Leq or
+ Lneq or
+ Lseq or
+ Lsne =>
+ strict := op == Lseq || op == Lsne;
+ v2 = epopval(ex);
+ v1 = epopval(ex);
+ v = false;
+ while(v1.ty != v2.ty){
+ if(strict)
+ break;
+ if(isnull(v1) && v2 == undefined
+ || v1 == undefined && isnull(v2))
+ v1 = v2;
+ else if(v1.ty == TNum && v2.ty == TStr)
+ v2 = numval(toNumber(ex, v2));
+ else if(v1.ty == TStr && v2.ty == TNum)
+ v1 = numval(toNumber(ex, v1));
+ else if(v1.ty == TBool)
+ v1 = numval(toNumber(ex, v1));
+ else if(v2.ty == TBool)
+ v2 = numval(toNumber(ex, v2));
+ else if(v2.ty == TObj && (v1.ty == TStr || v1.ty == TNum))
+ v2 = toPrimitive(ex, v2, NoHint);
+ else if(v1.ty == TObj && (v2.ty == TStr || v2.ty == TNum))
+ v1 = toPrimitive(ex, v1, NoHint);
+ else{
+ v1 = true;
+ v2 = false;
+ }
+ }
+ if(v1.ty != v2.ty)
+ v = false;
+ else{
+ case v1.ty{
+ TUndef or
+ TNull =>
+ v = true;
+ TNum =>
+ if(v1.num == v2.num)
+ v = true;
+ TBool =>
+ if(v1 == v2)
+ v = true;
+ TStr =>
+ if(v1.str == v2.str)
+ v = true;
+ TObj =>
+ if(v1.obj == v2.obj)
+ v = true;
+ TRegExp =>
+ if(v1.rev.p == v2.rev.p && v1.rev.f == v2.rev.f)
+ v = true;
+ }
+ }
+ if(op == Lneq || op == Lsne){
+ if(v == false)
+ v = true;
+ else
+ v = false;
+ }
+ v1 = v;
+ Landand =>
+ v1 = epopval(ex);
+ (pc, apc) = getjmp(code.ops, pc);
+ if(toBoolean(ex, v1) != false){
+ (pc, a1) = eexp(ex, code, pc, apc);
+ v1 = getValue(ex, a1);
+ }
+ pc = apc;
+ Loror =>
+ v1 = epopval(ex);
+ (pc, apc) = getjmp(code.ops, pc);
+ if(toBoolean(ex, v1) != true){
+ (pc, a1) = eexp(ex, code, pc, apc);
+ v1 = getValue(ex, a1);
+ }
+ pc = apc;
+ '?' =>
+ v1 = epopval(ex);
+ (pc, apc) = getjmp(code.ops, pc);
+ v1 = toBoolean(ex, v1);
+ if(v1 == true)
+ (pc, a1) = eexp(ex, code, pc, apc);
+ pc = apc;
+ (pc, apc) = getjmp(code.ops, pc);
+ if(v1 != true)
+ (pc, a1) = eexp(ex, code, pc, apc);
+ pc = apc;
+ v1 = getValue(ex, a1);
+ Lasop =>
+ a1 = epop(ex);
+ epush(ex, a1);
+ v1 = getValue(ex, a1);
+ Lgetval =>
+ v1 = epopval(ex);
+ ',' =>
+ v1 = epopval(ex);
+ epop(ex);
+ # a1's value already gotten by Lgetval
+ '(' or
+ ')' =>
+ continue;
+ Larrinit =>
+ o = narray(ex, nil, nil);
+ (pc, c) = getconst(code.ops, pc);
+ esput(ex, o, "length", numval(real c), 0);
+ c = ex.sp-c;
+ for(sp := c; sp < ex.sp; sp++){
+ v = getValue(ex, ex.stack[sp]);
+ if(v != undefined)
+ esput(ex, o, string (sp-c), v, 0);
+ }
+ ex.sp = c;
+ v1 = objval(o);
+ Lobjinit =>
+ o = nobj(ex, nil, nil);
+ (pc, c) = getconst(code.ops, pc);
+ c = ex.sp-2*c;
+ for(sp := c; sp < ex.sp; sp += 2){
+ v = getValue(ex, ex.stack[sp]);
+ if(isnum(v) || isstr(v))
+ p := toString(ex, v);
+ else
+ p = ex.stack[sp].name;
+ v = getValue(ex, ex.stack[sp+1]);
+ esput(ex, o, p, v, 0);
+ }
+ ex.sp = c;
+ v1 = objval(o);
+ Lcall or
+ Lnewcall =>
+ (pc, c) = getconst(code.ops, pc);
+ args := array[c] of ref Val;
+ c = ex.sp - c;
+ for(sp := c; sp < ex.sp; sp++)
+ args[sp-c] = getValue(ex, ex.stack[sp]);
+ ex.sp = c;
+ a1 = epop(ex);
+ v = getValue(ex, a1);
+ o = getobj(v);
+ if(op == Lcall){
+ if(o == nil || o.call == nil)
+ runtime(ex, TypeError, "can only call function objects ("+a1.name+")");
+ th = nil;
+ if(a1.isref){
+ th = getBase(ex, a1);
+ if(th != nil && isactobj(th))
+ th = nil;
+ }
+
+ # have to execute functions in the same context as they
+ # were defined, but need to use current stack.
+ if (o.call.ex == nil)
+ a1 = escall(ex, v.obj, th, args, 0);
+ else {
+ fnex := ref *o.call.ex;
+ fnex.stack = ex.stack;
+ fnex.sp = ex.sp;
+ fnex.scopechain = fnex.global :: nil;
+ # drop ref to stack to avoid array duplication should stack grow
+ ex.stack = nil;
+ osp := ex.sp;
+ # can get an exception here that corrupts ex etc.
+#aardvark:=99;
+#test:=99;
+# zebra:=99;
+ {
+ a1 = escall(fnex, v.obj, th, args, 0);
+ }
+ exception e{
+ "throw" =>
+ # copy up error so as it gets reported properly
+ ex.error = fnex.error;
+ ex.errval = fnex.errval;
+ ex.stack = fnex.stack;
+ ex.sp = osp;
+# raise e;
+ raise "throw";
+ }
+ # restore stack, sp is OK as escall() ensures that stack is balanced
+ ex.stack = fnex.stack;
+ }
+ }else{
+ if(o == nil || o.construct == nil)
+ runtime(ex, TypeError, "new must be given a constructor object");
+ a1 = valref(objval(esconstruct(ex, o, args)));
+ }
+ epush(ex, a1);
+ args = nil;
+ continue;
+ Lnew =>
+ v = epopval(ex);
+ o = getobj(v);
+ if(o == nil || o.construct == nil)
+ runtime(ex, TypeError, "new must be given a constructor object");
+ v1 = objval(esconstruct(ex, o, nil));
+ Lfunction =>
+ (pc, c) = getconst(code.ops, pc);
+ v1 = objval(code.fexps[c]);
+ ';' =>
+ break out;
+ * =>
+ fatal(ex, sprint("eexp: unknown op %s\n", tokname(op)));
+ }
+ epushval(ex, v1);
+ }
+
+ if(savesp == ex.sp)
+ return (pc, nil);
+
+ if(savesp != ex.sp-1)
+ print("unbalanced stack in eexp: %d %d\n", savesp, ex.sp);
+ return (pc, epop(ex));
+}
+
+epushval(ex: ref Exec, v: ref Val)
+{
+ epush(ex, valref(v));
+}
+
+epush(ex: ref Exec, r: ref Ref)
+{
+ if(ex.sp >= len ex.stack){
+ st := array[2 * len ex.stack] of ref Ref;
+ st[:] = ex.stack;
+ ex.stack = st;
+ }
+ ex.stack[ex.sp++] = r;
+}
+
+epop(ex: ref Exec): ref Ref
+{
+ if(ex.sp == 0)
+ fatal(ex, "popping too far off the estack\n");
+ return ex.stack[--ex.sp];
+}
+
+epopval(ex: ref Exec): ref Val
+{
+ if(ex.sp == 0)
+ fatal(ex, "popping too far off the estack\n");
+ return getValue(ex, ex.stack[--ex.sp]);
+}
+
+inlabs(lab: string, labs: list of string): int
+{
+ for(l := labs; l != nil; l = tl l)
+ if(hd l == lab)
+ return 1;
+ return 0;
+}
+
+initlabs(lab: string, labs: list of string): int
+{
+ return lab == nil || inlabs(lab, labs);
+}
diff --git a/appl/lib/ecmascript/mkfile b/appl/lib/ecmascript/mkfile
new file mode 100644
index 00000000..afcd2d52
--- /dev/null
+++ b/appl/lib/ecmascript/mkfile
@@ -0,0 +1,23 @@
+<../../../mkconfig
+
+TARG= ecmascript.dis\
+
+MODULES=\
+ builtin.b\
+ date.b\
+ exec.b\
+ obj.b\
+ pprint.b\
+ regexp.b\
+ uri.b\
+
+SYSMODULES= \
+ sys.m\
+ math.m\
+ string.m\
+ daytime.m\
+ ecmascript.m\
+
+DISBIN=$ROOT/dis/lib
+
+<$ROOT/mkfiles/mkdis
diff --git a/appl/lib/ecmascript/obj.b b/appl/lib/ecmascript/obj.b
new file mode 100644
index 00000000..ad3df676
--- /dev/null
+++ b/appl/lib/ecmascript/obj.b
@@ -0,0 +1,836 @@
+#
+# want to use the value in a context which
+# prefers an object, so coerce schizo vals
+# to object versions
+#
+coerceToObj(ex: ref Exec, v: ref Val): ref Val
+{
+ o: ref Obj;
+
+ case v.ty{
+ TBool =>
+ o = mkobj(ex.boolproto, "Boolean");
+ o.val = v;
+ TStr =>
+ o = mkobj(ex.strproto, "String");
+ o.val = v;
+ valinstant(o, DontEnum|DontDelete|ReadOnly, "length", numval(real len v.str));
+ TNum =>
+ o = mkobj(ex.numproto, "Number");
+ o.val = v;
+ TRegExp =>
+ o = mkobj(ex.regexpproto, "RegExp");
+ o.val = v;
+ valinstant(o, DontEnum|DontDelete|ReadOnly, "length", numval(real len v.rev.p));
+ valinstant(o, DontEnum|DontDelete|ReadOnly, "source", strval(v.rev.p));
+ valinstant(o, DontEnum|DontDelete|ReadOnly, "global", strhas(v.rev.f, 'g'));
+ valinstant(o, DontEnum|DontDelete|ReadOnly, "ignoreCase", strhas(v.rev.f, 'i'));
+ valinstant(o, DontEnum|DontDelete|ReadOnly, "multiline", strhas(v.rev.f, 'm'));
+ valinstant(o, DontEnum|DontDelete, "lastIndex", numval(real v.rev.i));
+ * =>
+ return v;
+ }
+ return objval(o);
+}
+
+coerceToVal(v: ref Val): ref Val
+{
+ if(v.ty != TObj)
+ return v;
+ o := v.obj;
+ if(o.host != nil && o.host != me
+ || o.class != "String"
+ || o.class != "Number"
+ || o.class != "Boolean")
+ return v;
+ return o.val;
+}
+
+isstrobj(o: ref Obj): int
+{
+ return (o.host == nil || o.host == me) && o.class == "String";
+}
+
+isnumobj(o: ref Obj): int
+{
+ return (o.host == nil || o.host == me) && o.class == "Number";
+}
+
+isboolobj(o: ref Obj): int
+{
+ return (o.host == nil || o.host == me) && o.class == "Boolean";
+}
+
+isdateobj(o: ref Obj): int
+{
+ return (o.host == nil || o.host == me) && o.class == "Date";
+}
+
+isregexpobj(o: ref Obj): int
+{
+ return (o.host == nil || o.host == me) && o.class == "RegExp";
+}
+
+isfuncobj(o: ref Obj): int
+{
+ return (o.host == nil || o.host == me) && o.class == "Function";
+}
+
+isarray(o: ref Obj): int
+{
+# return (o.host == nil || o.host == me) && o.class == "Array";
+ # relax the host test
+ # so that hosts can intercept Array operations and defer
+ # unhandled ops to the builtin
+ return o.class == "Array";
+}
+
+iserr(o: ref Obj): int
+{
+ return (o.host == nil || o.host == me) && o.class == "Error";
+}
+
+isactobj(o: ref Obj): int
+{
+ return o.host == nil && o.class == "Activation";
+}
+
+isnull(v: ref Val): int
+{
+ return v == null || v == nil;
+}
+
+isundefined(v: ref Val): int
+{
+ return v == undefined;
+}
+
+isstr(v: ref Val): int
+{
+ return v.ty == TStr;
+}
+
+isnum(v: ref Val): int
+{
+ return v.ty == TNum;
+}
+
+isbool(v: ref Val): int
+{
+ return v.ty == TBool;
+}
+
+isobj(v: ref Val): int
+{
+ return v.ty == TObj;
+}
+
+isregexp(v: ref Val): int
+{
+ return v.ty == TRegExp || v.ty == TObj && isregexpobj(v.obj);
+}
+
+#
+# retrieve the object field if it's valid
+#
+getobj(v: ref Val): ref Obj
+{
+ if(v.ty == TObj)
+ return v.obj;
+ return nil;
+}
+
+isprimval(v: ref Val): int
+{
+ return v.ty != TObj;
+}
+
+pushscope(ex: ref Exec, o: ref Obj)
+{
+ ex.scopechain = o :: ex.scopechain;
+}
+
+popscope(ex: ref Exec)
+{
+ ex.scopechain = tl ex.scopechain;
+}
+
+runtime(ex: ref Exec, o: ref Obj, s: string)
+{
+ ex.error = s;
+ if(o == nil)
+ ex.errval = undefined;
+ else
+ ex.errval = objval(o);
+ if(debug['r']){
+ print("ecmascript runtime error: %s\n", s);
+ if(""[5] == -1); # abort
+ }
+ raise "throw";
+ exit; # never reached
+}
+
+mkobj(proto: ref Obj, class: string): ref Obj
+{
+ if(class == nil)
+ class = "Object";
+ return ref Obj(nil, proto, nil, nil, nil, class, nil, nil);
+}
+
+valcheck(ex: ref Exec, v: ref Val, hint: int)
+{
+ if(v == nil
+ || v.ty < 0
+ || v.ty >= NoHint
+ || v.ty == TBool && v != true && v != false
+ || v.ty == TObj && v.obj == nil
+ || hint != NoHint && v.ty != hint)
+ runtime(ex, RangeError, "bad value generated by host object");
+}
+
+# builtin methods for properties
+esget(ex: ref Exec, o: ref Obj, prop: string, force: int): ref Val
+{
+ for( ; o != nil; o = o.prototype){
+ if(!force && o.host != nil && o.host != me){
+ v := o.host->get(ex, o, prop);
+ valcheck(ex, v, NoHint);
+ return v;
+ }
+
+ for(i := 0; i < len o.props; i++)
+ if(o.props[i] != nil && o.props[i].name == prop)
+ return o.props[i].val.val;
+ force = 0;
+ }
+ return undefined;
+}
+
+esputind(o: ref Obj, prop: string): int
+{
+ empty := -1;
+ props := o.props;
+ for(i := 0; i < len props; i++){
+ if(props[i] == nil)
+ empty = i;
+ else if(props[i].name == prop)
+ return i;
+ }
+ if(empty != -1)
+ return empty;
+
+ props = array[i+1] of ref Prop;
+ props[:] = o.props;
+ o.props = props;
+ return i;
+}
+
+esput(ex: ref Exec, o: ref Obj, prop: string, v: ref Val, force: int)
+{
+ ai: big;
+
+ if(!force && o.host != nil && o.host != me)
+ return o.host->put(ex, o, prop, v);
+
+ if(escanput(ex, o, prop, 0) != true)
+ return;
+
+ #
+ # should this test for prototype == ex.arrayproto?
+ # hard to say, but 15.4.5 "Properties of Array Instances" implies not
+ #
+ if(isarray(o))
+ al := toUint32(ex, esget(ex, o, "length", 1));
+
+ i := esputind(o, prop);
+ props := o.props;
+ if(props[i] != nil)
+ props[i].val.val = v;
+ else
+ props[i] = ref Prop(0, prop, ref RefVal(v));
+ if(!isarray(o))
+ return;
+
+ if(prop == "length"){
+ nl := toUint32(ex, v);
+ for(ai = nl; ai < al; ai++)
+ esdelete(ex, o, string ai, 1);
+ props[i].val.val = numval(real nl);
+ }else{
+ ai = big prop;
+ if(prop != string ai || ai < big 0 || ai >= 16rffffffff)
+ return;
+ i = esputind(o, "length");
+ if(props[i] == nil)
+ fatal(ex, "bogus array esput");
+ else if(toUint32(ex, props[i].val.val) <= ai)
+ props[i].val.val = numval(real(ai+big 1));
+ }
+}
+
+escanput(ex: ref Exec, o: ref Obj, prop: string, force: int): ref Val
+{
+ for( ; o != nil; o = o.prototype){
+ if(!force && o.host != nil && o.host != me){
+ v := o.host->canput(ex, o, prop);
+ valcheck(ex, v, TBool);
+ return v;
+ }
+
+ for(i := 0; i < len o.props; i++){
+ if(o.props[i] != nil && o.props[i].name == prop){
+ if(o.props[i].attr & ReadOnly)
+ return false;
+ else
+ return true;
+ }
+ }
+
+ force = 0;
+ }
+ return true;
+}
+
+eshasproperty(ex: ref Exec, o: ref Obj, prop: string, force: int): ref Val
+{
+ for(; o != nil; o = o.prototype){
+ if(!force && o.host != nil && o.host != me){
+ v := o.host->hasproperty(ex, o, prop);
+ valcheck(ex, v, TBool);
+ return v;
+ }
+ for(i := 0; i < len o.props; i++)
+ if(o.props[i] != nil && o.props[i].name == prop)
+ return true;
+ }
+ return false;
+}
+
+eshasenumprop(o: ref Obj, prop: string): ref Val
+{
+ for(i := 0; i < len o.props; i++)
+ if(o.props[i] != nil && o.props[i].name == prop){
+ if(o.props[i].attr & DontEnum)
+ return false;
+ return true;
+ }
+ return false;
+}
+
+propshadowed(start, end: ref Obj, prop: string): int
+{
+ for(o := start; o != end; o = o.prototype){
+ if(o.host != nil && o.host != me)
+ return 0;
+ for(i := 0; i < len o.props; i++)
+ if(o.props[i] != nil && o.props[i].name == prop)
+ return 1;
+ }
+ return 0;
+}
+
+esdelete(ex: ref Exec, o: ref Obj, prop: string, force: int)
+{
+ if(!force && o.host != nil && o.host != me)
+ return o.host->delete(ex, o, prop);
+
+ for(i := 0; i < len o.props; i++){
+ if(o.props[i] != nil && o.props[i].name == prop){
+ if(!(o.props[i].attr & DontDelete))
+ o.props[i] = nil;
+ return;
+ }
+ }
+}
+
+esdeforder := array[] of {"valueOf", "toString"};
+esdefaultval(ex: ref Exec, o: ref Obj, ty: int, force: int): ref Val
+{
+ v: ref Val;
+
+ if(!force && o.host != nil && o.host != me){
+ v = o.host->defaultval(ex, o, ty);
+ valcheck(ex, v, NoHint);
+ if(!isprimval(v))
+ runtime(ex, TypeError, "host object returned an object to [[DefaultValue]]");
+ return v;
+ }
+
+ hintstr := 0;
+ if(ty == TStr || ty == NoHint && isdateobj(o))
+ hintstr = 1;
+
+ for(i := 0; i < 2; i++){
+ v = esget(ex, o, esdeforder[hintstr ^ i], 0);
+ if(v != undefined && v.ty == TObj && v.obj.call != nil){
+ r := escall(ex, v.obj, o, nil, 0);
+ v = nil;
+ if(!r.isref)
+ v = r.val;
+ if(v != nil && isprimval(v))
+ return v;
+ }
+ }
+ runtime(ex, TypeError, "no default value");
+ return nil;
+}
+
+esprimid(ex: ref Exec, s: string): ref Ref
+{
+ for(sc := ex.scopechain; sc != nil; sc = tl sc){
+ o := hd sc;
+ if(eshasproperty(ex, o, s, 0) == true)
+ return ref Ref(1, nil, o, s);
+ }
+
+ #
+ # the right place to add literals?
+ #
+ case s{
+ "null" =>
+ return ref Ref(0, null, nil, "null");
+ "true" =>
+ return ref Ref(0, true, nil, "true");
+ "false" =>
+ return ref Ref(0, false, nil, "false");
+ }
+ return ref Ref(1, nil, nil, s);
+}
+
+bivar(ex: ref Exec, sc: list of ref Obj, s: string): ref Val
+{
+ for(; sc != nil; sc = tl sc){
+ o := hd sc;
+ if(eshasproperty(ex, o, s, 0) == true)
+ return esget(ex, o, s, 0);
+ }
+ return nil;
+}
+
+esconstruct(ex: ref Exec, func: ref Obj, args: array of ref Val): ref Obj
+{
+ o: ref Obj;
+
+ if(func.construct == nil)
+ runtime(ex, TypeError, "new must be applied to a constructor object");
+ if(func.host != nil)
+ o = func.host->construct(ex, func, args);
+ else{
+ o = getobj(esget(ex, func, "prototype", 0));
+ if(o == nil)
+ o = ex.objproto;
+ this := mkobj(o, "Object");
+ o = getobj(getValue(ex, escall(ex, func, this, args, 0)));
+
+ # Divergence from ECMA-262
+ #
+ # observed that not all script-defined constructors return an object,
+ # the value of 'this' is assumed to be the value of the constructor
+ if (o == nil)
+ o = this;
+ }
+ if(o == nil)
+ runtime(ex, TypeError, func.val.str+" failed to generate an object");
+ return o;
+}
+
+escall(ex: ref Exec, func, this: ref Obj, args: array of ref Val, eval: int): ref Ref
+{
+ if(func.call == nil)
+ runtime(ex, TypeError, "can only call function objects");
+ if(this == nil)
+ this = ex.global;
+
+ r: ref Ref = nil;
+ if(func.host != nil){
+ r = func.host->call(ex, func, this, args, 0);
+ if(r.isref && r.name == nil)
+ runtime(ex, ReferenceError, "host call returned a bad reference");
+ else if(!r.isref)
+ valcheck(ex, r.val, NoHint);
+ return r;
+ }
+
+ argobj := mkobj(ex.objproto, "Object");
+ actobj := mkobj(nil, "Activation");
+
+ oargs: ref RefVal = nil;
+ props := func.props;
+ empty := -1;
+ i := 0;
+ for(i = 0; i < len props; i++){
+ if(props[i] == nil)
+ empty = i;
+ else if(props[i].name == "arguments"){
+ oargs = props[i].val;
+ empty = i;
+ break;
+ }
+ }
+ if(i == len func.props){
+ if(empty == -1){
+ props = array[i+1] of ref Prop;
+ props[:] = func.props;
+ func.props = props;
+ empty = i;
+ }
+ props[empty] = ref Prop(DontDelete|DontEnum|ReadOnly, "arguments", nil);
+ }
+ props[empty].val = ref RefVal(objval(argobj));
+
+ #
+ #see section 10.1.3 page 33
+ # if multiple params share the same name, the last one takes effect
+ # vars don't override params of the same name, or earlier parms defs
+ #
+ actobj.props = array[] of {ref Prop(DontDelete, "arguments", ref RefVal(objval(argobj)))};
+
+ argobj.props = array[len args + 2] of {
+ ref Prop(DontEnum, "callee", ref RefVal(objval(func))),
+ ref Prop(DontEnum, "length", ref RefVal(numval(real len args))),
+ };
+
+ #
+ # instantiate the arguments by name in the activation object
+ # and by number in the arguments object, aliased to the same RefVal.
+ #
+ params := func.call.params;
+ for(i = 0; i < len args; i++){
+ rjv := ref RefVal(args[i]);
+ argobj.props[i+2] = ref Prop(DontEnum, string i, rjv);
+ if(i < len params)
+ fvarinstant(actobj, 1, DontDelete, params[i], rjv);
+ }
+ for(; i < len params; i++)
+ fvarinstant(actobj, 1, DontDelete, params[i], ref RefVal(undefined));
+
+ #
+ # instantiate the local variables defined within the function
+ #
+ vars := func.call.code.vars;
+ for(i = 0; i < len vars; i++)
+ valinstant(actobj, DontDelete, vars[i].name, undefined);
+
+ # NOTE: the treatment of scopechain here is wrong if nested functions are
+ # permitted. ECMA-262 currently does not support nested functions (so we
+ # are ok for now) - but other flavours of Javascript do.
+ # Difficulties are introduced by multiple execution contexts.
+ # e.g. in web browsers, one frame can ref a func in
+ # another frame (each frame has a distinct execution context), but the func
+ # ids must bind as if in original lexical context
+
+ osc := ex.scopechain;
+ ex.this = this;
+ ex.scopechain = actobj :: osc;
+ (k, v, nil) := exec(ex, func.call.code);
+ ex.scopechain = osc;
+
+ #
+ # i can find nothing in the docs which defines
+ # the value of a function call
+ # this seems like a reasonable definition
+ #
+ if (k == CThrow)
+ raise "throw";
+ if(!eval && k != CReturn || v == nil)
+ v = undefined;
+ r = valref(v);
+
+ props = func.props;
+ for(i = 0; i < len props; i++){
+ if(props[i] != nil && props[i].name == "arguments"){
+ if(oargs == nil)
+ props[i] = nil;
+ else
+ props[i].val = oargs;
+ break;
+ }
+ }
+
+ return r;
+}
+
+#
+# routines for instantiating variables
+#
+fvarinstant(o: ref Obj, force, attr: int, s: string, v: ref RefVal)
+{
+ props := o.props;
+ empty := -1;
+ for(i := 0; i < len props; i++){
+ if(props[i] == nil)
+ empty = i;
+ else if(props[i].name == s){
+ if(force){
+ props[i].attr = attr;
+ props[i].val = v;
+ }
+ return;
+ }
+ }
+ if(empty == -1){
+ props = array[i+1] of ref Prop;
+ props[:] = o.props;
+ o.props = props;
+ empty = i;
+ }
+ props[empty] = ref Prop(attr, s, v);
+}
+
+varinstant(o: ref Obj, attr: int, s: string, v: ref RefVal)
+{
+ fvarinstant(o, 0, attr, s, v);
+}
+
+valinstant(o: ref Obj, attr: int, s: string, v: ref Val)
+{
+ fvarinstant(o, 0, attr, s, ref RefVal(v));
+}
+
+#
+# instantiate global or val variables
+# note that only function variables are forced to be redefined;
+# all other variables have a undefined val.val field
+#
+globalinstant(o: ref Obj, vars: array of ref Prop)
+{
+ for(i := 0; i < len vars; i++){
+ force := vars[i].val.val != undefined;
+ fvarinstant(o, force, 0, vars[i].name, vars[i].val);
+ }
+}
+
+numval(r: real): ref Val
+{
+ return ref Val(TNum, r, nil, nil, nil);
+}
+
+strval(s: string): ref Val
+{
+ return ref Val(TStr, 0., s, nil, nil);
+}
+
+objval(o: ref Obj): ref Val
+{
+ return ref Val(TObj, 0., nil, o, nil);
+}
+
+regexpval(p: string, f: string, i: int): ref Val
+{
+ return ref Val(TRegExp, 0., nil, nil, ref REval(p, f, i));
+}
+
+#
+# operations on refereneces
+# note the substitution of nil for an object
+# version of null, implied in the discussion of
+# Reference Types, since there isn't a null object
+#
+valref(v: ref Val): ref Ref
+{
+ return ref Ref(0, v, nil, nil);
+}
+
+getBase(ex: ref Exec, r: ref Ref): ref Obj
+{
+ if(!r.isref)
+ runtime(ex, ReferenceError, "not a reference");
+ return r.base;
+}
+
+getPropertyName(ex: ref Exec, r: ref Ref): string
+{
+ if(!r.isref)
+ runtime(ex, ReferenceError, "not a reference");
+ return r.name;
+}
+
+getValue(ex: ref Exec, r: ref Ref): ref Val
+{
+ if(!r.isref)
+ return r.val;
+ b := r.base;
+ if(b == nil)
+ runtime(ex, ReferenceError, "reference " + r.name + " is null");
+ return esget(ex, b, r.name, 0);
+}
+
+putValue(ex: ref Exec, r: ref Ref, v: ref Val)
+{
+ if(!r.isref)
+ runtime(ex, ReferenceError, "not a reference: " + r.name);
+ b := r.base;
+ if(b == nil)
+ b = ex.global;
+ esput(ex, b, r.name, v, 0);
+}
+
+#
+# conversion routines defined by the abstract machine
+# see section 9.
+# note that string, boolean, and number objects are
+# not automaically coerced to values, and vice versa.
+#
+toPrimitive(ex: ref Exec, v: ref Val, ty: int): ref Val
+{
+ if(v.ty != TObj)
+ return v;
+ v = esdefaultval(ex, v.obj, ty, 0);
+ if(v.ty == TObj)
+ runtime(ex, TypeError, "toPrimitive returned an object");
+ return v;
+}
+
+toBoolean(ex: ref Exec, v: ref Val): ref Val
+{
+ case v.ty{
+ TUndef or
+ TNull =>
+ return false;
+ TBool =>
+ return v;
+ TNum =>
+ if(isnan(v.num))
+ return false;
+ if(v.num == 0.)
+ return false;
+ TStr =>
+ if(v.str == "")
+ return false;
+ TObj =>
+ break;
+ TRegExp =>
+ break;
+ * =>
+ runtime(ex, TypeError, "unknown type in toBoolean");
+ }
+ return true;
+}
+
+toNumber(ex: ref Exec, v: ref Val): real
+{
+ case v.ty{
+ TUndef =>
+ return NaN;
+ TNull =>
+ return 0.;
+ TBool =>
+ if(v == false)
+ return 0.;
+ return 1.;
+ TNum =>
+ return v.num;
+ TStr =>
+ (si, r) := parsenum(ex, v.str, 0, ParseReal|ParseHex|ParseTrim|ParseEmpty);
+ if(si != len v.str)
+ r = Math->NaN;
+ return r;
+ TObj =>
+ return toNumber(ex, toPrimitive(ex, v, TNum));
+ TRegExp =>
+ return NaN;
+ * =>
+ runtime(ex, TypeError, "unknown type in toNumber");
+ return 0.;
+ }
+}
+
+toInteger(ex: ref Exec, v: ref Val): real
+{
+ r := toNumber(ex, v);
+ if(isnan(r))
+ return 0.;
+ if(r == 0. || r == +Infinity || r == -Infinity)
+ return r;
+ return copysign(floor(fabs(r)), r);
+}
+
+#
+# toInt32 == toUint32, except for numbers > 2^31
+#
+toInt32(ex: ref Exec, v: ref Val): int
+{
+ r := toNumber(ex, v);
+ if(isnan(r) || r == 0. || r == +Infinity || r == -Infinity)
+ return 0;
+ r = copysign(floor(fabs(r)), r);
+ # need to convert to big since it might be unsigned
+ return int big fmod(r, 4294967296.);
+}
+
+toUint32(ex: ref Exec, v: ref Val): big
+{
+ r := toNumber(ex, v);
+ if(isnan(r) || r == 0. || r == +Infinity || r == -Infinity)
+ return big 0;
+ r = copysign(floor(fabs(r)), r);
+ # need to convert to big since it might be unsigned
+ b := big fmod(r, 4294967296.);
+ if(b < big 0)
+ fatal(ex, "uint32 < 0");
+ return b;
+}
+
+toUint16(ex: ref Exec, v: ref Val): int
+{
+ return toInt32(ex, v) & 16rffff;
+}
+
+toString(ex: ref Exec, v: ref Val): string
+{
+ case v.ty{
+ TUndef =>
+ return "undefined";
+ TNull =>
+ return "null";
+ TBool =>
+ if(v == false)
+ return "false";
+ return "true";
+ TNum =>
+ r := v.num;
+ if(isnan(r))
+ return "NaN";
+ if(r == 0.)
+ return "0";
+ if(r == Infinity)
+ return "Infinity";
+ if(r == -Infinity)
+ return "-Infinity";
+ # this is wrong, but right is too hard
+ if(r < 1000000000000000000000. && r >= 1./(1000000.)){
+ return string r;
+ }
+ return string r;
+ TStr =>
+ return v.str;
+ TObj =>
+ return toString(ex, toPrimitive(ex, v, TStr));
+ TRegExp =>
+ return "/" + v.rev.p + "/" + v.rev.f;
+ * =>
+ runtime(ex, TypeError, "unknown type in ToString");
+ return "";
+ }
+}
+
+toObject(ex: ref Exec, v: ref Val): ref Obj
+{
+ case v.ty{
+ TUndef =>
+ runtime(ex, TypeError, "can't convert undefined to an object");
+ TNull =>
+ runtime(ex, TypeError, "can't convert null to an object");
+ TBool or
+ TStr or
+ TNum or
+ TRegExp =>
+ return coerceToObj(ex, v).obj;
+ TObj =>
+ return v.obj;
+ * =>
+ runtime(ex, TypeError, "unknown type in toObject");
+ return nil;
+ }
+ return nil;
+}
diff --git a/appl/lib/ecmascript/pprint.b b/appl/lib/ecmascript/pprint.b
new file mode 100644
index 00000000..01fd0640
--- /dev/null
+++ b/appl/lib/ecmascript/pprint.b
@@ -0,0 +1,378 @@
+PPrint: adt
+{
+ ex: ref Exec;
+ code: ref Code;
+ stack: array of string;
+ sp: int;
+};
+
+mkpprint(ex: ref Exec, code: ref Code): ref PPrint
+{
+ return ref PPrint(ex, code, array[4] of string, 0);
+}
+
+funcprint(ex: ref Exec, func: ref Ecmascript->Obj): string
+{
+ params := func.call.params;
+ (nil, name) := str->splitr(func.val.str, ".");
+ s := "function " + name + "(";
+ sep := "";
+ for(i := 0; i < len params; i++){
+ s += sep + params[i];
+ sep = ", ";
+ }
+ s += "){";
+ if(func.host != nil)
+ s += "[host code]";
+ else
+ s += "\n" + pprint(ex, func.call.code, " ");
+ s += "}";
+ return s;
+}
+
+pprint(ex: ref Exec, code: ref Code, indent: string): string
+{
+ pp := ref PPrint(ex, code, array[4] of string, 0);
+#for(i:=0; i < code.npc; i++) sys->print("%d: %d\n", i, int code.ops[i]);
+ s := pstmt(pp, 0, code.npc, indent);
+
+ if(pp.sp != 0)
+ fatal(ex, "pprint stack not balanced");
+
+ return s;
+}
+
+pstmt(pp: ref PPrint, pc, epc: int, indent: string): string
+{
+ e, e1, e2: string;
+ c, apc: int;
+
+ code := pp.code;
+ s := "";
+ while(pc < epc){
+ op := int code.ops[pc++];
+ while(op == Llabel){
+ (pc, c) = getconst(code.ops, pc);
+ s += code.strs[c] + ":\n";
+ op = int code.ops[pc++];
+ }
+ s += indent;
+ case op{
+ Lbreak or
+ Lcontinue or
+ Lreturn =>
+ s += tokname(op);
+ if(op == Lreturn){
+ (pc, e) = pexp(pp, pc, code.npc);
+ s += " " + e;
+ }
+ s += ";\n";
+ Lbreaklab or
+ Lcontinuelab =>
+ s += tokname(op);
+ (pc, c) = getconst(code.ops, pc);
+ s += " " + code.strs[c] + ";\n";
+ '{' =>
+ (pc, apc) = getjmp(code.ops, pc);
+ s += "{\n" + pstmt(pp, pc, apc, indent+" ") + indent + "}\n";
+ pc = apc;
+ Lif or
+ Lwith or
+ Lwhile =>
+ (pc, apc) = getjmp(code.ops, pc);
+ (pc, e) = pexp(pp, pc, apc);
+ (pc, apc) = getjmp(code.ops, pc);
+ s += tokname(op) + "(" + e + "){\n";
+ s += pstmt(pp, pc, apc, indent+" ");
+ if(op == Lif){
+ (pc, apc) = getjmp(code.ops, apc);
+ if(pc != apc)
+ s += indent + "}else{\n";
+ s += pstmt(pp, pc, apc, indent+" ");
+ }
+ s += indent + "}\n";
+ pc = apc;
+ Ldo =>
+ (pc, apc) = getjmp(code.ops, pc);
+ e = pstmt(pp, pc, apc, indent+" ");
+ (pc, apc) = getjmp(code.ops, apc);
+ (pc, e1) = pexp(pp, pc, apc);
+ s += "do{\n" + e + indent + "}(while(" + e1 + ");\n";
+ pc = apc;
+ Lfor or
+ Lforvar or
+ Lforin or
+ Lforvarin =>
+ (pc, apc) = getjmp(code.ops, pc);
+ (pc, e) = pexp(pp, pc, apc);
+ (pc, apc) = getjmp(code.ops, pc);
+ (pc, e1) = pexp(pp, pc, apc);
+ s += "for(";
+ if(op == Lforvar || op == Lforvarin)
+ s += "var ";
+ s += e;
+ if(op == Lfor || op == Lforvar){
+ (pc, apc) = getjmp(code.ops, pc);
+ (pc, e2) = pexp(pp, pc, apc);
+ s += "; " + e1 + "; " + e2;
+ }else
+ s += " in " + e1;
+ s += "){\n";
+ (pc, apc) = getjmp(code.ops, pc);
+ s += pstmt(pp, pc, apc, indent+" ");
+ s += indent + "}\n";
+ pc = apc;
+ ';' =>
+ s += ";\n";
+ Lvar =>
+ (pc, apc) = getjmp(code.ops, pc);
+ (pc, e) = pexp(pp, pc, apc);
+ s += "var " + e + ";\n";
+ Lswitch =>
+ (pc, apc) = getjmp(code.ops, pc);
+ (pc, e) = pexp(pp, pc, apc);
+ s += "switch (" + e + ") {\n";
+ (pc, apc) = getjmp(code.ops, pc);
+ (pc, e) = pcaseblk(pp, pc, apc, indent);
+ s += e + indent + "}\n";
+ pc = apc;
+ Lthrow =>
+ (pc, e) = pexp(pp, pc, code.npc);
+ s += "throw " + e + "\n";
+ Ltry =>
+ s += "try\n";
+ (pc, apc) = getjmp(code.ops, pc);
+ s += pstmt(pp, pc, apc, indent+" ");
+ (pc, apc) = getjmp(code.ops, apc);
+ if(pc != apc){
+ (pc, c) = getconst(code.ops, ++pc);
+ s += "catch(" + code.strs[c] + ")\n";
+ s += pstmt(pp, pc, apc, indent+" ");
+ }
+ (pc, apc) = getjmp(code.ops, apc);
+ if(pc != apc){
+ s += "finally\n";
+ s += pstmt(pp, pc, apc, indent+" ");
+ }
+ pc = apc;
+ * =>
+ (pc, e) = pexp(pp, pc-1, code.npc);
+ s += e + ";\n";
+ }
+ }
+ return s;
+}
+
+pexp(pp: ref PPrint, pc, epc: int): (int, string)
+{
+ c, apc: int;
+ s, f, a, a1, a2: string;
+
+ code := pp.code;
+ savesp := pp.sp;
+out: while(pc < epc){
+ case op := int code.ops[pc++]{
+ Lthis =>
+ s = "this";
+ Lid or
+ Lnum or
+ Lstr or
+ Lregexp =>
+ (pc, c) = getconst(code.ops, pc);
+ if(op == Lnum)
+ s = string code.nums[c];
+ else{
+ s = code.strs[c];
+ if(op == Lstr)
+ s = "\""+escstr(code.strs[c])+"\"";
+ }
+ '*' or
+ '/' or
+ '%' or
+ '+' or
+ '-' or
+ Llsh or
+ Lrsh or
+ Lrshu or
+ '<' or
+ '>' or
+ Lleq or
+ Lgeq or
+ Lin or
+ Linstanceof or
+ Leq or
+ Lneq or
+ Lseq or
+ Lsne or
+ '&' or
+ '^' or
+ '|' or
+ '=' or
+ '.' or
+ ',' or
+ '[' =>
+ a2 = ppop(pp);
+ a1 = ppop(pp);
+ s = tokname(op);
+ if(a1[0] == '='){
+ s += "=";
+ a1 = a1[1:];
+ }
+ if(op == '[')
+ s = a1 + "[" + a2 + "]";
+ else{
+ if(op != '.'){
+ if(op != ',')
+ s = " " + s;
+ s = s + " ";
+ }
+ s = a1 + s + a2;
+ }
+ Ltypeof or
+ Ldelete or
+ Lvoid or
+ Lnew or
+ Linc or
+ Ldec or
+ Lpreadd or
+ Lpresub or
+ '~' or
+ '!' or
+ Lpostinc or
+ Lpostdec =>
+ a = ppop(pp);
+ s = tokname(op);
+ if(op == Lpostinc || op == Lpostdec)
+ s = a + s;
+ else{
+ if(op == Ltypeof || op == Ldelete || op == Lvoid || op == Lnew)
+ s += " ";
+ s += a;
+ }
+ '(' =>
+ s = "(";
+ ')' =>
+ s = ppop(pp);
+ if(ppop(pp) != "(")
+ fatal(pp.ex, "unbalanced () in pexp");
+ s = "(" + s + ")";
+ Lgetval or
+ Las =>
+ continue;
+ Lasop =>
+ s = "=" + ppop(pp);
+ Lcall or
+ Lnewcall =>
+ (pc, c) = getconst(code.ops, pc);
+ a = "";
+ sep := "";
+ for(sp := pp.sp-c; sp < pp.sp; sp++){
+ a += sep + pp.stack[sp];
+ sep = ", ";
+ }
+ pp.sp -= c;
+ f = ppop(pp);
+ if(op == Lnewcall)
+ f = "new " + f;
+ s = f + "(" + a + ")";
+ ';' =>
+ break out;
+ Landand or
+ Loror or
+ '?' =>
+ s = ppop(pp);
+ (pc, apc) = getjmp(code.ops, pc);
+ (pc, a1) = pexp(pp, pc, apc);
+ s += " " + tokname(op) + " " + a1;
+ if(op == '?'){
+ (pc, apc) = getjmp(code.ops, pc);
+ (pc, a2) = pexp(pp, pc, apc);
+ s += " : "+ a2;
+ }
+ * =>
+ fatal(pp.ex, "pexp: unknown op " + tokname(op));
+ }
+ ppush(pp, s);
+ }
+
+ if(savesp == pp.sp)
+ return (pc, "");
+
+ if(savesp != pp.sp-1)
+ fatal(pp.ex, "unbalanced stack in pexp");
+ return (pc, ppop(pp));
+}
+
+pcaseblk(pp: ref PPrint, pc, epc: int, indent: string): (int, string)
+{
+ code := pp.code;
+ defpc, clausepc, nextpc, apc: int;
+ s, a: string;
+
+ (pc, defpc) = getjmp(code.ops, pc);
+ clausepc = pc;
+ (pc, nextpc) = getjmp(code.ops, pc);
+ for (; pc < epc; (clausepc, (pc, nextpc)) = (nextpc, getjmp(code.ops, nextpc))) {
+ if (clausepc == defpc) {
+ s += indent + "default:\n";
+ } else {
+ (pc, apc) = getjmp(code.ops, pc);
+ (pc, a) = pexp(pp, pc, apc);
+ s += indent + "case " + a + ":\n";
+ }
+ s += pstmt(pp, pc, nextpc, indent+"\t");
+ }
+ return (epc, s);
+}
+
+ppush(pp: ref PPrint, s: string)
+{
+ if(pp.sp >= len pp.stack){
+ st := array[2 * len pp.stack] of string;
+ st[:] = pp.stack;
+ pp.stack = st;
+ }
+ pp.stack[pp.sp++] = s;
+}
+
+ppop(pp: ref PPrint): string
+{
+ if(pp.sp == 0)
+ fatal(pp.ex, "popping too far off the pstack");
+ return pp.stack[--pp.sp];
+}
+
+unescmap := array[128] of
+{
+ '\'' => byte '\'',
+ '"' => byte '"',
+ '\\' => byte '\\',
+ '\b' => byte 'b',
+ '\u000c' => byte 'f',
+ '\n' => byte 'n',
+ '\r' => byte 'r',
+ '\t' => byte 't',
+
+ * => byte 0
+};
+
+escstr(s: string): string
+{
+ n := len s;
+ sb := "";
+ for(i := 0; i < n; i++){
+ c := s[i];
+ if(c < 128 && (e := int unescmap[c])){
+ sb[len sb] = '\\';
+ sb[len sb] = e;
+ }else if(c > 128 || c < 32){
+ sb += "\\u0000";
+ for(j := 1; j <= 4; j++){
+ sb[len sb - j] = "0123456789abcdef"[c & 16rf];
+ c >>= 4;
+ }
+ }else
+ sb[len sb] = c;
+ }
+ return sb;
+}
diff --git a/appl/lib/ecmascript/regexp.b b/appl/lib/ecmascript/regexp.b
new file mode 100644
index 00000000..945e0cc5
--- /dev/null
+++ b/appl/lib/ecmascript/regexp.b
@@ -0,0 +1,1286 @@
+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 )");
+ }
+ } else {
+ pno := ++ar.pno;
+ lp := newRe(LPN, NONE, NONE, nil, ar, pno, 0);
+ rp := newRe(RPN, NONE, NONE, nil, ar, pno, 0);
+ if(s.peek() == ')') {
+ s.next();
+ prim = newRe(CAT, lp, rp, nil, ar, 0, 0);
+ } else {
+ if(s.peek() == '?'){
+ s.next();
+ case s.next(){
+ ':' => ar.rex[lp].kind = LPN0;
+ ar.rex[rp].kind = RPN0;
+ '=' => ar.rex[lp].kind = LPN1;
+ ar.rex[rp].kind = RPN1;
+ '!' => ar.rex[lp].kind = LPN2;
+ ar.rex[rp].kind = RPN2;
+ * => syntax("bad char after ?");
+ }
+ }
+ prim = re(s, ar);
+ if(prim==NIL || s.next()!=')')
+ syntax("( with no )");
+ else {
+ prim = newRe(CAT, prim, rp, nil, ar, 0, 0);
+ prim = newRe(CAT, lp, prim, nil, ar, 0, 0);
+ }
+ }
+ }
+ '[' => prim = newRe(SET, NONE, NONE, newSet(s), ar, 0, 0);
+ * => case kind {
+ '.' => kind = DOT;
+ '^' => kind = HAT;
+ '$' => kind = DOL;
+ }
+ (c, set, op) := esc(s, kind, 0);
+ if(set != nil)
+ prim = newRe(SET, NONE, NONE, set, ar, 0, 0);
+ else if(op == LCP){
+ if(c > ar.pno)
+ syntax("\num too big");
+ prim = newRe(LCP, NONE, NONE, nil, ar, 0, 0);
+ ar.rex[prim].ns = ref Nstate(c, c);
+ }
+ else
+ prim = newRe(c, NONE, NONE, nil, ar, 0, 0);
+ }
+ case s.peek() {
+ '*' => kind = CLO;
+ '+' => kind = PCLO;
+ '?' => kind = OPT;
+ '{' => s.next();
+ (n1, n2) = drange(s);
+ kind = MNCLO;
+ if(s.peek() != '}')
+ syntax("{ with no }");
+ * => return prim;
+ }
+ s.next();
+ greedy := 1;
+ if(s.peek() == '?'){
+ # non-greedy op
+ greedy = 0;
+ s.next();
+ }
+ prim = newRe(kind, prim, NONE, nil, ar, 0, greedy);
+ if(kind == MNCLO)
+ ns := ar.rex[prim].ns = ref Nstate(n1, n2);
+ return prim;
+}
+
+esc(s: ref ReStr, char: int, inset: int): (int, ref Set, int)
+{
+ set: ref Set;
+
+ op := 0;
+ if(char == '\\') {
+ char = s.next();
+ case char {
+ 'b' =>
+ if(inset)
+ char = '\b';
+ else
+ char = BEET;
+ 'B' => if(inset)
+ syntax("\\B in set");
+ else
+ char = BEEF;
+ 'f' => char = '\u000c';
+ 'n' => char = '\n';
+ 'r' => char = '\r';
+ 't' => char = '\t';
+ 'v' => char = '\v';
+ '0' to '9' =>
+ s.unput();
+ char = digits(s);
+ if(char == 0)
+ char = '\0';
+ else if(inset)
+ syntax("\num in set");
+ else
+ op = LCP;
+ 'x' => char = hexdigits(s, 2);
+ 'u' => char = hexdigits(s, 4);
+ 'c' => char = s.next()%32;
+ 'd' or 'D' =>
+ set = newset('0', '9');
+ if(char == 'D')
+ set.neg = 1;
+ 's' or 'S' =>
+ set = newset(' ', ' ');
+ addsets(set, "\t\v\u000c\u00a0\n\r\u2028\u2029");
+ if(char == 'S')
+ set.neg = 1;
+ 'w' or 'W' =>
+ set = newset('0', '9');
+ addset(set, 'a', 'z');
+ addset(set, 'A', 'Z');
+ addset(set, '_', '_');
+ if(char == 'W')
+ set.neg = 1;
+ * =>
+ ;
+ }
+ }
+ if(char == -1){
+ if(inset)
+ syntax("bad set");
+ else
+ syntax("bad character");
+ }
+ return (char, set, op);
+}
+
+isdigit(c: int): int
+{
+ return c >= '0' && c <= '9';
+}
+
+islower(c: int): int
+{
+ return c >= 'a' && c <= 'z';
+}
+
+isupper(c: int): int
+{
+ return c >= 'A' && c <= 'Z';
+}
+
+isalpha(c: int): int
+{
+ return islower(c) || isupper(c);
+}
+
+hexdigit(c: int): int
+{
+ if(isdigit(c))
+ return c-'0';
+ if('a' <= c && c <= 'f')
+ return c-'a'+10;
+ if('A' <= c && c <= 'F')
+ return c-'A'+10;
+ return -1;
+}
+
+digits(s: ref ReStr): int
+{
+ n := 0;
+ while(isdigit(s.peek()))
+ n = 10*n + s.next() -'0';
+ return n;
+}
+
+hexdigits(s: ref ReStr, n: int): int
+{
+ x := 0;
+ for(i := 0; i < n; i++){
+ v := hexdigit(s.next());
+ if(v < 0)
+ return -1;
+ x = 16*x+v;
+ }
+ return x;
+}
+
+drange(s: ref ReStr): (int, int)
+{
+ n1 := n2 := -1;
+ if(isdigit(s.peek()))
+ n1 = digits(s);
+ if(s.peek() == ','){
+ s.next();
+ if(isdigit(s.peek()))
+ n2 = digits(s);
+ else
+ n2 = HUGE;
+ }
+ else
+ n2 = n1;
+ if(n1 < 0 || n1 > n2)
+ syntax("bad number range");
+ return (n1, n2);
+}
+
+# walk the tree adjusting pointers to refer to
+# next state of the finite state machine
+
+walk(r: refRex, succ: refRex, ar: ref Arena)
+{
+ if(r==NONE)
+ return;
+ rex := ar.rex[r];
+ case rex.kind {
+ ALT => walk(rex.left, succ, ar);
+ walk(rex.right, succ, ar);
+ return;
+ CAT => walk(rex.left, rex.right, ar);
+ walk(rex.right, succ, ar);
+ ar.rex[r] = ar.rex[rex.left]; # optimization
+ return;
+ CLO or PCLO =>
+ end := newRe(OPT, r, succ, nil, ar, 0, rex.greedy); # here's the circularity
+ walk(rex.left, end, ar);
+ OPT => walk(rex.left, succ, ar);
+ MNCLO =>
+ ar.ptr++;
+ walk(rex.left, r, ar);
+ LCP =>
+ ar.rex[r].left = newRe(IDLE, NONE, succ, nil, ar, 0, 0);
+ }
+ ar.rex[r].right = succ;
+}
+
+prtree(r: refRex, ar: ref Arena, done: list of int, ind: string): list of int
+{
+ sys->print("%s", ind);
+ if(r==NIL){
+ sys->print("NIL\n");
+ return done;
+ }
+ if(r==NONE){
+ sys->print("NONE\n");
+ return done;
+ }
+ printed := 0;
+ for(li := done; li != nil; li = tl li){
+ if(hd li == r){
+ printed = 1;
+ break;
+ }
+ }
+ rex := ar.rex[r];
+ op := "";
+ z := "Z";
+ case rex.kind{
+ ALT => op = "|";
+ CAT => op = "and";
+ DOT => op = ".";
+ SET => op = "[]";
+ HAT => op = "^";
+ DOL => op = "$";
+ NUL => op = "NUL";
+ PCLO => op = "+";
+ CLO => op = "*";
+ OPT => op = "?";
+ LPN => op = "(";
+ RPN => op = ")";
+ LPN0 => op = "?:";
+ RPN0 => op = ":?";
+ LPN1 => op = "?=";
+ RPN1 => op = "=?";
+ LPN2 => op = "?!";
+ RPN2 => op = "!?";
+ BEET => op = "\\b";
+ BEEF => op = "\\B";
+ MNCLO => op = "{}";
+ LCP => op = "n";
+ IDLE => op = "i";
+ * => z[0] = rex.kind; op = z;
+ }
+ if(printed){
+ sys->print("node %d (%d)\n", r, r);
+ return done;
+ }
+ else{
+ if(rex.ns != nil)
+ sys->print("%s [%d-%d] (%d)\n", op, rex.ns.m, rex.ns.n, r);
+ else
+ sys->print("%s (%d)\n", op, r);
+ done = r :: done;
+ ind += " ";
+ done = prtree(rex.left, ar, done, ind);
+ done = prtree(rex.right, ar, done, ind);
+ return done;
+ }
+}
+
+compile(e: string, flag: int): (Re, string)
+{
+ if(e == nil)
+ return (nil, "missing expression");
+ s := ref ReStr(e, 0, len e);
+ ar := ref Arena(array[2*s.n] of Rex, 0, 0, (flag&1)-1);
+ start := ar.start = re(s, ar);
+ if(start==NIL || s.n!=0)
+ syntax("invalid regular expression");
+ walk(start, NIL, ar);
+ # prtree(start, ar, nil, "");
+ if(ar.pno < 0)
+ ar.pno = 0;
+ return (ar, nil);
+}
+
+# todo: queue for epsilon and advancing transitions
+
+Num: adt{
+ ns: ref Nstate;
+ m: int;
+ n: int;
+};
+Gaz: adt {
+ pno: int;
+ beg: int;
+ end: int;
+};
+Trace: adt {
+ cre: refRex; # cursor in Re
+ trans: int; # 0 epsilon transition, 1 advancing transition
+ beg: int; # where this trace began;
+ end: int; # where this trace ended if success (-1 by default)
+ gaz: list of Gaz;
+ ns: list of ref Num;
+};
+Queue: adt {
+ ptr: int;
+ q: array of Trace;
+};
+
+execute(re: Re, s: string): array of (int, int)
+{
+ return executese(re, s, (-1,-1), 1, 1, 1, 0);
+}
+
+executese(re: Re, s: string, range: (int, int), bol: int, eol: int, multiline: int, ignorecase: int): array of (int,int)
+{
+ if(re==nil)
+ return nil;
+ (s0, s1) := range;
+ if(s0 < 0)
+ s0 = 0;
+ if(s1 < 0)
+ s1 = len s;
+ match := 0;
+ todo := ref Queue(0, array[2*re.ptr] of Trace);
+ for(i:=s0; i<=s1; i++) {
+ if(!match) # no leftmost match yet
+ todo.q[todo.ptr++] = Trace(re.start, 0, i, -1, nil, nil);
+ for(k:=0; k<todo.ptr; k++) {
+ q := todo.q[k];
+ if(q.trans)
+ continue;
+ rex := re.rex[q.cre];
+ next0 := next1 := next2 := NONE;
+ case rex.kind {
+ NUL =>
+ next1 = rex.right;
+ DOT =>
+ if(i<len s && !islt(s[i]))
+ next2 = rex.right;
+ HAT =>
+ if(i == s0 && bol)
+ next1 = rex.right;
+ else if(multiline && i > 0 && islt(s[i-1]))
+ next1 = rex.right;
+ DOL =>
+ if(i == s1 && eol)
+ next1 = rex.right;
+ else if(multiline && i < s1 && islt(s[i]))
+ next1 = rex.right;
+ SET =>
+ if(i<len s && member(s[i], rex.set, ignorecase))
+ next2 = rex.right;
+ CAT or
+ PCLO =>
+ next1 = rex.left;
+ ALT or
+ CLO or
+ OPT =>
+ if(rex.kind == ALT || rex.greedy){
+ next0 = rex.left;
+ next1 = rex.right;
+ }
+ else{
+ next0 = rex.right;
+ next1 = rex.left;
+ }
+ LPN =>
+ next1 = rex.right;
+ q.gaz = Gaz(rex.pno,i,-1)::q.gaz;
+ RPN =>
+ next1 = rex.right;
+ for(r:=q.gaz; ; r=tl r) {
+ (pno,beg1,end1) := hd r;
+ if(rex.pno==pno && end1==-1) {
+ q.gaz = Gaz(pno,beg1,i)::q.gaz;
+ break;
+ }
+ }
+ LPN0 or RPN0 or RPN1 or RPN2 =>
+ next1 = rex.right;
+ LPN1 =>
+ (rpn, nxt, nre) := storetree(q.cre, re);
+ m := executese(nre, s, (i, -1), bol, eol, multiline, ignorecase);
+ if(m != nil && m[0].t0 == i){
+ next1 = nxt;
+ for(j := 1; j < len m; j++)
+ if(m[j].t0 >= 0)
+ q.gaz = Gaz(j, m[j].t0, m[j].t1)::q.gaz;
+ }
+ restoretree(LPN1, rpn, nxt, nre);
+ LPN2 =>
+ (rpn, nxt, nre) := storetree(q.cre, re);
+ m := executese(nre, s, (i, -1), bol, eol, multiline, ignorecase);
+ if(m == nil || m[0].t0 != i)
+ next1 = nxt;
+ restoretree(LPN2, rpn, nxt, nre);
+ MNCLO =>
+ num: ref Num;
+
+ (q.ns, num) = nextn(q.cre, q.ns, rex.ns.m, rex.ns.n, re);
+ if(num.m > 0)
+ next1 = rex.left;
+ else if(num.n > 0){
+ if(rex.greedy){
+ next0 = rex.left;
+ next1 = rex.right;
+ }
+ else{
+ next0 = rex.right;
+ next1 = rex.left;
+ }
+ }
+ else{
+ next1 = rex.right;
+ (num.m, num.n) = (-1, -1);
+ }
+ LCP =>
+ pno := rex.ns.m;
+ (beg1, end1) := lcpar(q.gaz, pno);
+ l := end1-beg1;
+ if(beg1 < 0) # undefined so succeeds
+ next1 = rex.right;
+ else if(i+l <= s1 && eqstr(s[beg1: end1], s[i: i+l], ignorecase)){
+ (q.ns, nil) = nextn(rex.left, q.ns, l, l, re);
+ next1 = rex.left; # idle
+ }
+ IDLE =>
+ num: ref Num;
+
+ (q.ns, num) = nextn(q.cre, q.ns, -1, -1, re);
+ if(num.m >= 0)
+ next2 = q.cre;
+ else{
+ next1 = rex.right;
+ (num.m, num.n) = (-1, -1);
+ }
+ BEET =>
+ if(iswordc(s, i-1) != iswordc(s, i))
+ next1 = rex.right;
+ BEEF =>
+ if(iswordc(s, i-1) == iswordc(s, i))
+ next1 = rex.right;
+ * =>
+ if(i<len s && (rex.kind==s[i] || (ignorecase && eqcase(rex.kind, s[i]))))
+ next2 = rex.right;
+ }
+ l := k;
+ if(next0 != NONE) {
+ if(next0 != NIL)
+ (k, l) = insert(next0, 0, q.beg, -1, q.gaz, q.ns, todo, k, l);
+ else{
+ match = 1;
+ (k, l) = insert(NIL, 2, q.beg, i, q.gaz, nil, todo, k, l);
+ }
+ }
+ if(next1 != NONE) {
+ if(next1 != NIL)
+ (k, l) = insert(next1, 0, q.beg, -1, q.gaz, q.ns, todo, k, l);
+ else{
+ match = 1;
+ (k, l) = insert(NIL, 2, q.beg, i, q.gaz, nil, todo, k, l);
+ }
+ }
+ if(next2 != NONE) {
+ if(next2 != NIL)
+ (k, l) = insert(next2, 1, q.beg, -1, q.gaz, q.ns, todo, k, l);
+ else{
+ match = 1;
+ (k, l) = insert(NIL, 2, q.beg, i+1, q.gaz, nil, todo, k, l);
+ }
+ }
+ }
+ if(!atoe(todo) && match)
+ break;
+ }
+ if(todo.ptr == 0)
+ return nil;
+ if(todo.ptr > 1)
+ rfatal(sys->sprint("todo.ptr = %d", todo.ptr));
+ if(todo.q[0].trans != 2)
+ rfatal(sys->sprint("trans = %d", todo.q[0].trans));
+ if(todo.q[0].cre != NIL)
+ rfatal(sys->sprint("cre = %d", todo.q[0].cre));
+ beg := todo.q[0].beg;
+ end := todo.q[0].end;
+ gaz := todo.q[0].gaz;
+ if(beg == -1)
+ return nil;
+ result := array[re.pno+1] of { 0 => (beg,end), * => (-1,-1) };
+ for( ; gaz!=nil; gaz=tl gaz) {
+ (pno, beg1, end1) := hd gaz;
+ (rbeg, nil) := result[pno];
+ if(rbeg==-1 && (beg1|end1)!=-1)
+ result[pno] = (beg1,end1);
+ }
+ return result;
+}
+
+better(newbeg, newend, oldbeg, oldend: int): int
+{
+ return oldbeg==-1 || newbeg<oldbeg ||
+ newbeg==oldbeg && newend>oldend;
+}
+
+insert(next: refRex, trans: int, tbeg: int, tend: int, tgaz: list of Gaz, tns: list of ref Num, todo: ref Queue, k: int, l: int): (int, int)
+{
+# sys->print("insert %d eps=%d beg=%d end=%d (k, l) = (%d %d) => ", next, trans, tbeg, tend, k, l);
+ for(j:=0; j<todo.ptr; j++){
+ if(todo.q[j].trans == trans){
+ if(todo.q[j].cre == next){
+ if(better(todo.q[j].beg, todo.q[j].end, tbeg, tend))
+ return (k, l);
+ else if(better(tbeg, tend, todo.q[j].beg, todo.q[j].end))
+ break;
+ else if(j < k)
+ return (k, l);
+ else
+ break;
+ }
+ }
+ }
+ if(j < k){
+ k--;
+ l--;
+ }
+ if(j < todo.ptr){
+ todo.q[j: ] = todo.q[j+1: todo.ptr];
+ todo.ptr--;
+ }
+ todo.q[l+2: ] = todo.q[l+1: todo.ptr];
+ todo.ptr++;
+ todo.q[l+1] = Trace(next, trans, tbeg, tend, tgaz, tns);
+# for(j=0; j < todo.ptr; j++) sys->print("%d(%d) ", todo.q[j].cre, todo.q[j].trans); sys->print("\n");
+ return (k, l+1);
+}
+
+# remove epsilon transitions and move advancing transitions to epsilon ones
+atoe(todo: ref Queue): int
+{
+ n := 0;
+ for(j := 0; j < todo.ptr; j++){
+ if(todo.q[j].trans){
+ if(todo.q[j].trans == 1){
+ todo.q[j].trans = 0;
+ n++;
+ }
+ }
+ else{
+ todo.q[j: ] = todo.q[j+1: todo.ptr];
+ todo.ptr--;
+ j--;
+ }
+ }
+ return n;
+}
+
+nextn(re: int, ln: list of ref Num, m: int, n: int, ar: ref Arena): (list of ref Num, ref Num)
+{
+ num: ref Num;
+
+ ns := ar.rex[re].ns;
+ for(l := ln; l != nil; l = tl l){
+ if((hd l).ns == ns){
+ num = hd l;
+ break;
+ }
+ }
+ if(num == nil)
+ ln = (num = ref Num(ns, -1, -1)) :: ln;
+ if(num.m == -1 && num.n == -1)
+ (num.m, num.n) = (m, n);
+ else
+ (nil, nil) = (--num.m, --num.n);
+ return (ln, num);
+}
+
+ASCII : con 128;
+WORD : con 32;
+
+mem(c: int, set: ref Set): int
+{
+ return (set.ascii[c/WORD]>>c%WORD)&1;
+}
+
+member(char: int, set: ref Set, ignorecase: int): int
+{
+ if(set.subset != nil){
+ for(l := set.subset; l != nil; l = tl l)
+ if(member(char, hd l, ignorecase))
+ return !set.neg;
+ }
+ if(char < 128){
+ if(ignorecase)
+ return (mem(tolower(char), set) || mem(toupper(char), set))^set.neg;
+ else
+ return ((set.ascii[char/WORD]>>char%WORD)&1)^set.neg;
+ }
+ for(l:=set.unicode; l!=nil; l=tl l) {
+ (beg, end) := hd l;
+ if(char>=beg && char<=end)
+ return !set.neg;
+ }
+ return set.neg;
+}
+
+newSet(s: ref ReStr): ref Set
+{
+ op: int;
+ set0: ref Set;
+
+ set := ref Set(0, array[ASCII/WORD] of {* => 0}, nil, nil);
+ if(s.peek() == '^') {
+ set.neg = 1;
+ s.next();
+ }
+ while(s.n > 0) {
+ char1 := s.next();
+ if(char1 == ']')
+ return set;
+ (char1, set0, op) = esc(s, char1, 1);
+ if(set0 != nil)
+ mergeset(set, set0);
+ char2 := char1;
+ if(s.peek() == '-') {
+ if(set0 != nil)
+ syntax("set in range");
+ s.next();
+ char2 = s.next();
+ if(char2 == ']')
+ break;
+ (char2, set0, op) = esc(s, char2, 1);
+ if(set0 != nil)
+ syntax("set in range");
+ if(char2 < char1)
+ break;
+ }
+ addset(set, char1, char2);
+ }
+ syntax("bad set");
+ return nil;
+}
+
+addset(set: ref Set, c1: int, c2: int)
+{
+ for(c := c1; c <= c2; c++){
+ if(c < ASCII)
+ set.ascii[c/WORD] |= 1<<c%WORD;
+ else{
+ set.unicode = (c, c2) :: set.unicode;
+ break;
+ }
+ }
+}
+
+addsets(set: ref Set, s: string)
+{
+ for(i := 0; i < len s; i++)
+ addset(set, s[i], s[i]);
+}
+
+mergeset(set: ref Set, set0: ref Set)
+{
+ if(!set0.neg){
+ for(i := 0; i < ASCII/WORD; i++)
+ set.ascii[i] |= set0.ascii[i];
+ for(l := set0.unicode; l != nil; l = tl l)
+ set.unicode = hd l :: set.unicode;
+ }
+ else
+ set.subset = set0 :: set.subset;
+}
+
+newset(c1: int, c2: int): ref Set
+{
+ set := ref Set(0, array[ASCII/WORD] of {* => 0}, nil, nil);
+ addset(set, c1, c2);
+ return set;
+}
+
+storetree(lpn: int, re: ref Arena): (int, int, ref Arena)
+{
+ rpn: int;
+
+ rex := re.rex[lpn];
+ k := rex.kind;
+ l := 1;
+ for(;;){
+ rpn = rex.right;
+ rex = re.rex[rpn];
+ if(rex.kind == k)
+ l++;
+ else if(rex.kind == k+1 && --l == 0)
+ break;
+ }
+ re.rex[lpn].kind = LPN;
+ re.rex[rpn].kind = RPN;
+ nxt := re.rex[rpn].right;
+ re.rex[rpn].right = NIL;
+ nre := ref *re;
+ nre.start = lpn;
+ return (rpn, nxt, nre);
+}
+
+restoretree(lop: int, rpn: int, nxt: int, re: ref Arena)
+{
+ lpn := re.start;
+ re.rex[lpn].kind = lop;
+ re.rex[rpn].kind = lop+1;
+ re.rex[rpn].right = nxt;
+}
+
+iswordc(s: string, i: int): int
+{
+ if(i < 0 || i >= len s)
+ return 0;
+ c := s[i];
+ return isdigit(c) || isalpha(c) || c == '_';
+}
+
+lcpar(gaz: list of Gaz, pno: int): (int, int)
+{
+ for(r := gaz; r != nil; r = tl r) {
+ (pno1, beg1, end1) := hd r;
+ if(pno == pno1)
+ return (beg1, end1);
+ }
+ return (-1, -1);
+}
+
+eqstr(s: string, t: string, ic: int): int
+{
+ if(!ic)
+ return s == t;
+ if(len s != len t)
+ return 0;
+ for(i := 0; i < len s; i++)
+ if(!eqcase(s[i], t[i]))
+ return 0;
+ return 1;
+}
+
+eqcase(c1: int, c2: int): int
+{
+ return toupper(c1) == toupper(c2);
+}
+
+syntax(s: string)
+{
+ runtime(regex, SyntaxError, s);
+}
+
+rfatal(s: string)
+{
+ runtime(regex, InternalError, s);
+}
diff --git a/appl/lib/ecmascript/uri.b b/appl/lib/ecmascript/uri.b
new file mode 100644
index 00000000..b571f286
--- /dev/null
+++ b/appl/lib/ecmascript/uri.b
@@ -0,0 +1,140 @@
+tohex(c: int): int
+{
+ if(c > 9)
+ return c-10+'A';
+ return c+'0';
+}
+
+fromhex(ex: ref Exec, c1: int, c2: int): int
+{
+ c1 = hexdigit(c1);
+ c2 = hexdigit(c2);
+ if(c1 < 0 || c2 < 0)
+ runtime(ex, URIError, "bad hex digit");
+ return 16*c1+c2;
+}
+
+isres(c: int): int
+{
+ return c == ';' || c == '/' || c == '?' || c == ':' || c == '@' || c == '&' || c == '=' || c == '+' || c == '$' || c == ',' || c == '#'; # add '#' here for convenience
+}
+
+isunesc(c: int): int
+{
+ return isalpha(c) || isdigit(c) || c == '-' || c == '_' || c == '.' || c == '!' || c == '~' || c == '*' || c == ''' || c == '(' || c == ')';
+}
+
+encode(ex: ref Exec, s: string, flag: int): string
+{
+ m := len s;
+ r := "";
+ n := len r;
+ for(k := 0; k < m; k++){
+ c := s[k];
+ if(isunesc(c) || (flag && isres(c)))
+ r[n++] = c;
+ else{
+ if(c >= 16rdc00 && c <= 16rdfff)
+ runtime(ex, URIError, "char out of range");
+ if(c < 16rd800 || c > 16rdbff)
+ ;
+ else{
+ if(++k == m)
+ runtime(ex, URIError, "char missing");
+ if(s[k] < 16rdc00 || s[k] > 16rdfff)
+ runtime(ex, URIError, "char out of range");
+ c = (c-16rd800)*16r400 + (s[k]-16rdc00) + 16r10000;
+ }
+ s1 := "Z";
+ s1[0] = c;
+ o := array of byte s1;
+ for(j := 0; j < len o; j++){
+ r += sys->sprint("%%%c%c", tohex(int o[j]/16), tohex(int o[j]%16));
+ n += 3;
+ }
+ }
+ }
+ return r;
+}
+
+decode(ex: ref Exec, s: string, flag: int): string
+{
+ m := len s;
+ r := "";
+ n := len r;
+ for(k := 0; k < m; k++){
+ c := s[k];
+ if(c != '%')
+ r[n++] = c;
+ else{
+ start := k;
+ if(k+2 >= m)
+ runtime(ex, URIError, "char missing");
+ c = fromhex(ex, s[k+1], s[k+2]);
+ k += 2;
+ if((c&16r80 == 0)){
+ if(flag && isres(c)){
+ r += s[start: k+1];
+ n += k+1-start;
+ }
+ else
+ r[n++] = c;
+ }
+ else{
+ for(i := 1; ((c<<i)&16r80) == 0; i++)
+ ;
+ if(i == 1 || i > 4)
+ runtime(ex, URIError, "bad hex number");
+ o := array[i] of byte;
+ o[0] = byte c;
+ if(k+3*(n-1) >= m)
+ runtime(ex, URIError, "char missing");
+ for(j := 1; j < i; j++){
+ if(s[++k] != '%')
+ runtime(ex, URIError, "% missing");
+ c = fromhex(ex, s[k+1], s[k+2]);
+ k += 2;
+ if((c&16rc0) != 2)
+ runtime(ex, URIError, "bad hex number");
+ o[j] = byte c;
+ }
+ (c, nil, nil) = sys->byte2char(o, 0);
+ if(c < 16r10000){
+ if(flag && isres(c)){
+ r += s[start: k+1];
+ n += k+1-start;
+ }
+ else
+ r[n++] = c;
+ }
+ else if(c > 16r10ffff)
+ runtime(ex, URIError, "bad byte sequence");
+ else{
+ r[n++] = ((c-16r10000)&16r3ff)+16rdc00;
+ r[n++] = (((c-16r10000)>>10)&16r3ff)+16rd800;
+ }
+ }
+ }
+ }
+ return r;
+}
+
+cdecodeuri(ex: ref Exec, nil, nil: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ return strval(decode(ex, toString(ex, biarg(args, 0)), 1));
+}
+
+cdecodeuric(ex: ref Exec, nil, nil: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ return strval(decode(ex, toString(ex, biarg(args, 0)), 0));
+}
+
+cencodeuri(ex: ref Exec, nil, nil: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ return strval(encode(ex, toString(ex, biarg(args, 0)), 1));
+}
+
+cencodeuric(ex: ref Exec, nil, nil: ref Ecmascript->Obj, args: array of ref Val): ref Val
+{
+ return strval(encode(ex, toString(ex, biarg(args, 0)), 0));
+}