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::axiom::{AxiomBlock, TheoryBlock};
14use super::definition::DefinitionBlock;
15use super::logic::LogicExpr;
16use super::theorem::TheoremBlock;
17use logicaffeine_base::Symbol;
18
19/// Type expression for explicit type annotations.
20///
21/// Represents type syntax like:
22/// - `Int` → Primitive(Int)
23/// - `User` → Named(User)
24/// - `List of Int` → Generic { base: List, params: [Primitive(Int)] }
25/// - `List of List of Int` → Generic { base: List, params: [Generic { base: List, params: [Primitive(Int)] }] }
26/// - `Result of Int and Text` → Generic { base: Result, params: [Primitive(Int), Primitive(Text)] }
27#[derive(Debug, Clone)]
28pub enum TypeExpr<'a> {
29 /// Primitive type: Int, Nat, Text, Bool
30 Primitive(Symbol),
31 /// Named type (user-defined): User, Point
32 Named(Symbol),
33 /// Generic type: List of Int, Option of Text, Result of Int and Text
34 Generic {
35 base: Symbol,
36 params: &'a [TypeExpr<'a>],
37 },
38 /// Function type: fn(A, B) -> C (for higher-order functions)
39 Function {
40 inputs: &'a [TypeExpr<'a>],
41 output: &'a TypeExpr<'a>,
42 },
43 /// Refinement type with predicate constraint.
44 /// Example: `Int where it > 0`
45 Refinement {
46 /// The base type being refined
47 base: &'a TypeExpr<'a>,
48 /// The bound variable (usually "it")
49 var: Symbol,
50 /// The predicate constraint (from Logic Kernel)
51 predicate: &'a LogicExpr<'a>,
52 },
53 /// Persistent storage wrapper type.
54 /// Example: `Persistent Counter`
55 /// Semantics: Wraps a Shared type with journal-backed storage
56 Persistent {
57 /// The inner type (must be a Shared/CRDT type)
58 inner: &'a TypeExpr<'a>,
59 },
60 /// Mutable-parameter marker (Mutable Value Semantics escape hatch).
61 /// Example: `## To addItem (items: mutable Seq of Int):` →
62 /// `Mutable { inner: Generic { List, [Int] } }`.
63 /// Semantics: under value semantics collections pass by value by default; a
64 /// `Mutable` parameter passes by reference so the callee's mutations are
65 /// visible to the caller (the explicit, opt-in form of the void-mutate
66 /// idiom). Carried on the parameter's type so the `(Symbol, &TypeExpr)`
67 /// parameter representation is unchanged. For every purpose other than the
68 /// parameter-passing convention, `Mutable { inner }` behaves as `inner`.
69 Mutable {
70 /// The underlying parameter type.
71 inner: &'a TypeExpr<'a>,
72 },
73}
74
75/// Source for Read statements.
76#[derive(Debug, Clone, Copy)]
77pub enum ReadSource<'a> {
78 /// Read from console (stdin)
79 Console,
80 /// Read from file at given path
81 File(&'a Expr<'a>),
82}
83
84/// Pattern for loop variable binding.
85/// Supports single identifiers and tuple destructuring for Map iteration.
86#[derive(Debug, Clone)]
87pub enum Pattern {
88 /// Single identifier: `Repeat for x in list`
89 Identifier(Symbol),
90 /// Tuple destructuring: `Repeat for (k, v) in map`
91 Tuple(Vec<Symbol>),
92}
93
94/// Binary operation kinds for imperative expressions.
95#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
96pub enum BinaryOpKind {
97 Add,
98 Subtract,
99 Multiply,
100 /// `a ** b` — exponentiation. Integer power is EXACT (promotes to BigInt on
101 /// overflow); a Float operand uses `powf`; a negative integer exponent is a
102 /// loud error. Binds tighter than `* / %`, right-associative.
103 Pow,
104 Divide,
105 /// EXACT division — the type-directed sibling of [`BinaryOpKind::Divide`].
106 /// `Divide` floors (`7 / 2 → 3`, the integer default); `ExactDivide` keeps the
107 /// quotient exact (`7 / 2 → 7/2`, a `Rational`). The `resolve_divisions` pass
108 /// rewrites `Divide → ExactDivide` only where the result flows into a `Rational`
109 /// context, so existing (floor) code is untouched.
110 ExactDivide,
111 /// `a // b` — FLOOR division, rounding toward negative infinity (`-7 // 2 → -4`).
112 /// Distinct from [`BinaryOpKind::Divide`], which truncates toward zero (`-7 / 2 →
113 /// -3`); the two agree when the operands share a sign. Exact (promotes to BigInt),
114 /// integer-producing, and immune to the `resolve_divisions` Rational rewrite — the
115 /// explicit spelling for "give me the floored integer quotient."
116 FloorDivide,
117 Modulo,
118 Eq,
119 NotEq,
120 Lt,
121 Gt,
122 LtEq,
123 GtEq,
124 // Logical/bitwise operators — type-aware in codegen (&&/|| for Bool, &/| for Int)
125 And,
126 Or,
127 /// String concatenation ("X combined with Y")
128 Concat,
129 /// Sequence concatenation ("A followed by B") — merge two sequences into one.
130 SeqConcat,
131 /// Tolerant float comparison ("a is approximately b") — Python-isclose
132 /// semantics (rel 1e-9, abs floor 1e-12). `==` stays IEEE bit-exact;
133 /// this is the EXPLICIT spelling for near-equality.
134 ApproxEq,
135 /// Bitwise XOR: `x ^ y` (the word `xor` is the English spelling); on
136 /// Sets, symmetric difference.
137 BitXor,
138 /// Bitwise AND: `x & y`; on Sets, intersection.
139 BitAnd,
140 /// Bitwise OR: `x | y`; on Sets, union.
141 BitOr,
142 /// Left shift: "x shifted left by y" → `x << y`
143 Shl,
144 /// Right shift: "x shifted right by y" → `x >> y`
145 Shr,
146}
147
148/// Block is a sequence of statements.
149pub type Block<'a> = &'a [Stmt<'a>];
150
151/// Match arm for pattern matching in Inspect statements.
152#[derive(Debug, Clone)]
153pub struct MatchArm<'a> {
154 pub enum_name: Option<Symbol>, // The enum type (e.g., Shape)
155 pub variant: Option<Symbol>, // None = Otherwise (wildcard)
156 pub bindings: Vec<(Symbol, Symbol)>, // (field_name, binding_name)
157 pub body: Block<'a>,
158}
159
160/// The wire compression codec a `Send compressed [with <codec>]` selects. The
161/// transpiler maps this to the runtime's wire codec; bare `compressed` = `Deflate`.
162#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
163pub enum CompressionCodec {
164 /// DEFLATE — the balanced default (bare `Send compressed`).
165 Deflate,
166 /// LZ4 — near-memcpy speed, lighter ratio.
167 Lz4,
168 /// Zstandard — the best ratio.
169 Zstd,
170}
171
172/// The wire LAYOUT a `Send` modifier picks — the size↔speed dial the sender chooses for
173/// their link. The transpiler maps this to the runtime's numeric codec. The sender knows
174/// their use case; this lets them express it in one word.
175#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
176pub enum SendLayout {
177 /// `compact` / `small` — smallest wire (LEB128 varint). For a bandwidth-bound link
178 /// (mobile, WAN, metered). This is also the default when no layout word is given.
179 Compact,
180 /// `fast` / `quickly` — fastest decode (fixed-width memcpy, zero parse). For a
181 /// latency-bound / fat link (LAN, datacenter, RDMA).
182 Fast,
183 /// `packed` — varint size with SIMD group-varint decode; the balanced middle.
184 Packed,
185 /// `smallest` / `best` — turn on the per-column compression menu (delta /
186 /// delta-of-delta / frame-of-reference / run-length / dictionary), auto-selecting
187 /// each column's smallest form and never exceeding plain varint. For a
188 /// bandwidth-bound link where CPU is cheap relative to bytes.
189 Smallest,
190 /// `redundant` / `tough` — forward error correction: the message is split into
191 /// Reed-Solomon shards and each is published as its own packet, so a receiver
192 /// reconstructs the exact message from any K even after some are lost. For a lossy
193 /// / one-way link (UDP, multicast, BLE, LoRa) where retransmit is impossible.
194 Redundant,
195}
196
197/// Backing-file source for a memory-mapped zone (`… mapped from <here>`).
198/// `Inside a zone called "D" mapped from "f.bin"` is [`ZoneSource::Literal`];
199/// `… mapped from path` (a runtime `Text` variable) is [`ZoneSource::Variable`].
200#[derive(Debug, Clone, Copy, PartialEq, Eq)]
201pub enum ZoneSource {
202 /// A string-literal path baked into the source.
203 Literal(Symbol),
204 /// A variable holding the path, resolved at runtime.
205 Variable(Symbol),
206}
207
208/// Imperative statement AST (LOGOS §15.0.0).
209///
210/// Stmt is the primary AST node for imperative code blocks like `## Main`
211/// and function bodies. The Assert variant bridges to the Logic Kernel.
212/// Which end of the shared pad this peer draws from, for the PNP one-time-pad tier.
213/// `as initiator` sends on the first directional half; `as responder` sends on the second.
214#[derive(Debug, Clone, Copy, PartialEq, Eq)]
215pub enum SecureRole {
216 /// Draws the send pad from the first directional half (`i2r`).
217 Initiator,
218 /// Draws the send pad from the second directional half (`r2i`).
219 Responder,
220}
221
222/// The `with pad "<path>" as <role>` binding on a `Connect`/`Listen` — activate the PNP one-time-pad
223/// session over the pad file at `pad`, with this peer taking `role`.
224#[derive(Debug, Clone, Copy)]
225pub struct SecurePad<'a> {
226 /// The pad file path (a string-literal or variable expression).
227 pub pad: &'a Expr<'a>,
228 /// Which directional half this peer sends on.
229 pub role: SecureRole,
230}
231
232#[derive(Debug, Clone)]
233pub enum Stmt<'a> {
234 /// Variable binding: `Let x be 5.` or `Let x: Int be 5.`
235 Let {
236 var: Symbol,
237 ty: Option<&'a TypeExpr<'a>>,
238 value: &'a Expr<'a>,
239 mutable: bool,
240 },
241
242 /// Mutation: `Set x to 10.`
243 Set {
244 target: Symbol,
245 value: &'a Expr<'a>,
246 },
247
248 /// Function call as statement: `Call process with data.`
249 Call {
250 function: Symbol,
251 args: Vec<&'a Expr<'a>>,
252 },
253
254 /// Conditional: `If condition: ... Otherwise: ...`
255 If {
256 cond: &'a Expr<'a>,
257 then_block: Block<'a>,
258 else_block: Option<Block<'a>>,
259 },
260
261 /// Loop: `While condition: ...` or `While condition (decreasing expr): ...`
262 While {
263 cond: &'a Expr<'a>,
264 body: Block<'a>,
265 /// Optional decreasing variant for termination proof.
266 decreasing: Option<&'a Expr<'a>>,
267 },
268
269 /// Iteration: `Repeat for x in list: ...` or `Repeat for i from 1 to 10: ...`
270 Repeat {
271 pattern: Pattern, // Changed from `var: Symbol` to support tuple destructuring
272 iterable: &'a Expr<'a>,
273 body: Block<'a>,
274 },
275
276 /// Return: `Return x.` or `Return.`
277 Return {
278 value: Option<&'a Expr<'a>>,
279 },
280
281 /// Break: `Break.` — exits the innermost while loop.
282 Break,
283
284 /// Bridge to Logic Kernel: `Assert that P.`
285 Assert {
286 proposition: &'a LogicExpr<'a>,
287 },
288
289 /// Documented assertion with justification.
290 /// `Trust that P because "reason".`
291 /// Semantics: Documented runtime check that could be verified statically.
292 Trust {
293 proposition: &'a LogicExpr<'a>,
294 justification: Symbol,
295 },
296
297 /// Runtime assertion with imperative condition.
298 /// `Assert that condition.` (`hard: false` → `debug_assert!`, a development check)
299 /// `Require that condition.` (`hard: true` → `assert!`, an enforced invariant that
300 /// survives release — the form a proven property lowers to).
301 RuntimeAssert {
302 condition: &'a Expr<'a>,
303 hard: bool,
304 },
305
306 /// Ownership transfer (move): `Give x to processor.`
307 /// Semantics: Move ownership of `object` to `recipient`.
308 Give {
309 object: &'a Expr<'a>,
310 recipient: &'a Expr<'a>,
311 },
312
313 /// Immutable borrow: `Show x to console.`
314 /// Semantics: Immutable borrow of `object` passed to `recipient`.
315 Show {
316 object: &'a Expr<'a>,
317 recipient: &'a Expr<'a>,
318 },
319
320 /// Field mutation: `Set p's x to 10.`
321 SetField {
322 object: &'a Expr<'a>,
323 field: Symbol,
324 value: &'a Expr<'a>,
325 },
326
327 /// Struct definition for codegen.
328 StructDef {
329 name: Symbol,
330 fields: Vec<(Symbol, Symbol, bool)>, // (name, type_name, is_public)
331 is_portable: bool, // Derives Serialize/Deserialize
332 },
333
334 /// Function definition.
335 FunctionDef {
336 name: Symbol,
337 /// Generic type parameters: empty for monomorphic functions, e.g. `[T, U]` for polymorphic.
338 generics: Vec<Symbol>,
339 params: Vec<(Symbol, &'a TypeExpr<'a>)>,
340 body: Block<'a>,
341 return_type: Option<&'a TypeExpr<'a>>,
342 is_native: bool,
343 /// Rust path for user-defined native functions (e.g., "reqwest::blocking::get").
344 /// None for system native functions (read, write, etc.) which use map_native_function().
345 native_path: Option<Symbol>,
346 /// Whether this function is exported for FFI (C ABI or WASM).
347 is_exported: bool,
348 /// Export target: None = C ABI (#\[no_mangle\] extern "C"), Some("wasm") = #\[wasm_bindgen\].
349 export_target: Option<Symbol>,
350 /// Per-function optimization config: each `## No <X>` annotation clears
351 /// that optimization's bit (default: all enabled).
352 opt_flags: crate::optimization::OptimizationConfig,
353 },
354
355 /// Pattern matching on sum types.
356 Inspect {
357 target: &'a Expr<'a>,
358 arms: Vec<MatchArm<'a>>,
359 has_otherwise: bool, // For exhaustiveness tracking
360 },
361
362 /// Push to collection: `Push x to items.`
363 Push {
364 value: &'a Expr<'a>,
365 collection: &'a Expr<'a>,
366 },
367
368 /// Pop from collection: `Pop from items.` or `Pop from items into y.`
369 Pop {
370 collection: &'a Expr<'a>,
371 into: Option<Symbol>,
372 },
373
374 /// Add to set: `Add x to set.`
375 Add {
376 value: &'a Expr<'a>,
377 collection: &'a Expr<'a>,
378 },
379
380 /// Remove from set: `Remove x from set.`
381 Remove {
382 value: &'a Expr<'a>,
383 collection: &'a Expr<'a>,
384 },
385
386 /// Index assignment: `Set item N of X to Y.`
387 SetIndex {
388 collection: &'a Expr<'a>,
389 index: &'a Expr<'a>,
390 value: &'a Expr<'a>,
391 },
392
393 /// A SCOPE-TRANSPARENT statement sequence — parser-desugar output, never
394 /// written by users. One surface statement can lower to several primitive
395 /// statements (a nested place-write `Set item j of (item i of grid) to v`
396 /// becomes read → copy-on-write element write → write-back; a multi-push
397 /// `Push a, b, c to xs` becomes one Push per element). The body runs in
398 /// the ENCLOSING scope with no block of its own: temporaries inside are
399 /// gensym'd (`__place_*`), so they can never collide with user names, and
400 /// engines that execute blocks with scoping must NOT scope this one.
401 Splice {
402 body: Block<'a>,
403 },
404
405 /// Memory arena block (Zone).
406 /// "Inside a new zone called 'Scratch':"
407 /// "Inside a zone called 'Buffer' of size 1 MB:"
408 /// "Inside a zone called 'Data' mapped from 'file.bin':"
409 Zone {
410 /// The variable name for the arena handle (e.g., "Scratch")
411 name: Symbol,
412 /// Optional pre-allocated capacity in bytes (Heap zones only)
413 capacity: Option<usize>,
414 /// Optional backing file for memory-mapped zones (literal path or runtime variable)
415 source_file: Option<ZoneSource>,
416 /// The code block executed within this memory context
417 body: Block<'a>,
418 },
419
420 /// Concurrent execution block (async, I/O-bound).
421 /// "Attempt all of the following:"
422 /// Semantics: All tasks run concurrently via tokio::join!
423 /// Best for: network requests, file I/O, waiting operations
424 Concurrent {
425 /// The statements to execute concurrently
426 tasks: Block<'a>,
427 },
428
429 /// Parallel execution block (CPU-bound).
430 /// "Simultaneously:"
431 /// Semantics: True parallelism via rayon::join or thread::spawn
432 /// Best for: computation, data processing, number crunching
433 Parallel {
434 /// The statements to execute in parallel
435 tasks: Block<'a>,
436 },
437
438 /// Read from console or file.
439 /// `Read input from the console.` or `Read data from file "path.txt".`
440 ReadFrom {
441 var: Symbol,
442 source: ReadSource<'a>,
443 },
444
445 /// Write to file.
446 /// `Write "content" to file "output.txt".`
447 WriteFile {
448 content: &'a Expr<'a>,
449 path: &'a Expr<'a>,
450 },
451
452 /// Spawn an agent.
453 /// `Spawn a Worker called "w1".`
454 Spawn {
455 agent_type: Symbol,
456 name: Symbol,
457 },
458
459 /// Send message to agent.
460 /// `Send Ping to "agent".`, `Send compressed Ping to "agent".`,
461 /// `Send cached Point to "agent".`, or `Send cached compressed Report to "agent".`
462 SendMessage {
463 message: &'a Expr<'a>,
464 destination: &'a Expr<'a>,
465 /// The wire compression codec. `None` for a plain `Send`; `Some(codec)` for
466 /// `Send compressed [with <codec>]` (bare `compressed` = deflate). Kept only
467 /// if it actually shrinks the body.
468 compression: Option<CompressionCodec>,
469 /// `Send cached …` — use the connection's schema dictionary, so a struct
470 /// schema is transmitted once and referenced thereafter (content-addressed,
471 /// footgun-free). `false` for a plain `Send`.
472 cached: bool,
473 /// `Send unchecked …` — drop the wire integrity checksum for the fastest path
474 /// (latency↔safety dial). `false` keeps the default checksum.
475 unchecked: bool,
476 /// `Send fast|compact|packed …` — the wire LAYOUT (size↔speed dial). `None` is
477 /// the default (compact / varint). The sender picks for their link.
478 layout: Option<SendLayout>,
479 /// `Send shared …` — OPT-IN type-id elision: drop struct/enum NAMES off the wire
480 /// (ship a small registry id both ends derive from their shared program type
481 /// defs). Only safe when the receiver runs the same program, so it is OFF by
482 /// default — the default `Send` stays self-describing for any peer / relay.
483 shared: bool,
484 /// `Send computed f …` — COMPUTE-SHIPPING: when the message is a pure single-arg
485 /// function, lower it to a sandboxed generator and ship the COMPUTATION, not data.
486 /// The receiver evaluates it in the bounded sandbox (never arbitrary code). OFF by
487 /// default; a non-lowerable function under `computed` is rejected at send.
488 computed: bool,
489 /// `Send indexed …` (alias `addressable`) — encode a record list in the random-access
490 /// struct-view LAYOUT (row + field offset tables), so the receiver reaches any
491 /// (row, field) in O(1) without decoding the rest — Cap'n Proto's home turf. Composes
492 /// with the other knobs (`compressed`, `cached`, `shared`, `unchecked`). OFF by default
493 /// (the dense columnar form is smaller); opt in when the peer does random field reads.
494 indexed: bool,
495 /// `Send deduped …` — Rc-DEDUP: a subtree the same value reaches more than once ships ONCE
496 /// (the first occurrence) plus a tiny backref for every repeat, and the receiver rebuilds the
497 /// SHARING (one aliased value, not N copies). OFF by default; opt in when the message has
498 /// shared sub-structure (a lookup table referenced by many records, one object aliased
499 /// across the payload). Self-describing by tag, so any peer decodes it.
500 deduped: bool,
501 },
502
503 /// Await response from agent.
504 /// `Await response from "agent" into result.`
505 AwaitMessage {
506 source: &'a Expr<'a>,
507 into: Symbol,
508 /// `Await view from …`: hold a received record-list LAZILY (the zero-copy receive —
509 /// decode-on-touch, no rows materialized until a field is read) instead of fully decoding
510 /// it. Ignored for non-record-list shapes, which always decode eagerly.
511 view: bool,
512 /// `Await stream from …`: receive a batch STREAM message and deframe it into a list (the
513 /// values a peer `Stream`ed), rather than a single message.
514 stream: bool,
515 },
516
517 /// Stream a batch of values to a peer in one framed message.
518 /// `Stream readings to "sink".` — frames each element of `values` length-delimited so the
519 /// receiver (`Await stream from …`) deframes them incrementally; one relay publish ships the
520 /// whole batch (Kafka-style streaming that amortizes per-message overhead).
521 StreamMessage {
522 values: &'a Expr<'a>,
523 destination: &'a Expr<'a>,
524 },
525
526 /// Merge CRDT state.
527 /// `Merge remote into local.` or `Merge remote's field into local's field.`
528 MergeCrdt {
529 source: &'a Expr<'a>,
530 target: &'a Expr<'a>,
531 },
532
533 /// Increment GCounter.
534 /// `Increase local's points by 10.`
535 IncreaseCrdt {
536 object: &'a Expr<'a>,
537 field: Symbol,
538 amount: &'a Expr<'a>,
539 },
540
541 /// Decrement PNCounter (Tally).
542 /// `Decrease game's score by 5.`
543 DecreaseCrdt {
544 object: &'a Expr<'a>,
545 field: Symbol,
546 amount: &'a Expr<'a>,
547 },
548
549 /// Append to SharedSequence (RGA).
550 /// `Append "Hello" to doc's lines.`
551 AppendToSequence {
552 sequence: &'a Expr<'a>,
553 value: &'a Expr<'a>,
554 },
555
556 /// Resolve MVRegister conflicts.
557 /// `Resolve page's title to "Final".`
558 ResolveConflict {
559 object: &'a Expr<'a>,
560 field: Symbol,
561 value: &'a Expr<'a>,
562 },
563
564 /// Security check - mandatory runtime guard.
565 /// `Check that user is admin.`
566 /// `Check that user can publish the document.`
567 /// Semantics: NEVER optimized out. Panics if condition is false.
568 Check {
569 /// The subject being checked (e.g., "user")
570 subject: Symbol,
571 /// The predicate name (e.g., "admin") or action (e.g., "publish")
572 predicate: Symbol,
573 /// True if this is a capability check (`can [action]`)
574 is_capability: bool,
575 /// For capabilities: the object being acted on (e.g., "document")
576 object: Option<Symbol>,
577 /// Original English text for error message
578 source_text: String,
579 /// Source location for error reporting
580 span: crate::token::Span,
581 },
582
583 /// Listen on network address.
584 /// `Listen on "/ip4/127.0.0.1/tcp/8000".`
585 /// Semantics: Bind to address, start accepting connections via libp2p
586 Listen {
587 address: &'a Expr<'a>,
588 /// Optional PNP one-time-pad binding: `with pad "<path>" as initiator|responder`.
589 secure: Option<SecurePad<'a>>,
590 },
591
592 /// Connect to remote peer.
593 /// `Connect to "/ip4/127.0.0.1/tcp/8000".`
594 /// Semantics: Dial peer via libp2p
595 ConnectTo {
596 address: &'a Expr<'a>,
597 /// Optional PNP one-time-pad binding: `with pad "<path>" as initiator|responder`.
598 secure: Option<SecurePad<'a>>,
599 },
600
601 /// Create PeerAgent remote handle.
602 /// `Let remote be a PeerAgent at "/ip4/127.0.0.1/tcp/8000".`
603 /// Semantics: Create handle for remote agent communication
604 LetPeerAgent {
605 var: Symbol,
606 address: &'a Expr<'a>,
607 },
608
609 /// Sleep for milliseconds.
610 /// `Sleep 1000.` or `Sleep delay.`
611 /// Semantics: Pause execution for N milliseconds (async)
612 Sleep {
613 milliseconds: &'a Expr<'a>,
614 },
615
616 /// Sync CRDT variable on topic.
617 /// `Sync x on "topic".`
618 /// Semantics: Subscribe to GossipSub topic, auto-publish on mutation, auto-merge on receive
619 Sync {
620 var: Symbol,
621 topic: &'a Expr<'a>,
622 },
623
624 /// Mount persistent CRDT from journal file.
625 /// `Mount counter at "data/counter.journal".`
626 /// Semantics: Load or create journal, replay operations to reconstruct state
627 Mount {
628 /// The variable name for the mounted value
629 var: Symbol,
630 /// The path expression for the journal file
631 path: &'a Expr<'a>,
632 },
633
634 // =========================================================================
635 // Go-like Concurrency (Green Threads, Channels, Select)
636 // =========================================================================
637
638 /// Launch a fire-and-forget task (green thread).
639 /// `Launch a task to process(data).`
640 /// Semantics: tokio::spawn with no handle capture
641 LaunchTask {
642 /// The function to call
643 function: Symbol,
644 /// Arguments to pass
645 args: Vec<&'a Expr<'a>>,
646 },
647
648 /// Launch a task with handle for control.
649 /// `Let worker be Launch a task to process(data).`
650 /// Semantics: tokio::spawn returning JoinHandle
651 LaunchTaskWithHandle {
652 /// Variable to bind the handle
653 handle: Symbol,
654 /// The function to call
655 function: Symbol,
656 /// Arguments to pass
657 args: Vec<&'a Expr<'a>>,
658 },
659
660 /// Create a bounded channel (pipe).
661 /// `Let jobs be a new Pipe of Int.`
662 /// Semantics: tokio::sync::mpsc::channel(32)
663 CreatePipe {
664 /// Variable for the pipe
665 var: Symbol,
666 /// Type of values in the pipe
667 element_type: Symbol,
668 /// Optional capacity (defaults to 32)
669 capacity: Option<u32>,
670 },
671
672 /// Blocking send into pipe.
673 /// `Send value into pipe.`
674 /// Semantics: pipe_tx.send(value).await
675 SendPipe {
676 /// The value to send
677 value: &'a Expr<'a>,
678 /// The pipe to send into
679 pipe: &'a Expr<'a>,
680 },
681
682 /// Blocking receive from pipe.
683 /// `Receive x from pipe.`
684 /// Semantics: let x = pipe_rx.recv().await
685 ReceivePipe {
686 /// Variable to bind the received value
687 var: Symbol,
688 /// The pipe to receive from
689 pipe: &'a Expr<'a>,
690 },
691
692 /// Non-blocking send (try).
693 /// `Try to send value into pipe.`
694 /// Semantics: pipe_tx.try_send(value) - returns immediately
695 TrySendPipe {
696 /// The value to send
697 value: &'a Expr<'a>,
698 /// The pipe to send into
699 pipe: &'a Expr<'a>,
700 /// Variable to bind the result (true/false)
701 result: Option<Symbol>,
702 },
703
704 /// Non-blocking receive (try).
705 /// `Try to receive x from pipe.`
706 /// Semantics: pipe_rx.try_recv() - returns Option
707 TryReceivePipe {
708 /// Variable to bind the received value (if any)
709 var: Symbol,
710 /// The pipe to receive from
711 pipe: &'a Expr<'a>,
712 },
713
714 /// Cancel a spawned task.
715 /// `Stop worker.`
716 /// Semantics: handle.abort()
717 StopTask {
718 /// The handle to cancel
719 handle: &'a Expr<'a>,
720 },
721
722 /// Select on multiple channels/timeouts.
723 /// `Await the first of:`
724 /// `Receive x from ch:`
725 /// `...`
726 /// `After 5 seconds:`
727 /// `...`
728 /// Semantics: tokio::select! with auto-cancel
729 Select {
730 /// The branches to select from
731 branches: Vec<SelectBranch<'a>>,
732 },
733
734 /// Theorem block.
735 /// `## Theorem: Name`
736 /// `Given: Premise.`
737 /// `Prove: Goal.`
738 /// `Proof: Auto.`
739 Theorem(TheoremBlock<'a>),
740
741 /// `## Define` block — a vernacular-logic predicate definition (Rung 0a).
742 /// `x is a bachelor if and only if x is unmarried and x is a man.`
743 /// Non-executable: like [`Stmt::Theorem`], it is a declaration the proof
744 /// layer consumes, not code the VM/AOT runs.
745 Definition(DefinitionBlock<'a>),
746
747 /// `## Axiom` block — a named first-order axiom in formal notation. Registers a
748 /// shared premise for later theorems (the seam for an axiomatic base like Tarski).
749 Axiom(AxiomBlock),
750
751 /// `## Theory` block — a named development grouping formal axioms and theorems.
752 Theory(TheoryBlock),
753
754 /// Escape hatch: embed raw foreign code.
755 /// `Escape to Rust:` followed by an indented block of raw code.
756 ///
757 /// Variables from the enclosing LOGOS scope are available in the
758 /// escape block as their generated Rust types. The raw code is
759 /// emitted verbatim inside a `{ ... }` block in the generated Rust.
760 Escape {
761 /// Target language ("Rust" for now, forward-compatible with "Python", "WGSL", etc.)
762 language: Symbol,
763 /// Raw foreign code, captured verbatim with base indentation stripped.
764 code: Symbol,
765 /// Source span covering the entire escape block (header + body).
766 span: crate::token::Span,
767 },
768
769 /// Dependency declaration from `## Requires` block.
770 /// The "serde" crate version "1.0" with features "derive".
771 Require {
772 crate_name: Symbol,
773 version: Symbol,
774 features: Vec<Symbol>,
775 span: crate::token::Span,
776 },
777}
778
779/// A branch in a Select statement.
780#[derive(Debug, Clone)]
781pub enum SelectBranch<'a> {
782 /// Receive from a pipe: `Receive x from ch:`
783 Receive {
784 var: Symbol,
785 pipe: &'a Expr<'a>,
786 body: Block<'a>,
787 },
788 /// Timeout: `After N seconds:` or `After N milliseconds:`
789 Timeout {
790 milliseconds: &'a Expr<'a>,
791 body: Block<'a>,
792 },
793}
794
795/// Shared expression type for pure computations (LOGOS §15.0.0).
796///
797/// Expr is used by both LogicExpr (as terms) and Stmt (as values).
798/// These are pure computations without side effects.
799#[derive(Debug)]
800pub enum Expr<'a> {
801 /// Literal value: 42, "hello", true, nothing
802 Literal(Literal),
803
804 /// Variable reference: x
805 Identifier(Symbol),
806
807 /// Binary operation: x plus y
808 BinaryOp {
809 op: BinaryOpKind,
810 left: &'a Expr<'a>,
811 right: &'a Expr<'a>,
812 },
813
814 /// Unary NOT: "not x" → `!x` (logical for Bool, bitwise for Int)
815 Not {
816 operand: &'a Expr<'a>,
817 },
818
819 /// Function call as expression: f(x, y)
820 Call {
821 function: Symbol,
822 args: Vec<&'a Expr<'a>>,
823 },
824
825 /// Dynamic index access: `items at i` (1-indexed).
826 Index {
827 collection: &'a Expr<'a>,
828 index: &'a Expr<'a>,
829 },
830
831 /// Dynamic slice access: `items 1 through mid` (1-indexed, inclusive).
832 Slice {
833 collection: &'a Expr<'a>,
834 start: &'a Expr<'a>,
835 end: &'a Expr<'a>,
836 },
837
838 /// Copy expression: `copy of slice` → slice.to_vec().
839 Copy {
840 expr: &'a Expr<'a>,
841 },
842
843 /// Give expression: `Give x` → transfers ownership, no clone needed.
844 /// Used in function calls to explicitly move values.
845 Give {
846 value: &'a Expr<'a>,
847 },
848
849 /// Length expression: `length of items` → items.len().
850 Length {
851 collection: &'a Expr<'a>,
852 },
853
854 /// Set contains: `set contains x` or `x in set`
855 Contains {
856 collection: &'a Expr<'a>,
857 value: &'a Expr<'a>,
858 },
859
860 /// Set union: `a union b`
861 Union {
862 left: &'a Expr<'a>,
863 right: &'a Expr<'a>,
864 },
865
866 /// Set intersection: `a intersection b`
867 Intersection {
868 left: &'a Expr<'a>,
869 right: &'a Expr<'a>,
870 },
871
872 /// Get manifest of a zone.
873 /// `the manifest of Zone` → FileSipper::from_zone(&zone).manifest()
874 ManifestOf {
875 zone: &'a Expr<'a>,
876 },
877
878 /// Get chunk at index from a zone.
879 /// `the chunk at N in Zone` → FileSipper::from_zone(&zone).get_chunk(N)
880 ChunkAt {
881 index: &'a Expr<'a>,
882 zone: &'a Expr<'a>,
883 },
884
885 /// List literal: [1, 2, 3]
886 List(Vec<&'a Expr<'a>>),
887
888 /// Tuple literal: (1, "hello", true)
889 Tuple(Vec<&'a Expr<'a>>),
890
891 /// Range: 1 to 10 (inclusive)
892 Range {
893 start: &'a Expr<'a>,
894 end: &'a Expr<'a>,
895 },
896
897 /// Field access: `p's x` or `the x of p`.
898 FieldAccess {
899 object: &'a Expr<'a>,
900 field: Symbol,
901 },
902
903 /// Constructor: `a new Point` or `a new Point with x 10 and y 20`.
904 /// Supports generics: `a new Box of Int` and nested types: `a new Seq of (Seq of Int)`
905 New {
906 type_name: Symbol,
907 type_args: Vec<TypeExpr<'a>>, // Empty for non-generic types - now supports nested types
908 init_fields: Vec<(Symbol, &'a Expr<'a>)>, // Optional field initialization
909 },
910
911 /// Enum variant constructor: `a new Circle with radius 10`.
912 NewVariant {
913 enum_name: Symbol, // Shape (resolved from registry)
914 variant: Symbol, // Circle
915 fields: Vec<(Symbol, &'a Expr<'a>)>, // [(radius, 10)]
916 },
917
918 /// Escape hatch expression: raw foreign code that produces a value.
919 /// Used in expression position: `Let x: Int be Escape to Rust:`
920 Escape {
921 language: Symbol,
922 code: Symbol,
923 },
924
925 /// Option Some: `some 30` → Some(30)
926 OptionSome {
927 value: &'a Expr<'a>,
928 },
929
930 /// Option None: `none` → None
931 OptionNone,
932
933 /// Pre-allocation capacity hint wrapping an inner value expression.
934 /// `"" with capacity 100` or `a new Seq of Int with capacity n`
935 /// Codegen uses with_capacity(); interpreter ignores the hint.
936 WithCapacity {
937 value: &'a Expr<'a>,
938 capacity: &'a Expr<'a>,
939 },
940
941 /// Closure expression: `(params) -> body` or `(params) ->:` block body.
942 /// Captures variables from the enclosing scope by value (snapshot/clone).
943 Closure {
944 params: Vec<(Symbol, &'a TypeExpr<'a>)>,
945 body: ClosureBody<'a>,
946 return_type: Option<&'a TypeExpr<'a>>,
947 },
948
949 /// Call an expression that evaluates to a callable value.
950 /// `f(x)` where `f` is a variable holding a closure, not a named function.
951 CallExpr {
952 callee: &'a Expr<'a>,
953 args: Vec<&'a Expr<'a>>,
954 },
955
956 /// Interpolated string: `"Hello, {name}! Value: {x:.2}"`
957 InterpolatedString(Vec<StringPart<'a>>),
958}
959
960/// A segment of an interpolated string.
961#[derive(Debug, Clone)]
962pub enum StringPart<'a> {
963 /// Literal text segment
964 Literal(Symbol),
965 /// Expression with optional format specifier
966 Expr {
967 value: &'a Expr<'a>,
968 format_spec: Option<Symbol>,
969 /// Self-documenting debug format: `{var=}` → `"var=42"`
970 debug: bool,
971 },
972}
973
974/// Body of a closure expression.
975#[derive(Debug, Clone)]
976pub enum ClosureBody<'a> {
977 /// Single expression: `(n: Int) -> n * 2`
978 Expression(&'a Expr<'a>),
979 /// Block of statements: `(n: Int) ->:` followed by indented body
980 Block(Block<'a>),
981}
982
983/// Literal values in LOGOS.
984#[derive(Debug, Clone)]
985pub enum Literal {
986 /// Integer literal
987 Number(i64),
988 /// Float literal
989 Float(f64),
990 /// Text literal
991 Text(Symbol),
992 /// Boolean literal
993 Boolean(bool),
994 /// The nothing literal (unit type)
995 Nothing,
996 /// Character literal
997 Char(char),
998 /// Duration literal (nanoseconds, signed for negative offsets like "5 minutes early")
999 Duration(i64),
1000 /// Date literal (days since Unix epoch 1970-01-01)
1001 Date(i32),
1002 /// Moment literal (nanoseconds since Unix epoch)
1003 Moment(i64),
1004 /// Calendar span (months, days) - NOT flattened to seconds
1005 /// Months and days are kept separate because they're incommensurable.
1006 Span { months: i32, days: i32 },
1007 /// Time-of-day literal (nanoseconds from midnight)
1008 /// Range: 0 to 86_399_999_999_999 (just under 24 hours)
1009 Time(i64),
1010}
1011
1012impl PartialEq for Literal {
1013 fn eq(&self, other: &Self) -> bool {
1014 match (self, other) {
1015 (Literal::Number(a), Literal::Number(b)) => a == b,
1016 (Literal::Float(a), Literal::Float(b)) => a.to_bits() == b.to_bits(),
1017 (Literal::Text(a), Literal::Text(b)) => a == b,
1018 (Literal::Boolean(a), Literal::Boolean(b)) => a == b,
1019 (Literal::Nothing, Literal::Nothing) => true,
1020 (Literal::Char(a), Literal::Char(b)) => a == b,
1021 (Literal::Duration(a), Literal::Duration(b)) => a == b,
1022 (Literal::Date(a), Literal::Date(b)) => a == b,
1023 (Literal::Moment(a), Literal::Moment(b)) => a == b,
1024 (Literal::Span { months: m1, days: d1 }, Literal::Span { months: m2, days: d2 }) => m1 == m2 && d1 == d2,
1025 (Literal::Time(a), Literal::Time(b)) => a == b,
1026 _ => false,
1027 }
1028 }
1029}
1030
1031impl Eq for Literal {}
1032
1033impl std::hash::Hash for Literal {
1034 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1035 std::mem::discriminant(self).hash(state);
1036 match self {
1037 Literal::Number(n) => n.hash(state),
1038 Literal::Float(f) => f.to_bits().hash(state),
1039 Literal::Text(s) => s.hash(state),
1040 Literal::Boolean(b) => b.hash(state),
1041 Literal::Nothing => {}
1042 Literal::Char(c) => c.hash(state),
1043 Literal::Duration(d) => d.hash(state),
1044 Literal::Date(d) => d.hash(state),
1045 Literal::Moment(m) => m.hash(state),
1046 Literal::Span { months, days } => {
1047 months.hash(state);
1048 days.hash(state);
1049 }
1050 Literal::Time(t) => t.hash(state),
1051 }
1052 }
1053}