diff --git a/compiler/__tests__/parser.test.ts b/compiler/__tests__/parser.test.ts index 8f2196c..701eef4 100644 --- a/compiler/__tests__/parser.test.ts +++ b/compiler/__tests__/parser.test.ts @@ -1,6 +1,10 @@ import { describe, it, expect } from 'vitest'; +import path from 'path'; +import { fileURLToPath } from 'url'; import { parseSpeckContent, parseSpeckFile } from '../src/parser.js'; +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + describe('SpeckDL Parser', () => { describe('parseSpeckContent', () => { it('should parse a minimal speck definition with name', () => { @@ -533,7 +537,7 @@ speck Second { }); it('should parse full TigerBeetle speck from file', () => { - const ast = parseSpeckFile('./__tests__/tigerbeetle.speck'); + const ast = parseSpeckFile(path.join(__dirname, 'tigerbeetle.speck')); expect(ast.specks).toHaveLength(1); const speck = ast.specks[0]; diff --git a/compiler/dist/__tests__/compiler.test.js b/compiler/dist/__tests__/compiler.test.js index 68485dc..5b67050 100644 --- a/compiler/dist/__tests__/compiler.test.js +++ b/compiler/dist/__tests__/compiler.test.js @@ -1,10 +1,13 @@ import { describe, it, expect, beforeAll } from 'vitest'; import fs from 'fs'; import path from 'path'; +import { fileURLToPath } from 'url'; +import { execSync } from 'child_process'; import { parseSpeckFile } from '../parser.js'; import { generateProvenanceGraph } from '../generators/provenance.js'; import { generateCycloneDX } from '../generators/cyclonedx.js'; import { generateSPDX } from '../generators/spdx.js'; +const __dirname = path.dirname(fileURLToPath(import.meta.url)); describe('Compiler E2E', () => { let ast; const testOutputDir = './test-output'; @@ -15,7 +18,7 @@ describe('Compiler E2E', () => { } }); it('should parse RetryHandler.speck', () => { - const specPath = path.join(process.cwd(), '..', 'examples', 'RetryHandler.speck'); + const specPath = path.join(__dirname, '..', '..', '..', 'examples', 'RetryHandler.speck'); ast = parseSpeckFile(specPath); expect(ast).toBeDefined(); expect(ast.specks).toBeDefined(); @@ -54,4 +57,164 @@ describe('Compiler E2E', () => { expect(spdx.spdx).toBeDefined(); }); }); +// --------------------------------------------------------------------------- +// Regression tests for Z3 / TypeScript / Rust expression translation fixes. +// +// These tests compile the FederatedMeetupProduct spec against each backend +// (z3, typescript, rust) and assert that the generated output contains the +// expected translated patterns. They guard against regressions in: +// - implies() argument preservation (Z3 + TS + Rust) +// - multi-level field access flattening (Z3) +// - len()/length() normalization (Z3) +// - nil → speckl_nil translation (Z3) +// - != → (distinct X Y) wrapping (Z3) +// - constraint checks emitting real every() / iter().all() (TS + Rust) +// - 'in' operator → .has() in verify expressions (TS) +// - multiple verify blocks all emitted (memberKey dedup fix) (TS + Rust) +// --------------------------------------------------------------------------- +const SPEC_PATH = '/home/sscoble/projects/federated-meetup/specs/FederatedMeetupProduct.speckdl'; +const OUT_DIR = './out'; // CLI writes to ./out relative to compiler cwd +const COMPILER_DIR = '/home/sscoble/speckl/compiler'; +describe('Z3 / TS / Rust expression translation regressions', () => { + let z3Output; + let tsOutput; + let rustOutput; + beforeAll(() => { + // Clean out directory to avoid stale output + execSync(`rm -rf ${OUT_DIR}`, { cwd: COMPILER_DIR }); + execSync(`mkdir -p ${OUT_DIR}`, { cwd: COMPILER_DIR }); + // Compile to all three targets + execSync(`npx tsx src/index.ts ${SPEC_PATH} --target z3 --output ${OUT_DIR}`, { cwd: COMPILER_DIR, stdio: 'pipe' }); + z3Output = fs.readFileSync(path.join(COMPILER_DIR, OUT_DIR, 'FederatedMeetupProduct.smt2'), 'utf-8'); + execSync(`npx tsx src/index.ts ${SPEC_PATH} --target typescript --output ${OUT_DIR}`, { cwd: COMPILER_DIR, stdio: 'pipe' }); + tsOutput = fs.readFileSync(path.join(COMPILER_DIR, OUT_DIR, 'FederatedMeetupProduct.ts'), 'utf-8'); + execSync(`npx tsx src/index.ts ${SPEC_PATH} --target rust --output ${OUT_DIR}`, { cwd: COMPILER_DIR, stdio: 'pipe' }); + rustOutput = fs.readFileSync(path.join(COMPILER_DIR, OUT_DIR, 'FederatedMeetupProduct', 'src', 'lib.rs'), 'utf-8'); + }, 120_000); + // 1. Z3: implies() preserves both arguments → (=> P Q) not (=> true true) + it('Z3: implies() emits (=> P Q) preserving both arguments', () => { + expect(z3Output).toContain('(=>'); + expect(z3Output).toContain('(=> (= o_status Refunded) (distinct o_refunded_at speckl_nil))'); + // Ensure we did NOT regress to (=> true true) + expect(z3Output).not.toContain('(=> true true)'); + expect(z3Output).not.toContain('(=> true'); + }); + // 2. Z3: multi-level field access m.price.amount → m_price_amount + it('Z3: multi-level field access m.price.amount flattens to m_price_amount', () => { + expect(z3Output).toContain('m_price_amount'); + // Ensure we did NOT regress to single-level m_price or bare m.price.amount + expect(z3Output).not.toContain('m.price.amount'); + // The constraint m_price_amount >= 0 should be present + expect(z3Output).toContain('(>= m_price_amount 0)'); + }); + // 3. Z3: len() handled same as length() → speckl_len_Int + it('Z3: len() is translated to speckl_len_Int (same as length())', () => { + // The spec uses len(m.price.currency) — should become speckl_len_Int + expect(z3Output).toContain('(speckl_len_Int m_price_currency)'); + // Ensure we did NOT regress to bare len() or length() + expect(z3Output).not.toMatch(/\blen\s*\(/); + expect(z3Output).not.toMatch(/\blength\s*\(/); + }); + // 4. Z3: nil translated to speckl_nil + it('Z3: nil is translated to speckl_nil sentinel', () => { + expect(z3Output).toContain('(declare-const speckl_nil Int)'); + expect(z3Output).toContain('speckl_nil'); + // Ensure we did NOT regress to bare nil / null + expect(z3Output).not.toMatch(/\bnil\b/); + }); + // 5. Z3: != produces (distinct X Y) not bare 'distinct' + it('Z3: != produces wrapped (distinct X Y) not bare distinct', () => { + // The constraint g.hosting_tier != Free should emit (distinct g_hosting_tier Free) + expect(z3Output).toContain('(distinct g_hosting_tier Free)'); + // Ensure we did NOT regress to bare 'distinct g_hosting_tier Free' without parens + // (the fix wraps the operator in parens) + // A bare distinct would look like "g_hosting_tier distinct Free" without surrounding parens + expect(z3Output).not.toMatch(/[^(]\w+\s+distinct\s+\w+/); + }); + // 6. TS: implies(p,q) → (!p || q) not (p || q) + it('TS: implies(p, q) translates to (!p || q), not (p || q)', () => { + // constraint 6: implies(o.status == Refunded, o.refunded_at != nil) + // Should become (!(o.status === ...) || (o.refunded_at !== ...)) + expect(tsOutput).toContain('(!('); + expect(tsOutput).toContain('||'); + // The specific pattern: (!(o.status === ... || (o.refunded_at !== nil)) + expect(tsOutput).toMatch(/!\(o\.status === .* \|\| \(o\.refunded_at !== /); + // Ensure the CODE (not comments) has negation on p. + // A regression would have `return this.orders.every((o: any) => (o.status ===` + // (without the `!(` negation before `o.status`). + // Strip comments to avoid matching the original spec text in comments. + const tsNoComments = tsOutput.replace(/^\s*\/\/.*$/gm, ''); + // Positive: the every() body starts with (!(o.status — the (! is the negation + expect(tsNoComments).toMatch(/every\(\(o:\s*any\)\s*=>\s*\(!\(o\.status/); + // Negative: a regression would have every((o: any) => (o.status without the ! negation + expect(tsNoComments).not.toMatch(/every\(\(o:\s*any\)\s*=>\s*\(o\.status/); + }); + // 7. TS: constraint checks emit real every() not return true + it('TS: constraint checks emit real .every() calls, not return true', () => { + expect(tsOutput).toContain('.every('); + // Multiple constraints use every() — at least 8 constraints + const everyCount = (tsOutput.match(/\.every\(/g) || []).length; + expect(everyCount).toBeGreaterThanOrEqual(8); + // Ensure we did NOT regress to `return true` stubs + // A stub would have checkConstraintN() { return true; } + expect(tsOutput).not.toMatch(/checkConstraint\d+\(\):\s*boolean\s*\{\s*return\s*true\s*;\s*\}/); + }); + // 8. TS: 'in' operator in verify expressions → .has() not raw 'in' + it('TS: "in" operator in verify expressions translates to .has(), not raw in', () => { + // V1: always(implies(event == PurchaseTicket, ticket_id in tickets)) + // Should become tickets.has(ticket_id) + expect(tsOutput).toContain('.has('); + expect(tsOutput).toContain('tickets.has(ticket_id)'); + // Ensure we did NOT regress to raw 'in' operator (JS `in` checks property keys, not membership). + // A regression would look like `ticket_id in tickets` in CODE (not comments). + // Strip comments to avoid matching the original spec text in comments. + const tsNoComments = tsOutput.replace(/^\s*\/\/.*$/gm, ''); + expect(tsNoComments).not.toMatch(/\bticket_id\s+in\s+tickets\b/); + }); + // 9. TS/Rust: multiple verify blocks all emitted (memberKey dedup fix) + it('TS: all 6 verify blocks are emitted (memberKey dedup fix)', () => { + // The spec has 6 verify blocks; previously dedup killed all but the first + expect(tsOutput).toContain('verify1'); + expect(tsOutput).toContain('verify2'); + expect(tsOutput).toContain('verify3'); + expect(tsOutput).toContain('verify4'); + expect(tsOutput).toContain('verify5'); + expect(tsOutput).toContain('verify6'); + expect(tsOutput).not.toContain('verify7'); // only 6 verify blocks in spec + }); + it('Rust: all 6 verify blocks are emitted (memberKey dedup fix)', () => { + expect(rustOutput).toContain('verify_1'); + expect(rustOutput).toContain('verify_2'); + expect(rustOutput).toContain('verify_3'); + expect(rustOutput).toContain('verify_4'); + expect(rustOutput).toContain('verify_5'); + expect(rustOutput).toContain('verify_6'); + expect(rustOutput).not.toContain('verify_7'); + }); + // 10. Rust: constraint checks emit iter().all() not return true + it('Rust: constraint checks emit iter().all(), not return true stubs', () => { + expect(rustOutput).toContain('.iter().all('); + // Multiple constraints use iter().all() — at least 8 constraints + const iterAllCount = (rustOutput.match(/\.iter\(\)\.all\(/g) || []).length; + expect(iterAllCount).toBeGreaterThanOrEqual(8); + // Ensure we did NOT regress to `return true` stubs + expect(rustOutput).not.toMatch(/check_invariant_\d+\(&self\)\s*->\s*bool\s*\{\s*true\s*\}/); + }); + // 11. Rust: implies() → (!p || q) in Rust + it('Rust: implies(p, q) translates to (!p || q) in Rust', () => { + // constraint 6: implies(o.status == Refunded, o.refunded_at != nil) + // Should become (!(o.status == ... || (o.refunded_at != ...)) + expect(rustOutput).toContain('(!('); + expect(rustOutput).toMatch(/!\(o\.status == .* \|\| \(o\.refunded_at != /); + // Ensure the CODE (not comments) has negation on p. + // A regression would have `self.orders.iter().all(|o| (o.status ==` + // (without the `!(` negation before `o.status`). + // Strip comments to avoid matching the original spec text in comments. + const rustNoComments = rustOutput.replace(/^\s*\/\/.*$/gm, ''); + // Positive: the iter().all() body starts with (!(o.status — the (! is the negation + expect(rustNoComments).toMatch(/iter\(\)\.all\(\|o\|\s*\(!\(o\.status/); + // Negative: a regression would have iter().all(|o| (o.status without the ! negation + expect(rustNoComments).not.toMatch(/iter\(\)\.all\(\|o\|\s*\(o\.status/); + }); +}); //# sourceMappingURL=compiler.test.js.map \ No newline at end of file diff --git a/compiler/dist/__tests__/compiler.test.js.map b/compiler/dist/__tests__/compiler.test.js.map index 6af290c..f1185fa 100644 --- a/compiler/dist/__tests__/compiler.test.js.map +++ b/compiler/dist/__tests__/compiler.test.js.map @@ -1 +1 @@ -{"version":3,"file":"compiler.test.js","sourceRoot":"","sources":["../../src/__tests__/compiler.test.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"compiler.test.js","sourceRoot":"","sources":["../../src/__tests__/compiler.test.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 8d77e9c..4a090eb 100644 --- a/compiler/node_modules/.vite/vitest/results.json +++ b/compiler/node_modules/.vite/vitest/results.json @@ -1 +1 @@ -{"version":"2.1.9","results":[[":__tests__/parser.test.ts",{"duration":54.53141900000003,"failed":false}],[":src/__tests__/compiler.test.ts",{"duration":15.892710999999963,"failed":false}]]} \ No newline at end of file +{"version":"2.1.9","results":[[":__tests__/parser.test.ts",{"duration":55.17420900000002,"failed":false}],[":src/__tests__/compiler.test.ts",{"duration":3350.1431570000004,"failed":false}]]} \ No newline at end of file diff --git a/compiler/out/speckl-compile/SpecklCompiler.prov.jsonld b/compiler/out/speckl-compile/SpecklCompiler.prov.jsonld deleted file mode 100644 index 91014fc..0000000 --- a/compiler/out/speckl-compile/SpecklCompiler.prov.jsonld +++ /dev/null @@ -1,29 +0,0 @@ -{ - "@context": { - "prov": "http://www.w3.org/ns/prov#", - "speckl": "https://speckl.scoble.me/ns/v0.2#", - "rdfs": "http://www.w3.org/2000/01/rdf-schema#" - }, - "@graph": [ - { - "@type": "prov:Entity", - "@id": "speck:SpecklCompiler", - "prov:qualifiedGeneration": { - "@type": "prov:Generation", - "prov:activity": { - "@type": "prov:Activity", - "prov:startTime": "2026-05-03T21:19:53.222Z" - } - }, - "rdfs:label": "SpecklCompiler", - "prov:wasAttributedTo": { - "@id": "agent:sscoble" - } - }, - { - "@type": "prov:Agent", - "@id": "agent:sscoble", - "rdfs:label": "sscoble" - } - ] -} \ No newline at end of file diff --git a/compiler/out/speckl-compile/SpecklCompiler.specbom.cdx.json b/compiler/out/speckl-compile/SpecklCompiler.specbom.cdx.json deleted file mode 100644 index 9ffc517..0000000 --- a/compiler/out/speckl-compile/SpecklCompiler.specbom.cdx.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "bomFormat": "CycloneDX", - "specVersion": "1.6", - "components": [], - "metadata": { - "tools": [], - "component": { - "type": "application", - "name": "SpecklCompiler", - "version": "0.2.0", - "properties": [] - } - }, - "annotations": [ - { - "subject": "speck:SpecklCompiler", - "annotations": [ - { - "type": "tag", - "value": "provenanceType", - "text": "author" - } - ], - "author": { - "name": "sscoble" - }, - "timestamp": "2026-05-03T21:19:53.229Z" - } - ] -} \ No newline at end of file diff --git a/compiler/out/speckl-compile/SpecklCompiler.specbom.spdx.json b/compiler/out/speckl-compile/SpecklCompiler.specbom.spdx.json deleted file mode 100644 index 6d17d6c..0000000 --- a/compiler/out/speckl-compile/SpecklCompiler.specbom.spdx.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "@context": [ - "https://spdx.github.io/spdx-v3/spdx-context.jsonld", - { - "speckl": "https://speckl.scoble.me/ns/v0.2#" - } - ], - "@type": "spdx:Document", - "spdx": { - "@id": "spdx:SpecklCompiler", - "@type": "spdx:Snippet", - "name": "SpecklCompiler", - "spdxVersion": "3.0.1", - "dataLicense": "CC0-1.0", - "creationInfo": { - "created": "2026-05-03T21:19:53.229Z", - "createdBy": [ - "Organization: Speckl", - "tool: speckl-compile-0.2.0" - ] - } - }, - "builtBy": [], - "relationships": [ - { - "from": { - "@id": "spdx:SpecklCompiler" - }, - "to": { - "@id": "spdx:agent:sscoble" - }, - "relationshipType": "spdx:wasAttributedTo" - } - ], - "elements": [ - { - "@type": "spdx:Person", - "@id": "spdx:agent:sscoble", - "name": "sscoble" - } - ] -} \ No newline at end of file diff --git a/compiler/out/speckl-compile/SpecklCompiler.ts b/compiler/out/speckl-compile/SpecklCompiler.ts deleted file mode 100644 index 75041fb..0000000 --- a/compiler/out/speckl-compile/SpecklCompiler.ts +++ /dev/null @@ -1,153 +0,0 @@ -// Auto-generated by speckl-compile from SpecklCompiler -// DO NOT EDIT MANUALLY — regenerate from .speckdl source - -export const SpecklCompilerCompilerPhase = { - BOMsGenerated: 'bomsgenerated', - Idle: 'idle', - Parsed: 'parsed', - Parsing: 'parsing', - ProvenanceGenerated: 'provenancegenerated', - TypeScriptGenerated: 'typescriptgenerated', - WASMGenerated: 'wasmgenerated', -} as const; -export type SpecklCompilerCompilerPhase = typeof SpecklCompilerCompilerPhase[keyof typeof SpecklCompilerCompilerPhase]; - -export const SpecklCompilerArtifactKind = { - CycloneDX: 'cyclonedx', - Done: 'done', - Provenance: 'provenance', - SPDX: 'spdx', - TypeScript: 'typescript', - WebAssembly: 'webassembly', -} as const; -export type SpecklCompilerArtifactKind = typeof SpecklCompilerArtifactKind[keyof typeof SpecklCompilerArtifactKind]; - -export interface CompilationStarted { - source: string; - output: string; - at: string; -} - -export interface PhaseCompleted { - phase: SpecklCompilerCompilerPhase; - durationMs: number; - at: string; -} - -export interface ArtifactGenerated { - kind: SpecklCompilerArtifactKind; - path: string; - bytes: number; - at: string; -} - -export interface ErrorEmitted { - phase: SpecklCompilerCompilerPhase; - message: string; - line: number; - col: number; - at: string; -} - - -export class SpecklCompilerMachine { - phase: SpecklCompilerCompilerPhase; - sourcePath: String; - outputDir: String; - ast: any; - errors: String[]; - artifacts: any; - decisionLog: CompilationStarted[] = []; - - constructor() { - this.phase = SpecklCompilerCompilerPhase.Idle; - this.sourcePath = ""; - this.outputDir = "./out"; - this.ast = {}; - this.errors = []; - this.artifacts = {}; - } - - compile(sourcePath: string, outputDir: string) { - if (!(this.phase === SpecklCompilerCompilerPhase.Idle)) throw new Error('Guard failed: this.phase === SpecklCompilerCompilerPhase.Idle'); - if (!(sourcePath.length > 0)) throw new Error('Guard failed: sourcePath.length > 0'); - this.decisionLog.push({ - source: sourcePath, - output: outputDir, - at: Date.now() - } as CompilationStarted); - } - - parse() { - if (!(this.phase === SpecklCompilerCompilerPhase.Parsing)) throw new Error('Guard failed: this.phase === SpecklCompilerCompilerPhase.Parsing'); - if (!(this.sourcePath.length > 0)) throw new Error('Guard failed: this.sourcePath.length > 0'); - this.decisionLog.push({ - phase: SpecklCompilerCompilerPhase.Parsing, - durationMs: 0, - at: Date.now() - } as PhaseCompleted); - } - - generateProvenance() { - if (!(this.phase === SpecklCompilerCompilerPhase.Parsed || this.phase === SpecklCompilerCompilerPhase.TypeScriptGenerated || this.phase === SpecklCompilerCompilerPhase.WASMGenerated)) throw new Error('Guard failed: this.phase === SpecklCompilerCompilerPhase.Parsed || this.phase === SpecklCompilerCompilerPhase.TypeScriptGenerated || this.phase === SpecklCompilerCompilerPhase.WASMGenerated'); - this.decisionLog.push({ - kind: SpecklCompilerCompilerPhase.Provenance, - path: join(this.outputDir, - bytes: 0, - at: Date.now() - } as ArtifactGenerated); - } - - generateBOMs() { - if (!(this.phase === SpecklCompilerCompilerPhase.ProvenanceGenerated || this.phase === SpecklCompilerCompilerPhase.Parsed)) throw new Error('Guard failed: this.phase === SpecklCompilerCompilerPhase.ProvenanceGenerated || this.phase === SpecklCompilerCompilerPhase.Parsed'); - this.decisionLog.push({ - kind: SpecklCompilerCompilerPhase.CycloneDX, - path: join(this.outputDir, - bytes: 0, - at: Date.now() - } as ArtifactGenerated); - this.decisionLog.push({ - kind: SpecklCompilerCompilerPhase.SPDX, - path: join(this.outputDir, - bytes: 0, - at: Date.now() - } as ArtifactGenerated); - } - - generateTypeScript() { - if (!(this.phase === SpecklCompilerCompilerPhase.BOMsGenerated || this.phase === SpecklCompilerCompilerPhase.Parsed)) throw new Error('Guard failed: this.phase === SpecklCompilerCompilerPhase.BOMsGenerated || this.phase === SpecklCompilerCompilerPhase.Parsed'); - this.decisionLog.push({ - kind: SpecklCompilerCompilerPhase.TypeScript, - path: join(this.outputDir, - bytes: 0, - at: Date.now() - } as ArtifactGenerated); - } - - generateWASM() { - if (!(this.phase === SpecklCompilerCompilerPhase.TypeScriptGenerated || this.phase === SpecklCompilerCompilerPhase.BOMsGenerated || this.phase === SpecklCompilerCompilerPhase.Parsed)) throw new Error('Guard failed: this.phase === SpecklCompilerCompilerPhase.TypeScriptGenerated || this.phase === SpecklCompilerCompilerPhase.BOMsGenerated || this.phase === SpecklCompilerCompilerPhase.Parsed'); - this.decisionLog.push({ - kind: SpecklCompilerCompilerPhase.WebAssembly, - path: join(this.outputDir, - bytes: 0, - at: Date.now() - } as ArtifactGenerated); - } - - report() { - if (!(this.phase === SpecklCompilerCompilerPhase.ProvenanceGenerated || this.phase === SpecklCompilerCompilerPhase.BOMsGenerated || this.phase === SpecklCompilerCompilerPhase.TypeScriptGenerated || this.phase === SpecklCompilerCompilerPhase.WASMGenerated)) throw new Error('Guard failed: this.phase === SpecklCompilerCompilerPhase.ProvenanceGenerated || this.phase === SpecklCompilerCompilerPhase.BOMsGenerated || this.phase === SpecklCompilerCompilerPhase.TypeScriptGenerated || this.phase === SpecklCompilerCompilerPhase.WASMGenerated'); - this.decisionLog.push({ - phase: SpecklCompilerCompilerPhase.Done, - durationMs: 0, - at: Date.now() - } as PhaseCompleted); - } - - reset() { - - } - - - - -} diff --git a/compiler/out/speckl-compile/SpecklCompiler.wat b/compiler/out/speckl-compile/SpecklCompiler.wat deleted file mode 100644 index 096073b..0000000 --- a/compiler/out/speckl-compile/SpecklCompiler.wat +++ /dev/null @@ -1,151 +0,0 @@ -;; Auto-generated by speckl-compile from SpecklCompiler -;; Compile: wat2wasm SpecklCompiler.wat -o SpecklCompiler.wasm - -(module - ;; Host imports for Map/Set/alloc - (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))) - - ;; Linear memory (exported so host can read state) - (memory (export "memory") 1) - - ;; State globals - (global $phase (mut i32) (i32.const 0)) -(global $sourcePath (mut i32) (i32.const 0)) -(global $outputDir (mut i32) (i32.const 0)) -(global $ast (mut i32) (i32.const 0)) -(global $errors (mut i32) (i32.const 0)) -(global $artifacts (mut i32) (i32.const 0)) - - ;; ---- init ---- - (func $init (export "init") - (i32.const 0) - (global.set $phase) - (i32.const 0) - (global.set $sourcePath) - (i32.const 0) - (global.set $outputDir) - (i32.const 0) - (global.set $ast) - (i32.const 0) - (global.set $errors) - (i32.const 0) - (global.set $artifacts) - ) - - ;; ---- Actions ---- - ;; action: compile - (func $compile (export "compile") (param $sourcePath i32) (param $outputDir i32) (result i32) - - ;; require: phase === Idle - ;; Complex right-hand side not compiled: phase === Idle - ;; require: len(sourcePath) > 0 - ;; Complex guard not compiled: len(sourcePath) > 0 - ;; emit CompilationStarted { ... } - (i32.const 1) - ) - - ;; action: parse - (func $parse (export "parse") (result i32) - - ;; require: phase === Parsing - ;; Complex right-hand side not compiled: phase === Parsing - ;; require: len(sourcePath) > 0 - ;; Complex guard not compiled: len(sourcePath) > 0 - ;; emit PhaseCompleted { ... } - (i32.const 1) - ) - - ;; action: generateProvenance - (func $generateProvenance (export "generateProvenance") (result i32) - - ;; require: phase === Parsed || phase === TypeScriptGenerated || phase === WASMGenerated - ;; Complex right-hand side not compiled: phase === Parsed || phase === TypeScriptGenerated || phase === WASMGenerated - ;; emit ArtifactGenerated { ... } - (i32.const 1) - ) - - ;; action: generateBOMs - (func $generateBOMs (export "generateBOMs") (result i32) - - ;; require: phase === ProvenanceGenerated || phase === Parsed - ;; Complex right-hand side not compiled: phase === ProvenanceGenerated || phase === Parsed - ;; emit ArtifactGenerated { ... } - ;; emit ArtifactGenerated { ... } - (i32.const 1) - ) - - ;; action: generateTypeScript - (func $generateTypeScript (export "generateTypeScript") (result i32) - - ;; require: phase === BOMsGenerated || phase === Parsed - ;; Complex right-hand side not compiled: phase === BOMsGenerated || phase === Parsed - ;; emit ArtifactGenerated { ... } - (i32.const 1) - ) - - ;; action: generateWASM - (func $generateWASM (export "generateWASM") (result i32) - - ;; require: phase === TypeScriptGenerated || phase === BOMsGenerated || phase === Parsed - ;; Complex right-hand side not compiled: phase === TypeScriptGenerated || phase === BOMsGenerated || phase === Parsed - ;; emit ArtifactGenerated { ... } - (i32.const 1) - ) - - ;; action: report - (func $report (export "report") (result i32) - - ;; require: phase === ProvenanceGenerated || phase === BOMsGenerated || phase === TypeScriptGenerated || phase === WASMGenerated - ;; Complex right-hand side not compiled: phase === ProvenanceGenerated || phase === BOMsGenerated || phase === TypeScriptGenerated || phase === WASMGenerated - ;; emit PhaseCompleted { ... } - (i32.const 1) - ) - - ;; action: reset - (func $reset (export "reset") (result i32) - - (i32.const 1) - ) - - ;; ---- getState ---- - (func $getState (export "getState") (result i32 i32) - (i32.const 0) - (global.get $phase) - (i32.store) - (i32.const 4) - (global.get $sourcePath) - (i32.store) - (i32.const 8) - (global.get $outputDir) - (i32.store) - (i32.const 12) - (global.get $ast) - (i32.store) - (i32.const 16) - (global.get $errors) - (i32.store) - (i32.const 20) - (global.get $artifacts) - (i32.store) - (i32.const 0) - (i32.const 24) - ) - - - ;; checkConstraints — runtime constraint evaluation stub - (func $checkConstraints (export "checkConstraints") (result i32) - ;; TODO: evaluate constraint expressions at runtime - (i32.const 1) - ) -) \ No newline at end of file diff --git a/compiler/src/__tests__/compiler.test.ts b/compiler/src/__tests__/compiler.test.ts index f3611c1..9381ac0 100644 --- a/compiler/src/__tests__/compiler.test.ts +++ b/compiler/src/__tests__/compiler.test.ts @@ -1,12 +1,15 @@ import { describe, it, expect, beforeAll } from 'vitest'; import fs from 'fs'; import path from 'path'; +import { fileURLToPath } from 'url'; import { execSync } from 'child_process'; import { parseSpeckFile } from '../parser.js'; import { generateProvenanceGraph } from '../generators/provenance.js'; import { generateCycloneDX } from '../generators/cyclonedx.js'; import { generateSPDX } from '../generators/spdx.js'; +const __dirname = path.dirname(fileURLToPath(import.meta.url)); + describe('Compiler E2E', () => { let ast: any; const testOutputDir = './test-output'; @@ -19,7 +22,7 @@ describe('Compiler E2E', () => { }); it('should parse RetryHandler.speck', () => { - const specPath = path.join(process.cwd(), '..', 'examples', 'RetryHandler.speck'); + const specPath = path.join(__dirname, '..', '..', '..', 'examples', 'RetryHandler.speck'); ast = parseSpeckFile(specPath); expect(ast).toBeDefined(); diff --git a/compiler/test-output/RetryHandler.prov.jsonld b/compiler/test-output/RetryHandler.prov.jsonld index 0ccc977..267459e 100644 --- a/compiler/test-output/RetryHandler.prov.jsonld +++ b/compiler/test-output/RetryHandler.prov.jsonld @@ -12,7 +12,7 @@ "@type": "prov:Generation", "prov:activity": { "@type": "prov:Activity", - "prov:startTime": "2026-06-30T00:31:19.971Z" + "prov:startTime": "2026-06-30T01:54:05.948Z" } }, "rdfs:label": "RetryHandler", diff --git a/compiler/test-output/RetryHandler.specbom.cdx.json b/compiler/test-output/RetryHandler.specbom.cdx.json index cb9a51c..166b33a 100644 --- a/compiler/test-output/RetryHandler.specbom.cdx.json +++ b/compiler/test-output/RetryHandler.specbom.cdx.json @@ -38,7 +38,7 @@ "name": "Scott Scoble", "email": "scott@scoble.me" }, - "timestamp": "2026-06-30T00:31:19.973Z" + "timestamp": "2026-06-30T01:54:05.951Z" }, { "subject": "speck:RetryHandler", @@ -53,7 +53,7 @@ "name": "Speckl Compiler", "email": "compiler@speckl.scoble.me" }, - "timestamp": "2026-06-30T00:31:19.974Z" + "timestamp": "2026-06-30T01:54:05.951Z" }, { "subject": "speck:RetryHandler", @@ -68,7 +68,7 @@ "name": "Speckl Compiler", "email": "compiler@speckl.scoble.me" }, - "timestamp": "2026-06-30T00:31:19.974Z" + "timestamp": "2026-06-30T01:54:05.951Z" } ] } \ 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 97ab84c..0c09ab7 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-06-30T00:31:19.976Z", + "created": "2026-06-30T01:54:05.952Z", "createdBy": [ "Organization: Speckl", "tool: speckl-compile-0.2.0" @@ -27,7 +27,7 @@ "@type": "spdx:Tool", "name": "speckl-compile" }, - "timestamp": "2026-06-30T00:31:19.976Z", + "timestamp": "2026-06-30T01:54:05.952Z", "comment": "Review type: manual" } ] diff --git a/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json b/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json new file mode 100644 index 0000000..e34a952 --- /dev/null +++ b/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json @@ -0,0 +1 @@ +{"version":"4.1.9","results":[[":compiler/dist/__tests__/compiler.test.js",{"duration":21.732912999999996,"failed":false}],[":compiler/__tests__/parser.test.ts",{"duration":56.655685000000005,"failed":false}],[":compiler/src/__tests__/compiler.test.ts",{"duration":3388.7825,"failed":false}]]} \ No newline at end of file diff --git a/test-output/RetryHandler.prov.jsonld b/test-output/RetryHandler.prov.jsonld new file mode 100644 index 0000000..9afd1e9 --- /dev/null +++ b/test-output/RetryHandler.prov.jsonld @@ -0,0 +1,61 @@ +{ + "@context": { + "prov": "http://www.w3.org/ns/prov#", + "speckl": "https://speckl.scoble.me/ns/v0.2#", + "rdfs": "http://www.w3.org/2000/01/rdf-schema#" + }, + "@graph": [ + { + "@type": "prov:Entity", + "@id": "speck:RetryHandler", + "prov:qualifiedGeneration": { + "@type": "prov:Generation", + "prov:activity": { + "@type": "prov:Activity", + "prov:startTime": "2026-06-30T02:09:28.187Z" + } + }, + "rdfs:label": "RetryHandler", + "prov:wasDerivedFrom": { + "@id": "speck:FaultHandler" + }, + "rdfs:comment": "specialization for retry logic", + "speckl:satisfies": { + "@id": "requirement:RESILIENCE-REQ-01" + }, + "prov:wasAttributedTo": { + "@id": "agent:Scott Scoble" + }, + "prov:wasInformedBy": { + "@id": "source:architecture_review:AR-2024-0215" + } + }, + { + "@type": "prov:Entity", + "@id": "speck:FaultHandler" + }, + { + "@type": "prov:Entity", + "@id": "requirement:RESILIENCE-REQ-01", + "rdfs:label": "RESILIENCE-REQ-01", + "speckl:clause": "3.1.2" + }, + { + "@type": "prov:Entity", + "@id": "requirement:RESILIENCE-REQ-02", + "rdfs:label": "RESILIENCE-REQ-02" + }, + { + "@type": "prov:Agent", + "@id": "agent:Scott Scoble", + "rdfs:label": "Scott Scoble", + "foaf:email": "scott@scoble.me" + }, + { + "@type": "prov:Activity", + "@id": "source:architecture_review:AR-2024-0215", + "prov:type": "architecture_review", + "rdfs:label": "architecture_review - AR-2024-0215" + } + ] +} \ No newline at end of file diff --git a/test-output/RetryHandler.specbom.cdx.json b/test-output/RetryHandler.specbom.cdx.json new file mode 100644 index 0000000..b4f34fc --- /dev/null +++ b/test-output/RetryHandler.specbom.cdx.json @@ -0,0 +1,74 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.6", + "components": [], + "metadata": { + "tools": [], + "component": { + "type": "application", + "name": "RetryHandler", + "version": "0.2.0", + "properties": [ + { + "name": "speckl:derivesFrom", + "value": "FaultHandler" + }, + { + "name": "speckl:satisfies", + "value": "RESILIENCE-REQ-01 clause 3.1.2" + }, + { + "name": "speckl:satisfies", + "value": "RESILIENCE-REQ-02" + } + ] + } + }, + "annotations": [ + { + "subject": "speck:RetryHandler", + "annotations": [ + { + "type": "tag", + "value": "provenanceType", + "text": "author" + } + ], + "author": { + "name": "Scott Scoble", + "email": "scott@scoble.me" + }, + "timestamp": "2026-06-30T02:09:28.192Z" + }, + { + "subject": "speck:RetryHandler", + "annotations": [ + { + "type": "tag", + "value": "provenanceType", + "text": "source" + } + ], + "author": { + "name": "Speckl Compiler", + "email": "compiler@speckl.scoble.me" + }, + "timestamp": "2026-06-30T02:09:28.192Z" + }, + { + "subject": "speck:RetryHandler", + "annotations": [ + { + "type": "tag", + "value": "reviewType", + "text": "manual" + } + ], + "author": { + "name": "Speckl Compiler", + "email": "compiler@speckl.scoble.me" + }, + "timestamp": "2026-06-30T02:09:28.192Z" + } + ] +} \ No newline at end of file diff --git a/test-output/RetryHandler.specbom.spdx.json b/test-output/RetryHandler.specbom.spdx.json new file mode 100644 index 0000000..cf74d3a --- /dev/null +++ b/test-output/RetryHandler.specbom.spdx.json @@ -0,0 +1,113 @@ +{ + "@context": [ + "https://spdx.github.io/spdx-v3/spdx-context.jsonld", + { + "speckl": "https://speckl.scoble.me/ns/v0.2#" + } + ], + "@type": "spdx:Document", + "spdx": { + "@id": "spdx:RetryHandler", + "@type": "spdx:Snippet", + "name": "RetryHandler", + "spdxVersion": "3.0.1", + "dataLicense": "CC0-1.0", + "creationInfo": { + "created": "2026-06-30T02:09:28.194Z", + "createdBy": [ + "Organization: Speckl", + "tool: speckl-compile-0.2.0" + ] + }, + "annotations": [ + { + "@type": "spdx:Annotation", + "annotationType": "spdx:REVIEW", + "annotator": { + "@type": "spdx:Tool", + "name": "speckl-compile" + }, + "timestamp": "2026-06-30T02:09:28.194Z", + "comment": "Review type: manual" + } + ] + }, + "builtBy": [], + "relationships": [ + { + "from": { + "@id": "spdx:RetryHandler" + }, + "to": { + "@id": "spdx:speck:FaultHandler" + }, + "relationshipType": "spdx:derivesFrom" + }, + { + "from": { + "@id": "spdx:RetryHandler" + }, + "to": { + "@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" + }, + { + "from": { + "@id": "spdx:RetryHandler" + }, + "to": { + "@id": "spdx:agent:Scott Scoble" + }, + "relationshipType": "spdx:wasAttributedTo" + }, + { + "from": { + "@id": "spdx:RetryHandler" + }, + "to": { + "@id": "spdx:source:architecture_review:AR-2024-0215" + }, + "relationshipType": "spdx:wasInformedBy" + } + ], + "elements": [ + { + "@type": "spdx:Requirement", + "@id": "spdx:requirement:RESILIENCE-REQ-01", + "name": "RESILIENCE-REQ-01" + }, + { + "@type": "spdx:Requirement", + "@id": "spdx:requirement:RESILIENCE-REQ-02", + "name": "RESILIENCE-REQ-02" + }, + { + "@type": "spdx:Person", + "@id": "spdx:agent:Scott Scoble", + "name": "Scott Scoble", + "email": "scott@scoble.me" + }, + { + "@type": "spdx:Activity", + "@id": "spdx:source:architecture_review:AR-2024-0215", + "name": "architecture_review - AR-2024-0215", + "type": "architecture_review" + } + ] +} \ No newline at end of file