shithub: scrax

ref: 81d1e4e01d7001e6a0107792e6167136787295d6
dir: /model.js/

View raw version
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)
		}
	}
}