// synchronous array signals for S.js import S from "@surplus/s"; export interface SArray { (): T[]; concat(...others: (() => T | T[])[]): SArray; every(pred: (v: T) => boolean): () => boolean; filter(pred: (v: T) => boolean): SArray; find(pred: (v: T) => boolean): () => T | undefined; forEach( fn: (v: T) => void, exit?: (v: T, i: number) => void, move?: (from: number[], to: number[]) => void, ): SArray; includes(v: T): () => boolean; map( fn: (v: T, m: U | undefined, i: number) => U, exit?: (v: T, m: U, i: number) => void, move?: (items: T[], mapped: U[], from: number[], to: number[]) => void, ): SArray; sort(fn: (a: T, b: T) => number): SArray; reduce( fn: (cur: U, v: T, i?: number, l?: T[]) => U, seed: U | (() => U), ): () => U; reduceRight( fn: (cur: U, v: T, i?: number, l?: T[]) => U, seed: U | (() => U), ): () => U; reverse(): SArray; slice(s: number, e: number): SArray; some(pred: (v: T) => boolean): () => boolean; mapS( fn: (v: T, m: U | undefined, i: number) => U, exit?: (v: T, m: U, i: number) => void, move?: ( items: T[], mapped: (() => U)[], from: number[], to: number[], ) => void, ): SSignalArray; mapSample( fn: (v: T, m: U | undefined, i: number) => U, exit?: (v: T, m: U, i: number) => void, move?: (items: T[], mapped: U[], from: number[], to: number[]) => void, ): SArray; mapSequentially(fn: (v: T, m: U | undefined, i: number) => U): SArray; orderBy(key: (v: T) => U): SArray; } export interface SSignalArray extends SArray<() => T> { combine(): SArray; } export interface SDataArray extends SArray { (v: T[]): T[]; push(v: T): SDataArray; pop(): T | undefined; unshift(v: T): SDataArray; shift(): T | undefined; splice(i: number, len: number, ...items: T[]): SDataArray; remove(v: T): SDataArray; removeAll(v: T): SDataArray; } // inline ES6 Map type definition, as we want Typescript to target ES5, but with Map interface Map { clear(): void; delete(key: K): boolean; forEach( callbackfn: (value: V, key: K, map: Map) => void, thisArg?: any, ): void; get(key: K): V | undefined; has(key: K): boolean; set(key: K, value: V): this; readonly size: number; } interface MapConstructor { new (): Map; new (entries?: [K, V][]): Map; readonly prototype: Map; } declare var Map: MapConstructor; export default function SArray(values: T[]): SDataArray { if (!Array.isArray(values)) throw new Error("SArray must be initialized with an array"); var dirty = S.data(false), mutations = [] as ((() => void) | null)[], mutcount = 0, pops = 0, shifts = 0, data = S.root(function () { return S.on(dirty, update, values, true); }); // add mutators var array = >function array(newvalues?: T[]) { if (arguments.length > 0) { mutation(function array() { values = newvalues!; }); return newvalues!; } else { return data(); } }; array.push = push; array.pop = pop; array.unshift = unshift; array.shift = shift; array.splice = splice; // not ES5 array.remove = remove; array.removeAll = removeAll; lift(array); return array; function mutation(m: () => void) { mutations[mutcount++] = m; dirty(true); } function update() { if (pops) values.splice(values.length - pops, pops); if (shifts) values.splice(0, shifts); pops = 0; shifts = 0; for (var i = 0; i < mutcount; i++) { mutations[i]!(); mutations[i] = null; } mutcount = 0; return values; } // mutators function push(item: T) { mutation(function push() { values.push(item); }); return array; } function pop() { array(); if (pops + shifts < values.length) { var value = values[values.length - ++pops]; dirty(true); return value; } } function unshift(item: T) { mutation(function unshift() { values.unshift(item); }); return array; } function shift() { array(); if (pops + shifts < values.length) { var value = values[shifts++]; dirty(true); return value; } } function splice(...args: Parameters) { mutation(function splice() { Array.prototype.splice.apply(values, args); }); return array; } function remove(item: T) { mutation(function remove() { for (var i = 0; i < values.length; i++) { if (values[i] === item) { values.splice(i, 1); break; } } }); return array; } function removeAll(item: T) { mutation(function removeAll() { for (var i = 0; i < values.length; ) { if (values[i] === item) { values.splice(i, 1); } else { i++; } } }); return array; } } // util to add transformer methods export function lift(seq: () => T[]) { var _seq = seq as SArray; _seq.concat = chainConcat; _seq.every = chainEvery; _seq.filter = chainFilter; _seq.find = chainFind; //s.findIndex = findIndex; _seq.forEach = chainForEach; _seq.includes = chainIncludes; //s.indexOf = indexOf; //s.join = join; //s.lastIndexOf = lastIndexOf; _seq.map = chainMap; _seq.sort = chainSort; _seq.reduce = chainReduce; _seq.reduceRight = chainReduceRight; _seq.reverse = chainReverse; _seq.slice = chainSlice; _seq.some = chainSome; // non-ES5 transformers _seq.mapS = chainMapS; _seq.mapSample = chainMapSample; _seq.mapSequentially = chainMapSequentially; _seq.orderBy = chainOrderBy; return _seq; } export function mapS( seq: () => T[], enter: (v: T, m: U | undefined, i: number) => U, exit?: (v: T, m: U, i: number) => void, move?: ( items: T[], mapped: (() => U)[], from: number[], to: number[], ) => void, ) { var items = [] as T[], mapped = [] as (() => U)[], disposers = [] as (() => void)[], len = 0; S(function () { S.cleanup(function () { disposers.forEach(function (d) { d(); }); }); }); return S.on(seq, function mapS() { var new_items = seq(), new_len = new_items.length, temp = new Array(new_len) as (() => U)[], tempdisposers = new Array(new_len) as (() => void)[], from = null! as number[], to = null! as number[], i: number, j: number, k: number, item: T; if (move) ((from = []), (to = [])); // 1) step through all old items and see if they can be found in the new set; if so, save them in a temp array and mark them moved; if not, exit them NEXT: for (i = 0, k = 0; i < len; i++) { item = items[i]; for (j = 0; j < new_len; j++, k = (k + 1) % new_len) { if (item === new_items[k] && !temp.hasOwnProperty(k.toString())) { temp[k] = mapped[i]; tempdisposers[k] = disposers[i]; if (move && i !== k) { from.push(i); to.push(k); } k = (k + 1) % new_len; continue NEXT; } } if (exit) exit(item, mapped[i](), i); disposers[i](); } if (move && from.length) move(items, mapped, from, to); // 2) set all the new values, pulling from the temp array if copied, otherwise entering the new value for (i = 0; i < new_len; i++) { if (temp.hasOwnProperty(i.toString())) { mapped[i] = temp[i]; disposers[i] = tempdisposers[i]; } else { mapped[i] = S.root(mapper); } } // 3) in case the new set is shorter than the old, set the length of the mapped array len = mapped.length = new_len; // 4) save a copy of the mapped items for the next update items = new_items.slice(); return mapped; function mapper(disposer: () => void) { disposers[i] = disposer; var _item = new_items[i], _i = i; return S(function (value: U) { return enter(_item, value, _i); }, undefined!); } }); } function chainMapS( this: () => T[], enter: (v: T, m: U | undefined, i: number) => U, exit?: (v: T, m: U, i: number) => void, move?: ( items: T[], mapped: (() => U)[], from: number[], to: number[], ) => void, ) { var r = lift(mapS(this, enter, exit, move)) as SSignalArray; r.combine = chainCombine; return r; } export function mapSample( seq: () => T[], enter: (v: T, m: U | undefined, i: number) => U, exit?: (v: T, m: U, i: number) => void, move?: (items: T[], mapped: U[], from: number[], to: number[]) => void, ) { var items = [] as T[], mapped = [] as U[], disposers = [] as (() => void)[], len = 0; S(function () { S.cleanup(function () { disposers.forEach(function (d) { d(); }); }); }); return S.on(seq, function mapSample() { var new_items = seq(), new_len = new_items.length, new_indices: Map, new_indices_next: number[], temp: U[], tempdisposers: (() => void)[], from = null! as number[], to = null! as number[], i: number, j: number, start: number, end: number, new_end: number, item: T; // fast path for empty arrays if (new_len === 0) { if (len !== 0) { if (exit !== undefined) { for (i = 0; i < len; i++) { item = items[i]; exit(item, mapped[i], i); disposers[i](); } } else { for (i = 0; i < len; i++) { disposers[i](); } } items = []; mapped = []; disposers = []; len = 0; } } else if (len === 0) { for (j = 0; j < new_len; j++) { items[j] = new_items[j]; mapped[j] = S.root(mapper); } len = new_len; } else { new_indices = new Map(); temp = new Array(new_len); tempdisposers = new Array(new_len); if (move) ((from = []), (to = [])); // skip common prefix and suffix for ( start = 0, end = Math.min(len, new_len); start < end && items[start] === new_items[start]; start++ ); for ( end = len - 1, new_end = new_len - 1; end >= 0 && new_end >= 0 && items[end] === new_items[new_end]; end--, new_end-- ) { temp[new_end] = mapped[end]; tempdisposers[new_end] = disposers[end]; } // 0) prepare a map of all indices in new_items, scanning backwards so we encounter them in natural order new_indices_next = new Array(new_end + 1); for (j = new_end; j >= start; j--) { item = new_items[j]; i = new_indices.get(item)!; new_indices_next[j] = i === undefined ? -1 : i; new_indices.set(item, j); } // 1) step through all old items and see if they can be found in the new set; if so, save them in a temp array and mark them moved; if not, exit them for (i = start; i <= end; i++) { item = items[i]; j = new_indices.get(item)!; if (j !== undefined && j !== -1) { temp[j] = mapped[i]; tempdisposers[j] = disposers[i]; if (move && i !== j) { from.push(i); to.push(j); } j = new_indices_next[j]; new_indices.set(item, j); } else { if (exit) exit(item, mapped[i], i); disposers[i](); } } if (move && (from.length !== 0 || end !== len - 1)) { (end++, new_end++); while (end < len) { from.push(end++); to.push(new_end++); } move(items, mapped, from, to); } // 2) set all the new values, pulling from the temp array if copied, otherwise entering the new value for (j = start; j < new_len; j++) { if (temp.hasOwnProperty(j as any)) { mapped[j] = temp[j]; disposers[j] = tempdisposers[j]; } else { mapped[j] = S.root(mapper); } } // 3) in case the new set is shorter than the old, set the length of the mapped array len = mapped.length = new_len; // 4) save a copy of the mapped items for the next update items = new_items.slice(); } return mapped; function mapper(disposer: () => void) { disposers[j] = disposer; return enter(new_items[j], mapped[j], j); } }); } function chainMapSample( this: () => T[], enter: (v: T, m: U | undefined, i: number) => U, exit?: (v: T, m: U, i: number) => void, move?: (items: T[], mapped: U[], from: number[], to: number[]) => void, ) { return lift(mapSample(this, enter, exit, move)); } export function mapSequentially( seq: () => T[], update: (v: T, m: U | undefined, i: number) => U, ) { var mapped = [] as U[]; return S(function mapSequentially() { var s = seq(); for (var i = 0; i < s.length; i++) { mapped[i] = update(s[i], mapped[i], i); } if (mapped.length > s.length) mapped.length = s.length; return mapped; }); } function chainMapSequentially( this: () => T[], enter: (v: T, m: U | undefined, i: number) => U, ) { return lift(mapSequentially(this, enter)); } export function forEach( seq: () => T[], enter: (v: T, i: number) => void, exit?: (v: T, i: number) => void, move?: (from: number[], to: number[]) => void, ) { var items = [] as T[], len = 0; return S.on(seq, function forEach() { var new_items = seq(), new_len = new_items.length, found = new Array(new_len) as boolean[], from = [] as number[], to = [] as number[], i: number, j: number, k: number, item: T; // 1) step through all old items and see if they can be found in the new set; if so, save them in a temp array and mark them moved; if not, exit them NEXT: for (i = 0, k = 0; i < len; i++) { item = items[i]; for (j = 0; j < new_len; j++, k = (k + 1) % new_len) { if (item === new_items[k] && !found[k]) { found[k] = true; if (i !== k) { from.push(i); to.push(k); } k = (k + 1) % new_len; continue NEXT; } } if (exit) exit(item, i); } if (move && from.length) move(from, to); // 2) set all the new values, pulling from the temp array if copied, otherwise entering the new value for (var i = 0; i < new_len; i++) { if (!found[i]) enter(new_items[i], i); } // 3) in case the new set is shorter than the old, set the length of the mapped array len = new_len; // 4) save a copy of the mapped items for the next update items = new_items.slice(); return items; }); } function chainForEach( this: () => T[], enter: (v: T, i: number) => void, exit?: (v: T, i: number) => void, move?: (from: number[], to: number[]) => void, ) { return lift(forEach(this, enter, exit, move)); } export function combine(seq: () => (() => T)[]) { return S(function combine() { var s = seq(), result = new Array(s.length) as T[]; for (var i = 0; i < s.length; i++) { result[i] = s[i](); } return result; }); } function chainCombine(this: () => (() => T)[]) { return lift(combine(this)); } export function map( seq: () => T[], enter: (v: T, m: U | undefined, i: number) => U, exit?: (v: T, m: U, i: number) => void, move?: (items: T[], mapped: U[], from: number[], to: number[]) => void, ) { return combine( mapS( seq, enter, exit, move == undefined ? undefined : function (items, mapped, from, to) { move( items, mapped.map((s) => s()), from, to, ); }, ), ); } function chainMap( this: () => T[], enter: (v: T, m: U | undefined, i: number) => U, exit?: (v: T, m: U, i: number) => void, move?: (items: T[], mapped: U[], from: number[], to: number[]) => void, ) { return lift(map(this, enter, exit, move)); } export function find(seq: () => T[], pred: (v: T) => boolean) { return S(function find() { var s = seq(), i: number, item: T; for (i = 0; i < s.length; i++) { item = s[i]; if (pred(item)) return item; } return undefined; }); } function chainFind(this: () => T[], pred: (v: T) => boolean) { return find(this, pred); } export function includes(seq: () => T[], o: T) { return S(function find() { var s = seq(); for (var i = 0; i < s.length; i++) { if (s[i] === o) return true; } return false; }); } function chainIncludes(this: () => T[], o: T) { return includes(this, o); } export function sort(seq: () => T[], fn?: (a: T, b: T) => number) { return S(function sort() { var copy = seq().slice(0); if (fn) copy.sort(fn); else copy.sort(); return copy; }); } function chainSort(this: () => T[], fn?: (a: T, b: T) => number) { return lift(sort(this, fn)); } export function orderBy(seq: () => T[], by: keyof T | ((v: T) => any)) { var key: keyof T, fn: (v: T) => any; if (typeof by !== "function") { key = by; fn = function (o: T) { return o[key]; }; } else { fn = by as (v: T) => any; } return S(function orderBy() { var copy = seq().slice(0); copy.sort(function (a, b) { a = fn(a); b = fn(b); return a < b ? -1 : a > b ? 1 : 0; }); return copy; }); } function chainOrderBy(this: () => T[], by: keyof T | ((v: T) => any)) { return lift(orderBy(this, by)); } export function filter(seq: () => T[], predicate: (v: T) => boolean) { return S(function filter() { var s = seq(), result = [], i, v; for (i = 0; i < s.length; i++) { v = s[i]; if (predicate(v)) result.push(v); } return result; }); } function chainFilter(this: () => T[], predicate: (v: T) => boolean) { return lift(filter(this, predicate)); } export function concat(seq: () => T[], ...others: (() => T | T[])[]) { return S(function concat() { var s = seq(); for (var i = 0; i < others.length; i++) { s = s.concat(others[i]()); } return s; }); } function chainConcat(this: () => T[], ...others: (() => T | T[])[]) { return lift(concat(this, ...others)); } export function reduce( seq: () => T[], fn: (r: U, t: T, i: number, s: T[]) => U, seed: U | (() => U), ) { return S(function reduce() { var s = seq(), result = seed instanceof Function ? seed() : seed; for (var i = 0; i < s.length; i++) { result = fn(result, s[i], i, s); } return result; }); } function chainReduce( this: () => T[], fn: (r: U, t: T, i: number, s: T[]) => U, seed: U | (() => U), ) { return reduce(this, fn, seed); } export function reduceRight( seq: () => T[], fn: (r: U, t: T, i: number, s: T[]) => U, seed: U | (() => U), ) { return S(function reduceRight() { var s = seq(), result = seed instanceof Function ? seed() : seed; for (var i = s.length - 1; i >= 0; i--) { result = fn(result, s[i], i, s); } return result; }); } function chainReduceRight( this: () => T[], fn: (r: U, t: T, i: number, s: T[]) => U, seed: U | (() => U), ) { return reduceRight(this, fn, seed); } export function every(seq: () => T[], fn: (v: T) => boolean) { return S(function every() { var s = seq(); for (var i = 0; i < s.length; i++) { if (!fn(s[i])) return false; } return true; }); } function chainEvery(this: () => T[], fn: (v: T) => boolean) { return every(this, fn); } export function some(seq: () => T[], fn?: (v: T) => boolean) { return S(function some() { var s = seq(); if (fn === undefined) return s.length !== 0; for (var i = 0; i < s.length; i++) { if (fn(s[i])) return true; } return false; }); } function chainSome(this: () => T[], fn?: (v: T) => boolean) { return some(this, fn); } export function reverse(seq: () => T[]) { return S(function () { var copy = seq().slice(0); copy.reverse(); return copy; }); } function chainReverse(this: () => T[]) { return lift(reverse(this)); } export function slice(seq: () => T[], s: number, e: number) { return S(function () { return seq().slice(s, e); }); } function chainSlice(this: () => T[], s: number, e: number) { return lift(slice(this, s, e)); }