ref: 86a1bffad7063d1f51dfe44c2126bbdf8b7fe5c8
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)
}
}
}