xref: /inferno-os/appl/lib/ecmascript/obj.b (revision 7ef44d652ae9e5e1f5b3465d73684e4a54de73c0)
1#
2# want to use the value in a context which
3# prefers an object, so coerce schizo vals
4# to object versions
5#
6coerceToObj(ex: ref Exec, v: ref Val): ref Val
7{
8	o: ref Obj;
9
10	case v.ty{
11	TBool =>
12		o = mkobj(ex.boolproto, "Boolean");
13		o.val = v;
14	TStr =>
15		o = mkobj(ex.strproto, "String");
16		o.val = v;
17		valinstant(o, DontEnum|DontDelete|ReadOnly, "length", numval(real len v.str));
18	TNum =>
19		o = mkobj(ex.numproto, "Number");
20		o.val = v;
21	TRegExp =>
22		o = mkobj(ex.regexpproto, "RegExp");
23		o.val = v;
24		valinstant(o, DontEnum|DontDelete|ReadOnly, "length", numval(real len v.rev.p));
25		valinstant(o, DontEnum|DontDelete|ReadOnly, "source", strval(v.rev.p));
26		valinstant(o, DontEnum|DontDelete|ReadOnly, "global", strhas(v.rev.f, 'g'));
27		valinstant(o, DontEnum|DontDelete|ReadOnly, "ignoreCase", strhas(v.rev.f, 'i'));
28		valinstant(o, DontEnum|DontDelete|ReadOnly, "multiline", strhas(v.rev.f, 'm'));
29		valinstant(o, DontEnum|DontDelete, "lastIndex", numval(real v.rev.i));
30	* =>
31		return v;
32	}
33	return objval(o);
34}
35
36coerceToVal(v: ref Val): ref Val
37{
38	if(v.ty != TObj)
39		return v;
40	o := v.obj;
41	if(o.host != nil && o.host != me
42	|| o.class != "String"
43	|| o.class != "Number"
44	|| o.class != "Boolean")
45		return v;
46	return o.val;
47}
48
49isstrobj(o: ref Obj): int
50{
51	return (o.host == nil || o.host == me) && o.class == "String";
52}
53
54isnumobj(o: ref Obj): int
55{
56	return (o.host == nil || o.host == me) && o.class == "Number";
57}
58
59isboolobj(o: ref Obj): int
60{
61	return (o.host == nil || o.host == me) && o.class == "Boolean";
62}
63
64isdateobj(o: ref Obj): int
65{
66	return (o.host == nil || o.host == me) && o.class == "Date";
67}
68
69isregexpobj(o: ref Obj): int
70{
71	return (o.host == nil || o.host == me) && o.class == "RegExp";
72}
73
74isfuncobj(o: ref Obj): int
75{
76	return (o.host == nil || o.host == me) && o.class == "Function";
77}
78
79isarray(o: ref Obj): int
80{
81#	return (o.host == nil || o.host == me) && o.class == "Array";
82	# relax the host test
83	# so that hosts can intercept Array operations and defer
84	# unhandled ops to the builtin
85	return o.class == "Array";
86}
87
88iserr(o: ref Obj): int
89{
90	return (o.host == nil || o.host == me) && o.class == "Error";
91}
92
93isactobj(o: ref Obj): int
94{
95	return o.host == nil && o.class == "Activation";
96}
97
98isnull(v: ref Val): int
99{
100	return v == null || v == nil;
101}
102
103isundefined(v: ref Val): int
104{
105	return v == undefined;
106}
107
108isstr(v: ref Val): int
109{
110	return v.ty == TStr;
111}
112
113isnum(v: ref Val): int
114{
115	return v.ty == TNum;
116}
117
118isbool(v: ref Val): int
119{
120	return v.ty == TBool;
121}
122
123isobj(v: ref Val): int
124{
125	return v.ty == TObj;
126}
127
128isregexp(v: ref Val): int
129{
130	return v.ty == TRegExp || v.ty == TObj && isregexpobj(v.obj);
131}
132
133#
134# retrieve the object field if it's valid
135#
136getobj(v: ref Val): ref Obj
137{
138	if(v.ty == TObj)
139		return v.obj;
140	return nil;
141}
142
143isprimval(v: ref Val): int
144{
145	return v.ty != TObj;
146}
147
148pushscope(ex: ref Exec, o: ref Obj)
149{
150	ex.scopechain = o :: ex.scopechain;
151}
152
153popscope(ex: ref Exec)
154{
155	ex.scopechain = tl ex.scopechain;
156}
157
158runtime(ex: ref Exec, o: ref Obj, s: string)
159{
160	ex.error = s;
161	if(o == nil)
162		ex.errval = undefined;
163	else
164		ex.errval = objval(o);
165	if(debug['r']){
166		print("ecmascript runtime error: %s\n", s);
167		if(""[5] == -1);	# abort
168	}
169	raise "throw";
170	exit;	# never reached
171}
172
173mkobj(proto: ref Obj, class: string): ref Obj
174{
175	if(class == nil)
176		class = "Object";
177	return ref Obj(nil, proto, nil, nil, nil, class, nil, nil);
178}
179
180valcheck(ex: ref Exec, v: ref Val, hint: int)
181{
182	if(v == nil
183	|| v.ty < 0
184	|| v.ty >= NoHint
185	|| v.ty == TBool && v != true && v != false
186	|| v.ty == TObj && v.obj == nil
187	|| hint != NoHint && v.ty != hint)
188		runtime(ex, RangeError, "bad value generated by host object");
189}
190
191# builtin methods for properties
192esget(ex: ref Exec, o: ref Obj, prop: string, force: int): ref Val
193{
194	for( ; o != nil; o = o.prototype){
195		if(!force && o.host != nil && o.host != me){
196			v := o.host->get(ex, o, prop);
197			valcheck(ex, v, NoHint);
198			return v;
199		}
200
201		for(i := 0; i < len o.props; i++)
202			if(o.props[i] != nil && o.props[i].name == prop)
203				return o.props[i].val.val;
204		force = 0;
205	}
206	return undefined;
207}
208
209esputind(o: ref Obj, prop: string): int
210{
211	empty := -1;
212	props := o.props;
213	for(i := 0; i < len props; i++){
214		if(props[i] == nil)
215			empty = i;
216		else if(props[i].name == prop)
217			return i;
218	}
219	if(empty != -1)
220		return empty;
221
222	props = array[i+1] of ref Prop;
223	props[:] = o.props;
224	o.props = props;
225	return i;
226}
227
228esput(ex: ref Exec, o: ref Obj, prop: string, v: ref Val, force: int)
229{
230	ai: big;
231
232	if(!force && o.host != nil && o.host != me)
233		return o.host->put(ex, o, prop, v);
234
235	if(escanput(ex, o, prop, 0) != true)
236		return;
237
238	#
239	# should this test for prototype == ex.arrayproto?
240	# hard to say, but 15.4.5 "Properties of Array Instances" implies not
241	#
242	if(isarray(o))
243		al := toUint32(ex, esget(ex, o, "length", 1));
244
245	i := esputind(o, prop);
246	props := o.props;
247	if(props[i] != nil)
248		props[i].val.val = v;
249	else
250		props[i] = ref Prop(0, prop, ref RefVal(v));
251	if(!isarray(o))
252		return;
253
254	if(prop == "length"){
255		nl := toUint32(ex, v);
256		for(ai = nl; ai < al; ai++)
257			esdelete(ex, o, string ai, 1);
258		props[i].val.val = numval(real nl);
259	}else{
260		ai = big prop;
261		if(prop != string ai || ai < big 0 || ai >= 16rffffffff)
262			return;
263		i = esputind(o, "length");
264		if(props[i] == nil)
265			fatal(ex, "bogus array esput");
266		else if(toUint32(ex, props[i].val.val) <= ai)
267			props[i].val.val = numval(real(ai+big 1));
268	}
269}
270
271escanput(ex: ref Exec, o: ref Obj, prop: string, force: int): ref Val
272{
273	for( ; o != nil; o = o.prototype){
274		if(!force && o.host != nil && o.host != me){
275			v := o.host->canput(ex, o, prop);
276			valcheck(ex, v, TBool);
277			return v;
278		}
279
280		for(i := 0; i < len o.props; i++){
281			if(o.props[i] != nil && o.props[i].name == prop){
282				if(o.props[i].attr & ReadOnly)
283					return false;
284				else
285					return true;
286			}
287		}
288
289		force = 0;
290	}
291	return true;
292}
293
294eshasproperty(ex: ref Exec, o: ref Obj, prop: string, force: int): ref Val
295{
296	for(; o != nil; o = o.prototype){
297		if(!force && o.host != nil && o.host != me){
298			v := o.host->hasproperty(ex, o, prop);
299			valcheck(ex, v, TBool);
300			return v;
301		}
302		for(i := 0; i < len o.props; i++)
303			if(o.props[i] != nil && o.props[i].name == prop)
304				return true;
305	}
306	return false;
307}
308
309eshasenumprop(o: ref Obj, prop: string): ref Val
310{
311	for(i := 0; i < len o.props; i++)
312		if(o.props[i] != nil && o.props[i].name == prop){
313			if(o.props[i].attr & DontEnum)
314				return false;
315			return true;
316		}
317	return false;
318}
319
320propshadowed(start, end: ref Obj, prop: string): int
321{
322	for(o := start; o != end; o = o.prototype){
323		if(o.host != nil && o.host != me)
324			return 0;
325		for(i := 0; i < len o.props; i++)
326			if(o.props[i] != nil && o.props[i].name == prop)
327				return 1;
328	}
329	return 0;
330}
331
332esdelete(ex: ref Exec, o: ref Obj, prop: string, force: int)
333{
334	if(!force && o.host != nil && o.host != me)
335		return o.host->delete(ex, o, prop);
336
337	for(i := 0; i < len o.props; i++){
338		if(o.props[i] != nil && o.props[i].name == prop){
339			if(!(o.props[i].attr & DontDelete))
340				o.props[i] = nil;
341			return;
342		}
343	}
344}
345
346esdeforder := array[] of {"valueOf", "toString"};
347esdefaultval(ex: ref Exec, o: ref Obj, ty: int, force: int): ref Val
348{
349	v: ref Val;
350
351	if(!force && o.host != nil && o.host != me){
352		v = o.host->defaultval(ex, o, ty);
353		valcheck(ex, v, NoHint);
354		if(!isprimval(v))
355			runtime(ex, TypeError, "host object returned an object to [[DefaultValue]]");
356		return v;
357	}
358
359	hintstr := 0;
360	if(ty == TStr || ty == NoHint && isdateobj(o))
361		hintstr = 1;
362
363	for(i := 0; i < 2; i++){
364		v = esget(ex, o, esdeforder[hintstr ^ i], 0);
365		if(v != undefined && v.ty == TObj && v.obj.call != nil){
366			r := escall(ex, v.obj, o, nil, 0);
367			v = nil;
368			if(!r.isref)
369				v = r.val;
370			if(v != nil && isprimval(v))
371				return v;
372		}
373	}
374	runtime(ex, TypeError, "no default value");
375	return nil;
376}
377
378esprimid(ex: ref Exec, s: string): ref Ref
379{
380	for(sc := ex.scopechain; sc != nil; sc = tl sc){
381		o := hd sc;
382		if(eshasproperty(ex, o, s, 0) == true)
383			return ref Ref(1, nil, o, s);
384	}
385
386	#
387	# the right place to add literals?
388	#
389	case s{
390	"null" =>
391		return ref Ref(0, null, nil, "null");
392	"true" =>
393		return ref Ref(0, true, nil, "true");
394	"false" =>
395		return ref Ref(0, false, nil, "false");
396	}
397	return ref Ref(1, nil, nil, s);
398}
399
400bivar(ex: ref Exec, sc: list of ref Obj, s: string): ref Val
401{
402	for(; sc != nil; sc = tl sc){
403		o := hd sc;
404		if(eshasproperty(ex, o, s, 0) == true)
405			return esget(ex, o, s, 0);
406	}
407	return nil;
408}
409
410esconstruct(ex: ref Exec, func: ref Obj, args: array of ref Val): ref Obj
411{
412	o: ref Obj;
413
414	if(func.construct == nil)
415		runtime(ex, TypeError, "new must be applied to a constructor object");
416	if(func.host != nil)
417		o = func.host->construct(ex, func, args);
418	else{
419		o = getobj(esget(ex, func, "prototype", 0));
420		if(o == nil)
421			o = ex.objproto;
422		this := mkobj(o, "Object");
423		o = getobj(getValue(ex, escall(ex, func, this, args, 0)));
424
425		# Divergence from ECMA-262
426		#
427		# observed that not all script-defined constructors return an object,
428		# the value of 'this' is assumed to be the value of the constructor
429		if (o == nil)
430			o = this;
431	}
432	if(o == nil)
433		runtime(ex, TypeError, func.val.str+" failed to generate an object");
434	return o;
435}
436
437escall(ex: ref Exec, func, this: ref Obj, args: array of ref Val, eval: int): ref Ref
438{
439	if(func.call == nil)
440		runtime(ex, TypeError, "can only call function objects");
441	if(this == nil)
442		this = ex.global;
443
444	r: ref Ref = nil;
445	if(func.host != nil){
446		r = func.host->call(ex, func, this, args, 0);
447		if(r.isref && r.name == nil)
448			runtime(ex, ReferenceError, "host call returned a bad reference");
449		else if(!r.isref)
450			valcheck(ex, r.val, NoHint);
451		return r;
452	}
453
454	argobj := mkobj(ex.objproto, "Object");
455	actobj := mkobj(nil, "Activation");
456
457	oargs: ref RefVal = nil;
458	props := func.props;
459	empty := -1;
460	i := 0;
461	for(i = 0; i < len props; i++){
462		if(props[i] == nil)
463			empty = i;
464		else if(props[i].name == "arguments"){
465			oargs = props[i].val;
466			empty = i;
467			break;
468		}
469	}
470	if(i == len func.props){
471		if(empty == -1){
472			props = array[i+1] of ref Prop;
473			props[:] = func.props;
474			func.props = props;
475			empty = i;
476		}
477		props[empty] = ref Prop(DontDelete|DontEnum|ReadOnly, "arguments", nil);
478	}
479	props[empty].val = ref RefVal(objval(argobj));
480
481	#
482	#see section 10.1.3 page 33
483	# if multiple params share the same name, the last one takes effect
484	# vars don't override params of the same name, or earlier parms defs
485	#
486	actobj.props = array[] of {ref Prop(DontDelete, "arguments", ref RefVal(objval(argobj)))};
487
488	argobj.props = array[len args + 2] of {
489		ref Prop(DontEnum, "callee", ref RefVal(objval(func))),
490		ref Prop(DontEnum, "length", ref RefVal(numval(real len args))),
491	};
492
493	#
494	# instantiate the arguments by name in the activation object
495	# and by number in the arguments object, aliased to the same RefVal.
496	#
497	params := func.call.params;
498	for(i = 0; i < len args; i++){
499		rjv := ref RefVal(args[i]);
500		argobj.props[i+2] = ref Prop(DontEnum, string i, rjv);
501		if(i < len params)
502			fvarinstant(actobj, 1, DontDelete, params[i], rjv);
503	}
504	for(; i < len params; i++)
505		fvarinstant(actobj, 1, DontDelete, params[i], ref RefVal(undefined));
506
507	#
508	# instantiate the local variables defined within the function
509	#
510	vars := func.call.code.vars;
511	for(i = 0; i < len vars; i++)
512		valinstant(actobj, DontDelete, vars[i].name, undefined);
513
514	# NOTE: the treatment of scopechain here is wrong if nested functions are
515	# permitted.  ECMA-262 currently does not support nested functions (so we
516	# are ok for now) - but other flavours of Javascript do.
517	# Difficulties are introduced by multiple execution contexts.
518	# e.g. in web browsers, one frame can ref a func in
519	# another frame (each frame has a distinct execution context), but the func
520	# ids must bind as if in original lexical context
521
522	osc := ex.scopechain;
523	ex.this = this;
524	ex.scopechain = actobj :: osc;
525	(k, v, nil) := exec(ex, func.call.code);
526	ex.scopechain = osc;
527
528	#
529	# i can find nothing in the docs which defines
530	# the value of a function call
531	# this seems like a reasonable definition
532	#
533	if (k == CThrow)
534		raise "throw";
535	if(!eval && k != CReturn || v == nil)
536		v = undefined;
537	r = valref(v);
538
539	props = func.props;
540	for(i = 0; i < len props; i++){
541		if(props[i] != nil && props[i].name == "arguments"){
542			if(oargs == nil)
543				props[i] = nil;
544			else
545				props[i].val = oargs;
546			break;
547		}
548	}
549
550	return r;
551}
552
553#
554# routines for instantiating variables
555#
556fvarinstant(o: ref Obj, force, attr: int, s: string, v: ref RefVal)
557{
558	props := o.props;
559	empty := -1;
560	for(i := 0; i < len props; i++){
561		if(props[i] == nil)
562			empty = i;
563		else if(props[i].name == s){
564			if(force){
565				props[i].attr = attr;
566				props[i].val = v;
567			}
568			return;
569		}
570	}
571	if(empty == -1){
572		props = array[i+1] of ref Prop;
573		props[:] = o.props;
574		o.props = props;
575		empty = i;
576	}
577	props[empty] = ref Prop(attr, s, v);
578}
579
580varinstant(o: ref Obj, attr: int, s: string, v: ref RefVal)
581{
582	fvarinstant(o, 0, attr, s, v);
583}
584
585valinstant(o: ref Obj, attr: int, s: string, v: ref Val)
586{
587	fvarinstant(o, 0, attr, s, ref RefVal(v));
588}
589
590#
591# instantiate global or val variables
592# note that only function variables are forced to be redefined;
593# all other variables have a undefined val.val field
594#
595globalinstant(o: ref Obj, vars: array of ref Prop)
596{
597	for(i := 0; i < len vars; i++){
598		force := vars[i].val.val != undefined;
599		fvarinstant(o, force, 0, vars[i].name, vars[i].val);
600	}
601}
602
603numval(r: real): ref Val
604{
605	return ref Val(TNum, r, nil, nil, nil);
606}
607
608strval(s: string): ref Val
609{
610	return ref Val(TStr, 0., s, nil, nil);
611}
612
613objval(o: ref Obj): ref Val
614{
615	return ref Val(TObj, 0., nil, o, nil);
616}
617
618regexpval(p: string, f: string, i: int): ref Val
619{
620	return ref Val(TRegExp, 0., nil, nil, ref REval(p, f, i));
621}
622
623#
624# operations on refereneces
625# note the substitution of nil for an object
626# version of null, implied in the discussion of
627# Reference Types, since there isn't a null object
628#
629valref(v: ref Val): ref Ref
630{
631	return ref Ref(0, v, nil, nil);
632}
633
634getBase(ex: ref Exec, r: ref Ref): ref Obj
635{
636	if(!r.isref)
637		runtime(ex, ReferenceError, "not a reference");
638	return r.base;
639}
640
641getPropertyName(ex: ref Exec, r: ref Ref): string
642{
643	if(!r.isref)
644		runtime(ex, ReferenceError, "not a reference");
645	return r.name;
646}
647
648getValue(ex: ref Exec, r: ref Ref): ref Val
649{
650	if(!r.isref)
651		return r.val;
652	b := r.base;
653	if(b == nil)
654		runtime(ex, ReferenceError, "reference " + r.name + " is null");
655	return esget(ex, b, r.name, 0);
656}
657
658putValue(ex: ref Exec, r: ref Ref, v: ref Val)
659{
660	if(!r.isref)
661		runtime(ex, ReferenceError, "not a reference: " + r.name);
662	b := r.base;
663	if(b == nil)
664		b = ex.global;
665	esput(ex, b, r.name, v, 0);
666}
667
668#
669# conversion routines defined by the abstract machine
670# see section 9.
671# note that string, boolean, and number objects are
672# not automaically coerced to values, and vice versa.
673#
674toPrimitive(ex: ref Exec, v: ref Val, ty: int): ref Val
675{
676	if(v.ty != TObj)
677		return v;
678	v = esdefaultval(ex, v.obj, ty, 0);
679	if(v.ty == TObj)
680		runtime(ex, TypeError, "toPrimitive returned an object");
681	return v;
682}
683
684toBoolean(ex: ref Exec, v: ref Val): ref Val
685{
686	case v.ty{
687	TUndef or
688	TNull =>
689		return false;
690	TBool =>
691		return v;
692	TNum =>
693		if(isnan(v.num))
694			return false;
695		if(v.num == 0.)
696			return false;
697	TStr =>
698		if(v.str == "")
699			return false;
700	TObj =>
701		break;
702	TRegExp =>
703		break;
704	* =>
705		runtime(ex, TypeError, "unknown type in toBoolean");
706	}
707	return true;
708}
709
710toNumber(ex: ref Exec, v: ref Val): real
711{
712	case v.ty{
713	TUndef =>
714		return NaN;
715	TNull =>
716		return 0.;
717	TBool =>
718		if(v == false)
719			return 0.;
720		return 1.;
721	TNum =>
722		return v.num;
723	TStr =>
724		(si, r) := parsenum(ex, v.str, 0, ParseReal|ParseHex|ParseTrim|ParseEmpty);
725		if(si != len v.str)
726			r = Math->NaN;
727		return r;
728	TObj =>
729		return toNumber(ex, toPrimitive(ex, v, TNum));
730	TRegExp =>
731		return NaN;
732	* =>
733		runtime(ex, TypeError, "unknown type in toNumber");
734		return 0.;
735	}
736}
737
738toInteger(ex: ref Exec, v: ref Val): real
739{
740	r := toNumber(ex, v);
741	if(isnan(r))
742		return 0.;
743	if(r == 0. || r == +Infinity || r == -Infinity)
744		return r;
745	return copysign(floor(fabs(r)), r);
746}
747
748#
749# toInt32 == toUint32, except for numbers > 2^31
750#
751toInt32(ex: ref Exec, v: ref Val): int
752{
753	r := toNumber(ex, v);
754	if(isnan(r) || r == 0. || r == +Infinity || r == -Infinity)
755		return 0;
756	r = copysign(floor(fabs(r)), r);
757	# need to convert to big since it might be unsigned
758	return int big fmod(r, 4294967296.);
759}
760
761toUint32(ex: ref Exec, v: ref Val): big
762{
763	r := toNumber(ex, v);
764	if(isnan(r) || r == 0. || r == +Infinity || r == -Infinity)
765		return big 0;
766	r = copysign(floor(fabs(r)), r);
767	# need to convert to big since it might be unsigned
768	b := big fmod(r, 4294967296.);
769	if(b < big 0)
770		fatal(ex, "uint32 < 0");
771	return b;
772}
773
774toUint16(ex: ref Exec, v: ref Val): int
775{
776	return toInt32(ex, v) & 16rffff;
777}
778
779toString(ex: ref Exec, v: ref Val): string
780{
781	case v.ty{
782	TUndef =>
783		return "undefined";
784	TNull =>
785		return "null";
786	TBool =>
787		if(v == false)
788			return "false";
789		return "true";
790	TNum =>
791		r := v.num;
792		if(isnan(r))
793			return "NaN";
794		if(r == 0.)
795			return "0";
796		if(r == Infinity)
797			return "Infinity";
798		if(r == -Infinity)
799			return "-Infinity";
800		# this is wrong, but right is too hard
801		if(r < 1000000000000000000000. && r >= 1./(1000000.)){
802			return string r;
803		}
804		return string r;
805	TStr =>
806		return v.str;
807	TObj =>
808		return toString(ex, toPrimitive(ex, v, TStr));
809	TRegExp =>
810		return "/" + v.rev.p + "/" + v.rev.f;
811	* =>
812		runtime(ex, TypeError, "unknown type in ToString");
813		return "";
814	}
815}
816
817toObject(ex: ref Exec, v: ref Val): ref Obj
818{
819	case v.ty{
820	TUndef =>
821		runtime(ex, TypeError, "can't convert undefined to an object");
822	TNull =>
823		runtime(ex, TypeError, "can't convert null to an object");
824	TBool or
825	TStr or
826	TNum or
827	TRegExp =>
828		return coerceToObj(ex, v).obj;
829	TObj =>
830		return v.obj;
831	* =>
832		runtime(ex, TypeError, "unknown type in toObject");
833		return nil;
834	}
835	return nil;
836}
837