ref: 81d1e4e01d7001e6a0107792e6167136787295d6
dir: /model.js/
export function remove(child) { let parent = child.parent if (!parent) return let i = parent.stack.indexOf(child) parent.stack.splice(i, 1) child.parent = undefined } export function removeInput(child) { if (!child.parent) return let i = child.parent.inputs.indexOf(child) let other = child.parent.type.slots[i]() child.parent.inputs[i] = other child.parent.inputs[i].parent = child.parent child.parent = undefined return other } export function setInput(block, i, child) { removeInput(block.inputs[i]) removeInput(child) block.inputs[i] = child child.parent = block } export function splice(child, n = Infinity) { if (!child.parent) return [child] let children = child.parent.stack.splice(child.parent.stack.indexOf(child), n) for (let child of children) child.parent = undefined return children } export function append(parent, ...children) { for (let child of children) { if (child.type.shape !== undefined) continue if (parent.stack.length > 0 && parent.stack.at(-1).type.cap) break remove(child) parent.stack.push(child) child.parent = parent } } export function prepend(parent, ...children) { if (!parent.stack[0]) append(parent, ...children) else append(parent, ...children, ...splice(parent.stack[0])) } export function after(before, ...after) { append(before.parent, before, ...after, ...splice(before).slice(1)) } export function before(after, ...before) { append(after.parent, ...before, ...splice(after)) } export function replace(block0, block1) { let parent = block0.parent if (!parent) return if (block0.type.shape !== undefined) { setInput(parent, parent.inputs.indexOf(block0), block1) return } let before = parent.stack[parent.stack.indexOf(block0) - 1] remove(block0) if (before) after(before, block1) else prepend(parent, block1) } export function flatten(block) { return [block, ...block.inputs.flatMap(flatten), ...block.stack?.flatMap(flatten) ?? [], ...block.complement ? flatten(block.complement) : []] } export function complement(block, complement) { if (block.type.complement === complement.type) block.complement = complement complement.parent = block } let id = 1 class Block { id = id++ parent = undefined inputs = [] constructor(type, inputs = [], stack, names) { this.type = type if (names?.length) this.names = names for (let input of inputs) { removeInput(input) this.inputs.push(input) input.parent = this } if (stack) { this.stack = [] for (let child of stack) append(this, child) } } } export function duplicate(block) { let other = new Block(block.type, block.inputs.map(duplicate), block.stack?.map(duplicate), structuredClone(block.names)) if (block.atomic) other.atomic = true if (block.complement) complement(other, duplicate(block.complement)) if (block.value !== undefined) other.value = block.value return other } export function map(block, fn) { let inputs = block.inputs.map(block => map(block, fn)) let stack = block.stack?.map(block => map(block, fn)) let other = fn(block, inputs, stack, block.complement ? map(block.complement, fn) : undefined) if (!other) { other = new Block(block.type, inputs, stack, structuredClone(block.names)) if (block.atomic) other.atomic = true if (block.complement) complement(other, map(block.complement, fn)) if (block.value !== undefined) other.value = block.value } return other } export function MakeBlock(type) { let length = type.slots?.length ?? 0 let inputs0 = type.defaults?.slice(0, length) ?? [] while (inputs0.length < length) inputs0.push("") make.type = type return make function make(...inputs1) { let names if (type.references?.length) { names = inputs1.splice(0, type.references.length) while (names.length < type.references.length) names.push("") } let inputs2 = inputs1.splice(0, length).map((n, i) => typeof n === "object" ? n : type.slots[i](String(n))) let inputs = [...inputs2, ...inputs0.slice(inputs2.length).map((n, i) => type.slots[inputs2.length + i](n))] let block = new Block(type, inputs, type.hat || type.stack ? inputs1 : undefined, names) type.complete?.(block) return block } } export function MakeSlot(type = {}) { type.shape = "slot" type.normalise ??= String let make0 = MakeBlock(type) make.type = type return make function make(value = "") { let block = make0() block.value = type.normalise(value) return block } } function compareValue(a, b) { if (typeof a !== typeof b) return false if (typeof a !== "object" && typeof a !== "function") return Object.is(a, b) if (a === null && b === null) return true if (a === null || b === null) return false if ((a instanceof Array) !== (b instanceof Array)) return false if (a instanceof Array) return a.length === b.length && a.every((a, i) => compareValue(a, b[i])) let ak = Object.keys(a) let bk = Object.keys(b) ak.sort() bk.sort() if (!compareValue(ak, bk)) return false return ak.every(key => compareValue(a[key], b[key])) } export function compare(a, b) { if (a.length !== b.length) return false for (let [i, ax] of a.entries()) { let bx = b[i] if (ax.block.id !== bx.block.id) return false if (ax.value !== bx.value) return false for (let [i, input] of ax.inputs.entries()) { if (input.id !== bx.inputs[i].id) return false } if (!compareValue(ax.names, bx.names)) return false if ((!ax.stack) !== (!bx.stack)) return false if (!ax.stack) continue if (ax.stack.length !== bx.stack.length) return false for (let [i, child] of ax.stack.entries()) { if (child.id !== bx.stack[i].id) return false } } return true } export function save(scripts) { let structure = [] for (let block of scripts.flatMap(flatten)) { structure.push({ block, inputs: block.inputs.slice(), stack: block.stack?.slice(), value: block.value, complement: block.complement, names: structuredClone(block.names), }) } return structure } export function load(structure) { for (let {block, inputs, stack, value, complement: complement1, names} of structure) { for (let [i, input] of inputs.entries()) setInput(block, i, input) if (value !== undefined) block.value = value if (!stack) continue while (block.stack.length !== 0) remove(block.stack[0]) for (let child of stack) append(block, child) if (complement1) complement(block, complement1) else block.complement = undefined if (names) { block.names.length = 0 block.names.push(...names) } } }