shithub: purgatorio

ref: 354b56d6bfdca067a6f04a39c4ce4bc9c0160861
dir: /appl/lib/ecmascript/exec.b/

View raw version
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);
}