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