diff options
Diffstat (limited to 'appl/lib/ecmascript')
| -rw-r--r-- | appl/lib/ecmascript/builtin.b | 1480 | ||||
| -rw-r--r-- | appl/lib/ecmascript/date.b | 495 | ||||
| -rw-r--r-- | appl/lib/ecmascript/ecmascript.b | 2626 | ||||
| -rw-r--r-- | appl/lib/ecmascript/exec.b | 863 | ||||
| -rw-r--r-- | appl/lib/ecmascript/mkfile | 23 | ||||
| -rw-r--r-- | appl/lib/ecmascript/obj.b | 836 | ||||
| -rw-r--r-- | appl/lib/ecmascript/pprint.b | 378 | ||||
| -rw-r--r-- | appl/lib/ecmascript/regexp.b | 1286 | ||||
| -rw-r--r-- | appl/lib/ecmascript/uri.b | 140 |
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)); +} |
