From e23ac97d9ac373c993a2cddd7014bf6fbcd3d44d Mon Sep 17 00:00:00 2001 From: jiyinyiyong Date: Fri, 11 Jun 2021 14:18:39 +0800 Subject: [PATCH] split ts files; bump 0.3.35 --- .gitattributes | 1 + .gitignore | 2 +- Cargo.lock | 2 +- Cargo.toml | 2 +- lib/calcit-data.ts | 885 --------------------- package.json | 3 +- ts-src/calcit-data.ts | 517 ++++++++++++ {lib => ts-src}/calcit.procs.ts | 596 +------------- {lib => ts-src}/custom-formatter.ts | 15 +- ts-src/js-cirru.ts | 246 ++++++ ts-src/js-list.ts | 312 ++++++++ ts-src/js-map.ts | 198 +++++ ts-src/js-primes.ts | 22 + lib/record-procs.ts => ts-src/js-record.ts | 77 +- ts-src/js-set.ts | 96 +++ ts-src/js-tuple.ts | 34 + tsconfig.json | 5 +- 17 files changed, 1526 insertions(+), 1487 deletions(-) delete mode 100644 lib/calcit-data.ts create mode 100644 ts-src/calcit-data.ts rename {lib => ts-src}/calcit.procs.ts (70%) rename {lib => ts-src}/custom-formatter.ts (85%) create mode 100644 ts-src/js-cirru.ts create mode 100644 ts-src/js-list.ts create mode 100644 ts-src/js-map.ts create mode 100644 ts-src/js-primes.ts rename lib/record-procs.ts => ts-src/js-record.ts (71%) create mode 100644 ts-src/js-set.ts create mode 100644 ts-src/js-tuple.ts diff --git a/.gitattributes b/.gitattributes index 72399e7f..aeff4d00 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,3 +2,4 @@ calcit.cirru -diff linguist-generated yarn.lock -diff linguist-generated Cargo.lock -diff linguist-generated +lib -diff linguist-generated diff --git a/.gitignore b/.gitignore index 03ead405..aff2b20c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,7 @@ js-out/ node_modules/ -lib/*.js +lib builds/ diff --git a/Cargo.lock b/Cargo.lock index 263c5c7b..67b6cfc7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -81,7 +81,7 @@ dependencies = [ [[package]] name = "calcit_runner" -version = "0.3.34" +version = "0.3.35" dependencies = [ "chrono", "cirru_edn", diff --git a/Cargo.toml b/Cargo.toml index a00de0b8..ce21f7a1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "calcit_runner" -version = "0.3.34" +version = "0.3.35" authors = ["jiyinyiyong "] edition = "2018" license = "MIT" diff --git a/lib/calcit-data.ts b/lib/calcit-data.ts deleted file mode 100644 index 7df1d1c4..00000000 --- a/lib/calcit-data.ts +++ /dev/null @@ -1,885 +0,0 @@ -import * as ternaryTree from "@calcit/ternary-tree"; - -import { - TernaryTreeList, - TernaryTreeMap, - overwriteComparator, - initTernaryTreeList, - initTernaryTreeMap, - listLen, - mapLen, - listGet, - mapGet, - assocMap, - assocList, - dissocMap, - isMapEmpty, - toPairs, - contains, - listToItems, - dissocList, - Hash, - overwriteHashGenerator, - valueHash, - mergeValueHash, - toPairsArray, - assocBefore, - assocAfter, - mapGetDefault, -} from "@calcit/ternary-tree"; - -/** need to compare by Calcit */ -let DATA_EQUAL = (x: CrDataValue, y: CrDataValue): boolean => { - return x == y; -}; - -export let overwriteDataComparator = (f: typeof DATA_EQUAL): void => { - DATA_EQUAL = f; -}; - -export class CrDataKeyword { - value: string; - cachedHash: Hash; - constructor(x: string) { - this.value = x; - } - toString() { - return `:${this.value}`; - } -} - -export class CrDataSymbol { - value: string; - cachedHash: Hash; - constructor(x: string) { - this.value = x; - } - toString() { - return `'${this.value}`; - } -} - -export class CrDataRecur { - args: CrDataValue[]; - constructor(xs: CrDataValue[]) { - this.args = xs; - } - - toString() { - return `(&recur ...)`; - } -} - -export let isNestedCrData = (x: CrDataValue): boolean => { - if (x instanceof CrDataList) { - return x.len() > 0; - } - if (x instanceof CrDataMap) { - return x.len() > 0; - } - if (x instanceof CrDataRecord) { - return x.fields.length > 0; - } - if (x instanceof CrDataSet) { - return false; - } - return false; -}; - -export let tipNestedCrData = (x: CrDataValue): string => { - if (x instanceof CrDataList) { - return "'[]..."; - } - if (x instanceof CrDataMap) { - return "'{}..."; - } - if (x instanceof CrDataRecord) { - return "'%{}..."; - } - if (x instanceof CrDataSet) { - return "'#{}..."; - } - return x.toString(); -}; - -export class CrDataRef { - value: CrDataValue; - path: string; - listeners: Map; - cachedHash: Hash; - constructor(x: CrDataValue, path: string) { - this.value = x; - this.path = path; - this.listeners = new Map(); - } - toString(): string { - return `(&ref ${this.value.toString()})`; - } -} - -export class CrDataTuple { - fst: CrDataValue; - snd: CrDataValue; - cachedHash: Hash; - constructor(a: CrDataValue, b: CrDataValue) { - this.fst = a; - this.snd = b; - } - get(n: number) { - if (n == 0) { - return this.fst; - } else if (n == 1) { - return this.snd; - } else { - throw new Error("Tuple only have 2 elements"); - } - } - assoc(n: number, v: CrDataValue) { - if (n == 0) { - return new CrDataTuple(v, this.snd); - } else if (n == 1) { - return new CrDataTuple(this.fst, v); - } else { - throw new Error("Tuple only have 2 elements"); - } - } - toString(): string { - return `(&tuple ${this.fst.toString()} ${this.snd.toString()})`; - } -} - -export type CrDataFn = (...xs: CrDataValue[]) => CrDataValue; - -export class CrDataList { - value: TernaryTreeList; - // array mode store bare array for performance - arrayValue: Array; - arrayMode: boolean; - arrayStart: number; - arrayEnd: number; - cachedHash: Hash; - constructor(value: Array | TernaryTreeList) { - if (value == null) { - value = []; // dirty, better handled from outside - } - if (Array.isArray(value)) { - this.arrayMode = true; - this.arrayValue = value; - this.arrayStart = 0; - this.arrayEnd = value.length; - this.value = null; - } else { - this.arrayMode = false; - this.value = value; - this.arrayValue = []; - this.arrayStart = null; - this.arrayEnd = null; - } - } - turnListMode() { - if (this.arrayMode) { - this.value = initTernaryTreeList(this.arrayValue.slice(this.arrayStart, this.arrayEnd)); - this.arrayValue = null; - this.arrayStart = null; - this.arrayEnd = null; - this.arrayMode = false; - } - } - len() { - if (this.arrayMode) { - return this.arrayEnd - this.arrayStart; - } else { - return listLen(this.value); - } - } - get(idx: number) { - if (this.arrayMode) { - return this.arrayValue[this.arrayStart + idx]; - } else { - return listGet(this.value, idx); - } - } - assoc(idx: number, v: CrDataValue) { - this.turnListMode(); - return new CrDataList(assocList(this.value, idx, v)); - } - assocBefore(idx: number, v: CrDataValue) { - this.turnListMode(); - return new CrDataList(assocBefore(this.value, idx, v)); - } - assocAfter(idx: number, v: CrDataValue) { - this.turnListMode(); - return new CrDataList(assocAfter(this.value, idx, v)); - } - dissoc(idx: number) { - this.turnListMode(); - return new CrDataList(dissocList(this.value, idx)); - } - slice(from: number, to: number) { - if (this.arrayMode) { - if (from < 0) { - throw new Error(`from index too small: ${from}`); - } - if (to > this.len()) { - throw new Error(`end index too large: ${to}`); - } - if (to < from) { - throw new Error("end index too small"); - } - let result = new CrDataList(this.arrayValue); - result.arrayStart = this.arrayStart + from; - result.arrayEnd = this.arrayStart + to; - return result; - } else { - return new CrDataList(ternaryTree.slice(this.value, from, to)); - } - } - toString(shorter = false): string { - let result = ""; - for (let item of this.items()) { - if (shorter && isNestedCrData(item)) { - result = `${result} ${tipNestedCrData(item)}`; - } else { - result = `${result} ${toString(item, true)}`; - } - } - return `([]${result})`; - } - isEmpty() { - return this.len() === 0; - } - /** usage: `for of` */ - items(): Generator { - if (this.arrayMode) { - return sliceGenerator(this.arrayValue, this.arrayStart, this.arrayEnd); - } else { - return listToItems(this.value); - } - } - append(v: CrDataValue) { - if (this.arrayMode && this.arrayEnd === this.arrayValue.length && this.arrayStart < 32) { - // dirty trick to reuse list memory, data storage actually appended at existing array - this.arrayValue.push(v); - let newList = new CrDataList(this.arrayValue); - newList.arrayStart = this.arrayStart; - newList.arrayEnd = this.arrayEnd + 1; - return newList; - } else { - this.turnListMode(); - return new CrDataList(ternaryTree.append(this.value, v)); - } - } - prepend(v: CrDataValue) { - this.turnListMode(); - return new CrDataList(ternaryTree.prepend(this.value, v)); - } - first() { - if (this.arrayMode) { - if (this.arrayValue.length > this.arrayStart) { - return this.arrayValue[this.arrayStart]; - } else { - return null; - } - } else { - return ternaryTree.first(this.value); - } - } - rest() { - if (this.arrayMode) { - return this.slice(1, this.arrayEnd - this.arrayStart); - } else { - return new CrDataList(ternaryTree.rest(this.value)); - } - } - concat(ys: CrDataList) { - if (!(ys instanceof CrDataList)) { - throw new Error("Expected list"); - } - if (this.arrayMode && ys.arrayMode) { - let size = this.arrayEnd - this.arrayStart; - let otherSize = ys.arrayEnd - ys.arrayStart; - let combined = new Array(size + otherSize); - for (let i = 0; i < size; i++) { - combined[i] = this.get(i); - } - for (let i = 0; i < otherSize; i++) { - combined[i + size] = ys.get(i); - } - return new CrDataList(combined); - } else { - this.turnListMode(); - ys.turnListMode(); - return new CrDataList(ternaryTree.concat(this.value, ys.value)); - } - } - map(f: (v: CrDataValue) => CrDataValue): CrDataList { - if (this.arrayMode) { - return new CrDataList(this.arrayValue.slice(this.arrayStart, this.arrayEnd).map(f)); - } else { - return new CrDataList(ternaryTree.listMapValues(this.value, f)); - } - } - toArray(): CrDataValue[] { - if (this.arrayMode) { - return this.arrayValue.slice(this.arrayStart, this.arrayEnd); - } else { - return [...ternaryTree.listToItems(this.value)]; - } - } - reverse() { - this.turnListMode(); - return new CrDataList(ternaryTree.reverse(this.value)); - } -} - -export class CrDataMap { - cachedHash: Hash; - /** in arrayMode, only flatten values, not tree structure */ - arrayMode: boolean; - arrayValue: CrDataValue[]; - value: TernaryTreeMap; - skipValue: CrDataValue; - constructor(value: CrDataValue[] | TernaryTreeMap) { - if (value == null) { - this.arrayMode = true; - this.arrayValue = []; - } else if (Array.isArray(value)) { - this.arrayMode = true; - this.arrayValue = value; - } else { - this.arrayMode = false; - this.value = value; - } - } - turnMap() { - if (this.arrayMode) { - var dict: Array<[CrDataValue, CrDataValue]> = []; - let halfLength = this.arrayValue.length >> 1; - for (let idx = 0; idx < halfLength; idx++) { - dict.push([this.arrayValue[idx << 1], this.arrayValue[(idx << 1) + 1]]); - } - this.value = initTernaryTreeMap(dict); - this.arrayMode = false; - this.arrayValue = null; - } - } - len() { - if (this.arrayMode) { - return this.arrayValue.length >> 1; - } else { - return mapLen(this.value); - } - } - get(k: CrDataValue) { - if (this.arrayMode && this.arrayValue.length <= 16) { - let size = this.arrayValue.length >> 1; - for (let i = 0; i < size; i++) { - let pos = i << 1; - if (DATA_EQUAL(this.arrayValue[pos], k)) { - return this.arrayValue[pos + 1]; - } - } - return null; - } else { - this.turnMap(); - return mapGetDefault(this.value, k, null); - } - } - assoc(k: CrDataValue, v: CrDataValue) { - if (this.arrayMode && this.arrayValue.length <= 16) { - let ret = this.arrayValue.slice(0); - for (let i = 0; i < ret.length; i += 2) { - if (DATA_EQUAL(k, ret[i])) { - ret[i + 1] = v; - return new CrDataMap(ret); - } - } - ret.push(k, v); - return new CrDataMap(ret); - } else { - this.turnMap(); - return new CrDataMap(assocMap(this.value, k, v)); - } - } - dissoc(k: CrDataValue) { - if (this.arrayMode && this.arrayValue.length <= 16) { - let ret: CrDataValue[] = []; - for (let i = 0; i < this.arrayValue.length; i += 2) { - if (!DATA_EQUAL(k, this.arrayValue[i])) { - ret.push(this.arrayValue[i], this.arrayValue[i + 1]); - } - } - return new CrDataMap(ret); - } else { - this.turnMap(); - return new CrDataMap(dissocMap(this.value, k)); - } - } - toString(shorter = false) { - let itemsCode = ""; - for (let [k, v] of this.pairs()) { - if (shorter) { - let keyPart = isNestedCrData(k) ? tipNestedCrData(k) : toString(k, true); - let valuePart = isNestedCrData(v) ? tipNestedCrData(v) : toString(v, true); - itemsCode = `${itemsCode} (${keyPart} ${valuePart})`; - } else { - itemsCode = `${itemsCode} (${toString(k, true)} ${toString(v, true)})`; - } - } - return `({}${itemsCode})`; - } - isEmpty() { - if (this.arrayMode) { - return this.arrayValue.length == 0; - } else { - return isMapEmpty(this.value); - } - } - pairs(): Array<[CrDataValue, CrDataValue]> { - if (this.arrayMode) { - let ret: Array<[CrDataValue, CrDataValue]> = []; - let size = this.arrayValue.length >> 1; - for (let i = 0; i < size; i++) { - let pos = i << 1; - ret.push([this.arrayValue[pos], this.arrayValue[pos + 1]]); - } - return ret; - } else { - return toPairsArray(this.value); - } - } - contains(k: CrDataValue) { - if (this.arrayMode && this.arrayValue.length <= 16) { - // guessed number - let size = this.arrayValue.length >> 1; - for (let i = 0; i < size; i++) { - let pos = i << 1; - if (DATA_EQUAL(this.arrayValue[pos], k)) { - return true; - } - } - return false; - } else { - this.turnMap(); - return ternaryTree.contains(this.value, k); - } - } - merge(ys: CrDataMap) { - return this.mergeSkip(ys, null); - } - mergeSkip(ys: CrDataMap, v: CrDataValue) { - if (ys == null) { - return this; - } - - if (!(ys instanceof CrDataMap)) { - console.error("value:", v); - throw new Error("Expected map to merge"); - } - - if (this.arrayMode && ys.arrayMode && this.arrayValue.length + ys.arrayValue.length <= 24) { - // probably this length < 16, ys length < 8 - let ret = this.arrayValue.slice(0); - outer: for (let i = 0; i < ys.arrayValue.length; i = i + 2) { - if (ys.arrayValue[i + 1] == v) { - continue; - } - for (let k = 0; k < ret.length; k = k + 2) { - if (DATA_EQUAL(ys.arrayValue[i], ret[k])) { - ret[k + 1] = ys.arrayValue[i + 1]; - continue outer; - } - } - ret.push(ys.arrayValue[i], ys.arrayValue[i + 1]); - } - return new CrDataMap(ret); - } - - this.turnMap(); - - if (ys.arrayMode) { - let ret = this.value; - let size = ys.arrayValue.length >> 1; - for (let i = 0; i < size; i++) { - let pos = i << 1; - if (ys.arrayValue[pos + 1] == v) { - continue; - } - ret = assocMap(ret, ys.arrayValue[pos], ys.arrayValue[pos + 1]); - } - return new CrDataMap(ret); - } else { - return new CrDataMap(ternaryTree.mergeSkip(this.value, ys.value, v)); - } - } -} - -export let getStringName = (x: CrDataValue): string => { - if (typeof x === "string") { - return x; - } - if (x instanceof CrDataKeyword) { - return x.value; - } - if (x instanceof CrDataSymbol) { - return x.value; - } - throw new Error("Cannot get string as name"); -}; - -/** returns -1 when not found */ -export function findInFields(xs: Array, y: string): number { - let lower = 0; - let upper = xs.length - 1; - - while (upper - lower > 1) { - let pos = (lower + upper) >> 1; - let v = xs[pos]; - if (y < v) { - upper = pos - 1; - } else if (y > v) { - lower = pos + 1; - } else { - return pos; - } - } - - if (y == xs[lower]) return lower; - if (y == xs[upper]) return upper; - return -1; -} - -export class CrDataRecord { - name: string; - fields: Array; - values: Array; - constructor(name: string, fields: Array, values?: Array) { - this.name = name; - let fieldNames = fields.map(getStringName); - this.fields = fieldNames; - if (values != null) { - if (values.length != fields.length) { - throw new Error("value length not match"); - } - this.values = values; - } else { - this.values = new Array(fieldNames.length); - } - } - get(k: CrDataValue) { - let field = getStringName(k); - let idx = findInFields(this.fields, field); - if (idx >= 0) { - return this.values[idx]; - } else { - throw new Error(`Cannot find :${field} among (${this.values.join(",")})`); - } - } - assoc(k: CrDataValue, v: CrDataValue): CrDataRecord { - let values: Array = new Array(this.fields.length); - let name = getStringName(k); - for (let idx in this.fields) { - if (this.fields[idx] === name) { - values[idx] = v; - } else { - values[idx] = this.values[idx]; - } - } - return new CrDataRecord(this.name, this.fields, values); - } - merge() { - // TODO - } - contains(k: CrDataValue) { - let field = getStringName(k); - let idx = findInFields(this.fields, field); - return idx >= 0; - } - toString(): string { - let ret = "(%{} " + this.name; - for (let idx in this.fields) { - ret += " (" + this.fields[idx] + " " + toString(this.values[idx], true) + ")"; - } - return ret + ")"; - } -} - -export type CrDataValue = - | string - | number - | boolean - | CrDataMap - | CrDataList - | CrDataSet - | CrDataKeyword - | CrDataSymbol - | CrDataRef - | CrDataTuple - | CrDataFn - | CrDataRecur // should not be exposed to function - | CrDataRecord - | null; - -var keywordRegistery: Record = {}; - -export let kwd = (content: string) => { - let item = keywordRegistery[content]; - if (item != null) { - return item; - } else { - let v = new CrDataKeyword(content); - keywordRegistery[content] = v; - return v; - } -}; - -export var refsRegistry = new Map(); - -let defaultHash_nil = valueHash("nil:"); -let defaultHash_number = valueHash("number:"); -let defaultHash_string = valueHash("string:"); -let defaultHash_keyword = valueHash("keyword:"); -let defaultHash_true = valueHash("true:"); -let defaultHash_false = valueHash("false:"); -let defaultHash_symbol = valueHash("symbol:"); -let defaultHash_fn = valueHash("fn:"); -let defaultHash_ref = valueHash("ref:"); -let defaultHash_tuple = valueHash("tuple:"); -let defaultHash_set = valueHash("set:"); -let defaultHash_list = valueHash("list:"); -let defaultHash_map = valueHash("map:"); - -let fnHashCounter = 0; - -let hashFunction = (x: CrDataValue): Hash => { - if (x == null) { - return defaultHash_nil; - } - if (typeof x === "number") { - return mergeValueHash(defaultHash_number, x); - } - if (typeof x === "string") { - return mergeValueHash(defaultHash_string, x); - } - // dirty solution of caching, trying to reduce cost - if ((x as any).cachedHash != null) { - return (x as any).cachedHash; - } - if (x instanceof CrDataKeyword) { - let h = mergeValueHash(defaultHash_keyword, x.value); - x.cachedHash = h; - return h; - } - if (x === true) { - return defaultHash_true; - } - if (x === false) { - return defaultHash_false; - } - if (x instanceof CrDataSymbol) { - let h = mergeValueHash(defaultHash_symbol, x.value); - x.cachedHash = h; - return h; - } - if (typeof x === "function") { - fnHashCounter = fnHashCounter + 1; - let h = mergeValueHash(defaultHash_fn, fnHashCounter); - (x as any).cachedHash = h; - return h; - } - if (x instanceof CrDataRef) { - let h = mergeValueHash(defaultHash_ref, x.path); - x.cachedHash = h; - return h; - } - if (x instanceof CrDataTuple) { - let base = defaultHash_list; - base = mergeValueHash(base, hashFunction(x.fst)); - base = mergeValueHash(base, hashFunction(x.snd)); - x.cachedHash = base; - return base; - } - if (x instanceof CrDataSet) { - // TODO not using dirty solution for code - let base = defaultHash_set; - for (let item of x.value) { - base = mergeValueHash(base, hashFunction(item)); - } - return base; - } - if (x instanceof CrDataList) { - let base = defaultHash_list; - for (let item of x.items()) { - base = mergeValueHash(base, hashFunction(item)); - } - x.cachedHash = base; - return base; - } - if (x instanceof CrDataMap) { - let base = defaultHash_map; - for (let [k, v] of x.pairs()) { - base = mergeValueHash(base, hashFunction(k)); - base = mergeValueHash(base, hashFunction(v)); - } - x.cachedHash = base; - return base; - } - throw new Error("Unknown data for hashing"); -}; - -// Dirty code to change ternary-tree behavior -overwriteHashGenerator(hashFunction); - -function* sliceGenerator(xs: Array, start: number, end: number): Generator { - for (let idx = start; idx < end; idx++) { - yield xs[idx]; - } -} - -export let toString = (x: CrDataValue, escaped: boolean): string => { - if (x == null) { - return "nil"; - } - if (typeof x === "string") { - if (escaped) { - // turn to visual string representation - if (/[\)\(\s\"]/.test(x)) { - return JSON.stringify("|" + x); - } else { - return "|" + x; - } - } else { - return x; - } - } - if (typeof x === "number") { - return x.toString(); - } - if (typeof x === "boolean") { - return x.toString(); - } - if (typeof x === "function") { - return `(&fn ...)`; - } - if (x instanceof CrDataSymbol) { - return x.toString(); - } - if (x instanceof CrDataKeyword) { - return x.toString(); - } - if (x instanceof CrDataList) { - return x.toString(); - } - if (x instanceof CrDataMap) { - return x.toString(); - } - if (x instanceof CrDataSet) { - return x.toString(); - } - if (x instanceof CrDataRecord) { - return x.toString(); - } - if (x instanceof CrDataRef) { - return x.toString(); - } - if (x instanceof CrDataTuple) { - return x.toString(); - } - - console.warn("Unknown structure to string, better use `console.log`", x); - return `${x}`; -}; - -export let cloneSet = (xs: Set): Set => { - if (!(xs instanceof Set)) { - throw new Error("Expected a set"); - } - var result: Set = new Set(); - for (let v of xs) { - result.add(v); - } - return result; -}; - -export class CrDataSet { - value: Set; - constructor(value: Set) { - this.value = value; - } - len() { - return this.value.size; - } - contains(y: CrDataValue) { - return this.value.has(y); - } - include(y: CrDataValue): CrDataSet { - var result = cloneSet(this.value); - result.add(y); - return new CrDataSet(result); - } - exclude(y: CrDataValue): CrDataSet { - var result = cloneSet(this.value); - result.delete(y); - return new CrDataSet(result); - } - - difference(ys: CrDataSet): CrDataSet { - var result = cloneSet(this.value); - ys.value.forEach((y) => { - if (result.has(y)) { - result.delete(y); - } - }); - return new CrDataSet(result); - } - union(ys: CrDataSet): CrDataSet { - var result = cloneSet(this.value); - ys.value.forEach((y) => { - if (!result.has(y)) { - result.add(y); - } - }); - return new CrDataSet(result); - } - intersection(ys: CrDataSet): CrDataSet { - let xs = this.value; - var result: Set = new Set(); - ys.value.forEach((y) => { - if (xs.has(y)) { - result.add(y); - } - }); - return new CrDataSet(result); - } - - first(): CrDataValue { - // rather suspicious solution since set has no logic order - if (this.value.size === 0) { - return null; - } - for (let x of this.value) { - return x; - } - } - rest(): CrDataSet { - if (this.value.size == 0) { - return null; - } - let x0 = this.first(); - let ys = cloneSet(this.value); - ys.delete(x0); - return new CrDataSet(ys); - } - - toString() { - let itemsCode = ""; - this.value.forEach((child, idx) => { - itemsCode = `${itemsCode} ${toString(child, true)}`; - }); - return `(#{}${itemsCode})`; - } - - values() { - return this.value.values(); - } -} diff --git a/package.json b/package.json index 33f0536b..07617d52 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@calcit/procs", - "version": "0.3.34", + "version": "0.3.35", "main": "./lib/calcit.procs.js", "devDependencies": { "@types/node": "^15.12.2", @@ -10,6 +10,7 @@ "webpack-cli": "^4.7.2" }, "scripts": { + "compile": "rm -rfv lib/* && tsc", "procs-link": "ln -s ../../ node_modules/@calcit/procs", "cp-mac": "cargo build --release && rm -rfv builds/* && node scripts/cp-version.js && scp builds/* rsync-user@calcit-lang.org:/web-assets/repo/calcit-lang/binaries/macos/", "test-js": "cargo run calcit/snapshots/test.cirru --emit-js && yarn tsc && target=node yarn webpack && node js-out/bundle.js" diff --git a/ts-src/calcit-data.ts b/ts-src/calcit-data.ts new file mode 100644 index 00000000..6e974bbb --- /dev/null +++ b/ts-src/calcit-data.ts @@ -0,0 +1,517 @@ +import { Hash, overwriteHashGenerator, valueHash, mergeValueHash } from "@calcit/ternary-tree"; +import { overwriteComparator, initTernaryTreeMap } from "@calcit/ternary-tree"; +import { overwriteDataComparator } from "./js-map"; + +import { CrDataRecord, fieldsEqual } from "./js-record"; +import { CrDataMap } from "./js-map"; + +import { CrDataValue } from "./js-primes"; +import { CrDataList } from "./js-list"; +import { CrDataSet } from "./js-set"; +import { CrDataTuple } from "./js-tuple"; + +export class CrDataKeyword { + value: string; + cachedHash: Hash; + constructor(x: string) { + this.value = x; + } + toString() { + return `:${this.value}`; + } +} + +export class CrDataSymbol { + value: string; + cachedHash: Hash; + constructor(x: string) { + this.value = x; + } + toString() { + return `'${this.value}`; + } +} + +export class CrDataRecur { + args: CrDataValue[]; + constructor(xs: CrDataValue[]) { + this.args = xs; + } + + toString() { + return `(&recur ...)`; + } +} + +export let isNestedCrData = (x: CrDataValue): boolean => { + if (x instanceof CrDataList) { + return x.len() > 0; + } + if (x instanceof CrDataMap) { + return x.len() > 0; + } + if (x instanceof CrDataRecord) { + return x.fields.length > 0; + } + if (x instanceof CrDataSet) { + return false; + } + return false; +}; + +export let tipNestedCrData = (x: CrDataValue): string => { + if (x instanceof CrDataList) { + return "'[]..."; + } + if (x instanceof CrDataMap) { + return "'{}..."; + } + if (x instanceof CrDataRecord) { + return "'%{}..."; + } + if (x instanceof CrDataSet) { + return "'#{}..."; + } + return x.toString(); +}; + +export class CrDataRef { + value: CrDataValue; + path: string; + listeners: Map; + cachedHash: Hash; + constructor(x: CrDataValue, path: string) { + this.value = x; + this.path = path; + this.listeners = new Map(); + } + toString(): string { + return `(&ref ${this.value.toString()})`; + } +} + +export type CrDataFn = (...xs: CrDataValue[]) => CrDataValue; + +export let getStringName = (x: CrDataValue): string => { + if (typeof x === "string") { + return x; + } + if (x instanceof CrDataKeyword) { + return x.value; + } + if (x instanceof CrDataSymbol) { + return x.value; + } + throw new Error("Cannot get string as name"); +}; + +/** returns -1 when not found */ +export function findInFields(xs: Array, y: string): number { + let lower = 0; + let upper = xs.length - 1; + + while (upper - lower > 1) { + let pos = (lower + upper) >> 1; + let v = xs[pos]; + if (y < v) { + upper = pos - 1; + } else if (y > v) { + lower = pos + 1; + } else { + return pos; + } + } + + if (y == xs[lower]) return lower; + if (y == xs[upper]) return upper; + return -1; +} + +var keywordRegistery: Record = {}; + +export let kwd = (content: string) => { + let item = keywordRegistery[content]; + if (item != null) { + return item; + } else { + let v = new CrDataKeyword(content); + keywordRegistery[content] = v; + return v; + } +}; + +export var refsRegistry = new Map(); + +let defaultHash_nil = valueHash("nil:"); +let defaultHash_number = valueHash("number:"); +let defaultHash_string = valueHash("string:"); +let defaultHash_keyword = valueHash("keyword:"); +let defaultHash_true = valueHash("true:"); +let defaultHash_false = valueHash("false:"); +let defaultHash_symbol = valueHash("symbol:"); +let defaultHash_fn = valueHash("fn:"); +let defaultHash_ref = valueHash("ref:"); +let defaultHash_tuple = valueHash("tuple:"); +let defaultHash_set = valueHash("set:"); +let defaultHash_list = valueHash("list:"); +let defaultHash_map = valueHash("map:"); + +let fnHashCounter = 0; + +let hashFunction = (x: CrDataValue): Hash => { + if (x == null) { + return defaultHash_nil; + } + if (typeof x === "number") { + return mergeValueHash(defaultHash_number, x); + } + if (typeof x === "string") { + return mergeValueHash(defaultHash_string, x); + } + // dirty solution of caching, trying to reduce cost + if ((x as any).cachedHash != null) { + return (x as any).cachedHash; + } + if (x instanceof CrDataKeyword) { + let h = mergeValueHash(defaultHash_keyword, x.value); + x.cachedHash = h; + return h; + } + if (x === true) { + return defaultHash_true; + } + if (x === false) { + return defaultHash_false; + } + if (x instanceof CrDataSymbol) { + let h = mergeValueHash(defaultHash_symbol, x.value); + x.cachedHash = h; + return h; + } + if (typeof x === "function") { + fnHashCounter = fnHashCounter + 1; + let h = mergeValueHash(defaultHash_fn, fnHashCounter); + (x as any).cachedHash = h; + return h; + } + if (x instanceof CrDataRef) { + let h = mergeValueHash(defaultHash_ref, x.path); + x.cachedHash = h; + return h; + } + if (x instanceof CrDataTuple) { + let base = defaultHash_tuple; + base = mergeValueHash(base, hashFunction(x.fst)); + base = mergeValueHash(base, hashFunction(x.snd)); + x.cachedHash = base; + return base; + } + if (x instanceof CrDataSet) { + // TODO not using dirty solution for code + let base = defaultHash_set; + for (let item of x.value) { + base = mergeValueHash(base, hashFunction(item)); + } + return base; + } + if (x instanceof CrDataList) { + let base = defaultHash_list; + for (let item of x.items()) { + base = mergeValueHash(base, hashFunction(item)); + } + x.cachedHash = base; + return base; + } + if (x instanceof CrDataMap) { + let base = defaultHash_map; + for (let [k, v] of x.pairs()) { + base = mergeValueHash(base, hashFunction(k)); + base = mergeValueHash(base, hashFunction(v)); + } + x.cachedHash = base; + return base; + } + throw new Error("Unknown data for hashing"); +}; + +// Dirty code to change ternary-tree behavior +overwriteHashGenerator(hashFunction); + +export let toString = (x: CrDataValue, escaped: boolean): string => { + if (x == null) { + return "nil"; + } + if (typeof x === "string") { + if (escaped) { + // turn to visual string representation + if (/[\)\(\s\"]/.test(x)) { + return JSON.stringify("|" + x); + } else { + return "|" + x; + } + } else { + return x; + } + } + if (typeof x === "number") { + return x.toString(); + } + if (typeof x === "boolean") { + return x.toString(); + } + if (typeof x === "function") { + return `(&fn ...)`; + } + if (x instanceof CrDataSymbol) { + return x.toString(); + } + if (x instanceof CrDataKeyword) { + return x.toString(); + } + if (x instanceof CrDataList) { + return x.toString(); + } + if (x instanceof CrDataMap) { + return x.toString(); + } + if (x instanceof CrDataSet) { + return x.toString(); + } + if (x instanceof CrDataRecord) { + return x.toString(); + } + if (x instanceof CrDataRef) { + return x.toString(); + } + if (x instanceof CrDataTuple) { + return x.toString(); + } + + console.warn("Unknown structure to string, better use `console.log`", x); + return `${x}`; +}; + +export let to_js_data = (x: CrDataValue, addColon: boolean = false): any => { + if (x == null) { + return null; + } + if (x === true || x === false) { + return x; + } + if (typeof x === "string") { + return x; + } + if (typeof x === "number") { + return x; + } + if (typeof x === "function") { + return x; + } + if (x instanceof CrDataKeyword) { + if (addColon) { + return `:${x.value}`; + } + return x.value; + } + if (x instanceof CrDataSymbol) { + if (addColon) { + return `:${x.value}`; + } + return Symbol(x.value); + } + if (x instanceof CrDataList) { + var result: any[] = []; + for (let item of x.items()) { + result.push(to_js_data(item, addColon)); + } + return result; + } + if (x instanceof CrDataMap) { + let result: Record = {}; + for (let [k, v] of x.pairs()) { + var key = to_js_data(k, addColon); + result[key] = to_js_data(v, addColon); + } + return result; + } + if (x instanceof CrDataSet) { + let result = new Set(); + x.value.forEach((v) => { + result.add(to_js_data(v, addColon)); + }); + return result; + } + if (x instanceof CrDataRecord) { + let result: Record = {}; + for (let idx in x.fields) { + result[x.fields[idx]] = to_js_data(x.values[idx]); + } + return result; + } + if (x instanceof CrDataRef) { + throw new Error("Cannot convert ref to plain data"); + } + if (x instanceof CrDataRecur) { + throw new Error("Cannot convert recur to plain data"); + } + + return x; +}; + +export let _AND_map_COL_get = function (xs: CrDataValue, k: CrDataValue) { + if (arguments.length !== 2) { + throw new Error("map &get takes 2 arguments"); + } + + if (xs instanceof CrDataMap) return xs.get(k); + + throw new Error("Does not support `&get` on this type"); +}; + +export let _AND__EQ_ = (x: CrDataValue, y: CrDataValue): boolean => { + if (x === y) { + return true; + } + if (x == null) { + if (y == null) { + return true; + } + return false; + } + + let tx = typeof x; + let ty = typeof y; + + if (tx !== ty) { + return false; + } + + if (tx === "string") { + return (x as string) === (y as string); + } + if (tx === "boolean") { + return (x as boolean) === (y as boolean); + } + if (tx === "number") { + return x === y; + } + if (tx === "function") { + // comparing functions by reference + return x === y; + } + if (x instanceof CrDataKeyword) { + if (y instanceof CrDataKeyword) { + return x === y; + } + return false; + } + if (x instanceof CrDataSymbol) { + if (y instanceof CrDataSymbol) { + return x.value === y.value; + } + return false; + } + if (x instanceof CrDataList) { + if (y instanceof CrDataList) { + if (x.len() !== y.len()) { + return false; + } + let size = x.len(); + for (let idx = 0; idx < size; idx++) { + let xItem = x.get(idx); + let yItem = y.get(idx); + if (!_AND__EQ_(xItem, yItem)) { + return false; + } + } + return true; + } + return false; + } + if (x instanceof CrDataMap) { + if (y instanceof CrDataMap) { + if (x.len() !== y.len()) { + return false; + } + for (let [k, v] of x.pairs()) { + if (!y.contains(k)) { + return false; + } + if (!_AND__EQ_(v, _AND_map_COL_get(y, k))) { + return false; + } + } + return true; + } + return false; + } + if (x instanceof CrDataRef) { + if (y instanceof CrDataRef) { + return x.path === y.path; + } + return false; + } + if (x instanceof CrDataTuple) { + if (y instanceof CrDataTuple) { + return _AND__EQ_(x.fst, y.fst) && _AND__EQ_(x.snd, y.snd); + } + return false; + } + if (x instanceof CrDataSet) { + if (y instanceof CrDataSet) { + if (x.len() !== y.len()) { + return false; + } + for (let v of x.value) { + let found = false; + // testing by doing iteration is O(n2), could be slow + // but Set::contains does not satisfy here + for (let yv of y.value) { + if (_AND__EQ_(v, yv)) { + found = true; + break; + } + } + if (found) { + continue; + } else { + return false; + } + } + return true; + } + return false; + } + if (x instanceof CrDataRecur) { + if (y instanceof CrDataRecur) { + console.warn("Do not compare Recur"); + return false; + } + return false; + } + if (x instanceof CrDataRecord) { + if (y instanceof CrDataRecord) { + if (x.name !== y.name) { + return false; + } + if (!fieldsEqual(x.fields, y.fields)) { + return false; + } + if (x.values.length !== y.values.length) { + return false; + } + for (let idx in x.fields) { + if (!_AND__EQ_(x.values[idx], y.values[idx])) { + return false; + } + } + return true; + } + return false; + } + throw new Error("Missing handler for this type"); +}; + +// overwrite internary comparator of ternary-tree +overwriteComparator(_AND__EQ_); +overwriteDataComparator(_AND__EQ_); diff --git a/lib/calcit.procs.ts b/ts-src/calcit.procs.ts similarity index 70% rename from lib/calcit.procs.ts rename to ts-src/calcit.procs.ts index cfe4db10..f72399f6 100644 --- a/lib/calcit.procs.ts +++ b/ts-src/calcit.procs.ts @@ -1,35 +1,41 @@ +// CALCIT VERSION +export const calcit_version = "0.3.35"; + import { overwriteComparator, initTernaryTreeMap } from "@calcit/ternary-tree"; import { parse } from "@cirru/parser.ts"; -import { CirruWriterNode, writeCirruCode } from "@cirru/writer.ts"; +import { CrDataValue } from "./js-primes"; import { CrDataSymbol, - CrDataValue, CrDataKeyword, - CrDataList, - CrDataMap, CrDataRef, CrDataFn, CrDataRecur, kwd, refsRegistry, toString, - CrDataSet, - cloneSet, - CrDataRecord, getStringName, - findInFields, - CrDataTuple, - overwriteDataComparator, + to_js_data, + _AND__EQ_, } from "./calcit-data"; -import { fieldsEqual } from "./record-procs"; +import { fieldsEqual, CrDataRecord } from "./js-record"; export * from "./calcit-data"; -export * from "./record-procs"; +export * from "./js-record"; +export * from "./js-map"; +export * from "./js-list"; +export * from "./js-set"; +export * from "./js-primes"; +export * from "./js-tuple"; export * from "./custom-formatter"; +export * from "./js-cirru"; -export const calcit_version = "0.3.34"; +import { CrDataList, foldl } from "./js-list"; +import { CrDataMap } from "./js-map"; +import { CrDataSet } from "./js-set"; +import { CrDataTuple } from "./js-tuple"; +import { to_calcit_data, extract_cirru_edn } from "./js-cirru"; let inNodeJs = typeof process !== "undefined" && process?.release?.name === "node"; @@ -162,108 +168,6 @@ export let deref = (x: CrDataRef): CrDataValue => { return a.value; }; -export let foldl = function (xs: CrDataValue, acc: CrDataValue, f: CrDataFn): CrDataValue { - if (arguments.length !== 3) { - throw new Error("foldl takes 3 arguments"); - } - - if (f == null) { - debugger; - throw new Error("Expected function for folding"); - } - if (xs instanceof CrDataList) { - var result = acc; - for (let idx = 0; idx < xs.len(); idx++) { - let item = xs.get(idx); - result = f(result, item); - } - return result; - } - if (xs instanceof CrDataSet) { - let result = acc; - xs.value.forEach((item) => { - result = f(result, item); - }); - return result; - } - if (xs instanceof CrDataMap) { - let result = acc; - xs.pairs().forEach(([k, item]) => { - result = f(result, new CrDataList([k, item])); - }); - return result; - } - throw new Error("Unknow data for foldl"); -}; - -export let foldl_shortcut = function (xs: CrDataValue, acc: CrDataValue, v0: CrDataValue, f: CrDataFn): CrDataValue { - if (arguments.length !== 4) { - throw new Error("foldl-shortcut takes 4 arguments"); - } - - if (f == null) { - debugger; - throw new Error("Expected function for folding"); - } - if (xs instanceof CrDataList) { - var state = acc; - for (let idx = 0; idx < xs.len(); idx++) { - let item = xs.get(idx); - let pair = f(state, item); - if (pair instanceof CrDataList && pair.len() == 2) { - if (typeof pair.get(0) == "boolean") { - if (pair.get(0)) { - return pair.get(1); - } else { - state = pair.get(1); - } - } - } else { - throw new Error("Expected return value in `[bool, acc]` structure"); - } - } - return v0; - } - if (xs instanceof CrDataSet) { - let state = acc; - for (let item of xs.values()) { - let pair = f(state, item); - if (pair instanceof CrDataList && pair.len() == 2) { - if (typeof pair.get(0) == "boolean") { - if (pair.get(0)) { - return pair.get(1); - } else { - state = pair.get(1); - } - } - } else { - throw new Error("Expected return value in `[bool, acc]` structure"); - } - } - return v0; - } - - if (xs instanceof CrDataMap) { - let state = acc; - for (let item of xs.pairs()) { - let pair = f(state, new CrDataList(item)); - if (pair instanceof CrDataList && pair.len() == 2) { - if (typeof pair.get(0) == "boolean") { - if (pair.get(0)) { - return pair.get(1); - } else { - state = pair.get(1); - } - } - } else { - throw new Error("Expected return value in `[bool, acc]` structure"); - } - } - return v0; - } - throw new Error("Unknow data for foldl-shortcut"); -}; - export let _AND__ADD_ = (x: number, y: number): number => { return x + y; }; @@ -272,154 +176,6 @@ export let _AND__STAR_ = (x: number, y: number): number => { return x * y; }; -export let _AND__EQ_ = (x: CrDataValue, y: CrDataValue): boolean => { - if (x === y) { - return true; - } - if (x == null) { - if (y == null) { - return true; - } - return false; - } - - let tx = typeof x; - let ty = typeof y; - - if (tx !== ty) { - return false; - } - - if (tx === "string") { - return (x as string) === (y as string); - } - if (tx === "boolean") { - return (x as boolean) === (y as boolean); - } - if (tx === "number") { - return x === y; - } - if (tx === "function") { - // comparing functions by reference - return x === y; - } - if (x instanceof CrDataKeyword) { - if (y instanceof CrDataKeyword) { - return x === y; - } - return false; - } - if (x instanceof CrDataSymbol) { - if (y instanceof CrDataSymbol) { - return x.value === y.value; - } - return false; - } - if (x instanceof CrDataList) { - if (y instanceof CrDataList) { - if (x.len() !== y.len()) { - return false; - } - let size = x.len(); - for (let idx = 0; idx < size; idx++) { - let xItem = x.get(idx); - let yItem = y.get(idx); - if (!_AND__EQ_(xItem, yItem)) { - return false; - } - } - return true; - } - return false; - } - if (x instanceof CrDataMap) { - if (y instanceof CrDataMap) { - if (x.len() !== y.len()) { - return false; - } - for (let [k, v] of x.pairs()) { - if (!y.contains(k)) { - return false; - } - if (!_AND__EQ_(v, _AND_map_COL_get(y, k))) { - return false; - } - } - return true; - } - return false; - } - if (x instanceof CrDataRef) { - if (y instanceof CrDataRef) { - return x.path === y.path; - } - return false; - } - if (x instanceof CrDataTuple) { - if (y instanceof CrDataTuple) { - return _AND__EQ_(x.fst, y.fst) && _AND__EQ_(x.snd, y.snd); - } - return false; - } - if (x instanceof CrDataSet) { - if (y instanceof CrDataSet) { - if (x.len() !== y.len()) { - return false; - } - for (let v of x.value) { - let found = false; - // testing by doing iteration is O(n2), could be slow - // but Set::contains does not satisfy here - for (let yv of y.value) { - if (_AND__EQ_(v, yv)) { - found = true; - break; - } - } - if (found) { - continue; - } else { - return false; - } - } - return true; - } - return false; - } - if (x instanceof CrDataRecur) { - if (y instanceof CrDataRecur) { - console.warn("Do not compare Recur"); - return false; - } - return false; - } - if (x instanceof CrDataRecord) { - if (y instanceof CrDataRecord) { - if (x.name !== y.name) { - return false; - } - if (!fieldsEqual(x.fields, y.fields)) { - return false; - } - if (x.values.length !== y.values.length) { - return false; - } - for (let idx in x.fields) { - if (!_AND__EQ_(x.values[idx], y.values[idx])) { - return false; - } - } - return true; - } - return false; - } - throw new Error("Missing handler for this type"); -}; - -// overwrite internary comparator of ternary-tree -overwriteComparator(_AND__EQ_); -overwriteDataComparator(_AND__EQ_); - export let _AND_str = (x: CrDataValue): string => { return `${x}`; }; @@ -553,16 +309,6 @@ export let _AND_record_COL_nth = function (xs: CrDataValue, k: CrDataValue) { throw new Error("Does not support `nth` on this type"); }; -export let _AND_map_COL_get = function (xs: CrDataValue, k: CrDataValue) { - if (arguments.length !== 2) { - throw new Error("map &get takes 2 arguments"); - } - - if (xs instanceof CrDataMap) return xs.get(k); - - throw new Error("Does not support `&get` on this type"); -}; - export let _AND_record_COL_get = function (xs: CrDataValue, k: CrDataValue) { if (arguments.length !== 2) { throw new Error("record &get takes 2 arguments"); @@ -1222,119 +968,6 @@ export let re_find_all = (content: string, re: string): CrDataList => { } }; -export let to_js_data = (x: CrDataValue, addColon: boolean = false): any => { - if (x == null) { - return null; - } - if (x === true || x === false) { - return x; - } - if (typeof x === "string") { - return x; - } - if (typeof x === "number") { - return x; - } - if (typeof x === "function") { - return x; - } - if (x instanceof CrDataKeyword) { - if (addColon) { - return `:${x.value}`; - } - return x.value; - } - if (x instanceof CrDataSymbol) { - if (addColon) { - return `:${x.value}`; - } - return Symbol(x.value); - } - if (x instanceof CrDataList) { - var result: any[] = []; - for (let item of x.items()) { - result.push(to_js_data(item, addColon)); - } - return result; - } - if (x instanceof CrDataMap) { - let result: Record = {}; - for (let [k, v] of x.pairs()) { - var key = to_js_data(k, addColon); - result[key] = to_js_data(v, addColon); - } - return result; - } - if (x instanceof CrDataSet) { - let result = new Set(); - x.value.forEach((v) => { - result.add(to_js_data(v, addColon)); - }); - return result; - } - if (x instanceof CrDataRecord) { - let result: Record = {}; - for (let idx in x.fields) { - result[x.fields[idx]] = to_js_data(x.values[idx]); - } - return result; - } - if (x instanceof CrDataRef) { - throw new Error("Cannot convert ref to plain data"); - } - if (x instanceof CrDataRecur) { - throw new Error("Cannot convert recur to plain data"); - } - - return x; -}; - -export let to_calcit_data = (x: any, noKeyword: boolean = false): CrDataValue => { - if (x == null) { - return null; - } - if (typeof x === "number") { - return x; - } - if (typeof x === "string") { - if (!noKeyword && x[0] === ":" && x.slice(1).match(/^[\w\d_\?\!\-]+$/)) { - return kwd(x.slice(1)); - } - return x; - } - if (x === true || x === false) { - return x; - } - if (typeof x === "function") { - return x; - } - if (Array.isArray(x)) { - var result: any[] = []; - x.forEach((v) => { - result.push(to_calcit_data(v, noKeyword)); - }); - return new CrDataList(result); - } - if (x instanceof Set) { - let result: Set = new Set(); - x.forEach((v) => { - result.add(to_calcit_data(v, noKeyword)); - }); - return new CrDataSet(result); - } - // detects object - if (x === Object(x)) { - let result: Array<[CrDataValue, CrDataValue]> = []; - Object.keys(x).forEach((k) => { - result.push([to_calcit_data(k, noKeyword), to_calcit_data(x[k], noKeyword)]); - }); - return new CrDataMap(initTernaryTreeMap(result)); - } - - console.error(x); - throw new Error("Unexpected data for converting"); -}; - export let parse_json = (x: string): CrDataValue => { return to_calcit_data(JSON.parse(x), false); }; @@ -1458,151 +1091,6 @@ export let ends_with_QUES_ = (xs: string, y: string): boolean => { return xs.endsWith(y); }; -type CirruEdnFormat = string | CirruEdnFormat[]; - -/** better use string version of Cirru EDN in future */ -export let to_cirru_edn = (x: CrDataValue): CirruEdnFormat => { - if (x == null) { - return "nil"; - } - if (typeof x === "string") { - return `|${x}`; - } - if (typeof x === "number") { - return x.toString(); - } - if (typeof x === "boolean") { - return x.toString(); - } - if (x instanceof CrDataKeyword) { - return x.toString(); - } - if (x instanceof CrDataSymbol) { - return x.toString(); - } - if (x instanceof CrDataList) { - // TODO can be faster - return (["[]"] as CirruEdnFormat[]).concat(x.toArray().map(to_cirru_edn)); - } - if (x instanceof CrDataMap) { - let buffer: CirruEdnFormat = ["{}"]; - for (let [k, v] of x.pairs()) { - buffer.push([to_cirru_edn(k), to_cirru_edn(v)]); - } - return buffer; - } - if (x instanceof CrDataRecord) { - let result: Record = {}; - let buffer: CirruEdnFormat = ["%{}", x.name]; - for (let idx in x.fields) { - buffer.push([x.fields[idx], to_cirru_edn(x.values[idx])]); - } - return buffer; - } - if (x instanceof CrDataSet) { - let buffer: CirruEdnFormat = ["#{}"]; - for (let y of x.value) { - buffer.push(to_cirru_edn(y)); - } - return buffer; - } - console.error(x); - throw new Error("Unexpected data to to-cirru-edn"); -}; - -export let extract_cirru_edn = (x: CirruEdnFormat): CrDataValue => { - if (typeof x === "string") { - if (x === "nil") { - return null; - } - if (x === "true") { - return true; - } - if (x === "false") { - return false; - } - if (x == "") { - throw new Error("cannot be empty"); - } - if (x[0] === "|" || x[0] === '"') { - return x.slice(1); - } - if (x[0] === ":") { - return kwd(x.substr(1)); - } - if (x[0] === "'") { - return new CrDataSymbol(x.substr(1)); - } - if (x.match(/^(-?)\d+(\.\d*$)?/)) { - return parseFloat(x); - } - // allow things cannot be parsed accepted as raw strings - // turned on since Cirru nodes passed from macros uses this - return x; - } - if (x instanceof Array) { - if (x.length === 0) { - throw new Error("Cannot be empty"); - } - if (x[0] === "{}") { - let result: Array<[CrDataValue, CrDataValue]> = []; - x.forEach((pair, idx) => { - if (idx == 0) { - return; // skip first `{}` symbol - } - if (pair instanceof Array && pair.length == 2) { - result.push([extract_cirru_edn(pair[0]), extract_cirru_edn(pair[1])]); - } else { - throw new Error("Expected pairs for map"); - } - }); - return new CrDataMap(initTernaryTreeMap(result)); - } - if (x[0] === "%{}") { - let name = x[1]; - if (typeof name != "string") { - throw new Error("Expected string for record name"); - } - let fields: Array = []; - let values: Array = []; - x.forEach((pair, idx) => { - if (idx <= 1) { - return; // skip %{} name - } - - if (pair instanceof Array && pair.length == 2) { - if (typeof pair[0] === "string") { - fields.push(pair[0]); - } else { - throw new Error("Expected string as field"); - } - values.push(extract_cirru_edn(pair[1])); - } else { - throw new Error("Expected pairs for map"); - } - }); - return new CrDataRecord(name, fields, values); - } - if (x[0] === "[]") { - return new CrDataList(x.slice(1).map(extract_cirru_edn)); - } - if (x[0] === "#{}") { - return new CrDataSet(new Set(x.slice(1).map(extract_cirru_edn))); - } - if (x[0] === "do" && x.length === 2) { - return extract_cirru_edn(x[1]); - } - if (x[0] === "quote") { - if (x.length !== 2) { - throw new Error("quote expects 1 argument"); - } - return to_calcit_data(x[1], true); - } - } - console.error(x); - throw new Error("Unexpected data from cirru-edn"); -}; - export let blank_QUES_ = (x: string): boolean => { if (x == null) { return true; @@ -1705,52 +1193,6 @@ export let parse_cirru_edn = (code: string) => { return extract_cirru_edn(parse(code)[0]); }; -let toWriterNode = (xs: CrDataList): CirruWriterNode => { - if (typeof xs === "string") { - return xs; - } - if (xs instanceof CrDataList) { - return xs.toArray().map(toWriterNode); - } else { - throw new Error("Unexpected type for CirruWriteNode"); - } -}; - -export let write_cirru = (data: CrDataList, useInline: boolean): string => { - let chunk = toWriterNode(data); - if (!Array.isArray(chunk)) { - throw new Error("Expected data of list"); - } - for (let item of chunk) { - if (!Array.isArray(item)) { - throw new Error("Expected data in a list of lists"); - } - } - return writeCirruCode(chunk, { useInline }); -}; - -export let write_cirru_edn = (data: CrDataValue, useInline: boolean = true): string => { - if (data == null) { - return "\ndo nil" + "\n"; - } - if (typeof data === "string") { - return "\ndo " + to_cirru_edn(data) + "\n"; - } - if (typeof data == "boolean") { - return "\ndo " + to_cirru_edn(data) + "\n"; - } - if (typeof data == "string") { - return "\ndo " + to_cirru_edn(data) + "\n"; - } - if (data instanceof CrDataSymbol) { - return "\ndo " + to_cirru_edn(data) + "\n"; - } - if (data instanceof CrDataKeyword) { - return "\ndo " + to_cirru_edn(data) + "\n"; - } - return writeCirruCode([to_cirru_edn(data)], { useInline: useInline }); -}; - /** return in seconds, like from Nim */ export let now_BANG_ = () => { return Date.now() / 1000; diff --git a/lib/custom-formatter.ts b/ts-src/custom-formatter.ts similarity index 85% rename from lib/custom-formatter.ts rename to ts-src/custom-formatter.ts index 52477839..e2e3ca1d 100644 --- a/lib/custom-formatter.ts +++ b/ts-src/custom-formatter.ts @@ -1,6 +1,13 @@ -import { CrDataRef, CrDataValue, CrDataSymbol, CrDataKeyword, CrDataList, CrDataMap, CrDataRecord, CrDataSet } from "./calcit-data"; +import { CrDataValue } from "./js-primes"; +import { CrDataRef, CrDataSymbol, CrDataKeyword } from "./calcit-data"; import { toPairs } from "@calcit/ternary-tree"; +import { CrDataRecord } from "./js-record"; +import { CrDataMap } from "./js-map"; +import { CrDataList } from "./js-list"; +import { CrDataSet } from "./js-set"; +import { CrDataTuple } from "./js-tuple"; + declare global { interface Window { devtoolsFormatters: { @@ -102,6 +109,12 @@ export let load_console_formatter_BANG_ = () => { } return ret; } + if (obj instanceof CrDataTuple) { + let ret: any[] = ["div", { style: "color: hsl(200, 90%, 60%)" }]; + ret.push(["div", { style: "margin-left: 8px; display: inline-block;" }, embedObject(obj.fst)]); + ret.push(["div", { style: "margin-left: 8px; display: inline-block;" }, embedObject(obj.snd)]); + return ret; + } if (obj instanceof CrDataMap) { let ret: any[] = ["div", { style: "color: hsl(280, 80%, 60%)" }]; obj.turnMap(); diff --git a/ts-src/js-cirru.ts b/ts-src/js-cirru.ts new file mode 100644 index 00000000..5349de5a --- /dev/null +++ b/ts-src/js-cirru.ts @@ -0,0 +1,246 @@ +import { overwriteComparator, initTernaryTreeMap } from "@calcit/ternary-tree"; +import { CirruWriterNode, writeCirruCode } from "@cirru/writer.ts"; + +import { CrDataValue } from "./js-primes"; +import { CrDataList } from "./js-list"; +import { CrDataRecord } from "./js-record"; +import { CrDataMap } from "./js-map"; +import { CrDataSet } from "./js-set"; +import { CrDataKeyword, CrDataSymbol, kwd } from "./calcit-data"; + +type CirruEdnFormat = string | CirruEdnFormat[]; + +export let write_cirru = (data: CrDataList, useInline: boolean): string => { + let chunk = toWriterNode(data); + if (!Array.isArray(chunk)) { + throw new Error("Expected data of list"); + } + for (let item of chunk) { + if (!Array.isArray(item)) { + throw new Error("Expected data in a list of lists"); + } + } + return writeCirruCode(chunk, { useInline }); +}; + +/** better use string version of Cirru EDN in future */ +export let to_cirru_edn = (x: CrDataValue): CirruEdnFormat => { + if (x == null) { + return "nil"; + } + if (typeof x === "string") { + return `|${x}`; + } + if (typeof x === "number") { + return x.toString(); + } + if (typeof x === "boolean") { + return x.toString(); + } + if (x instanceof CrDataKeyword) { + return x.toString(); + } + if (x instanceof CrDataSymbol) { + return x.toString(); + } + if (x instanceof CrDataList) { + // TODO can be faster + return (["[]"] as CirruEdnFormat[]).concat(x.toArray().map(to_cirru_edn)); + } + if (x instanceof CrDataMap) { + let buffer: CirruEdnFormat = ["{}"]; + for (let [k, v] of x.pairs()) { + buffer.push([to_cirru_edn(k), to_cirru_edn(v)]); + } + return buffer; + } + if (x instanceof CrDataRecord) { + let result: Record = {}; + let buffer: CirruEdnFormat = ["%{}", x.name]; + for (let idx in x.fields) { + buffer.push([x.fields[idx], to_cirru_edn(x.values[idx])]); + } + return buffer; + } + if (x instanceof CrDataSet) { + let buffer: CirruEdnFormat = ["#{}"]; + for (let y of x.value) { + buffer.push(to_cirru_edn(y)); + } + return buffer; + } + console.error(x); + throw new Error("Unexpected data to to-cirru-edn"); +}; + +export let extract_cirru_edn = (x: CirruEdnFormat): CrDataValue => { + if (typeof x === "string") { + if (x === "nil") { + return null; + } + if (x === "true") { + return true; + } + if (x === "false") { + return false; + } + if (x == "") { + throw new Error("cannot be empty"); + } + if (x[0] === "|" || x[0] === '"') { + return x.slice(1); + } + if (x[0] === ":") { + return kwd(x.substr(1)); + } + if (x[0] === "'") { + return new CrDataSymbol(x.substr(1)); + } + if (x.match(/^(-?)\d+(\.\d*$)?/)) { + return parseFloat(x); + } + // allow things cannot be parsed accepted as raw strings + // turned on since Cirru nodes passed from macros uses this + return x; + } + if (x instanceof Array) { + if (x.length === 0) { + throw new Error("Cannot be empty"); + } + if (x[0] === "{}") { + let result: Array<[CrDataValue, CrDataValue]> = []; + x.forEach((pair, idx) => { + if (idx == 0) { + return; // skip first `{}` symbol + } + if (pair instanceof Array && pair.length == 2) { + result.push([extract_cirru_edn(pair[0]), extract_cirru_edn(pair[1])]); + } else { + throw new Error("Expected pairs for map"); + } + }); + return new CrDataMap(initTernaryTreeMap(result)); + } + if (x[0] === "%{}") { + let name = x[1]; + if (typeof name != "string") { + throw new Error("Expected string for record name"); + } + let fields: Array = []; + let values: Array = []; + x.forEach((pair, idx) => { + if (idx <= 1) { + return; // skip %{} name + } + + if (pair instanceof Array && pair.length == 2) { + if (typeof pair[0] === "string") { + fields.push(pair[0]); + } else { + throw new Error("Expected string as field"); + } + values.push(extract_cirru_edn(pair[1])); + } else { + throw new Error("Expected pairs for map"); + } + }); + return new CrDataRecord(name, fields, values); + } + if (x[0] === "[]") { + return new CrDataList(x.slice(1).map(extract_cirru_edn)); + } + if (x[0] === "#{}") { + return new CrDataSet(new Set(x.slice(1).map(extract_cirru_edn))); + } + if (x[0] === "do" && x.length === 2) { + return extract_cirru_edn(x[1]); + } + if (x[0] === "quote") { + if (x.length !== 2) { + throw new Error("quote expects 1 argument"); + } + return to_calcit_data(x[1], true); + } + } + console.error(x); + throw new Error("Unexpected data from cirru-edn"); +}; + +export let write_cirru_edn = (data: CrDataValue, useInline: boolean = true): string => { + if (data == null) { + return "\ndo nil" + "\n"; + } + if (typeof data === "string") { + return "\ndo " + to_cirru_edn(data) + "\n"; + } + if (typeof data == "boolean") { + return "\ndo " + to_cirru_edn(data) + "\n"; + } + if (typeof data == "string") { + return "\ndo " + to_cirru_edn(data) + "\n"; + } + if (data instanceof CrDataSymbol) { + return "\ndo " + to_cirru_edn(data) + "\n"; + } + if (data instanceof CrDataKeyword) { + return "\ndo " + to_cirru_edn(data) + "\n"; + } + return writeCirruCode([to_cirru_edn(data)], { useInline: useInline }); +}; + +export let to_calcit_data = (x: any, noKeyword: boolean = false): CrDataValue => { + if (x == null) { + return null; + } + if (typeof x === "number") { + return x; + } + if (typeof x === "string") { + if (!noKeyword && x[0] === ":" && x.slice(1).match(/^[\w\d_\?\!\-]+$/)) { + return kwd(x.slice(1)); + } + return x; + } + if (x === true || x === false) { + return x; + } + if (typeof x === "function") { + return x; + } + if (Array.isArray(x)) { + var result: any[] = []; + x.forEach((v) => { + result.push(to_calcit_data(v, noKeyword)); + }); + return new CrDataList(result); + } + if (x instanceof Set) { + let result: Set = new Set(); + x.forEach((v) => { + result.add(to_calcit_data(v, noKeyword)); + }); + return new CrDataSet(result); + } + // detects object + if (x === Object(x)) { + let result: Array<[CrDataValue, CrDataValue]> = []; + Object.keys(x).forEach((k) => { + result.push([to_calcit_data(k, noKeyword), to_calcit_data(x[k], noKeyword)]); + }); + return new CrDataMap(initTernaryTreeMap(result)); + } + + console.error(x); + throw new Error("Unexpected data for converting"); +}; + +let toWriterNode = (xs: CrDataList): CirruWriterNode => { + if (typeof xs === "string") { + return xs; + } + if (xs instanceof CrDataList) { + return xs.toArray().map(toWriterNode); + } else { + throw new Error("Unexpected type for CirruWriteNode"); + } +}; diff --git a/ts-src/js-list.ts b/ts-src/js-list.ts new file mode 100644 index 00000000..c8dc8bb1 --- /dev/null +++ b/ts-src/js-list.ts @@ -0,0 +1,312 @@ +import * as ternaryTree from "@calcit/ternary-tree"; + +import { CrDataValue } from "./js-primes"; + +import { + TernaryTreeList, + initTernaryTreeList, + listLen, + listGet, + assocList, + listToItems, + dissocList, + Hash, + assocBefore, + assocAfter, +} from "@calcit/ternary-tree"; + +import { CrDataMap } from "./js-map"; +import { CrDataSet } from "./js-set"; +import { CrDataFn } from "./calcit.procs"; + +import { isNestedCrData, tipNestedCrData, toString } from "./calcit-data"; + +export class CrDataList { + value: TernaryTreeList; + // array mode store bare array for performance + arrayValue: Array; + arrayMode: boolean; + arrayStart: number; + arrayEnd: number; + cachedHash: Hash; + constructor(value: Array | TernaryTreeList) { + if (value == null) { + value = []; // dirty, better handled from outside + } + if (Array.isArray(value)) { + this.arrayMode = true; + this.arrayValue = value; + this.arrayStart = 0; + this.arrayEnd = value.length; + this.value = null; + } else { + this.arrayMode = false; + this.value = value; + this.arrayValue = []; + this.arrayStart = null; + this.arrayEnd = null; + } + } + turnListMode() { + if (this.arrayMode) { + this.value = initTernaryTreeList(this.arrayValue.slice(this.arrayStart, this.arrayEnd)); + this.arrayValue = null; + this.arrayStart = null; + this.arrayEnd = null; + this.arrayMode = false; + } + } + len() { + if (this.arrayMode) { + return this.arrayEnd - this.arrayStart; + } else { + return listLen(this.value); + } + } + get(idx: number) { + if (this.arrayMode) { + return this.arrayValue[this.arrayStart + idx]; + } else { + return listGet(this.value, idx); + } + } + assoc(idx: number, v: CrDataValue) { + this.turnListMode(); + return new CrDataList(assocList(this.value, idx, v)); + } + assocBefore(idx: number, v: CrDataValue) { + this.turnListMode(); + return new CrDataList(assocBefore(this.value, idx, v)); + } + assocAfter(idx: number, v: CrDataValue) { + this.turnListMode(); + return new CrDataList(assocAfter(this.value, idx, v)); + } + dissoc(idx: number) { + this.turnListMode(); + return new CrDataList(dissocList(this.value, idx)); + } + slice(from: number, to: number) { + if (this.arrayMode) { + if (from < 0) { + throw new Error(`from index too small: ${from}`); + } + if (to > this.len()) { + throw new Error(`end index too large: ${to}`); + } + if (to < from) { + throw new Error("end index too small"); + } + let result = new CrDataList(this.arrayValue); + result.arrayStart = this.arrayStart + from; + result.arrayEnd = this.arrayStart + to; + return result; + } else { + return new CrDataList(ternaryTree.slice(this.value, from, to)); + } + } + toString(shorter = false): string { + let result = ""; + for (let item of this.items()) { + if (shorter && isNestedCrData(item)) { + result = `${result} ${tipNestedCrData(item)}`; + } else { + result = `${result} ${toString(item, true)}`; + } + } + return `([]${result})`; + } + isEmpty() { + return this.len() === 0; + } + /** usage: `for of` */ + items(): Generator { + if (this.arrayMode) { + return sliceGenerator(this.arrayValue, this.arrayStart, this.arrayEnd); + } else { + return listToItems(this.value); + } + } + append(v: CrDataValue) { + if (this.arrayMode && this.arrayEnd === this.arrayValue.length && this.arrayStart < 32) { + // dirty trick to reuse list memory, data storage actually appended at existing array + this.arrayValue.push(v); + let newList = new CrDataList(this.arrayValue); + newList.arrayStart = this.arrayStart; + newList.arrayEnd = this.arrayEnd + 1; + return newList; + } else { + this.turnListMode(); + return new CrDataList(ternaryTree.append(this.value, v)); + } + } + prepend(v: CrDataValue) { + this.turnListMode(); + return new CrDataList(ternaryTree.prepend(this.value, v)); + } + first() { + if (this.arrayMode) { + if (this.arrayValue.length > this.arrayStart) { + return this.arrayValue[this.arrayStart]; + } else { + return null; + } + } else { + return ternaryTree.first(this.value); + } + } + rest() { + if (this.arrayMode) { + return this.slice(1, this.arrayEnd - this.arrayStart); + } else { + return new CrDataList(ternaryTree.rest(this.value)); + } + } + concat(ys: CrDataList) { + if (!(ys instanceof CrDataList)) { + throw new Error("Expected list"); + } + if (this.arrayMode && ys.arrayMode) { + let size = this.arrayEnd - this.arrayStart; + let otherSize = ys.arrayEnd - ys.arrayStart; + let combined = new Array(size + otherSize); + for (let i = 0; i < size; i++) { + combined[i] = this.get(i); + } + for (let i = 0; i < otherSize; i++) { + combined[i + size] = ys.get(i); + } + return new CrDataList(combined); + } else { + this.turnListMode(); + ys.turnListMode(); + return new CrDataList(ternaryTree.concat(this.value, ys.value)); + } + } + map(f: (v: CrDataValue) => CrDataValue): CrDataList { + if (this.arrayMode) { + return new CrDataList(this.arrayValue.slice(this.arrayStart, this.arrayEnd).map(f)); + } else { + return new CrDataList(ternaryTree.listMapValues(this.value, f)); + } + } + toArray(): CrDataValue[] { + if (this.arrayMode) { + return this.arrayValue.slice(this.arrayStart, this.arrayEnd); + } else { + return [...ternaryTree.listToItems(this.value)]; + } + } + reverse() { + this.turnListMode(); + return new CrDataList(ternaryTree.reverse(this.value)); + } +} + +function* sliceGenerator(xs: Array, start: number, end: number): Generator { + for (let idx = start; idx < end; idx++) { + yield xs[idx]; + } +} + +export let foldl = function (xs: CrDataValue, acc: CrDataValue, f: CrDataFn): CrDataValue { + if (arguments.length !== 3) { + throw new Error("foldl takes 3 arguments"); + } + + if (f == null) { + debugger; + throw new Error("Expected function for folding"); + } + if (xs instanceof CrDataList) { + var result = acc; + for (let idx = 0; idx < xs.len(); idx++) { + let item = xs.get(idx); + result = f(result, item); + } + return result; + } + if (xs instanceof CrDataSet) { + let result = acc; + xs.value.forEach((item) => { + result = f(result, item); + }); + return result; + } + if (xs instanceof CrDataMap) { + let result = acc; + xs.pairs().forEach(([k, item]) => { + result = f(result, new CrDataList([k, item])); + }); + return result; + } + throw new Error("Unknow data for foldl"); +}; + +export let foldl_shortcut = function (xs: CrDataValue, acc: CrDataValue, v0: CrDataValue, f: CrDataFn): CrDataValue { + if (arguments.length !== 4) { + throw new Error("foldl-shortcut takes 4 arguments"); + } + + if (f == null) { + debugger; + throw new Error("Expected function for folding"); + } + if (xs instanceof CrDataList) { + var state = acc; + for (let idx = 0; idx < xs.len(); idx++) { + let item = xs.get(idx); + let pair = f(state, item); + if (pair instanceof CrDataList && pair.len() == 2) { + if (typeof pair.get(0) == "boolean") { + if (pair.get(0)) { + return pair.get(1); + } else { + state = pair.get(1); + } + } + } else { + throw new Error("Expected return value in `[bool, acc]` structure"); + } + } + return v0; + } + if (xs instanceof CrDataSet) { + let state = acc; + for (let item of xs.values()) { + let pair = f(state, item); + if (pair instanceof CrDataList && pair.len() == 2) { + if (typeof pair.get(0) == "boolean") { + if (pair.get(0)) { + return pair.get(1); + } else { + state = pair.get(1); + } + } + } else { + throw new Error("Expected return value in `[bool, acc]` structure"); + } + } + return v0; + } + + if (xs instanceof CrDataMap) { + let state = acc; + for (let item of xs.pairs()) { + let pair = f(state, new CrDataList(item)); + if (pair instanceof CrDataList && pair.len() == 2) { + if (typeof pair.get(0) == "boolean") { + if (pair.get(0)) { + return pair.get(1); + } else { + state = pair.get(1); + } + } + } else { + throw new Error("Expected return value in `[bool, acc]` structure"); + } + } + return v0; + } + throw new Error("Unknow data for foldl-shortcut"); +}; diff --git a/ts-src/js-map.ts b/ts-src/js-map.ts new file mode 100644 index 00000000..4a31eb6a --- /dev/null +++ b/ts-src/js-map.ts @@ -0,0 +1,198 @@ +import * as ternaryTree from "@calcit/ternary-tree"; + +import { CrDataValue } from "./js-primes"; + +import { TernaryTreeMap, initTernaryTreeMap, mapLen, assocMap, dissocMap, isMapEmpty, Hash, toPairsArray, mapGetDefault } from "@calcit/ternary-tree"; + +import { isNestedCrData, tipNestedCrData, toString } from "./calcit-data"; + +/** need to compare by Calcit */ +let DATA_EQUAL = (x: CrDataValue, y: CrDataValue): boolean => { + return x == y; +}; + +export let overwriteDataComparator = (f: typeof DATA_EQUAL): void => { + DATA_EQUAL = f; +}; + +export class CrDataMap { + cachedHash: Hash; + /** in arrayMode, only flatten values, not tree structure */ + arrayMode: boolean; + arrayValue: CrDataValue[]; + value: TernaryTreeMap; + skipValue: CrDataValue; + constructor(value: CrDataValue[] | TernaryTreeMap) { + if (value == null) { + this.arrayMode = true; + this.arrayValue = []; + } else if (Array.isArray(value)) { + this.arrayMode = true; + this.arrayValue = value; + } else { + this.arrayMode = false; + this.value = value; + } + } + turnMap() { + if (this.arrayMode) { + var dict: Array<[CrDataValue, CrDataValue]> = []; + let halfLength = this.arrayValue.length >> 1; + for (let idx = 0; idx < halfLength; idx++) { + dict.push([this.arrayValue[idx << 1], this.arrayValue[(idx << 1) + 1]]); + } + this.value = initTernaryTreeMap(dict); + this.arrayMode = false; + this.arrayValue = null; + } + } + len() { + if (this.arrayMode) { + return this.arrayValue.length >> 1; + } else { + return mapLen(this.value); + } + } + get(k: CrDataValue) { + if (this.arrayMode && this.arrayValue.length <= 16) { + let size = this.arrayValue.length >> 1; + for (let i = 0; i < size; i++) { + let pos = i << 1; + if (DATA_EQUAL(this.arrayValue[pos], k)) { + return this.arrayValue[pos + 1]; + } + } + return null; + } else { + this.turnMap(); + return mapGetDefault(this.value, k, null); + } + } + assoc(k: CrDataValue, v: CrDataValue) { + if (this.arrayMode && this.arrayValue.length <= 16) { + let ret = this.arrayValue.slice(0); + for (let i = 0; i < ret.length; i += 2) { + if (DATA_EQUAL(k, ret[i])) { + ret[i + 1] = v; + return new CrDataMap(ret); + } + } + ret.push(k, v); + return new CrDataMap(ret); + } else { + this.turnMap(); + return new CrDataMap(assocMap(this.value, k, v)); + } + } + dissoc(k: CrDataValue) { + if (this.arrayMode && this.arrayValue.length <= 16) { + let ret: CrDataValue[] = []; + for (let i = 0; i < this.arrayValue.length; i += 2) { + if (!DATA_EQUAL(k, this.arrayValue[i])) { + ret.push(this.arrayValue[i], this.arrayValue[i + 1]); + } + } + return new CrDataMap(ret); + } else { + this.turnMap(); + return new CrDataMap(dissocMap(this.value, k)); + } + } + toString(shorter = false) { + let itemsCode = ""; + for (let [k, v] of this.pairs()) { + if (shorter) { + let keyPart = isNestedCrData(k) ? tipNestedCrData(k) : toString(k, true); + let valuePart = isNestedCrData(v) ? tipNestedCrData(v) : toString(v, true); + itemsCode = `${itemsCode} (${keyPart} ${valuePart})`; + } else { + itemsCode = `${itemsCode} (${toString(k, true)} ${toString(v, true)})`; + } + } + return `({}${itemsCode})`; + } + isEmpty() { + if (this.arrayMode) { + return this.arrayValue.length == 0; + } else { + return isMapEmpty(this.value); + } + } + pairs(): Array<[CrDataValue, CrDataValue]> { + if (this.arrayMode) { + let ret: Array<[CrDataValue, CrDataValue]> = []; + let size = this.arrayValue.length >> 1; + for (let i = 0; i < size; i++) { + let pos = i << 1; + ret.push([this.arrayValue[pos], this.arrayValue[pos + 1]]); + } + return ret; + } else { + return toPairsArray(this.value); + } + } + contains(k: CrDataValue) { + if (this.arrayMode && this.arrayValue.length <= 16) { + // guessed number + let size = this.arrayValue.length >> 1; + for (let i = 0; i < size; i++) { + let pos = i << 1; + if (DATA_EQUAL(this.arrayValue[pos], k)) { + return true; + } + } + return false; + } else { + this.turnMap(); + return ternaryTree.contains(this.value, k); + } + } + merge(ys: CrDataMap) { + return this.mergeSkip(ys, null); + } + mergeSkip(ys: CrDataMap, v: CrDataValue) { + if (ys == null) { + return this; + } + + if (!(ys instanceof CrDataMap)) { + console.error("value:", v); + throw new Error("Expected map to merge"); + } + + if (this.arrayMode && ys.arrayMode && this.arrayValue.length + ys.arrayValue.length <= 24) { + // probably this length < 16, ys length < 8 + let ret = this.arrayValue.slice(0); + outer: for (let i = 0; i < ys.arrayValue.length; i = i + 2) { + if (ys.arrayValue[i + 1] == v) { + continue; + } + for (let k = 0; k < ret.length; k = k + 2) { + if (DATA_EQUAL(ys.arrayValue[i], ret[k])) { + ret[k + 1] = ys.arrayValue[i + 1]; + continue outer; + } + } + ret.push(ys.arrayValue[i], ys.arrayValue[i + 1]); + } + return new CrDataMap(ret); + } + + this.turnMap(); + + if (ys.arrayMode) { + let ret = this.value; + let size = ys.arrayValue.length >> 1; + for (let i = 0; i < size; i++) { + let pos = i << 1; + if (ys.arrayValue[pos + 1] == v) { + continue; + } + ret = assocMap(ret, ys.arrayValue[pos], ys.arrayValue[pos + 1]); + } + return new CrDataMap(ret); + } else { + return new CrDataMap(ternaryTree.mergeSkip(this.value, ys.value, v)); + } + } +} diff --git a/ts-src/js-primes.ts b/ts-src/js-primes.ts new file mode 100644 index 00000000..db5a3444 --- /dev/null +++ b/ts-src/js-primes.ts @@ -0,0 +1,22 @@ +import { CrDataKeyword, CrDataSymbol, CrDataRef, CrDataFn, CrDataRecur } from "./calcit-data"; +import { CrDataList } from "./js-list"; +import { CrDataRecord } from "./js-record"; +import { CrDataMap } from "./js-map"; +import { CrDataSet } from "./js-set"; +import { CrDataTuple } from "./js-tuple"; + +export type CrDataValue = + | string + | number + | boolean + | CrDataMap + | CrDataList + | CrDataSet + | CrDataKeyword + | CrDataSymbol + | CrDataRef + | CrDataTuple + | CrDataFn + | CrDataRecur // should not be exposed to function + | CrDataRecord + | null; diff --git a/lib/record-procs.ts b/ts-src/js-record.ts similarity index 71% rename from lib/record-procs.ts rename to ts-src/js-record.ts index 3d607933..1245bb6f 100644 --- a/lib/record-procs.ts +++ b/ts-src/js-record.ts @@ -1,22 +1,63 @@ import { initTernaryTreeMap, valueHash } from "@calcit/ternary-tree"; -import { - CrDataSymbol, - CrDataValue, - CrDataKeyword, - CrDataList, - CrDataMap, - CrDataRef, - CrDataFn, - CrDataRecur, - kwd, - refsRegistry, - toString, - CrDataSet, - cloneSet, - getStringName, - CrDataRecord, - findInFields, -} from "./calcit-data"; +import { CrDataValue } from "./js-primes"; +import { kwd, toString, getStringName, findInFields } from "./calcit-data"; + +import { CrDataMap } from "./js-map"; + +export class CrDataRecord { + name: string; + fields: Array; + values: Array; + constructor(name: string, fields: Array, values?: Array) { + this.name = name; + let fieldNames = fields.map(getStringName); + this.fields = fieldNames; + if (values != null) { + if (values.length != fields.length) { + throw new Error("value length not match"); + } + this.values = values; + } else { + this.values = new Array(fieldNames.length); + } + } + get(k: CrDataValue) { + let field = getStringName(k); + let idx = findInFields(this.fields, field); + if (idx >= 0) { + return this.values[idx]; + } else { + throw new Error(`Cannot find :${field} among (${this.values.join(",")})`); + } + } + assoc(k: CrDataValue, v: CrDataValue): CrDataRecord { + let values: Array = new Array(this.fields.length); + let name = getStringName(k); + for (let idx in this.fields) { + if (this.fields[idx] === name) { + values[idx] = v; + } else { + values[idx] = this.values[idx]; + } + } + return new CrDataRecord(this.name, this.fields, values); + } + merge() { + // TODO + } + contains(k: CrDataValue) { + let field = getStringName(k); + let idx = findInFields(this.fields, field); + return idx >= 0; + } + toString(): string { + let ret = "(%{} " + this.name; + for (let idx in this.fields) { + ret += " (" + this.fields[idx] + " " + toString(this.values[idx], true) + ")"; + } + return ret + ")"; + } +} export let new_record = (name: CrDataValue, ...fields: Array): CrDataValue => { let fieldNames = fields.map(getStringName).sort(); diff --git a/ts-src/js-set.ts b/ts-src/js-set.ts new file mode 100644 index 00000000..5ae99486 --- /dev/null +++ b/ts-src/js-set.ts @@ -0,0 +1,96 @@ +import { CrDataValue } from "./js-primes"; +import { toString } from "./calcit-data"; + +export let cloneSet = (xs: Set): Set => { + if (!(xs instanceof Set)) { + throw new Error("Expected a set"); + } + var result: Set = new Set(); + for (let v of xs) { + result.add(v); + } + return result; +}; + +export class CrDataSet { + value: Set; + constructor(value: Set) { + this.value = value; + } + len() { + return this.value.size; + } + contains(y: CrDataValue) { + return this.value.has(y); + } + include(y: CrDataValue): CrDataSet { + var result = cloneSet(this.value); + result.add(y); + return new CrDataSet(result); + } + exclude(y: CrDataValue): CrDataSet { + var result = cloneSet(this.value); + result.delete(y); + return new CrDataSet(result); + } + + difference(ys: CrDataSet): CrDataSet { + var result = cloneSet(this.value); + ys.value.forEach((y) => { + if (result.has(y)) { + result.delete(y); + } + }); + return new CrDataSet(result); + } + union(ys: CrDataSet): CrDataSet { + var result = cloneSet(this.value); + ys.value.forEach((y) => { + if (!result.has(y)) { + result.add(y); + } + }); + return new CrDataSet(result); + } + intersection(ys: CrDataSet): CrDataSet { + let xs = this.value; + var result: Set = new Set(); + ys.value.forEach((y) => { + if (xs.has(y)) { + result.add(y); + } + }); + return new CrDataSet(result); + } + + first(): CrDataValue { + // rather suspicious solution since set has no logic order + if (this.value.size === 0) { + return null; + } + for (let x of this.value) { + return x; + } + } + rest(): CrDataSet { + if (this.value.size == 0) { + return null; + } + let x0 = this.first(); + let ys = cloneSet(this.value); + ys.delete(x0); + return new CrDataSet(ys); + } + + toString() { + let itemsCode = ""; + this.value.forEach((child, idx) => { + itemsCode = `${itemsCode} ${toString(child, true)}`; + }); + return `(#{}${itemsCode})`; + } + + values() { + return this.value.values(); + } +} diff --git a/ts-src/js-tuple.ts b/ts-src/js-tuple.ts new file mode 100644 index 00000000..0bb7c73c --- /dev/null +++ b/ts-src/js-tuple.ts @@ -0,0 +1,34 @@ +import { CrDataValue } from "./js-primes"; + +import { Hash } from "@calcit/ternary-tree"; + +export class CrDataTuple { + fst: CrDataValue; + snd: CrDataValue; + cachedHash: Hash; + constructor(a: CrDataValue, b: CrDataValue) { + this.fst = a; + this.snd = b; + } + get(n: number) { + if (n == 0) { + return this.fst; + } else if (n == 1) { + return this.snd; + } else { + throw new Error("Tuple only have 2 elements"); + } + } + assoc(n: number, v: CrDataValue) { + if (n == 0) { + return new CrDataTuple(v, this.snd); + } else if (n == 1) { + return new CrDataTuple(this.fst, v); + } else { + throw new Error("Tuple only have 2 elements"); + } + } + toString(): string { + return `(&tuple ${this.fst.toString()} ${this.snd.toString()})`; + } +} diff --git a/tsconfig.json b/tsconfig.json index 5c944917..19e07b2e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,6 @@ { "compilerOptions": { + "outDir": "lib/", "allowSyntheticDefaultImports": true, "experimentalDecorators": true, "sourceMap": false, @@ -12,8 +13,8 @@ "jsx": "react", "lib": ["es2016", "dom"], "types": ["node"], - "baseUrl": "./lib/", + "baseUrl": "./ts-src/", "importsNotUsedAsValues": "preserve" }, - "include": ["./lib/*"] + "include": ["./ts-src/*"] }