Skip to main content

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