logicaffeine_language/ast/stmt.rs
1//! Imperative statement AST types for the LOGOS language.
2//!
3//! This module defines statement types for the imperative fragment including:
4//!
5//! - **[`Stmt`]**: Statement variants (let, if, match, while, for, function defs)
6//! - **[`Expr`]**: Imperative expressions (field access, method calls, literals)
7//! - **[`TypeExpr`]**: Type annotations with refinements and generics
8//! - **[`Literal`]**: Literal values (numbers, strings, booleans)
9//! - **[`Block`]**: Statement blocks with optional return expressions
10//!
11//! The imperative AST is used in LOGOS mode for generating executable Rust code.
12
13use std::collections::HashSet;
14
15use super::logic::LogicExpr;
16use super::theorem::TheoremBlock;
17use logicaffeine_base::Symbol;
18
19/// Per-function optimization control flags.
20///
21/// Annotations placed above `## To` disable specific optimization passes
22/// for that function. This gives the programmer explicit control when
23/// an optimization hurts rather than helps (e.g., memoization on a function
24/// whose body is cheaper than a hash lookup).
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
26pub enum OptFlag {
27 /// `## No Memo` — disable auto-memoization (TLS FxHashMap cache)
28 NoMemo,
29 /// `## No TCO` — disable tail-call elimination (loop conversion)
30 NoTCO,
31 /// `## No Peephole` — disable peephole patterns (swap, vec-fill, for-range, etc.)
32 NoPeephole,
33 /// `## No Borrow` — disable readonly/mutable borrow analysis (&[T]/&mut [T] params)
34 NoBorrow,
35 /// `## No Optimize` — disable ALL of the above (master switch)
36 NoOptimize,
37}
38
39/// Type expression for explicit type annotations.
40///
41/// Represents type syntax like:
42/// - `Int` → Primitive(Int)
43/// - `User` → Named(User)
44/// - `List of Int` → Generic { base: List, params: [Primitive(Int)] }
45/// - `List of List of Int` → Generic { base: List, params: [Generic { base: List, params: [Primitive(Int)] }] }
46/// - `Result of Int and Text` → Generic { base: Result, params: [Primitive(Int), Primitive(Text)] }
47#[derive(Debug, Clone)]
48pub enum TypeExpr<'a> {
49 /// Primitive type: Int, Nat, Text, Bool
50 Primitive(Symbol),
51 /// Named type (user-defined): User, Point
52 Named(Symbol),
53 /// Generic type: List of Int, Option of Text, Result of Int and Text
54 Generic {
55 base: Symbol,
56 params: &'a [TypeExpr<'a>],
57 },
58 /// Function type: fn(A, B) -> C (for higher-order functions)
59 Function {
60 inputs: &'a [TypeExpr<'a>],
61 output: &'a TypeExpr<'a>,
62 },
63 /// Refinement type with predicate constraint.
64 /// Example: `Int where it > 0`
65 Refinement {
66 /// The base type being refined
67 base: &'a TypeExpr<'a>,
68 /// The bound variable (usually "it")
69 var: Symbol,
70 /// The predicate constraint (from Logic Kernel)
71 predicate: &'a LogicExpr<'a>,
72 },
73 /// Persistent storage wrapper type.
74 /// Example: `Persistent Counter`
75 /// Semantics: Wraps a Shared type with journal-backed storage
76 Persistent {
77 /// The inner type (must be a Shared/CRDT type)
78 inner: &'a TypeExpr<'a>,
79 },
80}
81
82/// Source for Read statements.
83#[derive(Debug, Clone, Copy)]
84pub enum ReadSource<'a> {
85 /// Read from console (stdin)
86 Console,
87 /// Read from file at given path
88 File(&'a Expr<'a>),
89}
90
91/// Pattern for loop variable binding.
92/// Supports single identifiers and tuple destructuring for Map iteration.
93#[derive(Debug, Clone)]
94pub enum Pattern {
95 /// Single identifier: `Repeat for x in list`
96 Identifier(Symbol),
97 /// Tuple destructuring: `Repeat for (k, v) in map`
98 Tuple(Vec<Symbol>),
99}
100
101/// Binary operation kinds for imperative expressions.
102#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
103pub enum BinaryOpKind {
104 Add,
105 Subtract,
106 Multiply,
107 Divide,
108 Modulo,
109 Eq,
110 NotEq,
111 Lt,
112 Gt,
113 LtEq,
114 GtEq,
115 // Logical/bitwise operators — type-aware in codegen (&&/|| for Bool, &/| for Int)
116 And,
117 Or,
118 /// String concatenation ("X combined with Y")
119 Concat,
120 /// Bitwise XOR: "x xor y" → `x ^ y`
121 BitXor,
122 /// Left shift: "x shifted left by y" → `x << y`
123 Shl,
124 /// Right shift: "x shifted right by y" → `x >> y`
125 Shr,
126}
127
128/// Block is a sequence of statements.
129pub type Block<'a> = &'a [Stmt<'a>];
130
131/// Match arm for pattern matching in Inspect statements.
132#[derive(Debug, Clone)]
133pub struct MatchArm<'a> {
134 pub enum_name: Option<Symbol>, // The enum type (e.g., Shape)
135 pub variant: Option<Symbol>, // None = Otherwise (wildcard)
136 pub bindings: Vec<(Symbol, Symbol)>, // (field_name, binding_name)
137 pub body: Block<'a>,
138}
139
140/// Imperative statement AST (LOGOS §15.0.0).
141///
142/// Stmt is the primary AST node for imperative code blocks like `## Main`
143/// and function bodies. The Assert variant bridges to the Logic Kernel.
144#[derive(Debug, Clone)]
145pub enum Stmt<'a> {
146 /// Variable binding: `Let x be 5.` or `Let x: Int be 5.`
147 Let {
148 var: Symbol,
149 ty: Option<&'a TypeExpr<'a>>,
150 value: &'a Expr<'a>,
151 mutable: bool,
152 },
153
154 /// Mutation: `Set x to 10.`
155 Set {
156 target: Symbol,
157 value: &'a Expr<'a>,
158 },
159
160 /// Function call as statement: `Call process with data.`
161 Call {
162 function: Symbol,
163 args: Vec<&'a Expr<'a>>,
164 },
165
166 /// Conditional: `If condition: ... Otherwise: ...`
167 If {
168 cond: &'a Expr<'a>,
169 then_block: Block<'a>,
170 else_block: Option<Block<'a>>,
171 },
172
173 /// Loop: `While condition: ...` or `While condition (decreasing expr): ...`
174 While {
175 cond: &'a Expr<'a>,
176 body: Block<'a>,
177 /// Optional decreasing variant for termination proof.
178 decreasing: Option<&'a Expr<'a>>,
179 },
180
181 /// Iteration: `Repeat for x in list: ...` or `Repeat for i from 1 to 10: ...`
182 Repeat {
183 pattern: Pattern, // Changed from `var: Symbol` to support tuple destructuring
184 iterable: &'a Expr<'a>,
185 body: Block<'a>,
186 },
187
188 /// Return: `Return x.` or `Return.`
189 Return {
190 value: Option<&'a Expr<'a>>,
191 },
192
193 /// Break: `Break.` — exits the innermost while loop.
194 Break,
195
196 /// Bridge to Logic Kernel: `Assert that P.`
197 Assert {
198 proposition: &'a LogicExpr<'a>,
199 },
200
201 /// Documented assertion with justification.
202 /// `Trust that P because "reason".`
203 /// Semantics: Documented runtime check that could be verified statically.
204 Trust {
205 proposition: &'a LogicExpr<'a>,
206 justification: Symbol,
207 },
208
209 /// Runtime assertion with imperative condition
210 /// `Assert that condition.` (for imperative mode)
211 RuntimeAssert {
212 condition: &'a Expr<'a>,
213 },
214
215 /// Ownership transfer (move): `Give x to processor.`
216 /// Semantics: Move ownership of `object` to `recipient`.
217 Give {
218 object: &'a Expr<'a>,
219 recipient: &'a Expr<'a>,
220 },
221
222 /// Immutable borrow: `Show x to console.`
223 /// Semantics: Immutable borrow of `object` passed to `recipient`.
224 Show {
225 object: &'a Expr<'a>,
226 recipient: &'a Expr<'a>,
227 },
228
229 /// Field mutation: `Set p's x to 10.`
230 SetField {
231 object: &'a Expr<'a>,
232 field: Symbol,
233 value: &'a Expr<'a>,
234 },
235
236 /// Struct definition for codegen.
237 StructDef {
238 name: Symbol,
239 fields: Vec<(Symbol, Symbol, bool)>, // (name, type_name, is_public)
240 is_portable: bool, // Derives Serialize/Deserialize
241 },
242
243 /// Function definition.
244 FunctionDef {
245 name: Symbol,
246 /// Generic type parameters: empty for monomorphic functions, e.g. `[T, U]` for polymorphic.
247 generics: Vec<Symbol>,
248 params: Vec<(Symbol, &'a TypeExpr<'a>)>,
249 body: Block<'a>,
250 return_type: Option<&'a TypeExpr<'a>>,
251 is_native: bool,
252 /// Rust path for user-defined native functions (e.g., "reqwest::blocking::get").
253 /// None for system native functions (read, write, etc.) which use map_native_function().
254 native_path: Option<Symbol>,
255 /// Whether this function is exported for FFI (C ABI or WASM).
256 is_exported: bool,
257 /// Export target: None = C ABI (#[no_mangle] extern "C"), Some("wasm") = #[wasm_bindgen].
258 export_target: Option<Symbol>,
259 /// Per-function optimization flags from `## No <X>` annotations.
260 opt_flags: HashSet<OptFlag>,
261 },
262
263 /// Pattern matching on sum types.
264 Inspect {
265 target: &'a Expr<'a>,
266 arms: Vec<MatchArm<'a>>,
267 has_otherwise: bool, // For exhaustiveness tracking
268 },
269
270 /// Push to collection: `Push x to items.`
271 Push {
272 value: &'a Expr<'a>,
273 collection: &'a Expr<'a>,
274 },
275
276 /// Pop from collection: `Pop from items.` or `Pop from items into y.`
277 Pop {
278 collection: &'a Expr<'a>,
279 into: Option<Symbol>,
280 },
281
282 /// Add to set: `Add x to set.`
283 Add {
284 value: &'a Expr<'a>,
285 collection: &'a Expr<'a>,
286 },
287
288 /// Remove from set: `Remove x from set.`
289 Remove {
290 value: &'a Expr<'a>,
291 collection: &'a Expr<'a>,
292 },
293
294 /// Index assignment: `Set item N of X to Y.`
295 SetIndex {
296 collection: &'a Expr<'a>,
297 index: &'a Expr<'a>,
298 value: &'a Expr<'a>,
299 },
300
301 /// Memory arena block (Zone).
302 /// "Inside a new zone called 'Scratch':"
303 /// "Inside a zone called 'Buffer' of size 1 MB:"
304 /// "Inside a zone called 'Data' mapped from 'file.bin':"
305 Zone {
306 /// The variable name for the arena handle (e.g., "Scratch")
307 name: Symbol,
308 /// Optional pre-allocated capacity in bytes (Heap zones only)
309 capacity: Option<usize>,
310 /// Optional file path for memory-mapped zones (Mapped zones only)
311 source_file: Option<Symbol>,
312 /// The code block executed within this memory context
313 body: Block<'a>,
314 },
315
316 /// Concurrent execution block (async, I/O-bound).
317 /// "Attempt all of the following:"
318 /// Semantics: All tasks run concurrently via tokio::join!
319 /// Best for: network requests, file I/O, waiting operations
320 Concurrent {
321 /// The statements to execute concurrently
322 tasks: Block<'a>,
323 },
324
325 /// Parallel execution block (CPU-bound).
326 /// "Simultaneously:"
327 /// Semantics: True parallelism via rayon::join or thread::spawn
328 /// Best for: computation, data processing, number crunching
329 Parallel {
330 /// The statements to execute in parallel
331 tasks: Block<'a>,
332 },
333
334 /// Read from console or file.
335 /// `Read input from the console.` or `Read data from file "path.txt".`
336 ReadFrom {
337 var: Symbol,
338 source: ReadSource<'a>,
339 },
340
341 /// Write to file.
342 /// `Write "content" to file "output.txt".`
343 WriteFile {
344 content: &'a Expr<'a>,
345 path: &'a Expr<'a>,
346 },
347
348 /// Spawn an agent.
349 /// `Spawn a Worker called "w1".`
350 Spawn {
351 agent_type: Symbol,
352 name: Symbol,
353 },
354
355 /// Send message to agent.
356 /// `Send Ping to "agent".`
357 SendMessage {
358 message: &'a Expr<'a>,
359 destination: &'a Expr<'a>,
360 },
361
362 /// Await response from agent.
363 /// `Await response from "agent" into result.`
364 AwaitMessage {
365 source: &'a Expr<'a>,
366 into: Symbol,
367 },
368
369 /// Merge CRDT state.
370 /// `Merge remote into local.` or `Merge remote's field into local's field.`
371 MergeCrdt {
372 source: &'a Expr<'a>,
373 target: &'a Expr<'a>,
374 },
375
376 /// Increment GCounter.
377 /// `Increase local's points by 10.`
378 IncreaseCrdt {
379 object: &'a Expr<'a>,
380 field: Symbol,
381 amount: &'a Expr<'a>,
382 },
383
384 /// Decrement PNCounter (Tally).
385 /// `Decrease game's score by 5.`
386 DecreaseCrdt {
387 object: &'a Expr<'a>,
388 field: Symbol,
389 amount: &'a Expr<'a>,
390 },
391
392 /// Append to SharedSequence (RGA).
393 /// `Append "Hello" to doc's lines.`
394 AppendToSequence {
395 sequence: &'a Expr<'a>,
396 value: &'a Expr<'a>,
397 },
398
399 /// Resolve MVRegister conflicts.
400 /// `Resolve page's title to "Final".`
401 ResolveConflict {
402 object: &'a Expr<'a>,
403 field: Symbol,
404 value: &'a Expr<'a>,
405 },
406
407 /// Security check - mandatory runtime guard.
408 /// `Check that user is admin.`
409 /// `Check that user can publish the document.`
410 /// Semantics: NEVER optimized out. Panics if condition is false.
411 Check {
412 /// The subject being checked (e.g., "user")
413 subject: Symbol,
414 /// The predicate name (e.g., "admin") or action (e.g., "publish")
415 predicate: Symbol,
416 /// True if this is a capability check (`can [action]`)
417 is_capability: bool,
418 /// For capabilities: the object being acted on (e.g., "document")
419 object: Option<Symbol>,
420 /// Original English text for error message
421 source_text: String,
422 /// Source location for error reporting
423 span: crate::token::Span,
424 },
425
426 /// Listen on network address.
427 /// `Listen on "/ip4/127.0.0.1/tcp/8000".`
428 /// Semantics: Bind to address, start accepting connections via libp2p
429 Listen {
430 address: &'a Expr<'a>,
431 },
432
433 /// Connect to remote peer.
434 /// `Connect to "/ip4/127.0.0.1/tcp/8000".`
435 /// Semantics: Dial peer via libp2p
436 ConnectTo {
437 address: &'a Expr<'a>,
438 },
439
440 /// Create PeerAgent remote handle.
441 /// `Let remote be a PeerAgent at "/ip4/127.0.0.1/tcp/8000".`
442 /// Semantics: Create handle for remote agent communication
443 LetPeerAgent {
444 var: Symbol,
445 address: &'a Expr<'a>,
446 },
447
448 /// Sleep for milliseconds.
449 /// `Sleep 1000.` or `Sleep delay.`
450 /// Semantics: Pause execution for N milliseconds (async)
451 Sleep {
452 milliseconds: &'a Expr<'a>,
453 },
454
455 /// Sync CRDT variable on topic.
456 /// `Sync x on "topic".`
457 /// Semantics: Subscribe to GossipSub topic, auto-publish on mutation, auto-merge on receive
458 Sync {
459 var: Symbol,
460 topic: &'a Expr<'a>,
461 },
462
463 /// Mount persistent CRDT from journal file.
464 /// `Mount counter at "data/counter.journal".`
465 /// Semantics: Load or create journal, replay operations to reconstruct state
466 Mount {
467 /// The variable name for the mounted value
468 var: Symbol,
469 /// The path expression for the journal file
470 path: &'a Expr<'a>,
471 },
472
473 // =========================================================================
474 // Go-like Concurrency (Green Threads, Channels, Select)
475 // =========================================================================
476
477 /// Launch a fire-and-forget task (green thread).
478 /// `Launch a task to process(data).`
479 /// Semantics: tokio::spawn with no handle capture
480 LaunchTask {
481 /// The function to call
482 function: Symbol,
483 /// Arguments to pass
484 args: Vec<&'a Expr<'a>>,
485 },
486
487 /// Launch a task with handle for control.
488 /// `Let worker be Launch a task to process(data).`
489 /// Semantics: tokio::spawn returning JoinHandle
490 LaunchTaskWithHandle {
491 /// Variable to bind the handle
492 handle: Symbol,
493 /// The function to call
494 function: Symbol,
495 /// Arguments to pass
496 args: Vec<&'a Expr<'a>>,
497 },
498
499 /// Create a bounded channel (pipe).
500 /// `Let jobs be a new Pipe of Int.`
501 /// Semantics: tokio::sync::mpsc::channel(32)
502 CreatePipe {
503 /// Variable for the pipe
504 var: Symbol,
505 /// Type of values in the pipe
506 element_type: Symbol,
507 /// Optional capacity (defaults to 32)
508 capacity: Option<u32>,
509 },
510
511 /// Blocking send into pipe.
512 /// `Send value into pipe.`
513 /// Semantics: pipe_tx.send(value).await
514 SendPipe {
515 /// The value to send
516 value: &'a Expr<'a>,
517 /// The pipe to send into
518 pipe: &'a Expr<'a>,
519 },
520
521 /// Blocking receive from pipe.
522 /// `Receive x from pipe.`
523 /// Semantics: let x = pipe_rx.recv().await
524 ReceivePipe {
525 /// Variable to bind the received value
526 var: Symbol,
527 /// The pipe to receive from
528 pipe: &'a Expr<'a>,
529 },
530
531 /// Non-blocking send (try).
532 /// `Try to send value into pipe.`
533 /// Semantics: pipe_tx.try_send(value) - returns immediately
534 TrySendPipe {
535 /// The value to send
536 value: &'a Expr<'a>,
537 /// The pipe to send into
538 pipe: &'a Expr<'a>,
539 /// Variable to bind the result (true/false)
540 result: Option<Symbol>,
541 },
542
543 /// Non-blocking receive (try).
544 /// `Try to receive x from pipe.`
545 /// Semantics: pipe_rx.try_recv() - returns Option
546 TryReceivePipe {
547 /// Variable to bind the received value (if any)
548 var: Symbol,
549 /// The pipe to receive from
550 pipe: &'a Expr<'a>,
551 },
552
553 /// Cancel a spawned task.
554 /// `Stop worker.`
555 /// Semantics: handle.abort()
556 StopTask {
557 /// The handle to cancel
558 handle: &'a Expr<'a>,
559 },
560
561 /// Select on multiple channels/timeouts.
562 /// `Await the first of:`
563 /// `Receive x from ch:`
564 /// `...`
565 /// `After 5 seconds:`
566 /// `...`
567 /// Semantics: tokio::select! with auto-cancel
568 Select {
569 /// The branches to select from
570 branches: Vec<SelectBranch<'a>>,
571 },
572
573 /// Theorem block.
574 /// `## Theorem: Name`
575 /// `Given: Premise.`
576 /// `Prove: Goal.`
577 /// `Proof: Auto.`
578 Theorem(TheoremBlock<'a>),
579
580 /// Escape hatch: embed raw foreign code.
581 /// `Escape to Rust:` followed by an indented block of raw code.
582 ///
583 /// Variables from the enclosing LOGOS scope are available in the
584 /// escape block as their generated Rust types. The raw code is
585 /// emitted verbatim inside a `{ ... }` block in the generated Rust.
586 Escape {
587 /// Target language ("Rust" for now, forward-compatible with "Python", "WGSL", etc.)
588 language: Symbol,
589 /// Raw foreign code, captured verbatim with base indentation stripped.
590 code: Symbol,
591 /// Source span covering the entire escape block (header + body).
592 span: crate::token::Span,
593 },
594
595 /// Dependency declaration from `## Requires` block.
596 /// The "serde" crate version "1.0" with features "derive".
597 Require {
598 crate_name: Symbol,
599 version: Symbol,
600 features: Vec<Symbol>,
601 span: crate::token::Span,
602 },
603}
604
605/// A branch in a Select statement.
606#[derive(Debug, Clone)]
607pub enum SelectBranch<'a> {
608 /// Receive from a pipe: `Receive x from ch:`
609 Receive {
610 var: Symbol,
611 pipe: &'a Expr<'a>,
612 body: Block<'a>,
613 },
614 /// Timeout: `After N seconds:` or `After N milliseconds:`
615 Timeout {
616 milliseconds: &'a Expr<'a>,
617 body: Block<'a>,
618 },
619}
620
621/// Shared expression type for pure computations (LOGOS §15.0.0).
622///
623/// Expr is used by both LogicExpr (as terms) and Stmt (as values).
624/// These are pure computations without side effects.
625#[derive(Debug)]
626pub enum Expr<'a> {
627 /// Literal value: 42, "hello", true, nothing
628 Literal(Literal),
629
630 /// Variable reference: x
631 Identifier(Symbol),
632
633 /// Binary operation: x plus y
634 BinaryOp {
635 op: BinaryOpKind,
636 left: &'a Expr<'a>,
637 right: &'a Expr<'a>,
638 },
639
640 /// Unary NOT: "not x" → `!x` (logical for Bool, bitwise for Int)
641 Not {
642 operand: &'a Expr<'a>,
643 },
644
645 /// Function call as expression: f(x, y)
646 Call {
647 function: Symbol,
648 args: Vec<&'a Expr<'a>>,
649 },
650
651 /// Dynamic index access: `items at i` (1-indexed).
652 Index {
653 collection: &'a Expr<'a>,
654 index: &'a Expr<'a>,
655 },
656
657 /// Dynamic slice access: `items 1 through mid` (1-indexed, inclusive).
658 Slice {
659 collection: &'a Expr<'a>,
660 start: &'a Expr<'a>,
661 end: &'a Expr<'a>,
662 },
663
664 /// Copy expression: `copy of slice` → slice.to_vec().
665 Copy {
666 expr: &'a Expr<'a>,
667 },
668
669 /// Give expression: `Give x` → transfers ownership, no clone needed.
670 /// Used in function calls to explicitly move values.
671 Give {
672 value: &'a Expr<'a>,
673 },
674
675 /// Length expression: `length of items` → items.len().
676 Length {
677 collection: &'a Expr<'a>,
678 },
679
680 /// Set contains: `set contains x` or `x in set`
681 Contains {
682 collection: &'a Expr<'a>,
683 value: &'a Expr<'a>,
684 },
685
686 /// Set union: `a union b`
687 Union {
688 left: &'a Expr<'a>,
689 right: &'a Expr<'a>,
690 },
691
692 /// Set intersection: `a intersection b`
693 Intersection {
694 left: &'a Expr<'a>,
695 right: &'a Expr<'a>,
696 },
697
698 /// Get manifest of a zone.
699 /// `the manifest of Zone` → FileSipper::from_zone(&zone).manifest()
700 ManifestOf {
701 zone: &'a Expr<'a>,
702 },
703
704 /// Get chunk at index from a zone.
705 /// `the chunk at N in Zone` → FileSipper::from_zone(&zone).get_chunk(N)
706 ChunkAt {
707 index: &'a Expr<'a>,
708 zone: &'a Expr<'a>,
709 },
710
711 /// List literal: [1, 2, 3]
712 List(Vec<&'a Expr<'a>>),
713
714 /// Tuple literal: (1, "hello", true)
715 Tuple(Vec<&'a Expr<'a>>),
716
717 /// Range: 1 to 10 (inclusive)
718 Range {
719 start: &'a Expr<'a>,
720 end: &'a Expr<'a>,
721 },
722
723 /// Field access: `p's x` or `the x of p`.
724 FieldAccess {
725 object: &'a Expr<'a>,
726 field: Symbol,
727 },
728
729 /// Constructor: `a new Point` or `a new Point with x 10 and y 20`.
730 /// Supports generics: `a new Box of Int` and nested types: `a new Seq of (Seq of Int)`
731 New {
732 type_name: Symbol,
733 type_args: Vec<TypeExpr<'a>>, // Empty for non-generic types - now supports nested types
734 init_fields: Vec<(Symbol, &'a Expr<'a>)>, // Optional field initialization
735 },
736
737 /// Enum variant constructor: `a new Circle with radius 10`.
738 NewVariant {
739 enum_name: Symbol, // Shape (resolved from registry)
740 variant: Symbol, // Circle
741 fields: Vec<(Symbol, &'a Expr<'a>)>, // [(radius, 10)]
742 },
743
744 /// Escape hatch expression: raw foreign code that produces a value.
745 /// Used in expression position: `Let x: Int be Escape to Rust:`
746 Escape {
747 language: Symbol,
748 code: Symbol,
749 },
750
751 /// Option Some: `some 30` → Some(30)
752 OptionSome {
753 value: &'a Expr<'a>,
754 },
755
756 /// Option None: `none` → None
757 OptionNone,
758
759 /// Pre-allocation capacity hint wrapping an inner value expression.
760 /// `"" with capacity 100` or `a new Seq of Int with capacity n`
761 /// Codegen uses with_capacity(); interpreter ignores the hint.
762 WithCapacity {
763 value: &'a Expr<'a>,
764 capacity: &'a Expr<'a>,
765 },
766
767 /// Closure expression: `(params) -> body` or `(params) ->:` block body.
768 /// Captures variables from the enclosing scope by value (snapshot/clone).
769 Closure {
770 params: Vec<(Symbol, &'a TypeExpr<'a>)>,
771 body: ClosureBody<'a>,
772 return_type: Option<&'a TypeExpr<'a>>,
773 },
774
775 /// Call an expression that evaluates to a callable value.
776 /// `f(x)` where `f` is a variable holding a closure, not a named function.
777 CallExpr {
778 callee: &'a Expr<'a>,
779 args: Vec<&'a Expr<'a>>,
780 },
781
782 /// Interpolated string: `"Hello, {name}! Value: {x:.2}"`
783 InterpolatedString(Vec<StringPart<'a>>),
784}
785
786/// A segment of an interpolated string.
787#[derive(Debug, Clone)]
788pub enum StringPart<'a> {
789 /// Literal text segment
790 Literal(Symbol),
791 /// Expression with optional format specifier
792 Expr {
793 value: &'a Expr<'a>,
794 format_spec: Option<Symbol>,
795 /// Self-documenting debug format: `{var=}` → `"var=42"`
796 debug: bool,
797 },
798}
799
800/// Body of a closure expression.
801#[derive(Debug, Clone)]
802pub enum ClosureBody<'a> {
803 /// Single expression: `(n: Int) -> n * 2`
804 Expression(&'a Expr<'a>),
805 /// Block of statements: `(n: Int) ->:` followed by indented body
806 Block(Block<'a>),
807}
808
809/// Literal values in LOGOS.
810#[derive(Debug, Clone)]
811pub enum Literal {
812 /// Integer literal
813 Number(i64),
814 /// Float literal
815 Float(f64),
816 /// Text literal
817 Text(Symbol),
818 /// Boolean literal
819 Boolean(bool),
820 /// The nothing literal (unit type)
821 Nothing,
822 /// Character literal
823 Char(char),
824 /// Duration literal (nanoseconds, signed for negative offsets like "5 minutes early")
825 Duration(i64),
826 /// Date literal (days since Unix epoch 1970-01-01)
827 Date(i32),
828 /// Moment literal (nanoseconds since Unix epoch)
829 Moment(i64),
830 /// Calendar span (months, days) - NOT flattened to seconds
831 /// Months and days are kept separate because they're incommensurable.
832 Span { months: i32, days: i32 },
833 /// Time-of-day literal (nanoseconds from midnight)
834 /// Range: 0 to 86_399_999_999_999 (just under 24 hours)
835 Time(i64),
836}
837
838impl PartialEq for Literal {
839 fn eq(&self, other: &Self) -> bool {
840 match (self, other) {
841 (Literal::Number(a), Literal::Number(b)) => a == b,
842 (Literal::Float(a), Literal::Float(b)) => a.to_bits() == b.to_bits(),
843 (Literal::Text(a), Literal::Text(b)) => a == b,
844 (Literal::Boolean(a), Literal::Boolean(b)) => a == b,
845 (Literal::Nothing, Literal::Nothing) => true,
846 (Literal::Char(a), Literal::Char(b)) => a == b,
847 (Literal::Duration(a), Literal::Duration(b)) => a == b,
848 (Literal::Date(a), Literal::Date(b)) => a == b,
849 (Literal::Moment(a), Literal::Moment(b)) => a == b,
850 (Literal::Span { months: m1, days: d1 }, Literal::Span { months: m2, days: d2 }) => m1 == m2 && d1 == d2,
851 (Literal::Time(a), Literal::Time(b)) => a == b,
852 _ => false,
853 }
854 }
855}
856
857impl Eq for Literal {}
858
859impl std::hash::Hash for Literal {
860 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
861 std::mem::discriminant(self).hash(state);
862 match self {
863 Literal::Number(n) => n.hash(state),
864 Literal::Float(f) => f.to_bits().hash(state),
865 Literal::Text(s) => s.hash(state),
866 Literal::Boolean(b) => b.hash(state),
867 Literal::Nothing => {}
868 Literal::Char(c) => c.hash(state),
869 Literal::Duration(d) => d.hash(state),
870 Literal::Date(d) => d.hash(state),
871 Literal::Moment(m) => m.hash(state),
872 Literal::Span { months, days } => {
873 months.hash(state);
874 days.hash(state);
875 }
876 Literal::Time(t) => t.hash(state),
877 }
878 }
879}