diff options
| author | Charles.Forsyth <devnull@localhost> | 2006-12-22 17:07:39 +0000 |
|---|---|---|
| committer | Charles.Forsyth <devnull@localhost> | 2006-12-22 17:07:39 +0000 |
| commit | 37da2899f40661e3e9631e497da8dc59b971cbd0 (patch) | |
| tree | cbc6d4680e347d906f5fa7fca73214418741df72 /appl/lib/ecmascript/obj.b | |
| parent | 54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff) | |
20060303a
Diffstat (limited to 'appl/lib/ecmascript/obj.b')
| -rw-r--r-- | appl/lib/ecmascript/obj.b | 836 |
1 files changed, 836 insertions, 0 deletions
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; +} |
