diff options
Diffstat (limited to 'appl/lib/ecmascript/exec.b')
| -rw-r--r-- | appl/lib/ecmascript/exec.b | 863 |
1 files changed, 863 insertions, 0 deletions
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); +} |
