Home Reference Source

src/stdlib/ArrayPrototype.js

'use strict';

const EasyObjectValue = require('../values/EasyObjectValue');
const ObjectValue = require('../values/ObjectValue');
const ArrayValue = require('../values/ArrayValue');
const PrimitiveValue = require('../values/PrimitiveValue');
const CompletionRecord = require('../CompletionRecord');
const Value = require('../Value');
const _g = require('../GenDash');

function *forceArrayness(v) {
	if ( !v.has('length') ) {
		yield * v.set('length', Value.zero);
	}
}

function *getLength(v) {
	let m = yield * v.get('length');
	return yield * m.toUIntNative();
}

var defaultSeperator = Value.fromNative(',');

function *shiftRight(arr, start, amt) {
	amt = amt || 1;
	let len = yield * getLength(arr);
	for ( let i = len - 1; i >= start; --i ) {
		let cur = yield * arr.get(i);
		yield * arr.set(i + amt, cur);
	}
	yield * arr.set(start, Value.undef);
}

function *shiftLeft(arr, start, amt) {
	let len = yield * getLength(arr);
	for ( let i = start; i < len; ++i ) {
		let cur = yield * arr.get(i);
		yield * arr.set(i - amt, cur);
	}
	for ( let i = len - amt; i < len; ++i ) {
		delete arr.properties[i];
	}
	yield * arr.set('length', Value.fromNative(len - amt));
}


class ArrayPrototype extends EasyObjectValue {

	static *concat$e(thiz, args, s) {
		let fx = Value.undef;
		let targ = Value.undef;
		if ( args.length > 0 ) fx = args[0];
		if ( args.length > 1 ) targ = args[1];

		var out = [];
		var toCopy = [thiz].concat(args);

		let idx = 0;
		for ( let arr of toCopy ) {
			if ( arr instanceof PrimitiveValue ) {
				out[idx++] = arr;
			} else if ( !arr.has('length') ) {
				out[idx++] = arr;
			} else {
				let l = yield * getLength(arr);
				for ( let i = 0; i < l; ++i ) {
					let tv = yield * arr.get(i, s.realm);
					out[idx++] = tv;
				}
			}
		}

		return ArrayValue.make(out, s.realm);
	}

	static *filter$e(thiz, args, s) {
		let fx = Value.undef;
		let targ = Value.undef;
		if ( args.length > 0 ) fx = args[0];
		if ( args.length > 1 ) targ = args[1];

		let test = function *(v, i) {
			let res = yield * fx.call(targ, [v, Value.fromNative(i), thiz], s);
			return res.truthy;
		};

		var out = [];

		let l = yield * getLength(thiz);
		for ( let i = 0; i < l; ++i ) {
			let tv = yield * thiz.get(i);
			let tru = yield * test(tv, i);
			if ( tru ) out.push(tv);
		}

		return ArrayValue.make(out, s.realm);
	}

	static *every$e(thiz, args, s) {
		let fx = Value.undef;
		let targ = Value.undef;
		if ( args.length > 0 ) fx = args[0];
		if ( args.length > 1 ) targ = args[1];

		let test = function *(v, i) {
			let res = yield * fx.call(targ, [v, Value.fromNative(i), thiz], s);
			return res.truthy;
		};

		let l = yield * getLength(thiz);
		for ( let i = 0; i < l; ++i ) {
			let tv = yield * thiz.get(i);
			let tru = yield * test(tv, i);
			if ( !tru ) return Value.false;
		}

		return Value.true;
	}

	static *some$e(thiz, args, s) {
		let fx = Value.undef;
		let targ = Value.undef;
		if ( args.length > 0 ) fx = args[0];
		if ( args.length > 1 ) targ = args[1];

		let test = function *(v, i) {
			let res = yield * fx.call(targ, [v, Value.fromNative(i), thiz], s);
			return res.truthy;
		};

		let l = yield * getLength(thiz);
		for ( let i = 0; i < l; ++i ) {
			let tv = yield * thiz.get(i);
			let tru = yield * test(tv, i);
			if ( tru ) return Value.true;
		}

		return Value.false;
	}

	static *find$e(thiz, args, s) {
		let fx = Value.undef;
		let targ = Value.undef;
		if ( args.length > 0 ) fx = args[0];
		if ( args.length > 1 ) targ = args[1];

		let test = function *(v, i) {
			let res = yield * fx.call(targ, [v, Value.fromNative(i), thiz], s);
			return res.truthy;
		};

		let l = yield * getLength(thiz);
		for ( let i = 0; i < l; ++i ) {
			let tv = yield * thiz.get(i);
			let tru = yield * test(tv, i);
			if ( tru ) return tv;
		}

		return Value.undef;
	}

	static *map$e(thiz, args, s) {
		let fx = Value.undef;
		let targ = Value.undef;
		if ( args.length > 0 ) fx = args[0];
		if ( !fx.isCallable ) return yield CompletionRecord.makeTypeError(s.realm, 'Arg2 not calalble.');

		if ( args.length > 1 ) targ = args[1];

		let l = yield * getLength(thiz);
		let out = new Array(l);
		for ( let i = 0; i < l; ++i ) {
			if ( !thiz.has(i) ) continue;
			let tv = yield * thiz.get(i);
			let v = yield yield * fx.call(targ, [tv, Value.fromNative(i), thiz], s);
			out[i] = v;
		}

		return ArrayValue.make(out, s.realm);
	}

	static *forEach$e(thiz, args, s) {
		let fx = Value.undef;
		let targ = Value.undef;
		if ( args.length > 0 ) fx = args[0];
		if ( args.length > 1 ) targ = args[1];

		let l = yield * getLength(thiz);
		for ( let i = 0; i < l; ++i ) {
			if ( !thiz.has(i) ) continue;
			let v = yield * thiz.get(i);
			let res = yield * fx.call(targ, [v, Value.fromNative(i), thiz], s);
		}

		return Value.undef;
	}

	static *indexOf$e(thiz, args) {
		//TODO: Call ToObject() on thisz;
		let l = yield * getLength(thiz);
		let match = args[0] || Value.undef;
		let start = args[1] || Value.zero;
		let startn = (yield * start.toNumberValue()).native;

		if ( isNaN(startn) ) startn = 0;
		else if ( startn < 0 ) startn = 0;

		if ( l > startn ) {
			for ( let i = startn; i < l; ++i ) {
				let v = yield * thiz.get(i);
				if ( !v ) v = Value.undef;
				if ( (yield * v.tripleEquals(match)).truthy ) return Value.fromNative(i);

			}
		}
		return Value.fromNative(-1);
	}


	static *lastIndexOf$e(thiz, args) {
		//TODO: Call ToObject() on thisz;
		let l = yield * getLength(thiz);
		let match = args[0] || Value.undef;
		let startn = l - 1;

		if ( args.length > 1 ) startn = yield * args[1].toIntNative();
		if ( isNaN(startn) ) startn = 0;
		if ( startn < 0 ) startn += l;
		if ( startn > l ) startn = l;
		if ( startn < 0 ) return Value.fromNative(-1);


		//if ( isNaN(startn) ) startn = l - 1;

		for ( let i = startn; i >= 0; --i ) {
			if ( !thiz.has(i) ) continue;
			let v = yield * thiz.get(i);
			if ( !v ) v = Value.undef;
			if ( (yield * v.tripleEquals(match)).truthy ) return Value.fromNative(i);

		}

		return Value.fromNative(-1);
	}

	static *join$e(thiz, args) {
		//TODO: Call ToObject() on thisz;
		let l = yield * getLength(thiz);
		let seperator = args[0] || defaultSeperator;
		let sepstr = (yield * seperator.toStringValue()).native;
		let strings = new Array(l);
		for ( let i = 0; i < l; ++i ) {
			if ( !thiz.has(i) ) continue;
			let v = yield * thiz.get(i);
			if ( !v ) strings[i] = '';
			else {
				if ( v.specTypeName == 'undefined' || v.specTypeName == 'null' ) {
					continue;
				}
				let sv = (yield * v.toStringValue());
				if ( sv ) strings[i] = sv.native;
				else strings[i] = undefined; //TODO: THROW HERE?
			}
		}
		return Value.fromNative(strings.join(sepstr));
	}

	static *push$e(thiz, args) {
		let l = yield * getLength(thiz);

		for ( let i = 0; i < args.length; ++i ) {
			yield * thiz.set(l + i, args[i]);
		}

		let nl = Value.fromNative(l + args.length);
		yield * thiz.set('length', nl);
		return Value.fromNative(l + args.length);
	}

	static *pop$e(thiz, args) {
		yield * forceArrayness(thiz);
		let l = yield * getLength(thiz);
		if ( l < 1 ) return Value.undef;
		let val = yield * thiz.get(l - 1);
		yield * thiz.set('length', Value.fromNative(l - 1));
		return val;
	}

	static *reverse$e(thiz, args, s) {
		let l = yield * getLength(thiz);
		for ( let i = 0; i < Math.floor(l / 2); ++i ) {
			let lv = yield * thiz.get(i);
			let rv = yield * thiz.get(l - i - 1);
			yield * thiz.set(l - i - 1, lv, s);
			yield * thiz.set(i, rv, s);
		}

		return thiz;
	}

	static *reduce$e(thiz, args, s) {
		let l = yield * getLength(thiz);
		let acc;
		let fx = args[0];

		if ( args.length < 1 || !fx.isCallable ) {
			return yield CompletionRecord.makeTypeError(s.realm, 'First argument to reduce must be a function.');
		}

		if ( args.length > 1 ) {
			acc = args[1];
		}

		for ( let i = 0; i < l; ++i ) {
			if ( !thiz.has(i) ) continue;
			let lv = yield * thiz.get(i);
			if ( !acc ) {
				acc = lv;
				continue;
			}
			acc = yield * fx.call(thiz, [acc, lv], s);
		}
		if ( !acc ) return yield CompletionRecord.makeTypeError(s.realm, 'Reduce an empty array with no initial value.');
		return acc;
	}

	//TODO: Factor some stuff out of reduce and reduce right into a common function.
	static *reduceRight$e(thiz, args, s) {
		let l = yield * getLength(thiz);
		let acc;
		let fx = args[0];

		if ( args.length < 1 || !fx.isCallable ) {
			return yield CompletionRecord.makeTypeError(s.realm, 'First argument to reduceRight must be a function.');
		}

		if ( args.length > 1 ) {
			acc = args[1];
		}

		for ( let i = l - 1; i >= 0; --i ) {
			if ( !thiz.has(i) ) continue;
			let lv = yield * thiz.get(i);
			if ( !acc ) {
				acc = lv;
				continue;
			}
			acc = yield * fx.call(thiz, [acc, lv], s);
		}

		if ( !acc ) return yield CompletionRecord.makeTypeError(s.realm, 'Reduce an empty array with no initial value.');
		return acc;
	}

	static *shift$e(thiz, args) {
		yield * forceArrayness(thiz);
		let l = yield * getLength(thiz);
		if ( l < 1 ) return Value.undef;

		let val = yield * thiz.get(0);
		yield * shiftLeft(thiz, 1, 1);
		return val;
	}

	static *slice$e(thiz, args, s) {
		//TODO: Call ToObject() on thisz;
		let length = yield * getLength(thiz);
		let result = [];

		let start = 0;
		let end = length;


		if ( args.length > 0 ) start = ( yield * args[0].toIntNative() );
		if ( args.length > 1 ) end = ( yield * args[1].toIntNative() );

		if ( start < 0 ) start = length + start;
		if ( end < 0 ) end = length + end;

		if ( end > length ) end = length;
		if ( start < 0 ) start = 0;


		for ( let i = start; i < end; ++i ) {
			result.push(yield * thiz.get('' + i ));
		}


		return ArrayValue.make(result, s.realm);
	}

	static *splice$e(thiz, args, s) {
		//TODO: Call ToObject() on thisz;


		let result = [];


		let deleteCount;
		let len = yield * getLength(thiz);
		let start = len;

		if ( isNaN(len) ) return thiz;

		if ( args.length > 0 ) start = yield * args[0].toIntNative();

		if ( start > len ) start = len;
		else if ( start < 0 ) start = len + start;

		if ( args.length > 1 ) deleteCount = yield * args[1].toIntNative();
		else deleteCount = len - start;

		if ( deleteCount > (len - start) ) deleteCount = len - start;
		if ( deleteCount < 0 ) deleteCount = 0;

		let deleted = [];
		let toAdd = args.slice(2);
		let delta = toAdd.length - deleteCount;

		for ( let i = start; i < start + deleteCount; ++i ) {
			deleted.push(yield * thiz.get(i));
		}

		if ( delta > 0 ) yield * shiftRight(thiz, start, delta);
		if ( delta < 0 ) yield * shiftLeft(thiz, start - delta, -delta);

		for ( let i = 0; i < toAdd.length; ++i ) {
			yield * thiz.set(start + i, toAdd[i]);
		}

		yield * thiz.set('length', Value.fromNative(len + delta));


		return ArrayValue.make(deleted, s.realm);
	}

	static *sort$e(thiz, args, s) {
		let length = yield * getLength(thiz);
		let vals = new Array(length);
		for ( let i = 0; i < length; ++i ) {
			vals[i] = yield * thiz.get(i);
		}

		let comp = function *(left, right) {
			let l = yield * left.toStringValue();
			if ( !l ) return false;
			let r = yield * right.toStringValue();
			if ( !r ) return true;
			return (yield * l.lt(r)).truthy;
		};

		if ( args.length > 0 ) {
			let fx = args[0];
			if ( !fx.isCallable ) return yield CompletionRecord.makeTypeError(s.realm, 'Arg2 not calalble.');
			comp = function *(left, right) {
				let res = yield * fx.call(Value.undef, [left, right], s);
				return ( yield * res.lt(Value.fromNative(0)) ).truthy;
			};
		}

		let nue = yield * _g.sort(vals, comp);

		for ( let i = 0; i < length; ++i ) {
			yield * thiz.set(i, nue[i]);
		}
		return thiz;
	}

	static *toString$e(thiz, args, s) {
		let joinfn = yield * thiz.get('join');
		if ( !joinfn || !joinfn.isCallable ) {
			let ots = yield * s.realm.ObjectPrototype.get('toString');
			return yield * ots.call(thiz, []);
		} else {
			return yield * joinfn.call(thiz, [defaultSeperator]);
		}

	}

	static *unshift$e(thiz, args, s) {
		let amt = args.length;
		let len = yield * getLength(thiz);
		if ( isNaN(len) ) len = 0;
		yield * shiftRight(thiz, 0, amt);
		for ( let i = 0; i < amt; ++i ) {
			yield * thiz.set(i, args[i]);
		}

		let nl = Value.fromNative(len + amt);
		yield * thiz.set('length', nl, s);
		return nl;
	}

	static *fill$e(thiz, args, s) {
		let l = yield * getLength(thiz);
		let value = args[0] || Value.undef;
		let start = args[1] || Value.zero;
		let startn = (yield * start.toNumberValue()).native;
		let end = args[2] || Value.fromNative(l);
		let endn = (yield * end.toNumberValue()).native;

		if ( isNaN(startn) ) startn = 0;
		else if ( startn < 0 ) startn = l + startn;

		if ( isNaN(endn) ) endn = 0;
		else if ( endn < 0 ) endn = l + endn;

		if ( l > startn ) {
			for ( let i = startn; i < endn; ++i ) {
				yield * thiz.set(i, value, s);
			}
		}
		return thiz;
	}

}

ArrayPrototype.prototype.wellKnownName = '%ArrayPrototype%';

module.exports = ArrayPrototype;