mirror of
https://github.com/ApfelTeeSaft/WebMetal.git
synced 2026-08-26 19:43:24 +00:00
125 lines
4.1 KiB
TypeScript
125 lines
4.1 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
|
|
import type { WmEdge, WmNode } from '../editor/nodeTypes'
|
|
import { compileGraph } from './compileGraph'
|
|
|
|
let counter = 0
|
|
function node(
|
|
type: string,
|
|
name: string,
|
|
params: Record<string, string | number> = {},
|
|
): WmNode {
|
|
return {
|
|
id: `n${++counter}`,
|
|
type,
|
|
position: { x: 0, y: 0 },
|
|
data: { name, doc: '', params },
|
|
}
|
|
}
|
|
|
|
function pcToMemEdge(pc: WmNode, mem: WmNode): WmEdge {
|
|
return {
|
|
id: `e${++counter}`,
|
|
source: pc.id,
|
|
sourceHandle: 'out',
|
|
target: mem.id,
|
|
targetHandle: 'addr',
|
|
data: { kind: 'data' },
|
|
}
|
|
}
|
|
|
|
function fullGraph() {
|
|
const pc = node('pc', 'PC1', { width: 12 })
|
|
const mem = node('memory', 'MAIN', { size: 4096, width: 8 })
|
|
const acc = node('register', 'ACC', { width: 8 })
|
|
const rf = node('registerFile', 'R', { count: 4, width: 8 })
|
|
const flags = node('flags', 'FLAGS1', { flags: 'Z, N, C' })
|
|
const alu = node('alu', 'ALU1', { width: 8 })
|
|
const note = node('comment', 'a note!', { text: 'hello' })
|
|
return {
|
|
nodes: [pc, mem, acc, rf, flags, alu, note],
|
|
edges: [pcToMemEdge(pc, mem)],
|
|
}
|
|
}
|
|
|
|
describe('compileGraph', () => {
|
|
it('compiles a full graph into a valid machine model', () => {
|
|
const { nodes, edges } = fullGraph()
|
|
const { model, diagnostics } = compileGraph(nodes, edges)
|
|
expect(diagnostics.filter((d) => d.severity === 'error')).toEqual([])
|
|
expect(model).not.toBeNull()
|
|
if (!model) return
|
|
expect(model.banks).toEqual([
|
|
{ name: 'ACC', width: 8, count: 1, sourceNodeId: expect.any(String) },
|
|
{ name: 'R', width: 8, count: 4, sourceNodeId: expect.any(String) },
|
|
])
|
|
expect(model.memories).toEqual([
|
|
{
|
|
name: 'MAIN',
|
|
size: 4096,
|
|
width: 8,
|
|
sourceNodeId: expect.any(String),
|
|
},
|
|
])
|
|
expect(model.flags).toEqual(['Z', 'N', 'C'])
|
|
expect(model.pc).toEqual({ width: 12 })
|
|
expect(model.programMemory).toBe('MAIN')
|
|
})
|
|
|
|
it('is deterministic regardless of node order', () => {
|
|
const { nodes, edges } = fullGraph()
|
|
const forward = compileGraph(nodes, edges).model
|
|
const backward = compileGraph([...nodes].reverse(), edges).model
|
|
// sourceNodeIds differ per construction run, so compare shapes without them
|
|
const strip = (m: typeof forward) =>
|
|
JSON.parse(
|
|
JSON.stringify(m, (k, v: unknown) =>
|
|
k === 'sourceNodeId' ? undefined : v,
|
|
),
|
|
) as unknown
|
|
expect(strip(backward)).toEqual(strip(forward))
|
|
})
|
|
|
|
it('returns no model when validation errors exist', () => {
|
|
const { model, diagnostics } = compileGraph([], [])
|
|
expect(model).toBeNull()
|
|
expect(diagnostics.some((d) => d.severity === 'error')).toBe(true)
|
|
})
|
|
|
|
it('resolves the program memory via the PC wire when there are several memories', () => {
|
|
const { nodes, edges } = fullGraph()
|
|
nodes.push(node('memory', 'DATA', { size: 256, width: 8 }))
|
|
const { model } = compileGraph(nodes, edges)
|
|
expect(model?.programMemory).toBe('MAIN')
|
|
})
|
|
|
|
it('errors when the program memory is ambiguous', () => {
|
|
const { nodes } = fullGraph()
|
|
nodes.push(node('memory', 'DATA', { size: 256, width: 8 }))
|
|
const { model, diagnostics } = compileGraph(nodes, []) // no PC wire
|
|
expect(model).toBeNull()
|
|
expect(diagnostics.map((d) => d.message).join()).toContain(
|
|
'cannot determine the program memory',
|
|
)
|
|
})
|
|
|
|
it('rejects bad flag names', () => {
|
|
const { nodes, edges } = fullGraph()
|
|
const flags = nodes.find((n) => n.type === 'flags')
|
|
if (flags) flags.data.params = { flags: 'Z,9bad,Z' }
|
|
const { model, diagnostics } = compileGraph(nodes, edges)
|
|
expect(model).toBeNull()
|
|
const text = diagnostics.map((d) => d.message).join()
|
|
expect(text).toContain('not a valid identifier')
|
|
expect(text).toContain('duplicate flag name')
|
|
})
|
|
|
|
it('clamps out-of-range params into the schema range', () => {
|
|
const { nodes, edges } = fullGraph()
|
|
const acc = nodes.find((n) => n.data.name === 'ACC')
|
|
if (acc) acc.data.params = { width: 99 }
|
|
const { model } = compileGraph(nodes, edges)
|
|
expect(model?.banks.find((b) => b.name === 'ACC')?.width).toBe(32)
|
|
})
|
|
})
|