Home Reference Source

src/stdlib/Object.js

'use strict';

const EasyObjectValue = require('../values/EasyObjectValue');
const ObjectValue = require('../values/ObjectValue');
const ArrayValue = require('../values/ArrayValue');
const CompletionRecord = require('../CompletionRecord');
const Value = require('../Value');
const PropertyDescriptor = require('../values/PropertyDescriptor');
const EmptyValue = require('../values/EmptyValue');
const BridgeValue = require('../values/BridgeValue');
const LinkValue = require('../values/LinkValue');

function *defObjectProperty(obj, name, desc, realm) {
	if ( name instanceof Value ) {
		name = (yield * name.toStringNative());
	}

	let value = yield * desc.get('value', realm);


	let v = new PropertyDescriptor(value);


	if ( desc.has('enumerable') ) {
		let enu = yield * desc.get('enumerable', realm);
		if ( !(enu instanceof EmptyValue) ) {
			v.enumerable = enu.truthy;
		}
	} else {
		v.enumerable = false;
	}

	if ( desc.has('writable') ) {
		let wri = yield * desc.get('writable', realm);
		if ( !(wri instanceof EmptyValue) ) {
			v.writable = wri.truthy;
		}
	} else {
		v.writable = false;
	}

	if ( desc.has('configurable') ) {
		let conf = yield * desc.get('configurable', realm);
		if ( !(conf instanceof EmptyValue) ) {
			v.writable = conf.truthy;
		}
	} else {
		v.writable = false;
	}


	if ( desc.has('get') ) {
		let get = yield * desc.get('get', realm);
		if ( !(get instanceof EmptyValue) ) {
			v.getter = get;
		}
	}

	if ( desc.has('set') ) {
		let set = yield * desc.get('set', realm);
		if ( !(set instanceof EmptyValue) ) {
			v.setter = set;
		}
	}

	obj.rawSetProperty(name, v);
	return true;
}

function *getDescriptor(target, name, realm) {
	if ( !Object.hasOwnProperty.call(target.properties, name) ) {
		return Value.undef;
	}

	let pdesc = target.properties[name];
	let out = new ObjectValue(realm);

	if ( pdesc.value  ) yield * out.set('value', pdesc.value);
	if ( pdesc.getter ) yield * out.set('get', pdesc.getter);
	if ( pdesc.setter ) yield * out.set('set', pdesc.setter);

	yield * out.set('writable', Value.fromNative(pdesc.writable));
	yield * out.set('enumerable', Value.fromNative(pdesc.enumerable));
	yield * out.set('configurable', Value.fromNative(pdesc.configurable));
	return out;
}

function *objOrThrow(i, realm) {
	let val = i ? i : Value.undef;

	if ( val instanceof EmptyValue ) {
		return yield CompletionRecord.makeTypeError(realm, 'Cannot convert undefined or null to object');
	}

	if ( !(val instanceof ObjectValue) ) {
		return yield CompletionRecord.makeTypeError(realm, 'Need an object');
	}
	return val;
}

class ObjectObject extends EasyObjectValue {
	*call(thiz, args, s, ext) {
		let asConstructor = ext && ext.asConstructor;
		if ( asConstructor ) {
			return new ObjectValue(s.realm);
		}
	}

	callPrototype(realm) { return realm.ObjectPrototype; }
	//objPrototype(realm) { return realm.Function; }

	static *create$e(thiz, args, s) {
		let v = new ObjectValue(s.realm);
		let p = Value.undef;
		if ( args.length > 0 ) {
			p = args[0];
		}

		if ( p.jsTypeName !== 'object' && p.jsTypeName !== 'function' ) {
			return yield CompletionRecord.makeTypeError(s.realm, 'Object prototype may only be an Object or null');
		}

		v.setPrototype(p);

		if ( args.length > 1 ) {
			let propsobj = args[1];
			for ( let p of propsobj.observableProperties(s.realm) ) {
				let strval = p.native;
				let podesc = yield * propsobj.get(strval, s.realm);
				yield * defObjectProperty(v, p, podesc, s.realm);
			}
		}
		return v;
	}

	static *defineProperty(thiz, args, s) {
		let target = yield * objOrThrow(args[0], s.realm);
		let name = yield * args[1].toStringNative();
		let desc = args[2];
		yield * defObjectProperty(target, name, desc, s.realm);
		return Value.true;
	}

	static *defineProperties(thiz, args, s) {

		let target = yield * objOrThrow(args[0], s.realm);
		//let props = yield * objOrThrow(args[1], s.realm);

		let propsobj = yield * objOrThrow(args[1], s.realm);

		for ( let p of propsobj.observableProperties(s.realm) ) {
			let strval = p.native;
			let podesc = yield * propsobj.get(strval, s.realm);
			yield * defObjectProperty(target, p, podesc, s.realm);
		}
		return Value.true;
	}

	static *seal$e(thiz, args, s) {
		let target = yield * objOrThrow(args[0], s.realm);

		target.extensable = false;
		for ( let p of Object.keys(target.properties) ) {
			target.properties[p].configurable = false;
		}
		return target;
	}

	static *isSealed(thiz, args, s) {
		let target = yield * objOrThrow(args[0], s.realm);
		if ( target.extensable ) return Value.false;
		for ( let p of Object.keys(target.properties) ) {
			let ps = target.properties[p];
			if ( ps.configurable ) return Value.false;
		}
		return Value.true;
	}

	static *freeze$e(thiz, args, s) {
		let target = yield * objOrThrow(args[0], s.realm);
		target.extensable = false;
		for ( let p in target.properties ) {
			if ( !Object.prototype.hasOwnProperty.call(target.properties, p) ) continue;
			target.properties[p].configurable = false;
			target.properties[p].writable = false;
		}
		return target;
	}

	static *isFrozen(thiz, args, s) {
		let target = yield * objOrThrow(args[0], s.realm);
		if ( target.extensable ) return Value.false;
		for ( let p of Object.keys(target.properties) ) {
			let ps = target.properties[p];
			if ( ps.configurable ) return Value.false;
			if ( ps.writable ) return Value.false;
		}
		return Value.true;
	}

	static *preventExtensions$e(thiz, args, s) {
		let target = yield * objOrThrow(args[0], s.realm);
		target.extensable = false;
		return target;
	}

	static *isExtensible$e(thiz, args, s) {
		let target = yield * objOrThrow(args[0], s.realm);
		return this.fromNative(target.extensable);
	}

	static *keys$e(thiz, args, s) {
		if ( args[0] instanceof BridgeValue ) {
			return ArrayValue.make(Object.keys(args[0].native), s.realm);
		}
		if ( args[0] instanceof LinkValue ) {
			let keys = [];
			for ( let o of args[0].observableProperties() ) keys.push(o);
			return ArrayValue.make(keys, s.realm);
		}
		let target = yield * objOrThrow(args[0], s.realm);
		let result = [];
		for ( let p of Object.keys(target.properties) ) {
			if ( !target.properties[p].enumerable ) continue;
			result.push(p);
		}
		return ArrayValue.make(result, s.realm);
	}

	static *getOwnPropertyNames$e(thiz, args, s) {
		let target = yield * objOrThrow(args[0], s.realm);
		return ArrayValue.make(Object.getOwnPropertyNames(target.properties), s.realm);
	}

	static *getOwnPropertyDescriptor(thiz, args, s) {
		let target = yield * objOrThrow(args[0], s.realm);
		let name = yield * args[1].toStringNative();
		return yield * getDescriptor(target, name, s.realm);
	}

	static *getPrototypeOf(thiz, args, s) {
		let target = EasyObjectValue.undef;
		if ( args.length > 0 ) target = args[0];
		if ( !target.getPrototype ) return yield CompletionRecord.makeTypeError(s.realm, 'No prototype.');
		let proto = target.getPrototype(s.realm);
		if ( proto ) return proto;
		return EasyObjectValue.null;
	}

	toNativeCounterpart() { return Object; }
}

ObjectObject.prototype.wellKnownName = '%Object%';

module.exports = ObjectObject;