diff --git a/compiler/src/generators/rust.ts b/compiler/src/generators/rust.ts index 45d5d3e..81cba2d 100644 --- a/compiler/src/generators/rust.ts +++ b/compiler/src/generators/rust.ts @@ -198,6 +198,29 @@ function emitSpeck(speck: SpeckNode): string { return `#[derive(Debug, Clone)]\npub struct ${e.name} {\n${fields}\n}`; }).join('\n\n'); + // Cycle 73: Event enum union for the decision log. + // Each variant wraps the corresponding event struct. + const eventEnum = events.length > 0 + ? `#[derive(Debug, Clone)]\npub enum ${speck.name}Event {\n` + + events.map(e => ` ${e.name}(${e.name}),`).join('\n') + + `\n}` + : ''; + + // Cycle 73: Interface record structs (Money, Ticket, Order, etc.) + // Previously only events were emitted as structs. Interface records + // with fields (kind === 'record') are now emitted too. + const recordInterfaces = allInterfaces.filter(iface => + iface?.kind === 'record' || (iface?.fields && iface.fields.length > 0) + ); + const recordStructs = recordInterfaces.map(iface => { + const fields = iface.fields.map((f: any) => { + const name = snakeCase(cleanName(f.name)); + const ty = rustType(f.type, speck.name, enumMap); + return ` pub ${name}: ${ty},`; + }).join('\n'); + return `#[derive(Debug, Clone)]\npub struct ${iface.name} {\n${fields}\n}`; + }).join('\n\n'); + const structName = `${speck.name}Machine`; // State variables with original name + snake_case name mapping @@ -276,8 +299,11 @@ function emitSpeck(speck: SpeckNode): string { ` }` ).join('\n\n'); - // Decision log type - const logEventType = events.length > 0 ? `Vec<${events[0].name}>` : 'Vec'; + // Decision log type — cycle 73: use an enum union of all event types, + // not just the first one. If there are no events, fall back to String. + const logEventType = events.length > 0 + ? `Vec<${speck.name}Event>` + : 'Vec'; // Struct definition const structDef = `/// Speck state machine: ${speck.name}\n` + @@ -318,11 +344,13 @@ function emitSpeck(speck: SpeckNode): string { `// DO NOT EDIT MANUALLY — regenerate from .speckdl source\n\n` + `use std::collections::{HashMap, HashSet};\n\n` + `${stateEnum}\n\n` + + `${recordStructs ? recordStructs + '\n\n' : ''}` + `${eventStructs}\n\n` + + `${eventEnum ? eventEnum + '\n\n' : ''}` + `${structDef}\n\n` + `${constructor}\n\n` + `${actionImpl}\n` + - `${invariantImpl}\n` + + `${invariantImpl}` + `${verifyImpl}\n`; return file; @@ -619,6 +647,8 @@ function rustType(t: any, speckName: string = '', enumMap?: Map', Number: 'f64', + // Cycle 73: Date → u64 Unix timestamp (avoids chrono dependency). + Date: 'u64', }; return map[t.name] || t.name.toLowerCase(); } @@ -645,6 +675,7 @@ function rustType(t: any, speckName: string = '', enumMap?: Map from SpeckDL's Option(Type) syntax const optionMatch = rawName.match(/^Option[_(]\s*(\w+)\s*\)?$/); if (optionMatch) {