ref: babf901b4a508c3ec5d1f89655f10377bbdf9637
dir: /appl/lib/ecmascript/exec.b/
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);
}