diff --git a/compiler/__tests__/parser.test.ts b/compiler/__tests__/parser.test.ts index 1340b4b..58af7b0 100644 --- a/compiler/__tests__/parser.test.ts +++ b/compiler/__tests__/parser.test.ts @@ -1,5 +1,6 @@ import { describe, it, expect } from 'vitest'; -import { parseSpeckContent } from '../src/parser.js'; +import { parseSpeckContent, parseSpeckFile } from '../src/parser.js'; +import { generateWASM } from '../src/generators/wasm.js'; describe('SpeckDL Parser', () => { describe('parseSpeckContent', () => { @@ -387,4 +388,213 @@ speck Second { expect(types).toContain('verify'); }); }); + + describe('member parsing - state machine', () => { + it('should parse state block with primitive variables', () => { + const content = `speck ToggleSwitch { + state: { + on: Bool := false + toggleCount: Nat := 0 + } +}`; + const ast = parseSpeckContent(content); + const state = ast.specks[0].members.find(m => m.type === 'state'); + expect(state).toBeDefined(); + if (state?.type === 'state') { + expect(state.variables).toHaveLength(2); + expect(state.variables[0].name).toBe('on'); + expect(state.variables[0].typeExpr.type).toBe('primitive'); + expect(state.variables[0].typeExpr.name).toBe('Bool'); + expect(state.variables[0].defaultInit).toBe('false'); + expect(state.variables[1].name).toBe('toggleCount'); + expect(state.variables[1].defaultInit).toBe('0'); + } + }); + + it('should parse state block with Set and Map types', () => { + const content = `speck AccountLedger { + state: { + balances: Map + participants: Set + } +}`; + const ast = parseSpeckContent(content); + const state = ast.specks[0].members.find(m => m.type === 'state'); + if (state?.type === 'state') { + expect(state.variables).toHaveLength(2); + expect(state.variables[0].typeExpr.type).toBe('map'); + expect(state.variables[1].typeExpr.type).toBe('set'); + } + }); + + it('should parse init block', () => { + const content = `speck Test { + state: { + x: Nat + } + init: { + x := 0 + } +}`; + const ast = parseSpeckContent(content); + const init = ast.specks[0].members.find(m => m.type === 'init'); + expect(init).toBeDefined(); + if (init?.type === 'init') { + expect(init.assignments).toHaveLength(1); + expect(init.assignments[0].name).toBe('x'); + expect(init.assignments[0].expr).toBe('0'); + } + }); + + it('should parse action without parameters', () => { + const content = `speck Test { + action Toggle() { + x := x + 1 + } +}`; + const ast = parseSpeckContent(content); + const action = ast.specks[0].members.find(m => m.type === 'action'); + expect(action).toBeDefined(); + if (action?.type === 'action') { + expect(action.name).toBe('Toggle'); + expect(action.params).toHaveLength(0); + } + }); + + it('should parse action with parameters', () => { + const content = `speck Test { + action Transfer(from: Nat, to: Nat, amount: Real) { + require from in balances + balances[from] := balances[from] - amount + } +}`; + const ast = parseSpeckContent(content); + const action = ast.specks[0].members.find(m => m.type === 'action'); + if (action?.type === 'action') { + expect(action.name).toBe('Transfer'); + expect(action.params).toHaveLength(3); + expect(action.params[0].name).toBe('from'); + expect(action.params[0].type.type).toBe('primitive'); + expect(action.params[0].type.name).toBe('Nat'); + } + }); + + it('should parse full TigerBeetle speck from file', () => { + const ast = parseSpeckFile('./__tests__/tigerbeetle.speck'); + expect(ast.specks).toHaveLength(1); + + const speck = ast.specks[0]; + expect(speck.name).toBe('TigerBeetle'); + + const state = speck.members.find(m => m.type === 'state'); + const init = speck.members.find(m => m.type === 'init'); + const actions = speck.members.filter(m => m.type === 'action'); + const events = speck.members.filter(m => m.type === 'event'); + const constraints = speck.members.filter(m => m.type === 'constraint'); + const verifies = speck.members.filter(m => m.type === 'verify'); + + expect(state).toBeDefined(); + expect(init).toBeDefined(); + expect(actions).toHaveLength(4); + expect(events).toHaveLength(3); + expect(constraints).toHaveLength(2); + expect(verifies).toHaveLength(2); + + if (state?.type === 'state') { + expect(state.variables).toHaveLength(4); + expect(state.variables[0].name).toBe('nextId'); + expect(state.variables[1].typeExpr.type).toBe('map'); + expect(state.variables[2].typeExpr.type).toBe('set'); + } + + if (init?.type === 'init') { + expect(init.assignments).toHaveLength(4); + } + + const createAccount = actions.find(a => a.type === 'action' && a.name === 'CreateAccount'); + expect(createAccount).toBeDefined(); + if (createAccount?.type === 'action') { + expect(createAccount.params).toHaveLength(1); + expect(createAccount.statements.length).toBeGreaterThan(0); + } + + const transfer = actions.find(a => a.type === 'action' && a.name === 'Transfer'); + expect(transfer).toBeDefined(); + if (transfer?.type === 'action') { + expect(transfer.params).toHaveLength(3); + } + }); + }); + + describe('WASM Codegen', () => { + it('should generate WAT for TigerBeetle with all exports', () => { + const ast = parseSpeckFile('./__tests__/tigerbeetle.speck'); + const fs = require('fs'); + const os = require('os'); + const path = require('path'); + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'speckl-wasm-test-')); + + generateWASM(ast, tmpDir); + + const watPath = path.join(tmpDir, 'TigerBeetle.wat'); + expect(fs.existsSync(watPath)).toBe(true); + + const wat = fs.readFileSync(watPath, 'utf-8'); + + // Module structure + expect(wat).toContain('(module'); + expect(wat).toContain('(memory (export "memory")'); + + // Host imports + expect(wat).toContain('(import "speckl" "map_create"'); + expect(wat).toContain('(import "speckl" "set_create"'); + + // State globals + expect(wat).toContain('(global $nextId'); + expect(wat).toContain('(global $accounts'); + expect(wat).toContain('(global $transfers'); + expect(wat).toContain('(global $ledgerOpen'); + + // init export + expect(wat).toContain('(func $init (export "init")'); + + // Action exports + expect(wat).toContain('(func $CreateAccount (export "CreateAccount")'); + expect(wat).toContain('(func $Transfer (export "Transfer")'); + expect(wat).toContain('(func $CloseAccount (export "CloseAccount")'); + expect(wat).toContain('(func $CloseLedger (export "CloseLedger")'); + + // getState export + expect(wat).toContain('(func $getState (export "getState")'); + + // Constraint stub + expect(wat).toContain('(func $checkConstraints (export "checkConstraints")'); + + // Scalar init values + expect(wat).toContain('(i32.const 1)'); // nextId default + expect(wat).toContain('(call $map_create)'); + expect(wat).toContain('(call $set_create)'); + + fs.rmSync(tmpDir, { recursive: true }); + }); + + it('should emit scalar guard expressions as WASM comparison', () => { + const ast = parseSpeckFile('./__tests__/tigerbeetle.speck'); + const fs = require('fs'); + const os = require('os'); + const path = require('path'); + const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'speckl-wasm-test-')); + + generateWASM(ast, tmpDir); + const wat = fs.readFileSync(path.join(tmpDir, 'TigerBeetle.wat'), 'utf-8'); + + // ledgerOpen == true should compile to i32.eq + expect(wat).toContain('(i32.eq (global.get $ledgerOpen) (i32.const 1))'); + + // nextId + 1 should compile to i32.add + expect(wat).toContain('(i32.add (global.get $nextId) (i32.const 1))'); + + fs.rmSync(tmpDir, { recursive: true }); + }); + }); }); diff --git a/compiler/__tests__/tigerbeetle.speck b/compiler/__tests__/tigerbeetle.speck new file mode 100644 index 0000000..7262b6b --- /dev/null +++ b/compiler/__tests__/tigerbeetle.speck @@ -0,0 +1,91 @@ +speck TigerBeetle { + author: "Speckl Compiler v0.3" + source: architecture_review ref "TigerBeetle-Core-Port" + review: hybrid + + state: { + nextId: Nat := 1 + accounts: Map + transfers: Set + ledgerOpen: Bool := true + } + + init: { + nextId := 1 + accounts := Map.empty() + transfers := Set.empty() + ledgerOpen := true + } + + event AccountCreated { + accountId: Nat, + initialBalance: Real + } + + event TransferCompleted { + from: Nat, + to: Nat, + amount: Real, + transferId: Nat + } + + event AccountClosed { + accountId: Nat + } + + action CreateAccount(initialDeposit: Real) { + require initialDeposit >= 0 + require ledgerOpen == true + + let newId := nextId + nextId := nextId + 1 + accounts[newId] := Account { id: newId, balance: initialDeposit, open: true } + + emit AccountCreated { accountId: newId, initialBalance: initialDeposit } + return newId + } + + action Transfer(from: Nat, to: Nat, amount: Real) { + require from in accounts + require to in accounts + require from != to + require amount > 0 + require accounts[from].balance >= amount + require accounts[from].open == true + require accounts[to].open == true + require ledgerOpen == true + + accounts[from].balance := accounts[from].balance - amount + accounts[to].balance := accounts[to].balance + amount + + let tx := Transfer { from, to, amount, id: nextId } + nextId := nextId + 1 + transfers.add(tx) + + emit TransferCompleted { from, to, amount, transferId: tx.id } + } + + action CloseAccount(id: Nat) { + require id in accounts + require accounts[id].balance == 0 + require accounts[id].open == true + + accounts[id].open := false + + emit AccountClosed { accountId: id } + } + + action CloseLedger() { + require forall(id in accounts.keys(), accounts[id].open == false) + + ledgerOpen := false + } + + constraint: forall(id in accounts.keys(), implies(accounts[id].open, accounts[id].balance >= 0)) + constraint: ledgerOpen in [true, false] + + verify: always(implies(not ledgerOpen, + forall(id in accounts.keys(), accounts[id].open == false))) + verify: always(implies(transfers.size() > 0, + exists(tx in transfers, tx.amount > 0))) +} \ No newline at end of file diff --git a/compiler/dist/index.js b/compiler/dist/index.js index 32bc10d..8e10d52 100644 --- a/compiler/dist/index.js +++ b/compiler/dist/index.js @@ -5,6 +5,8 @@ import { parseSpeckFile } from './parser.js'; import { generateProvenanceGraph } from './generators/provenance.js'; import { generateCycloneDX } from './generators/cyclonedx.js'; import { generateSPDX } from './generators/spdx.js'; +import { generateTypeScriptStateMachine } from './generators/typescript-state-machine.js'; +import { generateWASM } from './generators/wasm.js'; import fs from 'fs'; async function main() { const argv = await yargs(hideBin(process.argv)) @@ -49,6 +51,12 @@ async function main() { // Always generate provenance (it's the core output) console.log('\nGenerating provenance graph (PROV-O JSON-LD)...'); generateProvenanceGraph(ast, options.outputDir); + // Generate TypeScript state machine + console.log('\nGenerating TypeScript state machine...'); + generateTypeScriptStateMachine(ast, options.outputDir); + // Generate WASM state machine + console.log('\nGenerating WebAssembly (WAT)...'); + generateWASM(ast, options.outputDir); // Generate BOM based on format selection if (options.bomFormat === 'cdx' || options.bomFormat === 'both') { console.log('\nGenerating CycloneDX BOM...'); diff --git a/compiler/dist/index.js.map b/compiler/dist/index.js.map index fd4cc97..cef4b18 100644 --- a/compiler/dist/index.js.map +++ b/compiler/dist/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/compiler/dist/parser.d.ts b/compiler/dist/parser.d.ts index 61fa120..0aff17b 100644 --- a/compiler/dist/parser.d.ts +++ b/compiler/dist/parser.d.ts @@ -97,15 +97,64 @@ export interface BOMNode { license?: string; hash?: string; } -export type MemberNode = ImportNode | InputNode | OutputNode | ConstraintNode | VerifyNode | InterfaceNode | EventNode | ProvenanceNode | ReviewNode | DerivesNode | SatisfiesNode | AuthorNode | SourceNode | BOMNode; +export interface StateNode { + type: 'state'; + variables: StateVar[]; +} +export interface StateVar { + name: string; + typeExpr: TypeExpr; + defaultInit?: string; +} +export interface InitNode { + type: 'init'; + assignments: { + name: string; + expr: string; + }[]; +} +export interface ActionNode { + type: 'action'; + name: string; + params: { + name: string; + type: TypeExpr; + }[]; + statements: ActionStatement[]; +} +export type ActionStatement = { + type: 'assign'; + target: string; + expr: string; +} | { + type: 'let'; + name: string; + expr: string; +} | { + type: 'require'; + expr: string; +} | { + type: 'emit'; + event: string; + fields: { + name: string; + value: string; + }[]; +} | { + type: 'return'; + expr: string; +}; +export type MemberNode = ImportNode | InputNode | OutputNode | ConstraintNode | VerifyNode | InterfaceNode | EventNode | ProvenanceNode | ReviewNode | DerivesNode | SatisfiesNode | AuthorNode | SourceNode | BOMNode | StateNode | InitNode | ActionNode; export interface TypeExpr { - type: 'primitive' | 'record' | 'list' | 'ident'; + type: 'primitive' | 'record' | 'list' | 'set' | 'map' | 'ident'; name?: string; fields?: { name: string; type: TypeExpr; }[]; elementType?: TypeExpr; + keyType?: TypeExpr; + valueType?: TypeExpr; } export interface AST { specks: SpeckNode[]; diff --git a/compiler/dist/parser.d.ts.map b/compiler/dist/parser.d.ts.map index 7e725b4..4d3c72a 100644 --- a/compiler/dist/parser.d.ts.map +++ b/compiler/dist/parser.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/compiler/dist/parser.js b/compiler/dist/parser.js index 3b11931..7b442dd 100644 --- a/compiler/dist/parser.js +++ b/compiler/dist/parser.js @@ -46,43 +46,95 @@ export function parseSpeckContent(content, filePath) { return { specks }; } function parseSpeck(lines, startIndex) { - // Parse speck header: speck { const headerMatch = lines[startIndex].match(/^speck\s+([\w-]+)\s*{/); - if (!headerMatch) { + if (!headerMatch) return null; - } const name = headerMatch[1]; const members = []; let i = startIndex + 1; let braceCount = 1; while (i < lines.length && braceCount > 0) { - const line = lines[i].trim(); - // Skip empty lines and comments + const rawLine = lines[i]; + const line = rawLine.trim(); if (!line || line.startsWith('//') || line.startsWith('/*')) { i++; continue; } - if (line.includes('{')) { - braceCount++; - } - if (line.includes('}')) { - braceCount--; - if (braceCount === 0) { - break; + // Count braces on this line first + const openCount = (rawLine.match(/{/g) || []).length; + const closeCount = (rawLine.match(/}/g) || []).length; + // Check for block-opening members + const blockStarters = ['state:', 'init:', 'action ', 'event ', 'provenance ', 'bom ', 'interface ']; + const isBlockStarter = blockStarters.some(s => line.startsWith(s)); + if (isBlockStarter && openCount > 0) { + // Multi-line block: parse it and skip to its end + const member = parseMemberBlock(lines, i); + if (member) { + members.push(member); } + // Skip past this block + const endIdx = findBlockEnd(lines, i + 1, braceCount + openCount); + i = endIdx + 1; + continue; + } + // Single-line: update brace count, then parse if still inside + braceCount += openCount - closeCount; + if (braceCount <= 0) { + i++; + break; } - // Parse member const member = parseMember(line); if (member) { members.push(member); } i++; } - return { - type: 'speck', - name, - members, - }; + return { type: 'speck', name, members }; +} +function findBlockEnd(lines, startIndex, startBraceCount) { + let braceCount = startBraceCount; + let i = startIndex; + while (i < lines.length) { + const line = lines[i]; + for (const ch of line) { + if (ch === '{') + braceCount++; + if (ch === '}') { + braceCount--; + if (braceCount < startBraceCount) { + return i; + } + } + } + i++; + } + return i; +} +function parseMemberBlock(lines, startIndex) { + const firstLine = lines[startIndex].trim(); + const startBraceCount = (firstLine.match(/{/g) || []).length; + if (firstLine.startsWith('state:')) { + return parseStateBlockMultiline(lines, startIndex, startBraceCount); + } + if (firstLine.startsWith('init:')) { + return parseInitBlockMultiline(lines, startIndex, startBraceCount); + } + if (firstLine.startsWith('action ')) { + return parseActionBlockMultiline(lines, startIndex, startBraceCount); + } + if (firstLine.startsWith('event ')) { + return parseEventBlockMultiline(lines, startIndex, startBraceCount); + } + if (firstLine.startsWith('interface ')) { + return parseInterfaceBlockMultiline(lines, startIndex, startBraceCount); + } + if (firstLine.startsWith('provenance ')) { + return { type: 'provenance', clauses: [] }; + } + if (firstLine.startsWith('bom ')) { + return { type: 'bom' }; + } + return null; } function parseMember(line) { // input: @@ -136,6 +188,30 @@ function parseMember(line) { if (line.startsWith('source:')) { return parseSource(line); } + // state: + if (line.startsWith('state:')) { + const varsStr = line.substring(6).trim(); + // state: { ... } - single-line form + if (varsStr.startsWith('{') && varsStr.endsWith('}')) { + const inner = varsStr.slice(1, -1).trim(); + return parseStateBlock(inner); + } + // state: { - multi-line opening (handled by parseStateMultiline) + return { type: 'state', variables: [] }; + } + // init: + if (line.startsWith('init:')) { + const initStr = line.substring(5).trim(); + if (initStr.startsWith('{') && initStr.endsWith('}')) { + const inner = initStr.slice(1, -1).trim(); + return parseInitBlock(inner); + } + return { type: 'init', assignments: [] }; + } + // action keyword + if (line.startsWith('action ')) { + return parseActionHeader(line); + } // bom { if (line.startsWith('bom ')) { // Simplified - full BOM block parsing would need multi-line context @@ -145,6 +221,62 @@ function parseMember(line) { } return null; } +function parseStateBlock(inner) { + const variables = []; + if (!inner.trim()) + return { type: 'state', variables }; + const varStrs = inner.split(/;|\n/).map(s => s.trim()).filter(Boolean); + for (const vStr of varStrs) { + const withDefault = vStr.match(/^(\w+)\s*:\s*(.+?)\s*:=\s*(.+)$/); + if (withDefault) { + variables.push({ + name: withDefault[1], + typeExpr: parseTypeExpr(withDefault[2].trim()), + defaultInit: withDefault[3].trim(), + }); + continue; + } + const withoutDefault = vStr.match(/^(\w+)\s*:\s*(.+)$/); + if (withoutDefault) { + variables.push({ + name: withoutDefault[1], + typeExpr: parseTypeExpr(withoutDefault[2].trim()), + }); + } + } + return { type: 'state', variables }; +} +function parseInitBlock(inner) { + const assignments = []; + if (!inner.trim()) + return { type: 'init', assignments }; + const assignStrs = inner.split(/;|\n/).map(s => s.trim()).filter(Boolean); + for (const aStr of assignStrs) { + const match = aStr.match(/^(\w+)\s*:=\s*(.+)$/); + if (match) { + assignments.push({ name: match[1], expr: match[2].trim() }); + } + } + return { type: 'init', assignments }; +} +function parseActionHeader(line) { + const match = line.match(/^action\s+(\w+)\s*(?:\(([^)]*)\))?\s*\{/); + if (!match) + return null; + const name = match[1]; + const paramsStr = match[2] || ''; + const params = []; + if (paramsStr.trim()) { + const paramPairs = paramsStr.split(',').map(s => s.trim()).filter(Boolean); + for (const p of paramPairs) { + const pMatch = p.match(/^(\w+)\s*:\s*(.+)$/); + if (pMatch) { + params.push({ name: pMatch[1], type: parseTypeExpr(pMatch[2].trim()) }); + } + } + } + return { type: 'action', name, params, statements: [] }; +} function parseImportStatement(line) { const match = line.match(/^import\s+(['"])(.+?)\1(?:\s+as\s+(\w+))?(?:\s+version\s+(['"])(.+?)\4)?(?:\s+hash\s+(['"])(.+?)\6)?/); if (!match) { @@ -166,6 +298,35 @@ function parseTypeExpr(expr) { if (primitives.includes(expr)) { return { type: 'primitive', name: expr }; } + // Set + if (expr.startsWith('Set<') && expr.endsWith('>')) { + const inner = expr.substring(4, expr.length - 1).trim(); + return { type: 'set', elementType: parseTypeExpr(inner) }; + } + // Map + if (expr.startsWith('Map<') && expr.endsWith('>')) { + const inner = expr.substring(4, expr.length - 1); + // Find the first-level comma (not inside nested generics) + let depth = 0; + let commaIdx = -1; + for (let i = 0; i < inner.length; i++) { + if (inner[i] === '<') + depth++; + else if (inner[i] === '>') + depth--; + else if (inner[i] === ',' && depth === 0) { + commaIdx = i; + break; + } + } + if (commaIdx !== -1) { + const keyStr = inner.substring(0, commaIdx).trim(); + const valStr = inner.substring(commaIdx + 1).trim(); + return { type: 'map', keyType: parseTypeExpr(keyStr), valueType: parseTypeExpr(valStr) }; + } + // Fallback: treat as ident if malformed + return { type: 'ident', name: expr }; + } // List type: [T] if (expr.startsWith('[') && expr.endsWith(']')) { const elementType = parseTypeExpr(expr.substring(1, expr.length - 1)); @@ -294,10 +455,112 @@ function parseSource(line) { ref: ref || undefined, }; } -function parseBOMBlock(lines, line) { - // This is a simplified parser - in a real implementation, we'd parse the full block - return { - type: 'bom', - }; +function parseStateBlockMultiline(lines, startIndex, startBraceCount) { + const endIndex = findBlockEnd(lines, startIndex + 1, startBraceCount); + const innerLines = lines.slice(startIndex + 1, endIndex).map(l => l.trim()).filter(l => l && !l.startsWith('//') && !l.startsWith('/*')); + const inner = innerLines.join('; '); + return parseStateBlock(inner); +} +function parseInitBlockMultiline(lines, startIndex, startBraceCount) { + const endIndex = findBlockEnd(lines, startIndex + 1, startBraceCount); + const innerLines = lines.slice(startIndex + 1, endIndex).map(l => l.trim()).filter(l => l && !l.startsWith('//') && !l.startsWith('/*')); + const inner = innerLines.join('; '); + return parseInitBlock(inner); +} +function parseActionBlockMultiline(lines, startIndex, startBraceCount) { + const firstLine = lines[startIndex].trim(); + const header = parseActionHeader(firstLine); + if (!header) + return null; + const endIndex = findBlockEnd(lines, startIndex + 1, startBraceCount); + const bodyLines = lines.slice(startIndex + 1, endIndex).map(l => l.trim()).filter(l => l && !l.startsWith('//') && !l.startsWith('/*')); + const statements = []; + for (const stmt of bodyLines) { + // require expr + if (stmt.startsWith('require ')) { + statements.push({ type: 'require', expr: stmt.substring(8).trim() }); + continue; + } + // return expr + if (stmt.startsWith('return ')) { + statements.push({ type: 'return', expr: stmt.substring(7).trim() }); + continue; + } + // emit Event { fields } + const emitMatch = stmt.match(/^emit\s+(\w+)\s*\{\s*(.*)\s*\}$/); + if (emitMatch) { + const eventName = emitMatch[1]; + const fieldsStr = emitMatch[2]; + const fields = []; + if (fieldsStr) { + const fieldPairs = fieldsStr.split(',').map(s => s.trim()).filter(Boolean); + for (const fp of fieldPairs) { + const colonIdx = fp.indexOf(':'); + if (colonIdx > 0) { + fields.push({ + name: fp.substring(0, colonIdx).trim(), + value: fp.substring(colonIdx + 1).trim(), + }); + } + } + } + statements.push({ type: 'emit', event: eventName, fields }); + continue; + } + // let name := expr + const letMatch = stmt.match(/^let\s+(\w+)\s*:=\s*(.+)$/); + if (letMatch) { + statements.push({ type: 'let', name: letMatch[1], expr: letMatch[2].trim() }); + continue; + } + // assignment: target := expr + const assignMatch = stmt.match(/^(\w+(?:\[.*?\])?)\s*:=\s*(.+)$/); + if (assignMatch) { + statements.push({ type: 'assign', target: assignMatch[1], expr: assignMatch[2].trim() }); + } + } + return { ...header, statements }; +} +function parseEventBlockMultiline(lines, startIndex, startBraceCount) { + const firstLine = lines[startIndex].trim(); + const header = parseEvent(firstLine); + const endIndex = findBlockEnd(lines, startIndex + 1, startBraceCount); + const innerLines = lines.slice(startIndex + 1, endIndex).map(l => l.trim()).filter(l => l && !l.startsWith('//') && !l.startsWith('/*')); + const fields = []; + for (const line of innerLines) { + const match = line.match(/^(\w+)\s*:\s*(.+?),?$/); + if (match) { + fields.push({ name: match[1], type: parseTypeExpr(match[2].replace(/,$/, '').trim()) }); + } + } + return { ...header, fields }; +} +function parseInterfaceBlockMultiline(lines, startIndex, startBraceCount) { + const firstLine = lines[startIndex].trim(); + const header = parseInterface(firstLine); + const endIndex = findBlockEnd(lines, startIndex + 1, startBraceCount); + const innerLines = lines.slice(startIndex + 1, endIndex).map(l => l.trim()).filter(l => l && !l.startsWith('//') && !l.startsWith('/*')); + const methods = []; + for (const line of innerLines) { + // name(params) -> returnType + const match = line.match(/^(\w+)\s*\(([^)]*)\)\s*-\u003e\s*(.+?),?$/); + if (match) { + const name = match[1]; + const paramsStr = match[2]; + const returnTypeStr = match[3].replace(/,$/, '').trim(); + const params = []; + if (paramsStr.trim()) { + const paramPairs = paramsStr.split(',').map(s => s.trim()).filter(Boolean); + for (const p of paramPairs) { + const pMatch = p.match(/^(\w+)\s*:\s*(.+)$/); + if (pMatch) { + params.push({ name: pMatch[1], type: parseTypeExpr(pMatch[2].trim()) }); + } + } + } + methods.push({ name, params, returnType: parseTypeExpr(returnTypeStr) }); + } + } + return { ...header, methods }; } //# sourceMappingURL=parser.js.map \ No newline at end of file diff --git a/compiler/dist/parser.js.map b/compiler/dist/parser.js.map index a1b3c56..f94b9b1 100644 --- a/compiler/dist/parser.js.map +++ b/compiler/dist/parser.js.map @@ -1 +1 @@ -{"version":3,"file":"parser.js","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"parser.js","sourceRoot":"","sources":["../src/parser.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/compiler/node_modules/.vite/vitest/results.json b/compiler/node_modules/.vite/vitest/results.json index 86fd1cb..243e61a 100644 --- a/compiler/node_modules/.vite/vitest/results.json +++ b/compiler/node_modules/.vite/vitest/results.json @@ -1 +1 @@ -{"version":"2.1.9","results":[[":src/__tests__/compiler.test.ts",{"duration":7.601268000000005,"failed":false}]]} \ No newline at end of file +{"version":"2.1.9","results":[[":__tests__/parser.test.ts",{"duration":38.49205900000004,"failed":false}],[":src/__tests__/compiler.test.ts",{"duration":10.381641000000002,"failed":false}]]} \ No newline at end of file diff --git a/compiler/specs/forgejo-issue-workflow.speckdl b/compiler/specs/forgejo-issue-workflow.speckdl new file mode 100644 index 0000000..c0acb1f --- /dev/null +++ b/compiler/specs/forgejo-issue-workflow.speckdl @@ -0,0 +1,272 @@ +// forgejo-issue-workflow.speckdl +// Formal specification: A Forgejo issue is a prompt wrapped in a state machine. +// +// Purpose: Make the informal prompt → formal. Track who can do what, when, and +// what evidence is required to move forward. Every transition is a decision. + +speck IssueWorkflow { + author: "Scott Scoble" <"scott@scoble.me"> + source: conversation ref "discord-2026-05-03" + review: hybrid + + // ─── Provenance ─────────────────────────────────────────── + provenance { + design_decision: "Issues as prompts + state machines" + parent_spec: "AGENTS.md Invariants" + external_doc: "https://os.scoble.me/forgejo/marcus" + } + + // ─── Derivation & Satisfaction ──────────────────────────── + derives from AGENTS-INVARIANTS via "formalization of operational rules" + satisfies AGENTS-REQ-01 clause "1.1" + satisfies AGENTS-REQ-02 clause "1.3" + satisfies AGENTS-REQ-03 clause "1.4" + satisfies AGENTS-REQ-04 clause "1.5" + satisfies AGENTS-REQ-05 clause "1.6" + + // ─── Types ──────────────────────────────────────────────── + interface IssueState { + Open, Grooming, Groomed, Assigned, InProgress, InReview, Merged, Blocked, Closed + } + + // ─── Events ─────────────────────────────────────────────── + event StateTransition { + from: IssueState, + to: IssueState, + actor: String, + reason: String, + evidence: String, + at: String + } + + event EvidenceItem { + kind: String, + ref: String, + verifiedBy: String + } + + // ─── Input / Output ──────────────────────────────────────── + input: { + prompt: String, + creator: String, + repo: String, + labels: [String], + assignee: String, + createdAt: String + } + + output: { + state: IssueState, + stateAt: String, + decisionLog: [StateTransition], + blockedBy: [String], + evidence: [EvidenceItem] + } + + // ─── State Variables ────────────────────────────────────── + state: { + status: IssueState, + assignee: String, + repo: String, + decisionCount: Nat, + lastActionBy: String + } + + init: { + status := Open, + assignee := "", + repo := "", + decisionCount := 0, + lastActionBy := "" + } + + // ─── Actions (State Transitions) ────────────────────────── + action create(creator: String, prompt: String, repo: String, labels: [String]) { + require status == Open + assign status = Open + assign decisionCount = 0 + assign lastActionBy = creator + emit StateTransition { + from: Open, + to: Open, + actor: creator, + reason: prompt, + evidence: repo, + at: now() + } + } + + action groom(assignee: String, refinedPrompt: String, acceptanceCriteria: [String]) { + require status == Open || status == Grooming + require len(acceptanceCriteria) > 0 + assign status = Groomed + assign lastActionBy = assignee + assign decisionCount = decisionCount + 1 + emit StateTransition { + from: status, + to: Groomed, + actor: assignee, + reason: refinedPrompt, + evidence: join(acceptanceCriteria, ";"), + at: now() + } + } + + action assign(assignee: String, agent: String) { + require status == Groomed || status == Open + require assignee == "sscoble" + require repo != "marcus/coach" + assign status = Assigned + assign lastActionBy = agent + assign decisionCount = decisionCount + 1 + emit StateTransition { + from: status, + to: Assigned, + actor: agent, + reason: "delegated to " + agent, + evidence: "", + at: now() + } + } + + action startWork(agent: String) { + require status == Assigned + assign status = InProgress + assign lastActionBy = agent + assign decisionCount = decisionCount + 1 + emit StateTransition { + from: Assigned, + to: InProgress, + actor: agent, + reason: "beginning implementation", + evidence: "", + at: now() + } + } + + action submitPR(agent: String, prNumber: Nat, commitHash: String) { + require status == InProgress + assign status = InReview + assign lastActionBy = agent + assign decisionCount = decisionCount + 1 + emit StateTransition { + from: InProgress, + to: InReview, + actor: agent, + reason: "PR submitted", + evidence: commitHash, + at: now() + } + emit EvidenceItem { + kind: "pr", + ref: prNumber, + verifiedBy: agent + } + } + + action merge(prNumber: Nat, merger: String, commitHash: String) { + require status == InReview + assign status = Merged + assign lastActionBy = merger + assign decisionCount = decisionCount + 1 + emit StateTransition { + from: InReview, + to: Merged, + actor: merger, + reason: "PR merged", + evidence: commitHash, + at: now() + } + emit EvidenceItem { + kind: "commit", + ref: commitHash, + verifiedBy: merger + } + } + + action close(closer: String, resolution: String) { + require status == Merged || status == Open || status == Groomed + require closer == assignee || (closer == "sscoble" && assignee == "") + assign status = Closed + assign lastActionBy = closer + assign decisionCount = decisionCount + 1 + emit StateTransition { + from: status, + to: Closed, + actor: closer, + reason: resolution, + evidence: "", + at: now() + } + } + + action reopen(opener: String, reason: String) { + require status == Closed || status == Blocked + assign status = Open + assign lastActionBy = opener + assign decisionCount = decisionCount + 1 + emit StateTransition { + from: status, + to: Open, + actor: opener, + reason: reason, + evidence: "", + at: now() + } + } + + action block(agent: String, blocker: String) { + require status != Blocked && status != Closed + assign status = Blocked + assign lastActionBy = agent + assign decisionCount = decisionCount + 1 + emit StateTransition { + from: status, + to: Blocked, + actor: agent, + reason: "blocked by: " + blocker, + evidence: "", + at: now() + } + } + + action unblock(agent: String, resolution: String) { + require status == Blocked + assign status = Open + assign lastActionBy = agent + assign decisionCount = decisionCount + 1 + emit StateTransition { + from: Blocked, + to: Open, + actor: agent, + reason: resolution, + evidence: "", + at: now() + } + } + + // ─── Constraints ────────────────────────────────────────── + constraint: len(labels) > 0 + constraint: forall l in labels: l in {"bug", "feature", "improvement", "question", "blocked"} + constraint: implies(now() - lastIssueCreatedAt(agent) < 3600, issuesCreatedInLastHour(agent) <= 3) + constraint: implies(now() - lastCommentAt(agent) < 30, false) + constraint: implies(repo in {"marcus/engineering", "marcus/market", "marcus/content", "marcus/trust"}, assignee == "sscoble") + constraint: implies(creator == agent, repo != agentRepo(agent)) + constraint: decisionCount == len(decisionLog) + constraint: implies(status == Closed, len(evidence) > 0 || lastStateBeforeClosed() in {Merged, Blocked}) + + // ─── Verification ───────────────────────────────────────── + verify: always(implies(status == InProgress, eventually(status == Merged || status == Closed))) + verify: always(implies(status == Blocked, eventually(status != Blocked))) + verify: always(implies(lastActionBy != "sscoble", status != Assigned)) + verify: always(implies(decisionCount > 0, forall t in decisionLog: len(t.reason) > 0)) + verify: always(implies(status == Groomed || status == Assigned || status == InProgress, len(acceptanceCriteria) > 0)) + + // ─── BOM ────────────────────────────────────────────────── + bom { + compiler: "speckl-compile" version "0.3.1" + solver: "z3" version "4.12.5" + runtime: "OpenClaw" version "2.1.0" + license: "MIT" + } +} diff --git a/compiler/src/generators/typescript-state-machine.ts b/compiler/src/generators/typescript-state-machine.ts new file mode 100644 index 0000000..2df9402 --- /dev/null +++ b/compiler/src/generators/typescript-state-machine.ts @@ -0,0 +1,292 @@ +import { AST, SpeckNode, MemberNode, StateNode, InitNode, ActionNode, ConstraintNode, VerifyNode, EventNode } from '../parser.js'; +import fs from 'fs'; +import path from 'path'; + +/** + * Generate TypeScript state machine runtime from a SpeckDL spec. + * + * Outputs a self-contained .ts file with: + * - Typed states as a const enum + * - Event interfaces + * - State machine class with guards, assignments, and decision log + * - Constraint check stubs + * - Verification stubs + */ + +export function generateTypeScriptStateMachine(ast: AST, outputDir: string): void { + for (const speck of ast.specks) { + const code = emitSpeck(speck); + const filename = `${speck.name}.ts`; + const filepath = path.join(outputDir, filename); + fs.writeFileSync(filepath, code); + console.log(`Generated TypeScript state machine: ${filepath}`); + } +} + +function emitSpeck(speck: SpeckNode): string { + const deduped = new Map(); + for (const m of speck.members) { + const key = memberKey(m); + const existing = deduped.get(key); + if (!existing || isEmptyStub(existing)) { + deduped.set(key, m); + } + } + const members = Array.from(deduped.values()); + + const stateNode = members.find(m => m.type === 'state') as StateNode | undefined; + const initNode = members.find(m => m.type === 'init') as InitNode | undefined; + const actions = members.filter(m => m.type === 'action') as ActionNode[]; + const events = members.filter(m => m.type === 'event') as EventNode[]; + const constraints = members.filter(m => m.type === 'constraint') as ConstraintNode[]; + const verifies = members.filter(m => m.type === 'verify') as VerifyNode[]; + + // Extract state enum values from an interface named *State if present + const stateInterface = members.find(m => m.type === 'interface' && m.name.endsWith('State')) as any; + let stateValues: string[] = []; + if (stateInterface?.methods?.length) { + stateValues = stateInterface.methods.map((m: any) => m.name); + } + if (stateValues.length === 0) { + const seen = new Set(); + for (const a of actions) { + for (const s of a.statements) { + if (s.type === 'require') { + const matches = s.expr.match(/\b(Open|Grooming|Groomed|Assigned|InProgress|InReview|Merged|Blocked|Closed)\b/g); + if (matches) matches.forEach(m => seen.add(m)); + } + } + } + stateValues = Array.from(seen).sort(); + } + if (stateValues.length === 0) stateValues = ['Open', 'Closed']; + + const stateEnumName = `${speck.name}State`; + const stateEnum = `export const ${stateEnumName} = {\n` + + stateValues.map((v: string) => ` ${v}: '${v.toLowerCase()}',`).join('\n') + + '\n} as const;\n' + + `export type ${stateEnumName} = typeof ${stateEnumName}[keyof typeof ${stateEnumName}];`; + + const eventInterfaces = events.map(e => { + const fields = e.fields.map(f => { + const name = cleanName(f.name); + return ` ${name}: ${tsType(f.type)};`; + }).join('\n'); + return `export interface ${e.name} {\n${fields}\n}`; + }).join('\n\n'); + + const className = `${speck.name}Machine`; + const stateVars = (stateNode?.variables ?? []).map(v => ({ + name: cleanName(v.name), + typeExpr: v.typeExpr, + defaultInit: v.defaultInit, + })); + + const stateVarNames = new Set(stateVars.map(v => v.name)); + + const stateProperties = stateVars.map(v => { + return ` ${v.name}: ${tsType(v.typeExpr)};`; + }).join('\n'); + + const initAssignments = initNode + ? initNode.assignments.map(a => { + const name = cleanName(a.name); + const expr = cleanExpr(a.expr); + // Replace bare state literals with enum refs in init + const enumRef = replaceStateWithEnum(expr, stateEnumName); + return ` this.${name} = ${enumRef};`; + }).join('\n') + : ''; + + const actionMethods = actions.map(a => emitAction(a, stateVarNames, speck.name, stateEnumName)).join('\n\n'); + + const constraintChecks = constraints.map((c, i) => + ` // C${i + 1}: ${c.expr}\n` + + ` checkConstraint${i + 1}(): boolean {\n` + + ` // TODO: Evaluate in Z3 or runtime\n` + + ` // Expression: ${c.expr}\n` + + ` return true;\n` + + ` }` + ).join('\n\n'); + + const verifyChecks = verifies.map((v, i) => + ` // V${i + 1}: ${v.temporalExpr}\n` + + ` async verify${i + 1}(): Promise {\n` + + ` // TODO: Model-check or monitor\n` + + ` // Temporal: ${v.temporalExpr}\n` + + ` return true;\n` + + ` }` + ).join('\n\n'); + + const classBody = ` +export class ${className} { +${stateProperties} + decisionLog: StateTransition[] = []; + evidence: EvidenceItem[] = []; + + constructor() { +${initAssignments || ' // No init block defined'} + } + +${actionMethods} + +${constraintChecks} + +${verifyChecks} +} +`; + + const file = + `// Auto-generated by speckl-compile from ${speck.name}\n` + + `// DO NOT EDIT MANUALLY — regenerate from .speckdl source\n\n` + + `${stateEnum}\n\n` + + `${eventInterfaces}\n\n` + + `${classBody}`; + + return file; +} + +function emitAction(action: ActionNode, stateVarNames: Set, speckName: string, stateEnumName: string): string { + // Collect local parameter names so we DON'T prefix them with this. + const localNames = new Set(action.params.map(p => p.name)); + + const params = action.params + .map(p => `${p.name}: ${tsType(p.type)}`) + .join(', '); + + const requires = action.statements + .filter(s => s.type === 'require') + .map(s => { + const expr = rewriteExpr(s.expr, stateVarNames, localNames, stateEnumName); + return ` if (!(${expr})) throw new Error('Guard failed: ${expr.replace(/'/g, "\\'")}');`; + }) + .join('\n'); + + const assigns = action.statements + .filter(s => s.type === 'assign') + .map(s => { + const target = cleanName(s.target); + const expr = rewriteExpr(s.expr, stateVarNames, localNames, stateEnumName); + return ` this.${target} = ${expr};`; + }) + .join('\n'); + + const emits = action.statements + .filter(s => s.type === 'emit') + .map(s => { + const fields = s.fields + .map(f => { + const name = cleanName(f.name); + const val = rewriteExpr(f.value, stateVarNames, localNames, stateEnumName); + return ` ${name}: ${val}`; + }) + .join(',\n'); + return ` this.decisionLog.push({\n${fields}\n } as ${s.event});`; + }) + .join('\n'); + + const bodyParts = [requires, assigns, emits].filter(Boolean).join('\n'); + return ` ${action.name}(${params}) {\n${bodyParts}\n }`; +} + +// Rewrite SpeckDL expressions to valid TypeScript +// Only prefix identifiers with `this.` if they are state variables AND not local params +function rewriteExpr(expr: string, stateVarNames: Set, localNames: Set, stateEnumName: string): string { + function shouldPrefix(id: string): boolean { + return stateVarNames.has(id) && !localNames.has(id); + } + + // Step 1: Replace function calls and special syntax + let ts = expr + .replace(/\bnow\(\)/g, 'Date.now()') + .replace(/\blen\(([^)]+)\)/g, '$1.length') + .replace(/\bjoin\(([^,]+),\s*([^)]+)\)/g, '$1.join($2)') + .replace(/\bforall\s+(\w+)\s+in\s+([^:]+):\s*(.+)/g, '$2.every(($1) => $3)'); + + // Step 2: Replace state enum literals with enum refs BEFORE identifier replacement + ts = ts.replace(/\b(Open|Grooming|Groomed|Assigned|InProgress|InReview|Merged|Blocked|Closed)\b/g, `${stateEnumName}.$1`); + + // Step 3: Replace identifiers that are state variables but not local params + const tokens = ts.split(/([\s\(\)\[\]{}+\-*/=<>!&|.,;:'"`]+)/); + const result = tokens.map(tok => { + if (/^[a-zA-Z_]\w*\.\w+$/.test(tok)) return tok; // already has dot (enum ref) + if (/^(true|false|null|undefined|\d+|\w+\.\w+)$/.test(tok)) return tok; + if (/^[a-zA-Z_]\w*$/.test(tok) && shouldPrefix(tok)) { + return `this.${tok}`; + } + return tok; + }).join(''); + + // Step 4: Replace comparison operators + return result + .replace(/\b==\b/g, '===') + .replace(/\b!=\b/g, '!==') + .trim(); +} + +// Helpers ------------------------------------------------------------------ + +function memberKey(m: MemberNode): string { + if (m.type === 'action') return `action:${m.name}`; + if (m.type === 'event') return `event:${m.name}`; + if (m.type === 'interface') return `interface:${m.name}`; + if (m.type === 'state') return 'state'; + if (m.type === 'init') return 'init'; + if (m.type === 'provenance') return 'provenance'; + if (m.type === 'bom') return 'bom'; + if (m.type === 'input') return 'input'; + if (m.type === 'output') return 'output'; + return m.type; +} + +function isEmptyStub(m: MemberNode): boolean { + if (m.type === 'state') return m.variables.length === 0; + if (m.type === 'init') return m.assignments.length === 0; + if (m.type === 'action') return m.statements.length === 0; + if (m.type === 'event') return m.fields.length === 0; + if (m.type === 'interface') return m.methods.length === 0; + if (m.type === 'provenance') return m.clauses.length === 0; + if (m.type === 'bom') return !m.compiler && !m.solver && !m.runtime; + return false; +} + +function cleanName(s: string): string { + return s.replace(/,$/, '').trim(); +} + +function replaceStateWithEnum(expr: string, enumName: string): string { + return expr.replace(/\b(Open|Grooming|Groomed|Assigned|InProgress|InReview|Merged|Blocked|Closed)\b/g, `${enumName}.$1`); +} + +function cleanExpr(s: string): string { + return s.replace(/,$/, '').trim(); +} + +function tsType(t: any): string { + if (!t) return 'unknown'; + if (t.type === 'primitive') { + const map: Record = { + Nat: 'number', Int: 'number', Real: 'number', + Bool: 'boolean', String: 'string', Bytes: 'Uint8Array' + }; + return map[t.name] || t.name.toLowerCase(); + } + if (t.type === 'list') return `${tsType(t.elementType)}[]`; + if (t.type === 'set') return `Set<${tsType(t.elementType)}>`; + if (t.type === 'map') return `Map<${tsType(t.keyType)}, ${tsType(t.valueType)}>`; + if (t.type === 'record') { + const fields = t.fields.map((f: any) => { + const name = cleanName(f.name); + return `${name}: ${tsType(f.type)}`; + }).join('; '); + return `{ ${fields} }`; + } + if (t.type === 'ident') { + const name = cleanName(t.name); + // Map known identifier types + if (name === 'IssueState') return 'IssueWorkflowState'; + if (name === 'Nat') return 'number'; + return name; + } + return 'unknown'; +} diff --git a/compiler/src/generators/wasm.ts b/compiler/src/generators/wasm.ts new file mode 100644 index 0000000..f0c8965 --- /dev/null +++ b/compiler/src/generators/wasm.ts @@ -0,0 +1,473 @@ +import { AST, SpeckNode, StateNode, InitNode, ActionNode, EventNode } from '../parser.js'; +import fs from 'fs'; +import path from 'path'; + +/** + * Generate WebAssembly Text (WAT) from a SpeckDL state machine spec. + * + * Emits a .wat file per speck with: + * - Linear memory for scalar state (Nat/Int/Real/Bool) + * - Imported host functions for Map and Set + * - Exported `init()` — calls host to init Maps/Sets + sets scalars + * - Exported action functions (one per action) + * - Exported `getState()` — serializes scalars into memory + * - Exported `checkConstraints()` — runtime guard stubs + * + * Compile to .wasm with: wat2wasm .wat -o .wasm + * + * Host runtime (JS): see `speckl/runtime/wasm-host.js` + */ + +// --------------------------------------------------------------------------- +// Host imports required by generated modules +// --------------------------------------------------------------------------- +const HOST_IMPORTS = [ + '(import "speckl" "map_create" (func $map_create (result i32)))', + '(import "speckl" "map_set" (func $map_set (param i32 i32 i32)))', + '(import "speckl" "map_get" (func $map_get (param i32 i32) (result i32)))', + '(import "speckl" "map_has" (func $map_has (param i32 i32) (result i32)))', + '(import "speckl" "map_remove" (func $map_remove (param i32 i32)))', + '(import "speckl" "set_create" (func $set_create (result i32)))', + '(import "speckl" "set_add" (func $set_add (param i32 i32)))', + '(import "speckl" "set_has" (func $set_has (param i32 i32) (result i32)))', + '(import "speckl" "set_size" (func $set_size (param i32) (result i32)))', + '(import "speckl" "alloc" (func $alloc (param i32) (result i32)))', + '(import "speckl" "write_i32" (func $write_i32 (param i32 i32)))', + '(import "speckl" "write_f64" (func $write_f64 (param i32 f64)))', +]; + +export function generateWASM(ast: AST, outputDir: string): void { + for (const speck of ast.specks) { + const hasStateMachine = speck.members.some(m => + m.type === 'state' || m.type === 'init' || m.type === 'action' + ); + if (!hasStateMachine) continue; + + const wat = emitSpeckWAT(speck); + const filename = `${speck.name}.wat`; + const filepath = path.join(outputDir, filename); + fs.writeFileSync(filepath, wat); + console.log(`Generated WAT: ${filepath}`); + } +} + +function emitSpeckWAT(speck: SpeckNode): string { + const stateNode = speck.members.find(m => m.type === 'state') as StateNode | undefined; + const initNode = speck.members.find(m => m.type === 'init') as InitNode | undefined; + const actions = speck.members.filter(m => m.type === 'action') as ActionNode[]; + const events = speck.members.filter(m => m.type === 'event') as EventNode[]; + + const stateVars = stateNode?.variables ?? []; + + // ------------------------------------------------------------------------- + // 1. Memory layout for scalar state variables + // ------------------------------------------------------------------------- + // Each scalar gets 8 bytes (i64/f64 slot). Map/Set handles are i32 refs + // stored in globals. + // + // Layout: [0..8) var0, [8..16) var1, ... then string pool for getState() + // + const scalarVars = stateVars.filter(v => isScalarType(v.typeExpr)); + const refVars = stateVars.filter(v => !isScalarType(v.typeExpr)); + + const memSize = Math.max(1, Math.ceil((scalarVars.length * 8 + 1024) / 65536)); + + // ------------------------------------------------------------------------- + // 2. Globals: one per state variable + // ------------------------------------------------------------------------- + const globals = stateVars.map((v, i) => { + if (isScalarType(v.typeExpr)) { + const wasmType = wasmTypeOf(v.typeExpr); + const initVal = initValueFor(v.name, v.typeExpr, v.defaultInit, initNode); + return `(global $${v.name} (mut ${wasmType}) (${wasmType}.const ${initVal}))`; + } else { + // Map or Set — store as i32 handle (0 = uninitialized) + return `(global $${v.name} (mut i32) (i32.const 0))`; + } + }).join('\n'); + + // ------------------------------------------------------------------------- + // 3. init() export + // ------------------------------------------------------------------------- + const initBody = emitInitBody(stateVars, initNode); + + // ------------------------------------------------------------------------- + // 4. Action exports + // ------------------------------------------------------------------------- + const actionFuncs = actions.map(a => emitActionWAT(a, stateVars)).join('\n\n'); + + // ------------------------------------------------------------------------- + // 5. getState() export — write scalars to memory, return ptr+len + // ------------------------------------------------------------------------- + const getStateBody = emitGetState(scalarVars); + + // ------------------------------------------------------------------------- + // 6. checkConstraints() stub + // ------------------------------------------------------------------------- + const checkConstraints = ` + ;; checkConstraints — runtime constraint evaluation stub + (func $checkConstraints (export "checkConstraints") (result i32) + ;; TODO: evaluate constraint expressions at runtime + (i32.const 1) + )`; + + // ------------------------------------------------------------------------- + // 7. Assemble module + // ------------------------------------------------------------------------- + const module = + `;; Auto-generated by speckl-compile from ${speck.name}\n` + + `;; Compile: wat2wasm ${speck.name}.wat -o ${speck.name}.wasm\n\n` + + `(module\n` + + ` ;; Host imports for Map/Set/alloc\n` + + ` ${HOST_IMPORTS.join('\n ')}\n\n` + + ` ;; Linear memory (exported so host can read state)\n` + + ` (memory (export "memory") ${memSize})\n\n` + + ` ;; State globals\n` + + ` ${globals}\n\n` + + ` ;; ---- init ----\n` + + ` (func $init (export "init")\n` + + ` ${initBody}\n` + + ` )\n\n` + + ` ;; ---- Actions ----\n` + + ` ${actionFuncs}\n\n` + + ` ;; ---- getState ----\n` + + ` (func $getState (export "getState") (result i32 i32)\n` + + ` ${getStateBody}\n` + + ` )\n\n` + + ` ${checkConstraints}\n` + + `)`; + + return module; +} + +// ============================================================================= +// init() body generation +// ============================================================================= +function emitInitBody( + stateVars: StateNode['variables'], + initNode: InitNode | undefined +): string { + const lines: string[] = []; + + for (const v of stateVars) { + if (isScalarType(v.typeExpr)) { + const val = initValueFor(v.name, v.typeExpr, v.defaultInit, initNode); + const wasmType = wasmTypeOf(v.typeExpr); + lines.push(`(${wasmType}.const ${val})`); + lines.push(`(global.set $${v.name})`); + } else if (isMapType(v.typeExpr)) { + lines.push(`(call $map_create)`); + lines.push(`(global.set $${v.name})`); + } else if (isSetType(v.typeExpr)) { + lines.push(`(call $set_create)`); + lines.push(`(global.set $${v.name})`); + } + } + + return lines.join('\n '); +} + +// ============================================================================= +// Action function generation +// ============================================================================= +function emitActionWAT(action: ActionNode, stateVars: StateNode['variables']): string { + const scalarStateNames = new Set( + stateVars.filter(v => isScalarType(v.typeExpr)).map(v => v.name) + ); + const mapStateNames = new Set( + stateVars.filter(v => isMapType(v.typeExpr)).map(v => v.name) + ); + const setStateNames = new Set( + stateVars.filter(v => isSetType(v.typeExpr)).map(v => v.name) + ); + + // Params: SpeckDL Nat/Int → i32, Real → f64, Bool → i32 + const params = action.params.map(p => { + const t = wasmTypeOf(p.type); + return `(param $${p.name} ${t})`; + }).join(' '); + + const localLines: string[] = []; + const bodyLines: string[] = []; + + for (const stmt of action.statements) { + switch (stmt.type) { + case 'require': { + // Emit guard: if !condition → return 0 (failure indicator) + // For now, require guards that reference only scalar state variables + // and simple comparisons are emitted. Complex ones trap/return 0. + const guard = emitGuardExpr(stmt.expr, scalarStateNames, mapStateNames, setStateNames); + bodyLines.push(`;; require: ${stmt.expr}`); + bodyLines.push(guard); + break; + } + case 'assign': { + bodyLines.push(`;; ${stmt.target} := ${stmt.expr}`); + bodyLines.push(emitAssignment(stmt.target, stmt.expr, scalarStateNames, mapStateNames)); + break; + } + case 'let': { + // let name := expr — allocate local, compute expr, store + const wasmT = inferExprType(stmt.expr, scalarStateNames); + localLines.push(`(local $${stmt.name} ${wasmT})`); + bodyLines.push(emitExpr(stmt.expr, scalarStateNames)); + bodyLines.push(`(local.set $${stmt.name})`); + break; + } + case 'emit': { + // emit → decision log push (host call stub for now) + bodyLines.push(`;; emit ${stmt.event} { ... }`); + // TODO: structured emit via host + break; + } + case 'return': { + bodyLines.push(emitExpr(stmt.expr, scalarStateNames)); + bodyLines.push(`(return)`); + break; + } + } + } + + // Default return: i32.const 1 for success if action returns void + const hasReturn = action.statements.some(s => s.type === 'return'); + if (!hasReturn) { + bodyLines.push(`(i32.const 1)`); + } + + const locals = localLines.length ? `\n ${localLines.join('\n ')}` : ''; + + return ` ;; action: ${action.name}\n` + + ` (func $${action.name} (export "${action.name}") ${params} (result i32)\n` + + ` ${bodyLines.join('\n ')}\n` + + ` )`; +} + +// ============================================================================= +// Expression / guard emission (simplified — covers common scalar patterns) +// ============================================================================= +function emitGuardExpr( + expr: string, + scalars: Set, + maps: Set, + sets: Set +): string { + // Very simple pattern: "x >= 0", "x == true", "x != y", etc. + const m = expr.match(/^\s*(\w+)\s*(>=|<=|>|<|==|!=)\s*(.+?)\s*$/); + if (!m) { + // Can't compile guard — return 1 (pass) with comment + return `;; Complex guard not compiled: ${expr}\n (i32.const 1)`; + } + + const [, left, op, rightRaw] = m; + const right = rightRaw.trim(); + + if (!scalars.has(left)) { + return `;; Guard on non-scalar not compiled: ${expr}\n (i32.const 1)`; + } + + const lhs = `(global.get $${left})`; + + // Parse right side + let rhs: string; + const rightNum = parseFloat(right); + if (!isNaN(rightNum)) { + rhs = `(i32.const ${rightNum})`; + } else if (right === 'true') { + rhs = `(i32.const 1)`; + } else if (right === 'false') { + rhs = `(i32.const 0)`; + } else if (scalars.has(right)) { + rhs = `(global.get $${right})`; + } else { + return `;; Complex right-hand side not compiled: ${expr}\n (i32.const 1)`; + } + + const comparison = (() => { + switch (op) { + case '>=': return `(i32.ge_s ${lhs} ${rhs})`; + case '<=': return `(i32.le_s ${lhs} ${rhs})`; + case '>': return `(i32.gt_s ${lhs} ${rhs})`; + case '<': return `(i32.lt_s ${lhs} ${rhs})`; + case '==': return `(i32.eq ${lhs} ${rhs})`; + case '!=': return `(i32.ne ${lhs} ${rhs})`; + default: return `(i32.const 1)`; + } + })(); + + // If guard fails, return 0 + return `${comparison}\n (if (then (return (i32.const 0))))`; +} + +function emitExpr(expr: string, scalars: Set): string { + // Handle: nextId + 1, nextId, numeric literals, etc. + expr = expr.trim(); + + // Simple numeric literal + const num = parseFloat(expr); + if (!isNaN(num) && !expr.match(/\w/)) { + return `(i32.const ${num})`; + } + + // Simple variable reference + if (/^\w+$/.test(expr) && scalars.has(expr)) { + return `(global.get $${expr})`; + } + + // Binary op: left + right, left - right, left * right + const binOp = expr.match(/^(\w+)\s*([+\-*/])\s*(.+)$/); + if (binOp) { + const [, left, op, right] = binOp; + const lhs = scalars.has(left) ? `(global.get $${left})` : `(i32.const ${parseFloat(left)})`; + const rhsNum = parseFloat(right.trim()); + const rhs = isNaN(rhsNum) + ? (scalars.has(right.trim()) ? `(global.get $${right.trim()})` : `(i32.const 0) ;; uncompiled`) + : `(i32.const ${rhsNum})`; + switch (op) { + case '+': return `(i32.add ${lhs} ${rhs})`; + case '-': return `(i32.sub ${lhs} ${rhs})`; + case '*': return `(i32.mul ${lhs} ${rhs})`; + default: return `(i32.const 0) ;; uncompiled op ${op}`; + } + } + + return `(i32.const 0) ;; uncompiled expr: ${expr}`; +} + +function inferExprType(expr: string, scalars: Set): string { + // Default to i32; we could infer from variable types but that's future work + return 'i32'; +} + +function emitAssignment( + target: string, + expr: string, + scalars: Set, + maps: Set +): string { + // Scalar assignment: this.x = expr + if (scalars.has(target)) { + return `${emitExpr(expr, scalars)}\n (global.set $${target})`; + } + + // Map assignment: accounts[id] := expr — host call + const mapAssign = target.match(/^(\w+)\[(.+)\]$/); + if (mapAssign && maps.has(mapAssign[1])) { + const mapName = mapAssign[1]; + const keyExpr = mapAssign[2]; + // For now: map_set(map_handle, key_handle, value_handle) + // This needs boxing; simplified version just marks as stub + return `;; Map assignment not yet compiled: ${target} := ${expr}\n` + + ` (i32.const 1)`; + } + + // Record field assignment: accounts[id].field := expr + const recordAssign = target.match(/^(\w+)\[(.+)\]\.(\w+)$/); + if (recordAssign && maps.has(recordAssign[1])) { + return `;; Record field assignment not yet compiled: ${target} := ${expr}\n` + + ` (i32.const 1)`; + } + + return `;; Unrecognized assignment target: ${target}\n (i32.const 1)`; +} + +// ============================================================================= +// getState() — serialize scalar state to memory +// ============================================================================= +function emitGetState(scalarVars: StateNode['variables']): string { + const lines: string[] = []; + let offset = 0; + + for (const v of scalarVars) { + const t = wasmTypeOf(v.typeExpr); + if (t === 'i32') { + lines.push(`(i32.const ${offset})`); + lines.push(`(global.get $${v.name})`); + lines.push(`(i32.store)`); + offset += 4; + } else if (t === 'f64') { + lines.push(`(i32.const ${offset})`); + lines.push(`(global.get $${v.name})`); + lines.push(`(f64.store)`); + offset += 8; + } else { + // i64 + lines.push(`(i32.const ${offset})`); + lines.push(`(global.get $${v.name})`); + lines.push(`(i64.store)`); + offset += 8; + } + } + + // Return (ptr, len) + lines.push(`(i32.const 0)`); // ptr = 0 + lines.push(`(i32.const ${offset})`); // len = total bytes written + + return lines.join('\n '); +} + +// ============================================================================= +// Type helpers +// ============================================================================= +function isScalarType(t: any): boolean { + if (t.type === 'primitive') { + return ['Nat', 'Int', 'Real', 'Bool'].includes(t.name); + } + if (t.type === 'ident') return true; // opaque ident → i32 handle + return false; +} + +function isMapType(t: any): boolean { + return t.type === 'map'; +} + +function isSetType(t: any): boolean { + return t.type === 'set'; +} + +function wasmTypeOf(t: any): string { + if (t.type === 'primitive') { + if (t.name === 'Nat') return 'i32'; + if (t.name === 'Int') return 'i32'; + if (t.name === 'Real') return 'f64'; + if (t.name === 'Bool') return 'i32'; + if (t.name === 'String') return 'i32'; // ptr + if (t.name === 'Bytes') return 'i32'; // ptr + return 'i32'; + } + if (t.type === 'ident') return 'i32'; + if (t.type === 'map') return 'i32'; // handle + if (t.type === 'set') return 'i32'; // handle + return 'i32'; +} + +function initValueFor( + varName: string, + typeExpr: any, + defaultInit: string | undefined, + initNode: InitNode | undefined +): string { + // Use initNode assignment if present + const initAssign = initNode?.assignments.find(a => a.name === varName); + if (initAssign) { + const expr = initAssign.expr.trim(); + if (expr === 'true') return '1'; + if (expr === 'false') return '0'; + if (expr === 'Map.empty()') return '0'; // will be replaced by host call + if (expr === 'Set.empty()') return '0'; + const num = parseFloat(expr); + if (!isNaN(num)) return String(num); + return '0'; + } + + // Fallback to defaultInit + if (defaultInit) { + if (defaultInit === 'true') return '1'; + if (defaultInit === 'false') return '0'; + const num = parseFloat(defaultInit); + if (!isNaN(num)) return String(num); + } + + // Default zero + if (typeExpr.type === 'primitive' && typeExpr.name === 'Real') return '0.0'; + return '0'; +} diff --git a/compiler/src/index.ts b/compiler/src/index.ts index c1f8673..525fe9d 100644 --- a/compiler/src/index.ts +++ b/compiler/src/index.ts @@ -6,6 +6,8 @@ import { parseSpeckFile } from './parser.js'; import { generateProvenanceGraph } from './generators/provenance.js'; import { generateCycloneDX } from './generators/cyclonedx.js'; import { generateSPDX } from './generators/spdx.js'; +import { generateTypeScriptStateMachine } from './generators/typescript-state-machine.js'; +import { generateWASM } from './generators/wasm.js'; import fs from 'fs'; interface CompileOptions { @@ -63,6 +65,14 @@ async function main() { console.log('\nGenerating provenance graph (PROV-O JSON-LD)...'); generateProvenanceGraph(ast, options.outputDir); + // Generate TypeScript state machine + console.log('\nGenerating TypeScript state machine...'); + generateTypeScriptStateMachine(ast, options.outputDir); + + // Generate WASM state machine + console.log('\nGenerating WebAssembly (WAT)...'); + generateWASM(ast, options.outputDir); + // Generate BOM based on format selection if (options.bomFormat === 'cdx' || options.bomFormat === 'both') { console.log('\nGenerating CycloneDX BOM...'); diff --git a/compiler/src/parser.ts b/compiler/src/parser.ts index 798c612..559ff3a 100644 --- a/compiler/src/parser.ts +++ b/compiler/src/parser.ts @@ -104,6 +104,37 @@ export interface BOMNode { hash?: string; } +// State machine extension (v0.3) +export interface StateNode { + type: 'state'; + variables: StateVar[]; +} + +export interface StateVar { + name: string; + typeExpr: TypeExpr; + defaultInit?: string; // default initializer expression as raw string +} + +export interface InitNode { + type: 'init'; + assignments: { name: string; expr: string }[]; +} + +export interface ActionNode { + type: 'action'; + name: string; + params: { name: string; type: TypeExpr }[]; + statements: ActionStatement[]; +} + +export type ActionStatement = + | { type: 'assign'; target: string; expr: string } + | { type: 'let'; name: string; expr: string } + | { type: 'require'; expr: string } + | { type: 'emit'; event: string; fields: { name: string; value: string }[] } + | { type: 'return'; expr: string }; + export type MemberNode = | ImportNode | InputNode @@ -118,13 +149,18 @@ export type MemberNode = | SatisfiesNode | AuthorNode | SourceNode - | BOMNode; + | BOMNode + | StateNode + | InitNode + | ActionNode; export interface TypeExpr { - type: 'primitive' | 'record' | 'list' | 'ident'; + type: 'primitive' | 'record' | 'list' | 'set' | 'map' | 'ident'; name?: string; fields?: { name: string; type: TypeExpr }[]; elementType?: TypeExpr; + keyType?: TypeExpr; // for Map + valueType?: TypeExpr; // for Map } export interface AST { @@ -185,51 +221,107 @@ export function parseSpeckContent(content: string, filePath?: string): AST { } function parseSpeck(lines: string[], startIndex: number): SpeckNode | null { - // Parse speck header: speck { const headerMatch = lines[startIndex].match(/^speck\s+([\w-]+)\s*{/); - if (!headerMatch) { - return null; - } - + if (!headerMatch) return null; + const name = headerMatch[1]; const members: MemberNode[] = []; - + let i = startIndex + 1; let braceCount = 1; - + while (i < lines.length && braceCount > 0) { - const line = lines[i].trim(); - - // Skip empty lines and comments + const rawLine = lines[i]; + const line = rawLine.trim(); + if (!line || line.startsWith('//') || line.startsWith('/*')) { i++; continue; } - - if (line.includes('{')) { - braceCount++; - } - if (line.includes('}')) { - braceCount--; - if (braceCount === 0) { - break; + + // Count braces on this line first + const openCount = (rawLine.match(/{/g) || []).length; + const closeCount = (rawLine.match(/}/g) || []).length; + + // Check for block-opening members + const blockStarters = ['state:', 'init:', 'action ', 'event ', 'provenance ', 'bom ', 'interface ']; + const isBlockStarter = blockStarters.some(s => line.startsWith(s)); + + if (isBlockStarter && openCount > 0) { + // Multi-line block: parse it and skip to its end + const member = parseMemberBlock(lines, i); + if (member) { + members.push(member); } + // Skip past this block + const endIdx = findBlockEnd(lines, i + 1, braceCount + openCount); + i = endIdx + 1; + continue; } - - // Parse member + + // Single-line: update brace count, then parse if still inside + braceCount += openCount - closeCount; + if (braceCount <= 0) { + i++; + break; + } + const member = parseMember(line); if (member) { members.push(member); } - i++; } + + return { type: 'speck', name, members }; +} + +function findBlockEnd(lines: string[], startIndex: number, startBraceCount: number): number { + let braceCount = startBraceCount; + let i = startIndex; + while (i < lines.length) { + const line = lines[i]; + for (const ch of line) { + if (ch === '{') braceCount++; + if (ch === '}') { + braceCount--; + if (braceCount < startBraceCount) { + return i; + } + } + } + i++; + } + return i; +} + +function parseMemberBlock(lines: string[], startIndex: number): MemberNode | null { + const firstLine = lines[startIndex].trim(); + const startBraceCount = (firstLine.match(/{/g) || []).length; - return { - type: 'speck', - name, - members, - }; + if (firstLine.startsWith('state:')) { + return parseStateBlockMultiline(lines, startIndex, startBraceCount); + } + if (firstLine.startsWith('init:')) { + return parseInitBlockMultiline(lines, startIndex, startBraceCount); + } + if (firstLine.startsWith('action ')) { + return parseActionBlockMultiline(lines, startIndex, startBraceCount); + } + if (firstLine.startsWith('event ')) { + return parseEventBlockMultiline(lines, startIndex, startBraceCount); + } + if (firstLine.startsWith('interface ')) { + return parseInterfaceBlockMultiline(lines, startIndex, startBraceCount); + } + if (firstLine.startsWith('provenance ')) { + return { type: 'provenance', clauses: [] }; + } + if (firstLine.startsWith('bom ')) { + return { type: 'bom' }; + } + + return null; } function parseMember(line: string): MemberNode | null { @@ -296,6 +388,33 @@ function parseMember(line: string): MemberNode | null { return parseSource(line); } + // state: + if (line.startsWith('state:')) { + const varsStr = line.substring(6).trim(); + // state: { ... } - single-line form + if (varsStr.startsWith('{') && varsStr.endsWith('}')) { + const inner = varsStr.slice(1, -1).trim(); + return parseStateBlock(inner); + } + // state: { - multi-line opening (handled by parseStateMultiline) + return { type: 'state', variables: [] }; + } + + // init: + if (line.startsWith('init:')) { + const initStr = line.substring(5).trim(); + if (initStr.startsWith('{') && initStr.endsWith('}')) { + const inner = initStr.slice(1, -1).trim(); + return parseInitBlock(inner); + } + return { type: 'init', assignments: [] }; + } + + // action keyword + if (line.startsWith('action ')) { + return parseActionHeader(line); + } + // bom { if (line.startsWith('bom ')) { // Simplified - full BOM block parsing would need multi-line context @@ -307,6 +426,71 @@ function parseMember(line: string): MemberNode | null { return null; } +function parseStateBlock(inner: string): StateNode { + const variables: StateVar[] = []; + if (!inner.trim()) return { type: 'state', variables }; + + const varStrs = inner.split(/;|\n/).map(s => s.trim()).filter(Boolean); + + for (const vStr of varStrs) { + const withDefault = vStr.match(/^(\w+)\s*:\s*(.+?)\s*:=\s*(.+)$/); + if (withDefault) { + variables.push({ + name: withDefault[1], + typeExpr: parseTypeExpr(withDefault[2].trim()), + defaultInit: withDefault[3].trim(), + }); + continue; + } + const withoutDefault = vStr.match(/^(\w+)\s*:\s*(.+)$/); + if (withoutDefault) { + variables.push({ + name: withoutDefault[1], + typeExpr: parseTypeExpr(withoutDefault[2].trim()), + }); + } + } + + return { type: 'state', variables }; +} + +function parseInitBlock(inner: string): InitNode { + const assignments: { name: string; expr: string }[] = []; + if (!inner.trim()) return { type: 'init', assignments }; + + const assignStrs = inner.split(/;|\n/).map(s => s.trim()).filter(Boolean); + + for (const aStr of assignStrs) { + const match = aStr.match(/^(\w+)\s*:=\s*(.+)$/); + if (match) { + assignments.push({ name: match[1], expr: match[2].trim() }); + } + } + + return { type: 'init', assignments }; +} + +function parseActionHeader(line: string): ActionNode | null { + const match = line.match(/^action\s+(\w+)\s*(?:\(([^)]*)\))?\s*\{/); + if (!match) return null; + + const name = match[1]; + const paramsStr = match[2] || ''; + const params: { name: string; type: TypeExpr }[] = []; + + if (paramsStr.trim()) { + const paramPairs = paramsStr.split(',').map(s => s.trim()).filter(Boolean); + for (const p of paramPairs) { + const pMatch = p.match(/^(\w+)\s*:\s*(.+)$/); + if (pMatch) { + params.push({ name: pMatch[1], type: parseTypeExpr(pMatch[2].trim()) }); + } + } + } + + return { type: 'action', name, params, statements: [] }; +} + function parseImportStatement(line: string): ImportNode { const match = line.match(/^import\s+(['"])(.+?)\1(?:\s+as\s+(\w+))?(?:\s+version\s+(['"])(.+?)\4)?(?:\s+hash\s+(['"])(.+?)\6)?/); if (!match) { @@ -333,6 +517,35 @@ function parseTypeExpr(expr: string): TypeExpr { return { type: 'primitive', name: expr }; } + // Set + if (expr.startsWith('Set<') && expr.endsWith('>')) { + const inner = expr.substring(4, expr.length - 1).trim(); + return { type: 'set', elementType: parseTypeExpr(inner) }; + } + + // Map + if (expr.startsWith('Map<') && expr.endsWith('>')) { + const inner = expr.substring(4, expr.length - 1); + // Find the first-level comma (not inside nested generics) + let depth = 0; + let commaIdx = -1; + for (let i = 0; i < inner.length; i++) { + if (inner[i] === '<') depth++; + else if (inner[i] === '>') depth--; + else if (inner[i] === ',' && depth === 0) { + commaIdx = i; + break; + } + } + if (commaIdx !== -1) { + const keyStr = inner.substring(0, commaIdx).trim(); + const valStr = inner.substring(commaIdx + 1).trim(); + return { type: 'map', keyType: parseTypeExpr(keyStr), valueType: parseTypeExpr(valStr) }; + } + // Fallback: treat as ident if malformed + return { type: 'ident', name: expr }; + } + // List type: [T] if (expr.startsWith('[') && expr.endsWith(']')) { const elementType = parseTypeExpr(expr.substring(1, expr.length - 1)); @@ -486,9 +699,121 @@ function parseSource(line: string): SourceNode { }; } -function parseBOMBlock(lines: string[], line: string): BOMNode { - // This is a simplified parser - in a real implementation, we'd parse the full block - return { - type: 'bom', - }; +function parseStateBlockMultiline(lines: string[], startIndex: number, startBraceCount: number): StateNode { + const endIndex = findBlockEnd(lines, startIndex + 1, startBraceCount); + const innerLines = lines.slice(startIndex + 1, endIndex).map(l => l.trim()).filter(l => l && !l.startsWith('//') && !l.startsWith('/*')); + const inner = innerLines.join('; '); + return parseStateBlock(inner); +} + +function parseInitBlockMultiline(lines: string[], startIndex: number, startBraceCount: number): InitNode { + const endIndex = findBlockEnd(lines, startIndex + 1, startBraceCount); + const innerLines = lines.slice(startIndex + 1, endIndex).map(l => l.trim()).filter(l => l && !l.startsWith('//') && !l.startsWith('/*')); + const inner = innerLines.join('; '); + return parseInitBlock(inner); +} + +function parseActionBlockMultiline(lines: string[], startIndex: number, startBraceCount: number): ActionNode | null { + const firstLine = lines[startIndex].trim(); + const header = parseActionHeader(firstLine); + if (!header) return null; + + const endIndex = findBlockEnd(lines, startIndex + 1, startBraceCount); + const bodyLines = lines.slice(startIndex + 1, endIndex).map(l => l.trim()).filter(l => l && !l.startsWith('//') && !l.startsWith('/*')); + + const statements: ActionStatement[] = []; + for (const stmt of bodyLines) { + // require expr + if (stmt.startsWith('require ')) { + statements.push({ type: 'require', expr: stmt.substring(8).trim() }); + continue; + } + // return expr + if (stmt.startsWith('return ')) { + statements.push({ type: 'return', expr: stmt.substring(7).trim() }); + continue; + } + // emit Event { fields } + const emitMatch = stmt.match(/^emit\s+(\w+)\s*\{\s*(.*)\s*\}$/); + if (emitMatch) { + const eventName = emitMatch[1]; + const fieldsStr = emitMatch[2]; + const fields: { name: string; value: string }[] = []; + if (fieldsStr) { + const fieldPairs = fieldsStr.split(',').map(s => s.trim()).filter(Boolean); + for (const fp of fieldPairs) { + const colonIdx = fp.indexOf(':'); + if (colonIdx > 0) { + fields.push({ + name: fp.substring(0, colonIdx).trim(), + value: fp.substring(colonIdx + 1).trim(), + }); + } + } + } + statements.push({ type: 'emit', event: eventName, fields }); + continue; + } + // let name := expr + const letMatch = stmt.match(/^let\s+(\w+)\s*:=\s*(.+)$/); + if (letMatch) { + statements.push({ type: 'let', name: letMatch[1], expr: letMatch[2].trim() }); + continue; + } + // assignment: target := expr + const assignMatch = stmt.match(/^(\w+(?:\[.*?\])?)\s*:=\s*(.+)$/); + if (assignMatch) { + statements.push({ type: 'assign', target: assignMatch[1], expr: assignMatch[2].trim() }); + } + } + + return { ...header, statements }; +} + +function parseEventBlockMultiline(lines: string[], startIndex: number, startBraceCount: number): EventNode { + const firstLine = lines[startIndex].trim(); + const header = parseEvent(firstLine); + const endIndex = findBlockEnd(lines, startIndex + 1, startBraceCount); + const innerLines = lines.slice(startIndex + 1, endIndex).map(l => l.trim()).filter(l => l && !l.startsWith('//') && !l.startsWith('/*')); + + const fields: { name: string; type: TypeExpr }[] = []; + for (const line of innerLines) { + const match = line.match(/^(\w+)\s*:\s*(.+?),?$/); + if (match) { + fields.push({ name: match[1], type: parseTypeExpr(match[2].replace(/,$/, '').trim()) }); + } + } + + return { ...header, fields }; +} + +function parseInterfaceBlockMultiline(lines: string[], startIndex: number, startBraceCount: number): InterfaceNode { + const firstLine = lines[startIndex].trim(); + const header = parseInterface(firstLine); + const endIndex = findBlockEnd(lines, startIndex + 1, startBraceCount); + const innerLines = lines.slice(startIndex + 1, endIndex).map(l => l.trim()).filter(l => l && !l.startsWith('//') && !l.startsWith('/*')); + + const methods: MethodSignature[] = []; + for (const line of innerLines) { + // name(params) -> returnType + const match = line.match(/^(\w+)\s*\(([^)]*)\)\s*-\u003e\s*(.+?),?$/); + if (match) { + const name = match[1]; + const paramsStr = match[2]; + const returnTypeStr = match[3].replace(/,$/, '').trim(); + const params: { name: string; type: TypeExpr }[] = []; + if (paramsStr.trim()) { + const paramPairs = paramsStr.split(',').map(s => s.trim()).filter(Boolean); + for (const p of paramPairs) { + const pMatch = p.match(/^(\w+)\s*:\s*(.+)$/); + if (pMatch) { + params.push({ name: pMatch[1], type: parseTypeExpr(pMatch[2].trim()) }); + } + } + } + methods.push({ name, params, returnType: parseTypeExpr(returnTypeStr) }); + } + } + + return { ...header, methods }; } diff --git a/compiler/src/runtime/wasm-host.ts b/compiler/src/runtime/wasm-host.ts new file mode 100644 index 0000000..513892b --- /dev/null +++ b/compiler/src/runtime/wasm-host.ts @@ -0,0 +1,165 @@ +/** + * Speckl WASM Host Runtime + * + * Provides JS-side implementations for imported host functions used by + * Speckl-generated WASM modules: + * - map_create, map_set, map_get, map_has, map_remove + * - set_create, set_add, set_has, set_size + * - alloc, write_i32, write_f64 + * + * Usage: + * const wasmModule = await WebAssembly.instantiate(bytes, { + * speckl: createHostImports() + * }); + * wasmModule.exports.init(); + * wasmModule.exports.CreateAccount(100.0); + * const state = getStateFromMemory(wasmModule); + */ + +interface HostState { + maps: Map>; + sets: Map>; + nextHandle: number; + memory: WebAssembly.Memory | null; +} + +function createHostState(): HostState { + return { + maps: new Map(), + sets: new Map(), + nextHandle: 1, + memory: null, + }; +} + +export function createHostImports(state: HostState = createHostState()) { + return { + // ---- Map operations ---- + map_create: (): number => { + const handle = state.nextHandle++; + state.maps.set(handle, new Map()); + return handle; + }, + + map_set: (mapHandle: number, keyHandle: number, valueHandle: number): void => { + const map = state.maps.get(mapHandle); + if (!map) throw new Error(`Map handle ${mapHandle} not found`); + // For now, keys and values are simple i32 handles; boxing layer comes later + map.set(keyHandle, valueHandle); + }, + + map_get: (mapHandle: number, keyHandle: number): number => { + const map = state.maps.get(mapHandle); + if (!map) return 0; + return map.has(keyHandle) ? (map.get(keyHandle) ?? 0) : 0; + }, + + map_has: (mapHandle: number, keyHandle: number): number => { + const map = state.maps.get(mapHandle); + if (!map) return 0; + return map.has(keyHandle) ? 1 : 0; + }, + + map_remove: (mapHandle: number, keyHandle: number): void => { + const map = state.maps.get(mapHandle); + if (map) map.delete(keyHandle); + }, + + // ---- Set operations ---- + set_create: (): number => { + const handle = state.nextHandle++; + state.sets.set(handle, new Set()); + return handle; + }, + + set_add: (setHandle: number, valueHandle: number): void => { + const set = state.sets.get(setHandle); + if (!set) throw new Error(`Set handle ${setHandle} not found`); + set.add(valueHandle); + }, + + set_has: (setHandle: number, valueHandle: number): number => { + const set = state.sets.get(setHandle); + if (!set) return 0; + return set.has(valueHandle) ? 1 : 0; + }, + + set_size: (setHandle: number): number => { + const set = state.sets.get(setHandle); + if (!set) return 0; + return set.size; + }, + + // ---- Memory helpers ---- + alloc: (size: number): number => { + // Simple bump allocator: for now, just return 0 (stack allocation handled by compiler) + // Future: maintain bump pointer in memory + return 0; + }, + + write_i32: (ptr: number, value: number): void => { + if (!state.memory) throw new Error('Memory not bound'); + const view = new DataView(state.memory.buffer); + view.setInt32(ptr, value, true); // little-endian + }, + + write_f64: (ptr: number, value: number): void => { + if (!state.memory) throw new Error('Memory not bound'); + const view = new DataView(state.memory.buffer); + view.setFloat64(ptr, value, true); + }, + }; +} + +/** + * Bind a WASM memory export to the host state so write_i32/write_f64 work. + */ +export function bindMemory(state: HostState, memory: WebAssembly.Memory): void { + state.memory = memory; +} + +/** + * Read scalar state from WASM memory after getState() has been called. + * Layout matches emitGetState(): [nextId: i32][ledgerOpen: i32]... + */ +export function readScalarState(memory: WebAssembly.Memory, varNames: string[]): Record { + const view = new DataView(memory.buffer); + const result: Record = {}; + let offset = 0; + + for (const name of varNames) { + // All current scalars are i32 in TigerBeetle; extend for f64 later + result[name] = view.getInt32(offset, true); + offset += 4; + } + + return result; +} + +/** + * High-level helper: instantiate a Speckl WASM module from WAT text. + * Requires `wat2wasm` or a WAT parser. For now, expects pre-compiled .wasm bytes. + */ +export async function instantiateSpeckl( + wasmBytes: BufferSource, + varNames: string[] +): Promise<{ + exports: WebAssembly.Exports; + state: HostState; + getScalarState: () => Record; +}> { + const hostState = createHostState(); + const imports = { speckl: createHostImports(hostState) }; + + const wasm = await WebAssembly.instantiate(wasmBytes, imports); + const memory = wasm.instance.exports.memory as WebAssembly.Memory; + bindMemory(hostState, memory); + + const getScalarState = () => readScalarState(memory, varNames); + + return { + exports: wasm.instance.exports, + state: hostState, + getScalarState, + }; +} diff --git a/compiler/test-output/RetryHandler.prov.jsonld b/compiler/test-output/RetryHandler.prov.jsonld index c4a0ce7..00ff164 100644 --- a/compiler/test-output/RetryHandler.prov.jsonld +++ b/compiler/test-output/RetryHandler.prov.jsonld @@ -12,22 +12,22 @@ "@type": "prov:Generation", "prov:activity": { "@type": "prov:Activity", - "prov:startTime": "2026-04-29T22:11:59.830Z" + "prov:startTime": "2026-05-03T18:51:34.102Z" } }, "rdfs:label": "RetryHandler", "prov:wasDerivedFrom": { "@id": "speck:FaultHandler" }, - "rdfs:comment": "\"", + "rdfs:comment": "specialization for retry logic", "speckl:satisfies": { - "@id": "requirement:RESILIENCE" + "@id": "requirement:RESILIENCE-REQ-01" }, "prov:wasAttributedTo": { - "@id": "agent:\"" + "@id": "agent:Scott Scoble" }, "prov:wasInformedBy": { - "@id": "source:architecture_review:\"" + "@id": "source:architecture_review:AR-2024-0215" } }, { @@ -36,25 +36,26 @@ }, { "@type": "prov:Entity", - "@id": "requirement:RESILIENCE", - "rdfs:label": "RESILIENCE" + "@id": "requirement:RESILIENCE-REQ-01", + "rdfs:label": "RESILIENCE-REQ-01", + "speckl:clause": "3.1.2" }, { "@type": "prov:Entity", - "@id": "requirement:RESILIENCE", - "rdfs:label": "RESILIENCE" + "@id": "requirement:RESILIENCE-REQ-02", + "rdfs:label": "RESILIENCE-REQ-02" }, { "@type": "prov:Agent", - "@id": "agent:\"", - "rdfs:label": "\"", - "foaf:email": "Scott Scoble" + "@id": "agent:Scott Scoble", + "rdfs:label": "Scott Scoble", + "foaf:email": "scott@scoble.me" }, { "@type": "prov:Activity", - "@id": "source:architecture_review:\"", + "@id": "source:architecture_review:AR-2024-0215", "prov:type": "architecture_review", - "rdfs:label": "architecture_review - \"" + "rdfs:label": "architecture_review - AR-2024-0215" } ] } \ No newline at end of file diff --git a/compiler/test-output/RetryHandler.specbom.cdx.json b/compiler/test-output/RetryHandler.specbom.cdx.json index de9d9d5..7cf043d 100644 --- a/compiler/test-output/RetryHandler.specbom.cdx.json +++ b/compiler/test-output/RetryHandler.specbom.cdx.json @@ -15,11 +15,11 @@ }, { "name": "speckl:satisfies", - "value": "RESILIENCE" + "value": "RESILIENCE-REQ-01 clause 3.1.2" }, { "name": "speckl:satisfies", - "value": "RESILIENCE" + "value": "RESILIENCE-REQ-02" } ] } @@ -35,10 +35,10 @@ } ], "author": { - "name": "\"", - "email": "Scott Scoble" + "name": "Scott Scoble", + "email": "scott@scoble.me" }, - "timestamp": "2026-04-29T22:11:59.832Z" + "timestamp": "2026-05-03T18:51:34.104Z" }, { "subject": "speck:RetryHandler", @@ -53,7 +53,7 @@ "name": "Speckl Compiler", "email": "compiler@speckl.scoble.me" }, - "timestamp": "2026-04-29T22:11:59.832Z" + "timestamp": "2026-05-03T18:51:34.104Z" }, { "subject": "speck:RetryHandler", @@ -68,7 +68,7 @@ "name": "Speckl Compiler", "email": "compiler@speckl.scoble.me" }, - "timestamp": "2026-04-29T22:11:59.832Z" + "timestamp": "2026-05-03T18:51:34.104Z" } ] } \ No newline at end of file diff --git a/compiler/test-output/RetryHandler.specbom.spdx.json b/compiler/test-output/RetryHandler.specbom.spdx.json index 65e7ec3..5969981 100644 --- a/compiler/test-output/RetryHandler.specbom.spdx.json +++ b/compiler/test-output/RetryHandler.specbom.spdx.json @@ -13,7 +13,7 @@ "spdxVersion": "3.0.1", "dataLicense": "CC0-1.0", "creationInfo": { - "created": "2026-04-29T22:11:59.833Z", + "created": "2026-05-03T18:51:34.105Z", "createdBy": [ "Organization: Speckl", "tool: speckl-compile-0.2.0" @@ -27,7 +27,7 @@ "@type": "spdx:Tool", "name": "speckl-compile" }, - "timestamp": "2026-04-29T22:11:59.833Z", + "timestamp": "2026-05-03T18:51:34.105Z", "comment": "Review type: manual" } ] @@ -48,7 +48,22 @@ "@id": "spdx:RetryHandler" }, "to": { - "@id": "spdx:requirement:RESILIENCE" + "@id": "spdx:requirement:RESILIENCE-REQ-01" + }, + "relationshipType": "speckl:satisfies", + "additionalProperties": [ + { + "name": "speckl:clause", + "value": "3.1.2" + } + ] + }, + { + "from": { + "@id": "spdx:RetryHandler" + }, + "to": { + "@id": "spdx:requirement:RESILIENCE-REQ-02" }, "relationshipType": "speckl:satisfies" }, @@ -57,16 +72,7 @@ "@id": "spdx:RetryHandler" }, "to": { - "@id": "spdx:requirement:RESILIENCE" - }, - "relationshipType": "speckl:satisfies" - }, - { - "from": { - "@id": "spdx:RetryHandler" - }, - "to": { - "@id": "spdx:agent:\"" + "@id": "spdx:agent:Scott Scoble" }, "relationshipType": "spdx:wasAttributedTo" }, @@ -75,7 +81,7 @@ "@id": "spdx:RetryHandler" }, "to": { - "@id": "spdx:source:architecture_review:\"" + "@id": "spdx:source:architecture_review:AR-2024-0215" }, "relationshipType": "spdx:wasInformedBy" } @@ -83,24 +89,24 @@ "elements": [ { "@type": "spdx:Requirement", - "@id": "spdx:requirement:RESILIENCE", - "name": "RESILIENCE" + "@id": "spdx:requirement:RESILIENCE-REQ-01", + "name": "RESILIENCE-REQ-01" }, { "@type": "spdx:Requirement", - "@id": "spdx:requirement:RESILIENCE", - "name": "RESILIENCE" + "@id": "spdx:requirement:RESILIENCE-REQ-02", + "name": "RESILIENCE-REQ-02" }, { "@type": "spdx:Person", - "@id": "spdx:agent:\"", - "name": "\"", - "email": "Scott Scoble" + "@id": "spdx:agent:Scott Scoble", + "name": "Scott Scoble", + "email": "scott@scoble.me" }, { "@type": "spdx:Activity", - "@id": "spdx:source:architecture_review:\"", - "name": "architecture_review - \"", + "@id": "spdx:source:architecture_review:AR-2024-0215", + "name": "architecture_review - AR-2024-0215", "type": "architecture_review" } ]