summaryrefslogtreecommitdiff
path: root/appl/lib/ecmascript/obj.b
diff options
context:
space:
mode:
authorCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
committerCharles.Forsyth <devnull@localhost>2006-12-22 17:07:39 +0000
commit37da2899f40661e3e9631e497da8dc59b971cbd0 (patch)
treecbc6d4680e347d906f5fa7fca73214418741df72 /appl/lib/ecmascript/obj.b
parent54bc8ff236ac10b3eaa928fd6bcfc0cdb2ba46ae (diff)
20060303a
Diffstat (limited to 'appl/lib/ecmascript/obj.b')
-rw-r--r--appl/lib/ecmascript/obj.b836
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;
+}