Skip to main content

logicaffeine_data/
types.rs

1//! Core runtime type definitions.
2//!
3//! This module defines the primitive types used by LOGOS programs at runtime.
4//! These are type aliases that map LOGOS types to their Rust equivalents.
5//!
6//! ## Type Mappings
7//!
8//! | LOGOS Type | Rust Type | Description |
9//! |------------|-----------|-------------|
10//! | `Nat` | `u64` | Natural numbers (non-negative) |
11//! | `Int` | `i64` | Signed integers |
12//! | `Real` | `f64` | Floating-point numbers |
13//! | `Text` | `String` | UTF-8 strings |
14//! | `Bool` | `bool` | Boolean values |
15//! | `Unit` | `()` | The unit type |
16//! | `Char` | `char` | Unicode scalar values |
17//! | `Byte` | `u8` | Raw bytes |
18//! | `Seq<T>` | `LogosSeq<T>` | Ordered sequences (reference semantics) |
19//! | `Set<T>` | `HashSet<T>` | Unordered unique elements |
20//! | `Map<K,V>` | `LogosMap<K,V>` | Key-value mappings (reference semantics) |
21
22use std::cell::RefCell;
23use std::hash::Hash;
24use std::rc::Rc;
25
26/// Non-negative integers. Maps to Peano `Nat` in the kernel.
27pub type Nat = u64;
28/// Signed integers.
29pub type Int = i64;
30/// IEEE 754 floating-point numbers.
31pub type Real = f64;
32/// UTF-8 encoded text strings.
33pub type Text = String;
34/// Boolean truth values.
35pub type Bool = bool;
36/// The unit type (single value).
37pub type Unit = ();
38/// Unicode scalar values.
39pub type Char = char;
40/// Raw bytes (0-255).
41pub type Byte = u8;
42
43/// Ordered sequence with reference semantics.
44///
45/// `LogosSeq<T>` wraps `Rc<RefCell<Vec<T>>>` to provide shared mutable access.
46/// Cloning a `LogosSeq` produces a shallow copy (shared reference), not a deep copy.
47/// Use `.deep_clone()` for an independent copy (LOGOS `copy of`).
48#[derive(Debug)]
49pub struct LogosSeq<T>(pub Rc<RefCell<Vec<T>>>);
50
51impl<T> LogosSeq<T> {
52    pub fn new() -> Self {
53        Self(Rc::new(RefCell::new(Vec::new())))
54    }
55
56    pub fn from_vec(v: Vec<T>) -> Self {
57        Self(Rc::new(RefCell::new(v)))
58    }
59
60    pub fn with_capacity(cap: usize) -> Self {
61        Self(Rc::new(RefCell::new(Vec::with_capacity(cap))))
62    }
63
64    pub fn push(&self, value: T) {
65        self.0.borrow_mut().push(value);
66    }
67
68    pub fn pop(&self) -> Option<T> {
69        self.0.borrow_mut().pop()
70    }
71
72    pub fn len(&self) -> usize {
73        self.0.borrow().len()
74    }
75
76    pub fn is_empty(&self) -> bool {
77        self.0.borrow().is_empty()
78    }
79
80    pub fn remove(&self, index: usize) -> T {
81        self.0.borrow_mut().remove(index)
82    }
83
84    pub fn borrow(&self) -> std::cell::Ref<'_, Vec<T>> {
85        self.0.borrow()
86    }
87
88    pub fn borrow_mut(&self) -> std::cell::RefMut<'_, Vec<T>> {
89        self.0.borrow_mut()
90    }
91}
92
93/// A FILL-SLOT copy: collections copy DEEP, so every slot of a fill
94/// (`n copies of x`, `[x] * n`) is an independent row — never `n` aliases of
95/// one row (the classic `[[0]] * 3` footgun is designed out). Scalars copy
96/// plain. Mirrors the tree-walker's recursive `RuntimeValue::deep_clone`.
97pub trait FillClone {
98    fn fill_clone(&self) -> Self;
99}
100
101macro_rules! fill_by_clone {
102    ($($t:ty),* $(,)?) => {
103        $(impl FillClone for $t {
104            #[inline]
105            fn fill_clone(&self) -> Self {
106                self.clone()
107            }
108        })*
109    };
110}
111
112fill_by_clone!(i8, i16, i32, i64, i128, u8, u16, u32, u64, usize, f32, f64, bool, char, String);
113
114impl<T: FillClone> FillClone for LogosSeq<T> {
115    fn fill_clone(&self) -> Self {
116        Self(Rc::new(RefCell::new(
117            self.0.borrow().iter().map(|e| e.fill_clone()).collect(),
118        )))
119    }
120}
121
122impl<K: Clone + Eq + Hash, V: FillClone> FillClone for LogosMap<K, V> {
123    fn fill_clone(&self) -> Self {
124        Self(Rc::new(RefCell::new(
125            self.0.borrow().iter().map(|(k, v)| (k.clone(), v.fill_clone())).collect(),
126        )))
127    }
128}
129
130impl<T: Clone> LogosSeq<T> {
131    pub fn deep_clone(&self) -> Self {
132        Self(Rc::new(RefCell::new(self.0.borrow().clone())))
133    }
134
135    pub fn to_vec(&self) -> Vec<T> {
136        self.0.borrow().clone()
137    }
138
139    pub fn extend_from_slice(&self, other: &[T]) {
140        self.0.borrow_mut().extend_from_slice(other);
141    }
142
143    pub fn iter(&self) -> LogosSeqIter<T> {
144        LogosSeqIter {
145            data: self.to_vec(),
146            pos: 0,
147        }
148    }
149}
150
151pub struct LogosSeqIter<T> {
152    data: Vec<T>,
153    pos: usize,
154}
155
156impl<T: Clone> Iterator for LogosSeqIter<T> {
157    type Item = T;
158    fn next(&mut self) -> Option<T> {
159        if self.pos < self.data.len() {
160            let val = self.data[self.pos].clone();
161            self.pos += 1;
162            Some(val)
163        } else {
164            None
165        }
166    }
167}
168
169impl<T: Ord> LogosSeq<T> {
170    pub fn sort(&self) {
171        self.0.borrow_mut().sort();
172    }
173}
174
175impl<T> LogosSeq<T> {
176    pub fn reverse(&self) {
177        self.0.borrow_mut().reverse();
178    }
179}
180
181/// Whether Mutable Value Semantics is enabled for compiled (AOT) programs. The
182/// AOT binary is a separate process from the interpreter, so it reads the
183/// `LOGOS_VALUE_SEMANTICS` env var itself (inherited from the parent process),
184/// cached once. When on, collection `Clone` deep-copies (value semantics); when
185/// off it shares the `Rc` (the historical reference semantics — unchanged).
186pub fn value_semantics_enabled() -> bool {
187    use std::sync::OnceLock;
188    static ON: OnceLock<bool> = OnceLock::new();
189    // Value semantics is the DEFAULT; `LOGOS_VALUE_SEMANTICS=0` restores the
190    // historical reference semantics. Matches the compile-crate gate.
191    *ON.get_or_init(|| std::env::var("LOGOS_VALUE_SEMANTICS").as_deref() != Ok("0"))
192}
193
194impl<T: Clone> Clone for LogosSeq<T> {
195    fn clone(&self) -> Self {
196        // Both semantics now SHARE the allocation on `Clone` — a cheap `Rc`
197        // bump. Under value semantics, isolation is preserved LAZILY by `cow()`
198        // (below), which codegen/the engines call before an in-place mutation:
199        // clone-on-write instead of the historical clone-on-copy. Reference
200        // semantics never calls `cow()`, so the share is permanent (its
201        // historical behavior). This is the "best of all worlds" model: aliases
202        // share until one of them diverges via mutation.
203        Self(Rc::clone(&self.0))
204    }
205}
206
207impl<T: Clone> LogosSeq<T> {
208    /// Copy-on-write: make this handle uniquely own its buffer before an
209    /// in-place mutation, so the mutation cannot leak into a value-semantics
210    /// sibling that shares the same allocation. A no-op when already unique
211    /// (`strong_count == 1`), so the common case pays only a count read.
212    /// Mirrors the tree-walker's `ensure_collection_owned` and the VM's
213    /// `ensure_reg_owned`.
214    #[inline]
215    pub fn cow(&mut self) {
216        if Rc::strong_count(&self.0) > 1 {
217            // Bind the clone first so the `borrow()` temporary is released before the reassignment
218            // (edition-2024 extends the temporary's lifetime to the end of the statement otherwise).
219            let cloned = self.0.borrow().clone();
220            self.0 = Rc::new(RefCell::new(cloned));
221        }
222    }
223}
224
225impl<T: Clone> LogosSeq<LogosSeq<T>> {
226    /// Value-semantic nested write `grid[k][i] = v` (the through-write fast path): copy-on-write the
227    /// ROW at `k` (deep-copy only if its buffer is shared with a value-semantics sibling), then set
228    /// element `i` of that row in place. The compiled mirror of the parser's clone-modify-writeback
229    /// place desugar, but O(1) when the row is uniquely owned — no full-row clone. The caller `cow()`s
230    /// the OUTER handle first (as the codegen emits), so a shared `grid` is never mutated in place.
231    #[inline]
232    pub fn set_nested(&self, k: usize, i: usize, v: T) {
233        let mut outer = self.borrow_mut();
234        outer[k].cow();
235        outer[k].borrow_mut()[i] = v;
236    }
237}
238
239impl<T> Default for LogosSeq<T> {
240    fn default() -> Self {
241        Self::new()
242    }
243}
244
245impl<T: PartialEq> PartialEq for LogosSeq<T> {
246    fn eq(&self, other: &Self) -> bool {
247        *self.0.borrow() == *other.0.borrow()
248    }
249}
250
251impl<T: std::fmt::Display> std::fmt::Display for LogosSeq<T> {
252    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
253        let inner = self.0.borrow();
254        write!(f, "[")?;
255        for (i, item) in inner.iter().enumerate() {
256            if i > 0 { write!(f, ", ")?; }
257            write!(f, "{}", item)?;
258        }
259        write!(f, "]")
260    }
261}
262
263impl<T> From<Vec<T>> for LogosSeq<T> {
264    fn from(v: Vec<T>) -> Self {
265        Self::from_vec(v)
266    }
267}
268
269impl<T: serde::Serialize> serde::Serialize for LogosSeq<T> {
270    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
271        self.0.borrow().serialize(serializer)
272    }
273}
274
275impl<'de, T: serde::Deserialize<'de>> serde::Deserialize<'de> for LogosSeq<T> {
276    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
277        let vec = Vec::<T>::deserialize(deserializer)?;
278        Ok(Self::from_vec(vec))
279    }
280}
281
282impl<T: PartialEq> LogosContains<T> for LogosSeq<T> {
283    #[inline(always)]
284    fn logos_contains(&self, value: &T) -> bool {
285        self.0.borrow().contains(value)
286    }
287}
288
289impl<T: Clone> IntoIterator for LogosSeq<T> {
290    type Item = T;
291    type IntoIter = std::vec::IntoIter<T>;
292
293    fn into_iter(self) -> Self::IntoIter {
294        self.to_vec().into_iter()
295    }
296}
297
298/// The insertion-ordered map behind every LOGOS `Map`: `IndexMap` for the
299/// order contract (iteration, display, and serialization follow insertion,
300/// like a Python dict), Fx hashing for speed on the small keys LOGOS programs
301/// use (no DoS-resistance requirement).
302pub type FxIndexMap<K, V> =
303    indexmap::IndexMap<K, V, std::hash::BuildHasherDefault<rustc_hash::FxHasher>>;
304
305/// Key-value mapping with reference semantics.
306///
307/// `LogosMap<K, V>` wraps `Rc<RefCell<FxIndexMap<K, V>>>` to provide shared mutable access.
308/// Cloning a `LogosMap` produces a shallow copy (shared reference), not a deep copy.
309/// Use `.deep_clone()` for an independent copy (LOGOS `copy of`).
310#[derive(Debug)]
311pub struct LogosMap<K, V>(pub Rc<RefCell<FxIndexMap<K, V>>>);
312
313impl<K: Eq + Hash, V> LogosMap<K, V> {
314    pub fn new() -> Self {
315        Self(Rc::new(RefCell::new(FxIndexMap::default())))
316    }
317
318    pub fn with_capacity(cap: usize) -> Self {
319        Self(Rc::new(RefCell::new(
320            FxIndexMap::with_capacity_and_hasher(cap, Default::default()),
321        )))
322    }
323
324    pub fn from_map(m: FxIndexMap<K, V>) -> Self {
325        Self(Rc::new(RefCell::new(m)))
326    }
327
328    pub fn insert(&self, key: K, value: V) -> Option<V> {
329        self.0.borrow_mut().insert(key, value)
330    }
331
332    /// Removes a key while PRESERVING the insertion order of the remaining
333    /// entries (`shift_remove` — Python `del` semantics, O(n)).
334    pub fn remove(&self, key: &K) -> Option<V> {
335        self.0.borrow_mut().shift_remove(key)
336    }
337
338    pub fn len(&self) -> usize {
339        self.0.borrow().len()
340    }
341
342    pub fn is_empty(&self) -> bool {
343        self.0.borrow().is_empty()
344    }
345
346    pub fn contains_key(&self, key: &K) -> bool {
347        self.0.borrow().contains_key(key)
348    }
349
350    pub fn borrow(&self) -> std::cell::Ref<'_, FxIndexMap<K, V>> {
351        self.0.borrow()
352    }
353
354    pub fn borrow_mut(&self) -> std::cell::RefMut<'_, FxIndexMap<K, V>> {
355        self.0.borrow_mut()
356    }
357}
358
359impl<K: Eq + Hash + Clone, V: Clone> LogosMap<K, V> {
360    pub fn deep_clone(&self) -> Self {
361        Self(Rc::new(RefCell::new(self.0.borrow().clone())))
362    }
363
364    pub fn get(&self, key: &K) -> Option<V> {
365        self.0.borrow().get(key).cloned()
366    }
367
368    pub fn values(&self) -> Vec<V> {
369        self.0.borrow().values().cloned().collect()
370    }
371
372    pub fn keys(&self) -> Vec<K> {
373        self.0.borrow().keys().cloned().collect()
374    }
375}
376
377impl<K: Clone, V: Clone> Clone for LogosMap<K, V> {
378    fn clone(&self) -> Self {
379        // Share on `Clone` under both semantics; value-semantics isolation is
380        // preserved lazily by `cow()` before an in-place mutation. See
381        // `LogosSeq::clone`/`cow` for the full rationale.
382        Self(Rc::clone(&self.0))
383    }
384}
385
386impl<K: Clone, V: Clone> LogosMap<K, V> {
387    /// Copy-on-write: make this handle uniquely own its map before an in-place
388    /// mutation. See [`LogosSeq::cow`].
389    #[inline]
390    pub fn cow(&mut self) {
391        if Rc::strong_count(&self.0) > 1 {
392            // Bind the clone first so the `borrow()` temporary is released before the reassignment
393            // (edition-2024 extends the temporary's lifetime to the end of the statement otherwise).
394            let cloned = self.0.borrow().clone();
395            self.0 = Rc::new(RefCell::new(cloned));
396        }
397    }
398}
399
400impl<K: Eq + Hash, V> Default for LogosMap<K, V> {
401    fn default() -> Self {
402        Self::new()
403    }
404}
405
406impl<K: PartialEq + Eq + Hash, V: PartialEq> PartialEq for LogosMap<K, V> {
407    fn eq(&self, other: &Self) -> bool {
408        *self.0.borrow() == *other.0.borrow()
409    }
410}
411
412impl<K: std::fmt::Display + Eq + Hash, V: std::fmt::Display> std::fmt::Display for LogosMap<K, V> {
413    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
414        let inner = self.0.borrow();
415        write!(f, "{{")?;
416        for (i, (k, v)) in inner.iter().enumerate() {
417            if i > 0 { write!(f, ", ")?; }
418            write!(f, "{}: {}", k, v)?;
419        }
420        write!(f, "}}")
421    }
422}
423
424impl<K: serde::Serialize + Eq + Hash, V: serde::Serialize> serde::Serialize for LogosMap<K, V> {
425    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
426        self.0.borrow().serialize(serializer)
427    }
428}
429
430impl<'de, K: serde::Deserialize<'de> + Eq + Hash, V: serde::Deserialize<'de>> serde::Deserialize<'de> for LogosMap<K, V> {
431    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
432        let map = FxIndexMap::<K, V>::deserialize(deserializer)?;
433        Ok(Self::from_map(map))
434    }
435}
436
437/// Iterating a map yields `(key, value)` pairs in INSERTION order — the same
438/// order the tree-walker and VM iterate. Snapshot semantics (like
439/// [`LogosSeq`]'s `IntoIterator`): the entries are collected up front, so
440/// mutating the map inside the loop never invalidates the iteration.
441impl<K: Clone, V: Clone> IntoIterator for LogosMap<K, V> {
442    type Item = (K, V);
443    type IntoIter = std::vec::IntoIter<(K, V)>;
444
445    fn into_iter(self) -> Self::IntoIter {
446        let entries: Vec<(K, V)> =
447            self.0.borrow().iter().map(|(k, v)| (k.clone(), v.clone())).collect();
448        entries.into_iter()
449    }
450}
451
452impl<K: Eq + Hash, V> LogosContains<K> for LogosMap<K, V> {
453    #[inline(always)]
454    fn logos_contains(&self, key: &K) -> bool {
455        self.0.borrow().contains_key(key)
456    }
457}
458
459/// A specialized open-addressing `i64 → i64` map with VALUE semantics and no
460/// `Rc<RefCell>` indirection.
461///
462/// The code generator emits this in place of `LogosMap<i64, i64>` for
463/// non-aliased local `Map of Int to Int` variables (see `codegen::i64_map`):
464/// linear probing over two flat `Vec`s with `Copy` keys and values — no
465/// per-operation `RefCell` borrow, no key/value clone, `&mut self` mutation
466/// that LLVM can keep in registers. This is the C open-addressing shape, and it
467/// is only selected where the alias analysis proves the map never escapes or is
468/// shared, so the loss of reference semantics is invisible to the program.
469///
470/// `0` is the empty-slot sentinel; the real key `0` is tracked separately so the
471/// map is correct for the entire `i64` key space. The sentinel is `0`, not
472/// `i64::MIN`, so the probe table allocates via `vec![[0, 0]; slots]` — the bit
473/// pattern Rust's `IsZero` specialization lowers to `alloc_zeroed` (calloc):
474/// lazily-zeroed pages, no eager memset/page-fault storm up front.
475#[derive(Debug, Clone)]
476pub struct LogosI64Map {
477    /// AoS probe table of `[key, value]` slots; `key == EMPTY` marks empty.
478    /// Interleaving key and value keeps both on the SAME cache line, so a lookup
479    /// hit reads ONE line, not two (the SoA tax) — matching C's `struct Entry`.
480    /// `[i64; 2]` (not a tuple) so the zero fill hits `alloc_zeroed`.
481    entries: Vec<[i64; 2]>,
482    mask: usize,
483    len: usize,
484    has_zero_key: bool,
485    zero_key_val: i64,
486}
487
488impl LogosI64Map {
489    const EMPTY: i64 = 0;
490
491    pub fn new() -> Self {
492        Self {
493            entries: Vec::new(),
494            mask: 0,
495            len: 0,
496            has_zero_key: false,
497            zero_key_val: 0,
498        }
499    }
500
501    pub fn with_capacity(cap: usize) -> Self {
502        let mut m = Self::new();
503        if cap > 0 {
504            // Headroom for a ≤0.75 load factor, rounded up to a power of two
505            // (so the probe mask is `slots - 1`), with a floor of 8 slots.
506            let slots = ((cap * 4) / 3 + 1).next_power_of_two().max(8);
507            // `[Self::EMPTY, 0] == [0, 0]` → `alloc_zeroed` (calloc): lazily
508            // zeroed, no eager memset of the whole table.
509            m.entries = vec![[Self::EMPTY, 0]; slots];
510            m.mask = slots - 1;
511        }
512        m
513    }
514
515    /// Live entries stored in the probe table (excludes the zero key, which
516    /// lives outside the table).
517    #[inline]
518    fn table_len(&self) -> usize {
519        self.len - self.has_zero_key as usize
520    }
521
522    /// See [`LogosI64Set::assume_table_invariant`]: `entries.len() == mask + 1`
523    /// once `mask != 0`, so masked probe indices need no bounds check.
524    #[inline(always)]
525    fn assume_table_invariant(&self) {
526        unsafe { std::hint::assert_unchecked(self.mask + 1 == self.entries.len()); }
527    }
528
529    #[inline]
530    fn slot(&self, key: i64) -> usize {
531        // Fibonacci hashing with an avalanche xor-shift: mixes the high bits of
532        // the multiply down so sequential keys (0, 1, 2, …) spread across slots.
533        let mut h = key as u64;
534        h = h.wrapping_mul(0x9E37_79B9_7F4A_7C15);
535        h ^= h >> 32;
536        (h as usize) & self.mask
537    }
538
539    pub fn insert(&mut self, key: i64, value: i64) {
540        if key == Self::EMPTY {
541            if !self.has_zero_key {
542                self.has_zero_key = true;
543                self.len += 1;
544            }
545            self.zero_key_val = value;
546            return;
547        }
548        if self.mask == 0 || (self.table_len() + 1) * 4 > (self.mask + 1) * 3 {
549            self.grow();
550        }
551        self.assume_table_invariant();
552        let mut i = self.slot(key);
553        loop {
554            let k = self.entries[i][0];
555            if k == Self::EMPTY {
556                self.entries[i] = [key, value];
557                self.len += 1;
558                return;
559            }
560            if k == key {
561                self.entries[i][1] = value;
562                return;
563            }
564            i = (i + 1) & self.mask;
565        }
566    }
567
568    fn grow(&mut self) {
569        let new_slots = if self.mask == 0 { 8 } else { (self.mask + 1) * 2 };
570        let old = std::mem::replace(&mut self.entries, vec![[Self::EMPTY, 0]; new_slots]);
571        self.mask = new_slots - 1;
572        self.assume_table_invariant();
573        for &[k, v] in old.iter() {
574            if k != Self::EMPTY {
575                let mut i = self.slot(k);
576                while self.entries[i][0] != Self::EMPTY {
577                    i = (i + 1) & self.mask;
578                }
579                self.entries[i] = [k, v];
580            }
581        }
582    }
583
584    pub fn get(&self, key: &i64) -> Option<i64> {
585        let key = *key;
586        if key == Self::EMPTY {
587            return if self.has_zero_key {
588                Some(self.zero_key_val)
589            } else {
590                None
591            };
592        }
593        if self.mask == 0 {
594            return None;
595        }
596        self.assume_table_invariant();
597        let mut i = self.slot(key);
598        loop {
599            let [k, v] = self.entries[i];
600            if k == key {
601                return Some(v);
602            }
603            if k == Self::EMPTY {
604                return None;
605            }
606            i = (i + 1) & self.mask;
607        }
608    }
609
610    pub fn contains_key(&self, key: &i64) -> bool {
611        self.get(key).is_some()
612    }
613
614    pub fn len(&self) -> usize {
615        self.len
616    }
617
618    pub fn is_empty(&self) -> bool {
619        self.len == 0
620    }
621}
622
623impl Default for LogosI64Map {
624    fn default() -> Self {
625        Self::new()
626    }
627}
628
629impl LogosContains<i64> for LogosI64Map {
630    #[inline(always)]
631    fn logos_contains(&self, key: &i64) -> bool {
632        self.get(key).is_some()
633    }
634}
635
636/// Open-addressing `i64` SET — the keys-only sibling of [`LogosI64Map`], emitted
637/// for a `Map of Int to Int` the alias analysis proves is used ONLY as a set
638/// (insert + contains; the value is never read — two_sum's `seen`). ONE flat
639/// `Vec<i64>` with linear probing and a `0` empty-slot sentinel (the real key
640/// `0` tracked separately for full key-space correctness). With no value array
641/// it has HALF the store traffic and footprint of the map — 8 bytes/slot,
642/// smaller than C's `struct Entry { long key; int occupied; }`.
643///
644/// The sentinel is `0`, not `i64::MIN`, on purpose: `vec![0; slots]` is the bit
645/// pattern Rust's `IsZero` specialization lowers to `alloc_zeroed` (calloc), so
646/// the table comes from the OS lazily-zeroed — no eager 1 GiB memset/page-fault
647/// storm up front (the kernel-time gap that made two_sum lose to C's `calloc`).
648#[derive(Debug, Clone)]
649pub struct LogosI64Set {
650    keys: Vec<i64>,
651    mask: usize,
652    len: usize,
653    has_zero_key: bool,
654}
655
656impl LogosI64Set {
657    const EMPTY: i64 = 0;
658
659    pub fn new() -> Self {
660        Self { keys: Vec::new(), mask: 0, len: 0, has_zero_key: false }
661    }
662
663    pub fn with_capacity(cap: usize) -> Self {
664        let mut s = Self::new();
665        if cap > 0 {
666            let slots = ((cap * 4) / 3 + 1).next_power_of_two().max(8);
667            // `Self::EMPTY == 0` → `alloc_zeroed` (calloc): lazily-zeroed pages,
668            // no eager memset/fault of the whole table.
669            s.keys = vec![Self::EMPTY; slots];
670            s.mask = slots - 1;
671        }
672        s
673    }
674
675    #[inline]
676    fn table_len(&self) -> usize {
677        self.len - self.has_zero_key as usize
678    }
679
680    /// The probe table is a power-of-two `Vec` whose length is exactly
681    /// `mask + 1` (maintained by `with_capacity`/`grow`); every probe index is
682    /// `… & mask`, so once `mask != 0` every `self.keys[i]` is in bounds. Telling
683    /// LLVM the invariant lets it drop the per-probe bounds check (matching C's
684    /// raw indexed loads). Caller must have established `mask != 0`.
685    #[inline(always)]
686    fn assume_table_invariant(&self) {
687        unsafe { std::hint::assert_unchecked(self.mask + 1 == self.keys.len()); }
688    }
689
690    #[inline]
691    fn slot(&self, key: i64) -> usize {
692        let mut h = key as u64;
693        h = h.wrapping_mul(0x9E37_79B9_7F4A_7C15);
694        h ^= h >> 32;
695        (h as usize) & self.mask
696    }
697
698    /// Insert a key. The `_value` parameter mirrors [`LogosI64Map::insert`]'s
699    /// call shape so codegen needs no special-casing at the insert site — by the
700    /// set-usage proof the value is never read.
701    #[inline]
702    pub fn insert(&mut self, key: i64, _value: i64) {
703        if key == Self::EMPTY {
704            if !self.has_zero_key {
705                self.has_zero_key = true;
706                self.len += 1;
707            }
708            return;
709        }
710        if self.mask == 0 || (self.table_len() + 1) * 4 > (self.mask + 1) * 3 {
711            self.grow();
712        }
713        self.assume_table_invariant();
714        let mut i = self.slot(key);
715        loop {
716            let k = self.keys[i];
717            if k == Self::EMPTY {
718                self.keys[i] = key;
719                self.len += 1;
720                return;
721            }
722            if k == key {
723                return;
724            }
725            i = (i + 1) & self.mask;
726        }
727    }
728
729    fn grow(&mut self) {
730        let new_slots = if self.mask == 0 { 8 } else { (self.mask + 1) * 2 };
731        let old_keys = std::mem::replace(&mut self.keys, vec![Self::EMPTY; new_slots]);
732        self.mask = new_slots - 1;
733        self.assume_table_invariant();
734        for &k in old_keys.iter() {
735            if k != Self::EMPTY {
736                let mut i = self.slot(k);
737                while self.keys[i] != Self::EMPTY {
738                    i = (i + 1) & self.mask;
739                }
740                self.keys[i] = k;
741            }
742        }
743    }
744
745    #[inline]
746    pub fn contains_key(&self, key: &i64) -> bool {
747        let key = *key;
748        if key == Self::EMPTY {
749            return self.has_zero_key;
750        }
751        if self.mask == 0 {
752            return false;
753        }
754        self.assume_table_invariant();
755        let mut i = self.slot(key);
756        loop {
757            let k = self.keys[i];
758            if k == key {
759                return true;
760            }
761            if k == Self::EMPTY {
762                return false;
763            }
764            i = (i + 1) & self.mask;
765        }
766    }
767
768    pub fn len(&self) -> usize {
769        self.len
770    }
771
772    pub fn is_empty(&self) -> bool {
773        self.len == 0
774    }
775}
776
777impl Default for LogosI64Set {
778    fn default() -> Self {
779        Self::new()
780    }
781}
782
783impl LogosContains<i64> for LogosI64Set {
784    #[inline(always)]
785    fn logos_contains(&self, key: &i64) -> bool {
786        self.contains_key(key)
787    }
788}
789
790/// i32-narrowed open-addressing `i64 → i64` map — the same probe-table shape as
791/// [`LogosI64Map`] but with 8-byte `(i32, i32)` slots, emitted when the compiler
792/// PROVES every key AND value stays in `i32` range. Halving the slot width halves
793/// the table's memory traffic — the dominant cost of this random-access workload —
794/// for maps the dense gate cannot capture (no proven contiguous key window). The
795/// call surface is `i64` (keys/values widen at the boundary; the proof makes the
796/// narrowing cast lossless), so codegen emits it identically to `LogosI64Map`.
797#[derive(Debug, Clone)]
798pub struct LogosI32Map {
799    entries: Vec<[i32; 2]>,
800    mask: usize,
801    len: usize,
802    has_zero_key: bool,
803    zero_key_val: i32,
804}
805
806impl LogosI32Map {
807    const EMPTY: i32 = 0;
808
809    pub fn new() -> Self {
810        Self { entries: Vec::new(), mask: 0, len: 0, has_zero_key: false, zero_key_val: 0 }
811    }
812
813    pub fn with_capacity(cap: usize) -> Self {
814        let mut m = Self::new();
815        if cap > 0 {
816            let slots = ((cap * 4) / 3 + 1).next_power_of_two().max(8);
817            // `[0, 0]` → `alloc_zeroed` (calloc): lazily zeroed, no eager memset.
818            m.entries = vec![[Self::EMPTY, 0]; slots];
819            m.mask = slots - 1;
820        }
821        m
822    }
823
824    #[inline]
825    fn table_len(&self) -> usize {
826        self.len - self.has_zero_key as usize
827    }
828
829    /// See [`LogosI64Set::assume_table_invariant`]: `entries.len() == mask + 1`
830    /// once `mask != 0`, so masked probe indices need no bounds check.
831    #[inline(always)]
832    fn assume_table_invariant(&self) {
833        unsafe { std::hint::assert_unchecked(self.mask + 1 == self.entries.len()); }
834    }
835
836    #[inline]
837    fn slot(&self, key: i32) -> usize {
838        let mut h = key as u32 as u64;
839        h = h.wrapping_mul(0x9E37_79B9_7F4A_7C15);
840        h ^= h >> 32;
841        (h as usize) & self.mask
842    }
843
844    pub fn insert(&mut self, key: i64, value: i64) {
845        let key = key as i32;
846        let value = value as i32;
847        if key == Self::EMPTY {
848            if !self.has_zero_key {
849                self.has_zero_key = true;
850                self.len += 1;
851            }
852            self.zero_key_val = value;
853            return;
854        }
855        if self.mask == 0 || (self.table_len() + 1) * 4 > (self.mask + 1) * 3 {
856            self.grow();
857        }
858        self.assume_table_invariant();
859        let mut i = self.slot(key);
860        loop {
861            let k = self.entries[i][0];
862            if k == Self::EMPTY {
863                self.entries[i] = [key, value];
864                self.len += 1;
865                return;
866            }
867            if k == key {
868                self.entries[i][1] = value;
869                return;
870            }
871            i = (i + 1) & self.mask;
872        }
873    }
874
875    fn grow(&mut self) {
876        let new_slots = if self.mask == 0 { 8 } else { (self.mask + 1) * 2 };
877        let old = std::mem::replace(&mut self.entries, vec![[Self::EMPTY, 0]; new_slots]);
878        self.mask = new_slots - 1;
879        self.assume_table_invariant();
880        for &[k, v] in old.iter() {
881            if k != Self::EMPTY {
882                let mut i = self.slot(k);
883                while self.entries[i][0] != Self::EMPTY {
884                    i = (i + 1) & self.mask;
885                }
886                self.entries[i] = [k, v];
887            }
888        }
889    }
890
891    pub fn get(&self, key: &i64) -> Option<i64> {
892        let key = *key as i32;
893        if key == Self::EMPTY {
894            return if self.has_zero_key { Some(self.zero_key_val as i64) } else { None };
895        }
896        if self.mask == 0 {
897            return None;
898        }
899        self.assume_table_invariant();
900        let mut i = self.slot(key);
901        loop {
902            let [k, v] = self.entries[i];
903            if k == key {
904                return Some(v as i64);
905            }
906            if k == Self::EMPTY {
907                return None;
908            }
909            i = (i + 1) & self.mask;
910        }
911    }
912
913    pub fn contains_key(&self, key: &i64) -> bool {
914        self.get(key).is_some()
915    }
916
917    pub fn len(&self) -> usize {
918        self.len
919    }
920
921    pub fn is_empty(&self) -> bool {
922        self.len == 0
923    }
924}
925
926impl Default for LogosI32Map {
927    fn default() -> Self {
928        Self::new()
929    }
930}
931
932impl LogosContains<i64> for LogosI32Map {
933    #[inline(always)]
934    fn logos_contains(&self, key: &i64) -> bool {
935        self.get(key).is_some()
936    }
937}
938
939/// i32-narrowed open-addressing set — the keys-only sibling of [`LogosI32Map`]
940/// (the i32 analogue of [`LogosI64Set`]). One flat `Vec<i32>`: 4 bytes per slot,
941/// a quarter of `LogosI64Map`'s footprint. Emitted for a set-usage `Int → Int`
942/// map whose keys are all proven to fit `i32`. The empty-slot sentinel is `0`
943/// (the real key `0` tracked separately) so `vec![0; slots]` allocates via
944/// `alloc_zeroed` — lazily-zeroed, no eager memset of the table.
945#[derive(Debug, Clone)]
946pub struct LogosI32Set {
947    keys: Vec<i32>,
948    mask: usize,
949    len: usize,
950    has_zero_key: bool,
951}
952
953impl LogosI32Set {
954    const EMPTY: i32 = 0;
955
956    pub fn new() -> Self {
957        Self { keys: Vec::new(), mask: 0, len: 0, has_zero_key: false }
958    }
959
960    pub fn with_capacity(cap: usize) -> Self {
961        let mut s = Self::new();
962        if cap > 0 {
963            let slots = ((cap * 4) / 3 + 1).next_power_of_two().max(8);
964            s.keys = vec![Self::EMPTY; slots];
965            s.mask = slots - 1;
966        }
967        s
968    }
969
970    #[inline]
971    fn table_len(&self) -> usize {
972        self.len - self.has_zero_key as usize
973    }
974
975    /// See [`LogosI64Set::assume_table_invariant`]: `keys.len() == mask + 1` once
976    /// `mask != 0`, so masked probe indices need no bounds check.
977    #[inline(always)]
978    fn assume_table_invariant(&self) {
979        unsafe { std::hint::assert_unchecked(self.mask + 1 == self.keys.len()); }
980    }
981
982    #[inline]
983    fn slot(&self, key: i32) -> usize {
984        let mut h = key as u32 as u64;
985        h = h.wrapping_mul(0x9E37_79B9_7F4A_7C15);
986        h ^= h >> 32;
987        (h as usize) & self.mask
988    }
989
990    pub fn insert(&mut self, key: i64, _value: i64) {
991        let key = key as i32;
992        if key == Self::EMPTY {
993            if !self.has_zero_key {
994                self.has_zero_key = true;
995                self.len += 1;
996            }
997            return;
998        }
999        if self.mask == 0 || (self.table_len() + 1) * 4 > (self.mask + 1) * 3 {
1000            self.grow();
1001        }
1002        self.assume_table_invariant();
1003        let mut i = self.slot(key);
1004        loop {
1005            let k = self.keys[i];
1006            if k == Self::EMPTY {
1007                self.keys[i] = key;
1008                self.len += 1;
1009                return;
1010            }
1011            if k == key {
1012                return;
1013            }
1014            i = (i + 1) & self.mask;
1015        }
1016    }
1017
1018    fn grow(&mut self) {
1019        let new_slots = if self.mask == 0 { 8 } else { (self.mask + 1) * 2 };
1020        let old_keys = std::mem::replace(&mut self.keys, vec![Self::EMPTY; new_slots]);
1021        self.mask = new_slots - 1;
1022        self.assume_table_invariant();
1023        for &k in old_keys.iter() {
1024            if k != Self::EMPTY {
1025                let mut i = self.slot(k);
1026                while self.keys[i] != Self::EMPTY {
1027                    i = (i + 1) & self.mask;
1028                }
1029                self.keys[i] = k;
1030            }
1031        }
1032    }
1033
1034    pub fn contains_key(&self, key: &i64) -> bool {
1035        let key = *key as i32;
1036        if key == Self::EMPTY {
1037            return self.has_zero_key;
1038        }
1039        if self.mask == 0 {
1040            return false;
1041        }
1042        self.assume_table_invariant();
1043        let mut i = self.slot(key);
1044        loop {
1045            let k = self.keys[i];
1046            if k == key {
1047                return true;
1048            }
1049            if k == Self::EMPTY {
1050                return false;
1051            }
1052            i = (i + 1) & self.mask;
1053        }
1054    }
1055
1056    pub fn len(&self) -> usize {
1057        self.len
1058    }
1059
1060    pub fn is_empty(&self) -> bool {
1061        self.len == 0
1062    }
1063}
1064
1065impl Default for LogosI32Set {
1066    fn default() -> Self {
1067        Self::new()
1068    }
1069}
1070
1071impl LogosContains<i64> for LogosI32Set {
1072    #[inline(always)]
1073    fn logos_contains(&self, key: &i64) -> bool {
1074        self.contains_key(key)
1075    }
1076}
1077
1078/// Direct-addressed `i64 → i64` map — the densest representation of a
1079/// `Map of Int to Int`, emitted when the compiler PROVES every key ever inserted
1080/// or queried lands in a statically bounded window `[lo, lo + slots)` whose width
1081/// is ≤ the map's own `with capacity` hint. Keys index a flat value array
1082/// (`data[key - lo]`); a parallel presence bitset distinguishes a stored value
1083/// from an absent slot, so `get` returns `None` for an in-range key that was
1084/// never inserted. No hashing, no probing, no sparse table — both build and scan
1085/// phases are sequential array accesses, which the backend vectorizes and the
1086/// prefetcher loves. This is perfect hashing for dense integer keys: it removes
1087/// the random-access hash table that costs `LogosI64Map` its cache locality.
1088#[derive(Debug, Clone)]
1089pub struct LogosDenseI64Map {
1090    /// Value slots, indexed by `key - lo`. Zero-initialized; a zero is only a
1091    /// real value when its presence bit is set.
1092    data: Vec<i64>,
1093    /// One bit per slot: set ⇔ the slot holds an inserted value.
1094    present: Vec<u64>,
1095    /// The window's lower bound; `data[key - lo]` rebases the key space (so a
1096    /// 1-based or negative key domain maps onto `[0, slots)`).
1097    lo: i64,
1098    len: usize,
1099}
1100
1101impl LogosDenseI64Map {
1102    pub fn new() -> Self {
1103        Self { data: Vec::new(), present: Vec::new(), lo: 0, len: 0 }
1104    }
1105
1106    /// A window `[0, cap)` — the offset-free form. Mirrors the `LogosI64Map`
1107    /// constructor name so a non-offset dense map reuses the same emission.
1108    pub fn with_capacity(cap: usize) -> Self {
1109        Self::with_bounds(0, cap)
1110    }
1111
1112    /// A window `[lo, lo + slots)`. `slots` is sized to the proven capacity hint;
1113    /// the soundness gate guarantees every key falls inside, so `key - lo` is a
1114    /// valid index for every insert and get the program performs.
1115    pub fn with_bounds(lo: i64, slots: usize) -> Self {
1116        Self {
1117            data: vec![0; slots],
1118            present: vec![0; slots.div_ceil(64)],
1119            lo,
1120            len: 0,
1121        }
1122    }
1123
1124    #[inline]
1125    pub fn insert(&mut self, key: i64, value: i64) {
1126        let idx = (key - self.lo) as usize;
1127        debug_assert!(
1128            key >= self.lo && idx < self.data.len(),
1129            "dense map key {key} outside proven window [{}, {})",
1130            self.lo,
1131            self.lo + self.data.len() as i64
1132        );
1133        let bit = 1u64 << (idx & 63);
1134        let word = &mut self.present[idx >> 6];
1135        if *word & bit == 0 {
1136            *word |= bit;
1137            self.len += 1;
1138        }
1139        self.data[idx] = value;
1140    }
1141
1142    #[inline]
1143    pub fn get(&self, key: &i64) -> Option<i64> {
1144        let idx = (*key - self.lo) as usize;
1145        debug_assert!(
1146            *key >= self.lo && idx < self.data.len(),
1147            "dense map key {key} outside proven window [{}, {})",
1148            self.lo,
1149            self.lo + self.data.len() as i64
1150        );
1151        if self.present[idx >> 6] & (1u64 << (idx & 63)) != 0 {
1152            Some(self.data[idx])
1153        } else {
1154            None
1155        }
1156    }
1157
1158    #[inline]
1159    pub fn contains_key(&self, key: &i64) -> bool {
1160        let idx = (*key - self.lo) as usize;
1161        debug_assert!(*key >= self.lo && idx < self.data.len());
1162        self.present[idx >> 6] & (1u64 << (idx & 63)) != 0
1163    }
1164
1165    pub fn len(&self) -> usize {
1166        self.len
1167    }
1168
1169    pub fn is_empty(&self) -> bool {
1170        self.len == 0
1171    }
1172}
1173
1174impl Default for LogosDenseI64Map {
1175    fn default() -> Self {
1176        Self::new()
1177    }
1178}
1179
1180impl LogosContains<i64> for LogosDenseI64Map {
1181    #[inline(always)]
1182    fn logos_contains(&self, key: &i64) -> bool {
1183        self.contains_key(key)
1184    }
1185}
1186
1187/// The presence-elided sibling of [`LogosDenseI64Map`], emitted ONLY when the
1188/// compiler additionally proves the get/contains key domain is a subset of a
1189/// CONTIGUOUSLY FULLY-COVERED insert range — i.e. every key the program reads was
1190/// definitely written first. With that proof the presence bit is invariably set,
1191/// so it is dropped: `get` is a bare `Some(data[key - lo])` load, byte-identical
1192/// to a C array read, and no presence bitset is allocated. The gate forbids this
1193/// type for any map with a `contains` use (there is no way to answer membership
1194/// without presence), so `contains_key`/`LogosContains` are intentionally absent
1195/// — a generated `contains` on this type would fail to compile, surfacing a gate
1196/// bug loudly rather than silently miscompiling.
1197#[derive(Debug, Clone)]
1198pub struct LogosDenseI64MapNoPresence {
1199    data: Vec<i64>,
1200    lo: i64,
1201    /// Insert count. Exact under the proven regime (each covered key written
1202    /// once); never emitted (a `length of m` use disqualifies the map upstream).
1203    len: usize,
1204}
1205
1206impl LogosDenseI64MapNoPresence {
1207    pub fn new() -> Self {
1208        Self { data: Vec::new(), lo: 0, len: 0 }
1209    }
1210
1211    pub fn with_capacity(cap: usize) -> Self {
1212        Self::with_bounds(0, cap)
1213    }
1214
1215    pub fn with_bounds(lo: i64, slots: usize) -> Self {
1216        Self { data: vec![0; slots], lo, len: 0 }
1217    }
1218
1219    #[inline]
1220    pub fn insert(&mut self, key: i64, value: i64) {
1221        let idx = (key - self.lo) as usize;
1222        debug_assert!(
1223            key >= self.lo && idx < self.data.len(),
1224            "dense map key {key} outside proven window [{}, {})",
1225            self.lo,
1226            self.lo + self.data.len() as i64
1227        );
1228        self.data[idx] = value;
1229        self.len += 1;
1230    }
1231
1232    #[inline]
1233    pub fn get(&self, key: &i64) -> Option<i64> {
1234        let idx = (*key - self.lo) as usize;
1235        debug_assert!(
1236            *key >= self.lo && idx < self.data.len(),
1237            "dense map key {key} outside proven window [{}, {})",
1238            self.lo,
1239            self.lo + self.data.len() as i64
1240        );
1241        Some(self.data[idx])
1242    }
1243
1244    pub fn len(&self) -> usize {
1245        self.len
1246    }
1247
1248    pub fn is_empty(&self) -> bool {
1249        self.len == 0
1250    }
1251}
1252
1253impl Default for LogosDenseI64MapNoPresence {
1254    fn default() -> Self {
1255        Self::new()
1256    }
1257}
1258
1259/// Direct-addressed `i64` SET — the keys-only, value-free sibling of
1260/// [`LogosDenseI64Map`] (the dense analogue of [`LogosI64Set`]). Membership lives
1261/// in a presence bitset over the proven window `[lo, lo + slots)`; `insert` sets
1262/// a bit, `contains` tests one. No value array → 1 bit per key, the smallest
1263/// footprint of any map/set representation.
1264#[derive(Debug, Clone)]
1265pub struct LogosDenseI64Set {
1266    present: Vec<u64>,
1267    lo: i64,
1268    len: usize,
1269}
1270
1271impl LogosDenseI64Set {
1272    pub fn new() -> Self {
1273        Self { present: Vec::new(), lo: 0, len: 0 }
1274    }
1275
1276    pub fn with_capacity(cap: usize) -> Self {
1277        Self::with_bounds(0, cap)
1278    }
1279
1280    pub fn with_bounds(lo: i64, slots: usize) -> Self {
1281        Self { present: vec![0; slots.div_ceil(64)], lo, len: 0 }
1282    }
1283
1284    /// Insert a key. The `_value` mirrors [`LogosDenseI64Map::insert`]'s call
1285    /// shape so codegen needs no special-casing at the insert site.
1286    #[inline]
1287    pub fn insert(&mut self, key: i64, _value: i64) {
1288        let idx = (key - self.lo) as usize;
1289        debug_assert!(
1290            key >= self.lo && idx < self.present.len() * 64,
1291            "dense set key {key} outside proven window starting at {}",
1292            self.lo
1293        );
1294        let bit = 1u64 << (idx & 63);
1295        let word = &mut self.present[idx >> 6];
1296        if *word & bit == 0 {
1297            *word |= bit;
1298            self.len += 1;
1299        }
1300    }
1301
1302    #[inline]
1303    pub fn contains_key(&self, key: &i64) -> bool {
1304        let idx = (*key - self.lo) as usize;
1305        debug_assert!(*key >= self.lo && idx < self.present.len() * 64);
1306        self.present[idx >> 6] & (1u64 << (idx & 63)) != 0
1307    }
1308
1309    pub fn len(&self) -> usize {
1310        self.len
1311    }
1312
1313    pub fn is_empty(&self) -> bool {
1314        self.len == 0
1315    }
1316}
1317
1318impl Default for LogosDenseI64Set {
1319    fn default() -> Self {
1320        Self::new()
1321    }
1322}
1323
1324impl LogosContains<i64> for LogosDenseI64Set {
1325    #[inline(always)]
1326    fn logos_contains(&self, key: &i64) -> bool {
1327        self.contains_key(key)
1328    }
1329}
1330
1331/// Precomputed unsigned division by a **loop-invariant runtime divisor** — the
1332/// magic-multiply that replaces a hardware `div`/`idiv` once the divisor is
1333/// pinned. Codegen emits `LogosDivU64::new(n)` in a loop's preheader (computed
1334/// ONCE) and rewrites each in-loop `x % n` / `x / n` to `.rem(x)` / `.div(x)`,
1335/// turning a ~20–40-cycle division into a multiply-high plus a shift. gcc and
1336/// rustc both leave a runtime-invariant divisor as a real `div` (neither
1337/// synthesizes this), so it is a strict win over the C baseline on division-hot
1338/// loops (graph_bfs's `% n` adjacency build).
1339///
1340/// The construction is the standard Granlund–Montgomery / libdivide unsigned
1341/// algorithm, exact for every divisor in `1..=u64::MAX` (power-of-two fast path,
1342/// the 64-bit-magic path, and the 65-bit "add marker" path). Codegen only emits
1343/// it where the dividend is proven non-negative and `n > 0` (the existing
1344/// positivity guard), so the `i64`→`u64` reinterpretation is value-preserving.
1345#[derive(Debug, Clone, Copy)]
1346pub struct LogosDivU64 {
1347    magic: u64,
1348    /// Low 6 bits: shift amount. `ADD_MARKER` (0x40): the 65-bit-magic path.
1349    /// `SHIFT_PATH` (0x80): the divisor is a power of two (pure shift).
1350    more: u8,
1351    d: u64,
1352}
1353
1354impl LogosDivU64 {
1355    const SHIFT_MASK: u8 = 0x3F;
1356    const ADD_MARKER: u8 = 0x40;
1357    const SHIFT_PATH: u8 = 0x80;
1358
1359    /// Build the magic numbers for divisor `d` (must be non-zero). Runs once per
1360    /// loop in the preheader, so its cost (a single 128/64 division) amortizes
1361    /// over the whole loop.
1362    #[inline]
1363    pub fn new(d: u64) -> Self {
1364        debug_assert!(d != 0, "LogosDivU64: divisor must be non-zero");
1365        if d & (d - 1) == 0 {
1366            // Power of two (including d == 1, shift 0): the division is a shift.
1367            return Self { magic: 0, more: (d.trailing_zeros() as u8) | Self::SHIFT_PATH, d };
1368        }
1369        let floor_log_2_d = 63 - d.leading_zeros();
1370        // proposed_m = floor(2^(64 + floor_log_2_d) / d); rem the remainder.
1371        let numer = 1u128 << (64 + floor_log_2_d);
1372        let proposed_m = (numer / d as u128) as u64;
1373        let rem = (numer % d as u128) as u64;
1374        let e = d - rem;
1375        let (magic, more) = if e < (1u64 << floor_log_2_d) {
1376            // The 64-bit magic suffices.
1377            (proposed_m + 1, floor_log_2_d as u8)
1378        } else {
1379            // Need the 65th bit — fold it into an extra add at division time.
1380            let twice_rem = rem.wrapping_mul(2);
1381            let bump = (twice_rem >= d || twice_rem < rem) as u64;
1382            (proposed_m.wrapping_mul(2).wrapping_add(bump) + 1,
1383             (floor_log_2_d as u8) | Self::ADD_MARKER)
1384        };
1385        Self { magic, more, d }
1386    }
1387
1388    #[inline(always)]
1389    pub fn div(&self, numer: u64) -> u64 {
1390        if self.more & Self::SHIFT_PATH != 0 {
1391            return numer >> (self.more & Self::SHIFT_MASK);
1392        }
1393        let q = (((self.magic as u128) * (numer as u128)) >> 64) as u64;
1394        if self.more & Self::ADD_MARKER != 0 {
1395            let t = ((numer - q) >> 1).wrapping_add(q);
1396            t >> (self.more & Self::SHIFT_MASK)
1397        } else {
1398            q >> self.more
1399        }
1400    }
1401
1402    #[inline(always)]
1403    pub fn rem(&self, numer: u64) -> u64 {
1404        numer - self.div(numer).wrapping_mul(self.d)
1405    }
1406
1407    /// The raw `(magic, more)` pair, for backends (the VM / JIT interpreter
1408    /// tiers) that bake the precomputed constants into a single fused op instead
1409    /// of holding a `LogosDivU64` struct. The encoding of `more` is exactly the
1410    /// one `div`/`rem` consume (low 6 bits = shift; `0x40` = the 65-bit
1411    /// add-marker path; `0x80` = the pure-shift power-of-two path).
1412    #[inline]
1413    pub fn parts(&self) -> (u64, u8) {
1414        (self.magic, self.more)
1415    }
1416}
1417
1418/// Ordered sequences with reference semantics.
1419pub type Seq<T> = LogosSeq<T>;
1420
1421/// Key-value mappings with reference semantics.
1422pub type Map<K, V> = LogosMap<K, V>;
1423
1424/// Unordered collections of unique elements with FxHash.
1425/// The insertion-ordered set behind every LOGOS `Set`: display and iteration
1426/// follow first-insertion order, identical to the tree-walker's Vec-backed
1427/// sets and the direct-WASM linear sets. Fx hashing for speed. NOTE: removal
1428/// must go through `shift_remove` (order-preserving), never `swap_remove`.
1429pub type FxIndexSet<T> =
1430    indexmap::IndexSet<T, std::hash::BuildHasherDefault<rustc_hash::FxHasher>>;
1431pub type Set<T> = FxIndexSet<T>;
1432
1433/// Unified containment testing for all collection types.
1434///
1435/// This trait provides a consistent `logos_contains` method across Logos's
1436/// collection types, abstracting over the different containment semantics
1437/// of vectors (by value), sets (by membership), maps (by key), and
1438/// strings (by substring or character).
1439///
1440/// # Implementations
1441///
1442/// - [`Vec<T>`]: Tests if the vector contains an element equal to the value
1443/// - `HashSet<T>`: Tests if the element is a member of the set
1444/// - `HashMap<K, V>`: Tests if a key exists in the map
1445/// - [`String`]: Tests for substring (`&str`) or character (`char`) presence
1446/// - `ORSet<T, B>`: Tests if the element is in the CRDT set
1447///
1448/// # Examples
1449///
1450/// ```
1451/// use logicaffeine_data::LogosContains;
1452///
1453/// // Vector: contains by value equality
1454/// let v = vec![1, 2, 3];
1455/// assert!(v.logos_contains(&2));
1456/// assert!(!v.logos_contains(&5));
1457///
1458/// // String: contains by substring
1459/// let s = String::from("hello world");
1460/// assert!(s.logos_contains(&"world"));
1461///
1462/// // String: contains by character
1463/// assert!(s.logos_contains(&'o'));
1464/// ```
1465pub trait LogosContains<T> {
1466    /// Check if this collection contains the given value.
1467    fn logos_contains(&self, value: &T) -> bool;
1468}
1469
1470impl<T: PartialEq> LogosContains<T> for Vec<T> {
1471    #[inline(always)]
1472    fn logos_contains(&self, value: &T) -> bool {
1473        self.contains(value)
1474    }
1475}
1476
1477impl<T: PartialEq> LogosContains<T> for [T] {
1478    #[inline(always)]
1479    fn logos_contains(&self, value: &T) -> bool {
1480        self.contains(value)
1481    }
1482}
1483
1484impl<T: Eq + Hash, S: std::hash::BuildHasher> LogosContains<T> for indexmap::IndexSet<T, S> {
1485    #[inline(always)]
1486    fn logos_contains(&self, value: &T) -> bool {
1487        self.contains(value)
1488    }
1489}
1490
1491impl<T: Eq + Hash> LogosContains<T> for rustc_hash::FxHashSet<T> {
1492    #[inline(always)]
1493    fn logos_contains(&self, value: &T) -> bool {
1494        self.contains(value)
1495    }
1496}
1497
1498impl<K: Eq + Hash, V> LogosContains<K> for rustc_hash::FxHashMap<K, V> {
1499    #[inline(always)]
1500    fn logos_contains(&self, key: &K) -> bool {
1501        self.contains_key(key)
1502    }
1503}
1504
1505impl LogosContains<&str> for String {
1506    #[inline(always)]
1507    fn logos_contains(&self, value: &&str) -> bool {
1508        self.contains(*value)
1509    }
1510}
1511
1512impl LogosContains<String> for String {
1513    #[inline(always)]
1514    fn logos_contains(&self, value: &String) -> bool {
1515        self.contains(value.as_str())
1516    }
1517}
1518
1519impl LogosContains<char> for String {
1520    #[inline(always)]
1521    fn logos_contains(&self, value: &char) -> bool {
1522        self.contains(*value)
1523    }
1524}
1525
1526impl<T: Eq + Hash + Clone, B: crate::crdt::SetBias> LogosContains<T>
1527    for crate::crdt::ORSet<T, B>
1528{
1529    #[inline(always)]
1530    fn logos_contains(&self, value: &T) -> bool {
1531        self.contains(value)
1532    }
1533}
1534
1535/// Dynamic value type for heterogeneous collections.
1536///
1537/// `Value` enables tuples and other heterogeneous data structures in Logos.
1538/// It supports basic arithmetic between compatible types and provides
1539/// runtime type coercion where sensible.
1540///
1541/// # Variants
1542///
1543/// - `Int(i64)` - Integer values
1544/// - `Float(f64)` - Floating-point values
1545/// - `Bool(bool)` - Boolean values
1546/// - `Text(String)` - String values
1547/// - `Char(char)` - Single character values
1548/// - `Nothing` - Unit/null value
1549///
1550/// # Arithmetic
1551///
1552/// Arithmetic operations are supported between numeric types:
1553/// - `Int op Int` → `Int`
1554/// - `Float op Float` → `Float`
1555/// - `Int op Float` or `Float op Int` → `Float` (promotion)
1556/// - `Text + Text` → `Text` (concatenation)
1557///
1558/// # Panics
1559///
1560/// Arithmetic on incompatible variants panics at runtime.
1561///
1562/// # Examples
1563///
1564/// ```
1565/// use logicaffeine_data::Value;
1566///
1567/// let a = Value::Int(10);
1568/// let b = Value::Int(3);
1569/// assert_eq!(a + b, Value::Int(13));
1570///
1571/// let x = Value::Float(2.5);
1572/// let y = Value::Int(2);
1573/// assert_eq!(x * y, Value::Float(5.0));
1574/// ```
1575#[derive(Clone, Debug, PartialEq)]
1576pub enum Value {
1577    /// Integer values.
1578    Int(i64),
1579    /// Floating-point values.
1580    Float(f64),
1581    /// Boolean values.
1582    Bool(bool),
1583    /// String values.
1584    Text(String),
1585    /// Single character values.
1586    Char(char),
1587    /// Unit/null value.
1588    Nothing,
1589}
1590
1591impl std::fmt::Display for Value {
1592    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1593        match self {
1594            Value::Int(n) => write!(f, "{}", n),
1595            Value::Float(n) => write!(f, "{}", n),
1596            Value::Bool(b) => write!(f, "{}", b),
1597            Value::Text(s) => write!(f, "{}", s),
1598            Value::Char(c) => write!(f, "{}", c),
1599            Value::Nothing => write!(f, "nothing"),
1600        }
1601    }
1602}
1603
1604// Conversion traits for Value
1605impl From<i64> for Value {
1606    fn from(n: i64) -> Self { Value::Int(n) }
1607}
1608
1609impl From<f64> for Value {
1610    fn from(n: f64) -> Self { Value::Float(n) }
1611}
1612
1613impl From<bool> for Value {
1614    fn from(b: bool) -> Self { Value::Bool(b) }
1615}
1616
1617impl From<String> for Value {
1618    fn from(s: String) -> Self { Value::Text(s) }
1619}
1620
1621impl From<&str> for Value {
1622    fn from(s: &str) -> Self { Value::Text(s.to_string()) }
1623}
1624
1625impl From<char> for Value {
1626    fn from(c: char) -> Self { Value::Char(c) }
1627}
1628
1629/// Tuple type: Vec of heterogeneous Values (uses LogosIndex from indexing module)
1630pub type Tuple = Vec<Value>;
1631
1632// NOTE: Showable impl for Value is in logicaffeine_system (io module)
1633// This crate (logicaffeine_data) has NO IO dependencies.
1634
1635// Arithmetic operations for Value
1636impl std::ops::Add for Value {
1637    type Output = Value;
1638
1639    #[inline]
1640    fn add(self, other: Value) -> Value {
1641        match (self, other) {
1642            (Value::Int(a), Value::Int(b)) => Value::Int(a + b),
1643            (Value::Float(a), Value::Float(b)) => Value::Float(a + b),
1644            (Value::Int(a), Value::Float(b)) => Value::Float(a as f64 + b),
1645            (Value::Float(a), Value::Int(b)) => Value::Float(a + b as f64),
1646            (Value::Text(a), Value::Text(b)) => Value::Text(format!("{}{}", a, b)),
1647            _ => panic!("Cannot add these value types"),
1648        }
1649    }
1650}
1651
1652impl std::ops::Sub for Value {
1653    type Output = Value;
1654
1655    #[inline]
1656    fn sub(self, other: Value) -> Value {
1657        match (self, other) {
1658            (Value::Int(a), Value::Int(b)) => Value::Int(a - b),
1659            (Value::Float(a), Value::Float(b)) => Value::Float(a - b),
1660            (Value::Int(a), Value::Float(b)) => Value::Float(a as f64 - b),
1661            (Value::Float(a), Value::Int(b)) => Value::Float(a - b as f64),
1662            _ => panic!("Cannot subtract these value types"),
1663        }
1664    }
1665}
1666
1667impl std::ops::Mul for Value {
1668    type Output = Value;
1669
1670    #[inline]
1671    fn mul(self, other: Value) -> Value {
1672        match (self, other) {
1673            (Value::Int(a), Value::Int(b)) => Value::Int(a * b),
1674            (Value::Float(a), Value::Float(b)) => Value::Float(a * b),
1675            (Value::Int(a), Value::Float(b)) => Value::Float(a as f64 * b),
1676            (Value::Float(a), Value::Int(b)) => Value::Float(a * b as f64),
1677            _ => panic!("Cannot multiply these value types"),
1678        }
1679    }
1680}
1681
1682impl std::ops::Div for Value {
1683    type Output = Value;
1684
1685    #[inline]
1686    fn div(self, other: Value) -> Value {
1687        match (self, other) {
1688            (Value::Int(a), Value::Int(b)) => Value::Int(a / b),
1689            (Value::Float(a), Value::Float(b)) => Value::Float(a / b),
1690            (Value::Int(a), Value::Float(b)) => Value::Float(a as f64 / b),
1691            (Value::Float(a), Value::Int(b)) => Value::Float(a / b as f64),
1692            _ => panic!("Cannot divide these value types"),
1693        }
1694    }
1695}
1696
1697/// Exact rational number for compiled LOGOS programs — the AOT counterpart of the
1698/// interpreter's `RuntimeValue::Rational`.
1699///
1700/// Wraps the always-reduced [`logicaffeine_base::Rational`] (a `BigInt` numerator over a
1701/// positive `BigInt` denominator) so a `Let x: Rational be 7 / 2` compiles to an exact
1702/// `7/2` instead of flooring to `3`. The type-directed `resolve_divisions` pass only ever
1703/// produces these in a `Rational`-typed context, so the integer floor default is untouched.
1704/// `Display` reduces a whole value to a bare integer (`6 / 2 → "3"`), matching the interpreter.
1705#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1706pub struct LogosRational(pub logicaffeine_base::Rational);
1707
1708impl LogosRational {
1709    /// A whole integer as an exact rational (`5 → 5/1`, shown `5`).
1710    #[inline]
1711    pub fn from_i64(n: i64) -> Self {
1712        LogosRational(logicaffeine_base::Rational::from_i64(n))
1713    }
1714
1715    /// The exact quotient `n / d`. Panics on a zero denominator, mirroring integer `/ 0`.
1716    #[inline]
1717    pub fn from_ratio(n: i64, d: i64) -> Self {
1718        LogosRational(
1719            logicaffeine_base::Rational::from_ratio_i64(n, d)
1720                .expect("LOGOS runtime error: division by zero"),
1721        )
1722    }
1723
1724    #[inline]
1725    pub fn add(&self, other: &LogosRational) -> LogosRational {
1726        LogosRational(self.0.add(&other.0))
1727    }
1728
1729    #[inline]
1730    pub fn sub(&self, other: &LogosRational) -> LogosRational {
1731        LogosRational(self.0.sub(&other.0))
1732    }
1733
1734    #[inline]
1735    pub fn mul(&self, other: &LogosRational) -> LogosRational {
1736        LogosRational(self.0.mul(&other.0))
1737    }
1738
1739    /// Exact division. Panics on a zero divisor, mirroring integer `/ 0`.
1740    #[inline]
1741    pub fn div_exact(&self, other: &LogosRational) -> LogosRational {
1742        LogosRational(
1743            self.0
1744                .div(&other.0)
1745                .expect("LOGOS runtime error: division by zero"),
1746        )
1747    }
1748
1749    /// The exact absolute value (a rational stays a rational: `|-7/2| = 7/2`).
1750    #[inline]
1751    pub fn abs(&self) -> LogosRational {
1752        LogosRational(self.0.abs())
1753    }
1754
1755    /// The greatest integer ≤ this rational (toward −∞), computed EXACTLY on the
1756    /// BigInt numerator/denominator — never through a lossy `f64`.
1757    #[inline]
1758    pub fn floor(&self) -> i64 {
1759        self.0.floor().to_i64().expect("LOGOS runtime error: floor exceeds i64")
1760    }
1761
1762    /// The least integer ≥ this rational (toward +∞), computed exactly.
1763    #[inline]
1764    pub fn ceil(&self) -> i64 {
1765        self.0.ceil().to_i64().expect("LOGOS runtime error: ceiling exceeds i64")
1766    }
1767
1768    /// The nearest integer, ties away from zero (matching `f64::round`), computed exactly.
1769    #[inline]
1770    pub fn round(&self) -> i64 {
1771        self.0.round().to_i64().expect("LOGOS runtime error: round exceeds i64")
1772    }
1773}
1774
1775impl std::fmt::Display for LogosRational {
1776    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1777        std::fmt::Display::fmt(&self.0, f)
1778    }
1779}
1780
1781impl From<i64> for LogosRational {
1782    #[inline]
1783    fn from(n: i64) -> Self {
1784        LogosRational::from_i64(n)
1785    }
1786}
1787
1788/// An exact base-10 fixed-point number — money's runtime type on the compiled-to-Rust
1789/// path, the AOT mirror of the interpreter's `Decimal`. `decimal("19.99")` compiles to
1790/// `LogosDecimal::parse("19.99")`; `+ − ×` stay exact `Decimal` (scale preserved), and `÷`
1791/// widens to the exact `LogosRational` (base-10 division need not terminate). `Display`
1792/// shows the scale faithfully (`19.99`, `20.00`), matching the interpreter.
1793#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
1794pub struct LogosDecimal(pub logicaffeine_base::Decimal);
1795
1796impl LogosDecimal {
1797    /// A whole integer as a scale-0 decimal (`5 → "5"`).
1798    #[inline]
1799    pub fn from_i64(n: i64) -> Self {
1800        LogosDecimal(logicaffeine_base::Decimal::from_i64(n))
1801    }
1802
1803    /// Parse the exact value from its literal text (`"19.99"`). Panics on malformed input,
1804    /// which the LOGOS front-end has already rejected before codegen.
1805    #[inline]
1806    pub fn parse(s: &str) -> Self {
1807        LogosDecimal(
1808            logicaffeine_base::Decimal::parse(s)
1809                .expect("LOGOS runtime error: malformed decimal literal"),
1810        )
1811    }
1812
1813    #[inline]
1814    pub fn add(&self, other: &LogosDecimal) -> LogosDecimal {
1815        LogosDecimal(self.0.add(&other.0))
1816    }
1817
1818    #[inline]
1819    pub fn sub(&self, other: &LogosDecimal) -> LogosDecimal {
1820        LogosDecimal(self.0.sub(&other.0))
1821    }
1822
1823    #[inline]
1824    pub fn mul(&self, other: &LogosDecimal) -> LogosDecimal {
1825        LogosDecimal(self.0.mul(&other.0))
1826    }
1827
1828    /// The exact rational view (`coeff / 10^scale`) — the bridge `÷` widens through.
1829    #[inline]
1830    pub fn to_rational(&self) -> LogosRational {
1831        LogosRational(self.0.to_rational())
1832    }
1833
1834    /// Exact division, widening to a `LogosRational` (base-10 division need not terminate).
1835    /// Panics on a zero divisor, mirroring integer `/ 0`.
1836    #[inline]
1837    pub fn div_exact(&self, other: &LogosDecimal) -> LogosRational {
1838        self.to_rational().div_exact(&other.to_rational())
1839    }
1840
1841    /// The exact absolute value (scale preserved).
1842    #[inline]
1843    pub fn abs(&self) -> LogosDecimal {
1844        LogosDecimal(self.0.abs())
1845    }
1846}
1847
1848impl std::fmt::Display for LogosDecimal {
1849    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1850        std::fmt::Display::fmt(&self.0, f)
1851    }
1852}
1853
1854impl From<i64> for LogosDecimal {
1855    #[inline]
1856    fn from(n: i64) -> Self {
1857        LogosDecimal::from_i64(n)
1858    }
1859}
1860
1861/// An exact complex number `re + im·i` — the AOT mirror of the interpreter's `Complex`.
1862/// `complex(0, 1)` compiles to `LogosComplex::new(..)`; `+ − × ÷` stay exact and closed
1863/// (`i·i = −1`). NOT ordered. `Display` shows `3+4i` / `i` / `-2i`, matching the interpreter.
1864#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1865pub struct LogosComplex(pub logicaffeine_base::Complex);
1866
1867impl LogosComplex {
1868    /// A real integer as `re + 0i`.
1869    #[inline]
1870    pub fn from_i64(n: i64) -> Self {
1871        LogosComplex(logicaffeine_base::Complex::from_i64(n))
1872    }
1873
1874    /// `re + im·i` from two exact rationals.
1875    #[inline]
1876    pub fn new(re: LogosRational, im: LogosRational) -> Self {
1877        LogosComplex(logicaffeine_base::Complex::new(re.0, im.0))
1878    }
1879
1880    #[inline]
1881    pub fn add(&self, other: &LogosComplex) -> LogosComplex {
1882        LogosComplex(self.0.add(&other.0))
1883    }
1884
1885    #[inline]
1886    pub fn sub(&self, other: &LogosComplex) -> LogosComplex {
1887        LogosComplex(self.0.sub(&other.0))
1888    }
1889
1890    #[inline]
1891    pub fn mul(&self, other: &LogosComplex) -> LogosComplex {
1892        LogosComplex(self.0.mul(&other.0))
1893    }
1894
1895    /// Exact division (the complex field is closed). Panics on a zero divisor, mirroring `/ 0`.
1896    #[inline]
1897    pub fn div_exact(&self, other: &LogosComplex) -> LogosComplex {
1898        LogosComplex(self.0.div(&other.0).expect("LOGOS runtime error: division by zero"))
1899    }
1900}
1901
1902impl std::fmt::Display for LogosComplex {
1903    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1904        std::fmt::Display::fmt(&self.0, f)
1905    }
1906}
1907
1908impl From<i64> for LogosComplex {
1909    #[inline]
1910    fn from(n: i64) -> Self {
1911        LogosComplex::from_i64(n)
1912    }
1913}
1914
1915/// An element of the ring ℤ/nℤ — the AOT mirror of the interpreter's `Modular`.
1916/// `modular(value, modulus)` compiles to `LogosModular::new(..)`; `+ − ×` wrap in the ring,
1917/// `pow` is fast modular exponentiation, and `÷` multiplies by the modular inverse. NOT ordered.
1918#[derive(Clone, Debug, PartialEq, Eq, Hash)]
1919pub struct LogosModular(pub logicaffeine_base::Modular);
1920
1921impl LogosModular {
1922    /// `value mod modulus`. Panics on a non-positive modulus (a LOGOS runtime error).
1923    #[inline]
1924    pub fn new(value: i64, modulus: i64) -> Self {
1925        LogosModular(
1926            logicaffeine_base::Modular::from_i64(value, modulus)
1927                .expect("LOGOS runtime error: modulus must be positive"),
1928        )
1929    }
1930
1931    #[inline]
1932    pub fn add(&self, other: &LogosModular) -> LogosModular {
1933        LogosModular(self.0.add(&other.0).expect("LOGOS runtime error: modular ring mismatch"))
1934    }
1935
1936    #[inline]
1937    pub fn sub(&self, other: &LogosModular) -> LogosModular {
1938        LogosModular(self.0.sub(&other.0).expect("LOGOS runtime error: modular ring mismatch"))
1939    }
1940
1941    #[inline]
1942    pub fn mul(&self, other: &LogosModular) -> LogosModular {
1943        LogosModular(self.0.mul(&other.0).expect("LOGOS runtime error: modular ring mismatch"))
1944    }
1945
1946    /// Division by the modular inverse. Panics if the divisor is not coprime to the modulus.
1947    #[inline]
1948    pub fn div_exact(&self, other: &LogosModular) -> LogosModular {
1949        LogosModular(self.0.div(&other.0).expect("LOGOS runtime error: modular divisor has no inverse"))
1950    }
1951
1952    /// Fast modular exponentiation `self^exp`.
1953    #[inline]
1954    pub fn pow(&self, exp: u64) -> LogosModular {
1955        LogosModular(self.0.pow(exp))
1956    }
1957}
1958
1959impl std::fmt::Display for LogosModular {
1960    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1961        std::fmt::Display::fmt(&self.0, f)
1962    }
1963}
1964
1965/// A physical quantity on the compiled-to-Rust path — the AOT mirror of the interpreter's
1966/// `Quantity` value. The magnitude rides the exact rational tower (no float drift) and the display
1967/// unit travels with it so `Show` renders faithfully (`42/127 ft`). `+ −` require the same
1968/// dimension — the front-end's type checker proves this, so a mismatch is a *compile* error, not a
1969/// runtime one; the runtime check here is a defensive backstop. `× ÷` combine dimensions, and a
1970/// quantity may be scaled by a dimensionless number (unit preserved).
1971#[derive(Clone, Debug)]
1972pub struct LogosQuantity {
1973    pub q: logicaffeine_base::Quantity,
1974    pub unit: logicaffeine_base::Unit,
1975}
1976
1977impl LogosQuantity {
1978    /// `value` of the named unit (`LogosQuantity::of(2, "inch")`).
1979    #[inline]
1980    pub fn of(value: i64, unit_name: &str) -> Self {
1981        Self::from_rational(LogosRational::from_i64(value), unit_name)
1982    }
1983
1984    /// An exact-rational magnitude of the named unit. Panics on an unknown unit, which the
1985    /// front-end has already rejected before codegen.
1986    pub fn from_rational(value: LogosRational, unit_name: &str) -> Self {
1987        let unit = logicaffeine_base::quantity::units::by_name(unit_name)
1988            .unwrap_or_else(|| panic!("LOGOS runtime error: unknown unit '{unit_name}'"));
1989        LogosQuantity { q: logicaffeine_base::Quantity::of(value.0, &unit), unit }
1990    }
1991
1992    #[inline]
1993    pub fn add(&self, other: &LogosQuantity) -> LogosQuantity {
1994        let q = self
1995            .q
1996            .add(&other.q)
1997            .expect("LOGOS runtime error: cannot add quantities of different dimensions");
1998        LogosQuantity { q, unit: self.unit.clone() }
1999    }
2000
2001    #[inline]
2002    pub fn sub(&self, other: &LogosQuantity) -> LogosQuantity {
2003        let q = self
2004            .q
2005            .sub(&other.q)
2006            .expect("LOGOS runtime error: cannot subtract quantities of different dimensions");
2007        LogosQuantity { q, unit: self.unit.clone() }
2008    }
2009
2010    /// `× ÷` combine dimensions; the result is shown in SI/dimension form (empty display unit).
2011    #[inline]
2012    pub fn mul(&self, other: &LogosQuantity) -> LogosQuantity {
2013        let q = self.q.mul(&other.q);
2014        let unit = Self::si_unit(&q);
2015        LogosQuantity { q, unit }
2016    }
2017
2018    #[inline]
2019    pub fn div_exact(&self, other: &LogosQuantity) -> LogosQuantity {
2020        let q = self.q.div(&other.q).expect("LOGOS runtime error: cannot divide by a zero quantity");
2021        let unit = Self::si_unit(&q);
2022        LogosQuantity { q, unit }
2023    }
2024
2025    /// Scale by a dimensionless number (unit preserved): `q * k`.
2026    #[inline]
2027    pub fn scale(&self, k: &LogosRational) -> LogosQuantity {
2028        let mag = self.q.magnitude_si().mul(&k.0);
2029        LogosQuantity { q: logicaffeine_base::Quantity::si(mag, self.q.dimension()), unit: self.unit.clone() }
2030    }
2031
2032    /// Scale by a dimensionless integer (unit preserved) — the common `q * 3` case from codegen.
2033    #[inline]
2034    pub fn scale_int(&self, k: i64) -> LogosQuantity {
2035        self.scale(&LogosRational::from_i64(k))
2036    }
2037
2038    /// Divide by a dimensionless integer (unit preserved) — the common `q / 2` case from codegen.
2039    #[inline]
2040    pub fn div_int(&self, k: i64) -> LogosQuantity {
2041        self.div_scalar(&LogosRational::from_i64(k))
2042    }
2043
2044    /// Divide by a dimensionless number (unit preserved): `q / k`.
2045    #[inline]
2046    pub fn div_scalar(&self, k: &LogosRational) -> LogosQuantity {
2047        let mag = self
2048            .q
2049            .magnitude_si()
2050            .div(&k.0)
2051            .expect("LOGOS runtime error: cannot divide a quantity by zero");
2052        LogosQuantity { q: logicaffeine_base::Quantity::si(mag, self.q.dimension()), unit: self.unit.clone() }
2053    }
2054
2055    /// Re-express in another unit of the SAME dimension. A different dimension is the forbidden
2056    /// cast — the type checker rejects it, so this panic is a defensive backstop.
2057    pub fn convert(&self, unit_name: &str) -> LogosQuantity {
2058        let unit = logicaffeine_base::quantity::units::by_name(unit_name)
2059            .unwrap_or_else(|| panic!("LOGOS runtime error: unknown unit '{unit_name}'"));
2060        if self.q.dimension() != unit.dimension {
2061            panic!("LOGOS runtime error: cannot convert across dimensions");
2062        }
2063        LogosQuantity { q: self.q.clone(), unit }
2064    }
2065
2066    /// A synthetic SI-base unit (empty symbol) for a combined dimension.
2067    fn si_unit(q: &logicaffeine_base::Quantity) -> logicaffeine_base::Unit {
2068        logicaffeine_base::Unit::linear("", q.dimension(), logicaffeine_base::Rational::one())
2069    }
2070}
2071
2072// Equality is PHYSICAL (SI magnitude + dimension); the display unit is presentation only, so
2073// `100 cm` equals `1 m`. Ordering is by magnitude within a shared dimension (the type checker
2074// guarantees comparisons are same-dimension).
2075impl PartialEq for LogosQuantity {
2076    fn eq(&self, other: &Self) -> bool {
2077        self.q == other.q
2078    }
2079}
2080impl Eq for LogosQuantity {}
2081
2082impl PartialOrd for LogosQuantity {
2083    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
2084        if self.q.dimension() != other.q.dimension() {
2085            return None;
2086        }
2087        self.q.magnitude_si().partial_cmp(other.q.magnitude_si())
2088    }
2089}
2090
2091impl std::fmt::Display for LogosQuantity {
2092    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2093        let magnitude = self
2094            .q
2095            .in_unit(&self.unit)
2096            .expect("a Quantity's display unit always shares its dimension");
2097        if self.unit.symbol.is_empty() {
2098            write!(f, "{} {}", magnitude, self.q.dimension())
2099        } else {
2100            write!(f, "{} {}", magnitude, self.unit.symbol)
2101        }
2102    }
2103}
2104
2105/// Money for the compiled tier — an exact amount in a currency, mirroring `base::Money`. `+ −`
2106/// require the SAME currency (a runtime-panic backstop; the type checker rejects mismatches first),
2107/// `× ÷` scale by an exact number, and a same-currency `÷` is the dimensionless ratio.
2108#[derive(Clone, Debug)]
2109pub struct LogosMoney(pub logicaffeine_base::Money);
2110
2111impl LogosMoney {
2112    /// Money of a `Decimal` amount in the named currency. Panics on an unknown currency, which the
2113    /// front-end has already rejected before codegen.
2114    pub fn of(amount: LogosDecimal, code: &str) -> Self {
2115        let currency = logicaffeine_base::money::currency::by_code(code)
2116            .unwrap_or_else(|| panic!("LOGOS runtime error: unknown currency '{code}'"));
2117        LogosMoney(logicaffeine_base::Money::of(amount.0, currency))
2118    }
2119
2120    /// Money of an integer amount — the `5 USD` case (codegen passes the literal directly).
2121    pub fn from_i64(amount: i64, code: &str) -> Self {
2122        Self::of(LogosDecimal(logicaffeine_base::Decimal::from_i64(amount)), code)
2123    }
2124
2125    #[inline]
2126    pub fn add(&self, other: &LogosMoney) -> LogosMoney {
2127        LogosMoney(
2128            self.0
2129                .add(&other.0)
2130                .expect("LOGOS runtime error: cannot add money of different currencies"),
2131        )
2132    }
2133
2134    #[inline]
2135    pub fn sub(&self, other: &LogosMoney) -> LogosMoney {
2136        LogosMoney(
2137            self.0
2138                .sub(&other.0)
2139                .expect("LOGOS runtime error: cannot subtract money of different currencies"),
2140        )
2141    }
2142
2143    /// Scale by an integer (`19.99 USD × 3`), re-quantised to the currency.
2144    #[inline]
2145    pub fn scale_int(&self, k: i64) -> LogosMoney {
2146        LogosMoney(self.0.scale_int(k))
2147    }
2148
2149    /// Scale by an exact decimal (`price × 1.5`), re-quantised to the currency.
2150    #[inline]
2151    pub fn scale_decimal(&self, k: &LogosDecimal) -> LogosMoney {
2152        LogosMoney(logicaffeine_base::Money::of(self.0.amount.mul(&k.0), self.0.currency))
2153    }
2154
2155    /// Divide by an integer (split a bill), re-quantised to the currency.
2156    #[inline]
2157    pub fn div_int(&self, k: i64) -> LogosMoney {
2158        let d = self
2159            .0
2160            .amount
2161            .div(
2162                &logicaffeine_base::Decimal::from_i64(k),
2163                self.0.currency.scale,
2164                logicaffeine_base::RoundingMode::HalfEven,
2165            )
2166            .expect("LOGOS runtime error: cannot divide money by zero");
2167        LogosMoney(logicaffeine_base::Money::of(d, self.0.currency))
2168    }
2169
2170    /// The exact dimensionless ratio of two same-currency amounts.
2171    #[inline]
2172    pub fn ratio(&self, other: &LogosMoney) -> LogosRational {
2173        LogosRational(
2174            self.0
2175                .ratio(&other.0)
2176                .expect("LOGOS runtime error: cannot take a money ratio (currency mismatch or zero)"),
2177        )
2178    }
2179}
2180
2181impl PartialEq for LogosMoney {
2182    fn eq(&self, other: &Self) -> bool {
2183        self.0 == other.0
2184    }
2185}
2186impl Eq for LogosMoney {}
2187
2188impl PartialOrd for LogosMoney {
2189    /// Ordered by amount within a currency; across currencies it is incomparable (`None`).
2190    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
2191        if self.0.currency != other.0.currency {
2192            return None;
2193        }
2194        self.0.amount.to_rational().partial_cmp(&other.0.amount.to_rational())
2195    }
2196}
2197
2198impl std::fmt::Display for LogosMoney {
2199    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2200        write!(f, "{}", self.0)
2201    }
2202}
2203
2204/// A 128-bit UUID for the compiled tier (RFC 9562), mirroring `logicaffeine_base::Uuid`. A `Copy` newtype over
2205/// `[u8; 16]` — no allocation, byte-ordered comparison (so v6/v7 sort by time), canonical text form.
2206#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
2207pub struct LogosUuid(pub logicaffeine_base::Uuid);
2208
2209impl LogosUuid {
2210    /// Parse from canonical/simple/braced/urn text. Panics on a malformed id (the front-end has
2211    /// already validated literal forms; a runtime parse of bad data is a clean program error).
2212    pub fn parse(s: &str) -> Self {
2213        LogosUuid(
2214            logicaffeine_base::Uuid::parse(s)
2215                .unwrap_or_else(|| panic!("LOGOS runtime error: invalid UUID '{s}'")),
2216        )
2217    }
2218    /// The nil (all-zero) UUID.
2219    pub fn nil() -> Self {
2220        LogosUuid(logicaffeine_base::Uuid::NIL)
2221    }
2222    /// The max (all-one) UUID.
2223    pub fn max() -> Self {
2224        LogosUuid(logicaffeine_base::Uuid::MAX)
2225    }
2226    /// The version nibble (0 for nil, 1–8 for the defined versions, 15 for max).
2227    pub fn version(&self) -> i64 {
2228        self.0.version() as i64
2229    }
2230    /// The well-known DNS / URL / OID / X.500 namespaces.
2231    pub fn namespace_dns() -> Self {
2232        LogosUuid(logicaffeine_base::Uuid::NAMESPACE_DNS)
2233    }
2234    pub fn namespace_url() -> Self {
2235        LogosUuid(logicaffeine_base::Uuid::NAMESPACE_URL)
2236    }
2237    pub fn namespace_oid() -> Self {
2238        LogosUuid(logicaffeine_base::Uuid::NAMESPACE_OID)
2239    }
2240    pub fn namespace_x500() -> Self {
2241        LogosUuid(logicaffeine_base::Uuid::NAMESPACE_X500)
2242    }
2243}
2244
2245impl LogosUuid {
2246    /// The UUID's 16 bytes as a `Seq of Int` — the byte view the Logos-written version constructors
2247    /// (uuid.lg) prepend as the namespace.
2248    pub fn byte_seq(&self) -> LogosSeq<i64> {
2249        LogosSeq::from_vec(self.0.as_bytes().iter().map(|&b| b as i64).collect())
2250    }
2251    /// Build a UUID from the first 16 bytes of a `Seq of Int` (the compiled mirror of the
2252    /// `uuid_from_bytes` builtin). Panics on fewer than 16 bytes — the front-end feeds a digest.
2253    pub fn from_byte_seq(seq: &LogosSeq<i64>) -> LogosUuid {
2254        let v = seq.0.borrow();
2255        assert!(v.len() >= 16, "LOGOS runtime error: uuid_from_bytes needs 16 bytes");
2256        let mut b = [0u8; 16];
2257        for (i, slot) in b.iter_mut().enumerate() {
2258            *slot = (v[i] & 0xff) as u8;
2259        }
2260        LogosUuid(logicaffeine_base::Uuid::from_bytes(b))
2261    }
2262}
2263
2264/// A Text's UTF-8 bytes as a `Seq of Int` (the `text_bytes` builtin, AOT).
2265pub fn text_bytes(s: &str) -> LogosSeq<i64> {
2266    LogosSeq::from_vec(s.as_bytes().iter().map(|&b| b as i64).collect())
2267}
2268
2269/// Rebuild a Text from a `Seq of Int` of UTF-8 bytes (the `text_from_bytes` builtin, AOT —
2270/// the exact inverse of [`text_bytes`]). Inputs originate from `text_bytes`, so the bytes are
2271/// always valid UTF-8; the lossy fallback only guards against a hand-built malformed Seq.
2272pub fn text_from_bytes(bytes: &LogosSeq<i64>) -> String {
2273    let b: Vec<u8> = bytes.iter().map(|v| v as u8).collect();
2274    match String::from_utf8(b) {
2275        Ok(s) => s,
2276        Err(e) => String::from_utf8_lossy(e.as_bytes()).into_owned(),
2277    }
2278}
2279
2280impl std::fmt::Display for LogosUuid {
2281    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2282        write!(f, "{}", self.0)
2283    }
2284}
2285
2286/// An exchange rate accepted by [`set_rate`] — a plain integer (`1`), an exact `LogosDecimal`
2287/// (`1.10`), or a `LogosRational`. All widen losslessly onto the Rational tower so normalisation
2288/// stays exact, matching the interpreter's `set_rate` builtin.
2289pub trait IntoRate {
2290    fn into_rate(self) -> logicaffeine_base::Rational;
2291}
2292impl IntoRate for i64 {
2293    fn into_rate(self) -> logicaffeine_base::Rational {
2294        logicaffeine_base::Rational::from_i64(self)
2295    }
2296}
2297impl IntoRate for LogosDecimal {
2298    fn into_rate(self) -> logicaffeine_base::Rational {
2299        self.0.to_rational()
2300    }
2301}
2302impl IntoRate for LogosRational {
2303    fn into_rate(self) -> logicaffeine_base::Rational {
2304        self.0
2305    }
2306}
2307
2308/// Install (or replace) one exchange rate in the ambient rate context — `1 <code> = rate` reference
2309/// units. The compiled-tier mirror of the `set_rate` builtin; a fresh process starts with no rates,
2310/// so a program installs them before any `<money> in <currency>` conversion.
2311pub fn set_rate<R: IntoRate>(code: String, rate: R) {
2312    logicaffeine_base::money::set_ambient_rate(&code, rate.into_rate());
2313}
2314
2315/// Bulk-install a whole exchange-rate table from a `Map of Text to <number>` (currency code → rate
2316/// vs the reference) into the ambient rate context. The compiled-tier mirror of the `set_rates`
2317/// builtin and the bridge a literal, network-synced, or fetched table feeds. Order-independent.
2318pub fn set_rates<V: IntoRate + Clone>(map: LogosMap<String, V>) {
2319    for (code, rate) in map.0.borrow().iter() {
2320        logicaffeine_base::money::set_ambient_rate(code, rate.clone().into_rate());
2321    }
2322}
2323
2324/// Convert money to another currency via the ambient rate context — the `<money> in <currency>`
2325/// surface. Panics (the typed-error backstop, like a currency-mismatch `+`) when no rates are in
2326/// scope or the currency lacks a rate; the front-end / interpreter surface these as clean errors.
2327pub fn to_currency(money: LogosMoney, code: String) -> LogosMoney {
2328    let to = logicaffeine_base::money::currency::by_code(&code)
2329        .unwrap_or_else(|| panic!("LOGOS runtime error: unknown currency '{code}'"));
2330    let converted = logicaffeine_base::money::ambient_convert(&money.0, to).unwrap_or_else(|| {
2331        if logicaffeine_base::money::has_ambient_rates() {
2332            panic!(
2333                "LOGOS runtime error: no exchange rate for {} or {}",
2334                money.0.currency.code, to.code
2335            )
2336        } else {
2337            panic!("LOGOS runtime error: no exchange rates in scope (set a rate first)")
2338        }
2339    });
2340    LogosMoney(converted)
2341}
2342
2343#[cfg(test)]
2344mod logos_money_tests {
2345    use super::{LogosDecimal, LogosMoney};
2346
2347    fn money(s: &str, code: &str) -> LogosMoney {
2348        LogosMoney::of(LogosDecimal(logicaffeine_base::Decimal::parse(s).unwrap()), code)
2349    }
2350
2351    #[test]
2352    fn money_aot_arithmetic_is_exact_and_currency_safe() {
2353        assert_eq!(money("19.99", "USD").to_string(), "19.99 USD");
2354        assert_eq!(LogosMoney::from_i64(5, "USD").to_string(), "5.00 USD");
2355        assert_eq!(money("0.10", "USD").add(&money("0.20", "USD")).to_string(), "0.30 USD");
2356        assert_eq!(money("24.99", "USD").sub(&money("5.00", "USD")).to_string(), "19.99 USD");
2357        assert_eq!(money("19.99", "USD").scale_int(3).to_string(), "59.97 USD");
2358        assert_eq!(money("10.00", "USD").div_int(4).to_string(), "2.50 USD");
2359        assert_eq!(money("100", "JPY").to_string(), "100 JPY"); // zero-decimal
2360        // Same currency orders; different currencies are incomparable.
2361        assert!(money("5.00", "USD") > money("1.00", "USD"));
2362        assert_eq!(money("5.00", "USD").partial_cmp(&money("1.00", "EUR")), None);
2363        assert_ne!(money("5.00", "USD"), money("5.00", "EUR"));
2364    }
2365
2366    #[test]
2367    #[should_panic(expected = "different currencies")]
2368    fn money_aot_cross_currency_add_panics_as_a_backstop() {
2369        let _ = money("5.00", "USD").add(&money("1.00", "EUR"));
2370    }
2371}
2372
2373#[cfg(test)]
2374mod logos_quantity_tests {
2375    use super::{LogosQuantity, LogosRational};
2376
2377    #[test]
2378    fn golden_two_inches_plus_five_cm_in_feet_is_exactly_42_over_127() {
2379        let a = LogosQuantity::of(2, "inch");
2380        let b = LogosQuantity::of(5, "centimeter");
2381        assert_eq!(a.to_string(), "2 in");
2382        assert_eq!(a.add(&b).convert("foot").to_string(), "42/127 ft");
2383    }
2384
2385    #[test]
2386    fn arithmetic_scaling_equality_and_ordering_are_exact() {
2387        let m = |v: i64, u: &str| LogosQuantity::of(v, u);
2388        // Same-dimension subtraction keeps the left operand's unit, exactly.
2389        assert_eq!(m(1, "meter").sub(&m(50, "centimeter")).to_string(), "1/2 m");
2390        // Scalar scaling preserves the unit.
2391        assert_eq!(m(2, "inch").scale(&LogosRational::from_i64(3)).to_string(), "6 in");
2392        assert_eq!(m(6, "inch").div_scalar(&LogosRational::from_i64(2)).to_string(), "3 in");
2393        // Dimension-combining product shows in dimension form.
2394        assert_eq!(m(3, "meter").mul(&m(4, "meter")).to_string(), "12 L^2");
2395        assert_eq!(m(100, "meter").div_exact(&m(10, "second")).to_string(), "10 L·T^-1");
2396        // Physical equality (display unit ignored): 100 cm == 1 m.
2397        assert_eq!(m(100, "centimeter"), m(1, "meter"));
2398        // Ordering by magnitude within a shared dimension.
2399        assert!(m(2, "meter") > m(1, "meter"));
2400        assert!(m(100, "centimeter") <= m(1, "meter"));
2401        // Cross-dimension ordering is undefined (no shared magnitude).
2402        assert_eq!(m(1, "meter").partial_cmp(&m(1, "kilogram")), None);
2403    }
2404
2405    #[test]
2406    #[should_panic(expected = "different dimensions")]
2407    fn adding_different_dimensions_panics_as_a_backstop() {
2408        let _ = LogosQuantity::of(1, "meter").add(&LogosQuantity::of(1, "kilogram"));
2409    }
2410
2411    #[test]
2412    #[should_panic(expected = "cannot convert across dimensions")]
2413    fn converting_across_dimensions_panics_as_a_backstop() {
2414        let _ = LogosQuantity::of(1, "meter").convert("kilogram");
2415    }
2416}
2417
2418#[cfg(test)]
2419mod logos_rational_tests {
2420    use super::LogosRational;
2421
2422    #[test]
2423    fn exact_fraction_displays_unreduced_pair() {
2424        assert_eq!(LogosRational::from_ratio(7, 2).to_string(), "7/2");
2425        assert_eq!(LogosRational::from_ratio(1, 3).to_string(), "1/3");
2426    }
2427
2428    #[test]
2429    fn whole_value_displays_as_a_bare_integer() {
2430        assert_eq!(LogosRational::from_ratio(6, 2).to_string(), "3");
2431        assert_eq!(LogosRational::from_i64(5).to_string(), "5");
2432    }
2433
2434    #[test]
2435    fn arithmetic_is_exact() {
2436        // 1/3 + 1/6 = 1/2
2437        let a = LogosRational::from_ratio(1, 3);
2438        let b = LogosRational::from_ratio(1, 6);
2439        assert_eq!(a.add(&b).to_string(), "1/2");
2440        // 1/3 + 1/3 + 1/3 = 1 (where 0.1+0.2 ≠ 0.3 in f64)
2441        let third = LogosRational::from_ratio(1, 3);
2442        assert_eq!(third.add(&third).add(&third).to_string(), "1");
2443        // (2/3) * (3/4) = 1/2 ; (7/2) / (7/2) = 1
2444        assert_eq!(
2445            LogosRational::from_ratio(2, 3).mul(&LogosRational::from_ratio(3, 4)).to_string(),
2446            "1/2"
2447        );
2448        let r = LogosRational::from_ratio(7, 2);
2449        assert_eq!(r.div_exact(&r).to_string(), "1");
2450    }
2451
2452    #[test]
2453    #[should_panic(expected = "division by zero")]
2454    fn zero_denominator_panics_like_integer_division() {
2455        let _ = LogosRational::from_ratio(1, 0);
2456    }
2457}
2458
2459#[cfg(test)]
2460mod tests {
2461    use super::*;
2462
2463    /// `div_floor` rounds toward negative infinity across every sign combination,
2464    /// stays exact past `i64` (BigInt promotion), and raises on a zero divisor —
2465    /// differentially checked against an `i128` floor oracle over an exhaustive
2466    /// small grid so no sign case escapes.
2467    #[test]
2468    fn div_floor_rounds_toward_negative_infinity_exhaustively() {
2469        let small = |x: i64| LogosInt::Small(x);
2470        // Exhaustive small grid including both signs and zero dividend.
2471        for a in -20i64..=20 {
2472            for b in -20i64..=20 {
2473                if b == 0 {
2474                    assert!(small(a).div_floor(&small(b)).is_err(), "{a} // 0 must error");
2475                    continue;
2476                }
2477                let oracle = (a as i128).div_euclid(b as i128); // euclid == floor for these
2478                // div_euclid is NOT floor for a>=0,b<0 — recompute a true floor oracle.
2479                let q = (a as i128) / (b as i128);
2480                let r = (a as i128) % (b as i128);
2481                let floor = if r != 0 && (r < 0) != (b < 0) { q - 1 } else { q };
2482                let _ = oracle;
2483                let got = small(a).div_floor(&small(b)).unwrap();
2484                assert_eq!(got, LogosInt::Small(floor as i64), "{a} // {b}");
2485            }
2486        }
2487        // The canonical distinguishing case: truncation gives -3, floor gives -4.
2488        assert_eq!(small(-7).div_floor(&small(2)).unwrap(), LogosInt::Small(-4));
2489        assert_eq!(small(-7).div(&small(2)).unwrap(), LogosInt::Small(-3));
2490        // Exact past i64: (10^30) // 7 stays exact and floored.
2491        let ten30 = LogosInt::Small(10).pow(&LogosInt::Small(30)).unwrap();
2492        let q = ten30.div_floor(&small(7)).unwrap();
2493        assert_eq!(q.to_string(), "142857142857142857142857142857");
2494        // Negative BigInt numerator floors down, not toward zero.
2495        let neg = LogosInt::Small(-1).pow(&LogosInt::Small(1)).unwrap(); // -1
2496        let big_neg = ten30.mul(&neg);
2497        // -(10^30) // 7 = floor(-1.428...e29) = -(10^30)/7 rounded down.
2498        let qn = big_neg.div_floor(&small(7)).unwrap();
2499        assert_eq!(qn.to_string(), "-142857142857142857142857142858");
2500    }
2501
2502    /// A deterministic LCG so the `LogosI64Map` fuzz is reproducible without a
2503    /// `rand` dependency (same seed → same op stream every run).
2504    struct Lcg(u64);
2505    impl Lcg {
2506        fn next_u64(&mut self) -> u64 {
2507            self.0 = self
2508                .0
2509                .wrapping_mul(6364136223846793005)
2510                .wrapping_add(1442695040888963407);
2511            self.0
2512        }
2513    }
2514
2515    /// Differential fuzz: a long random stream of inserts/gets/contains on a
2516    /// `LogosI64Map` must agree with `std::collections::HashMap` (the oracle) on
2517    /// every read, across many resizes and over a key space that forces both
2518    /// overwrites (small range → collisions) and the `0` sentinel path (the real
2519    /// key `0`, tracked outside the table) as well as `i64::MIN` as an ordinary
2520    /// in-table key.
2521    #[test]
2522    fn i64map_matches_hashmap_oracle() {
2523        use std::collections::HashMap;
2524        // The key pool deliberately includes `0` (the sentinel value) and both
2525        // `i64` extremes so the empty-slot encoding and the side-tracked zero key
2526        // are exercised under fuzz, not just in isolation.
2527        let pool: Vec<i64> = {
2528            let mut v: Vec<i64> = (-40..40).collect();
2529            v.extend_from_slice(&[i64::MIN, i64::MAX, i64::MIN + 1, i64::MAX - 1, 0]);
2530            v
2531        };
2532        for seed in [1u64, 7, 1234567, 0x9E3779B9, u64::MAX / 3] {
2533            let mut rng = Lcg(seed);
2534            let mut map = LogosI64Map::new();
2535            let mut oracle: HashMap<i64, i64> = HashMap::new();
2536            for _ in 0..50_000 {
2537                let key = pool[(rng.next_u64() as usize) % pool.len()];
2538                match rng.next_u64() % 3 {
2539                    0 => {
2540                        let val = rng.next_u64() as i64;
2541                        map.insert(key, val);
2542                        oracle.insert(key, val);
2543                    }
2544                    1 => {
2545                        assert_eq!(
2546                            map.get(&key),
2547                            oracle.get(&key).copied(),
2548                            "get disagreed for key {key} (seed {seed})"
2549                        );
2550                    }
2551                    _ => {
2552                        assert_eq!(
2553                            map.contains_key(&key),
2554                            oracle.contains_key(&key),
2555                            "contains disagreed for key {key} (seed {seed})"
2556                        );
2557                    }
2558                }
2559                assert_eq!(map.len(), oracle.len(), "len diverged (seed {seed})");
2560            }
2561            // Final sweep: every pooled key must read identically.
2562            for &k in &pool {
2563                assert_eq!(map.get(&k), oracle.get(&k).copied(), "final get key {k}");
2564            }
2565        }
2566    }
2567
2568    /// `LogosI64Set` agrees with `std::HashSet` under a 50k-op fuzz across seeds,
2569    /// with `0` (the sentinel value) and both `i64` extremes in the key pool. The
2570    /// set's `insert(key, _value)` mirrors the map call shape; the value is ignored.
2571    #[test]
2572    fn i64set_matches_hashset_oracle() {
2573        use std::collections::HashSet;
2574        let pool: Vec<i64> = {
2575            let mut v: Vec<i64> = (-40..40).collect();
2576            v.extend_from_slice(&[i64::MIN, i64::MAX, i64::MIN + 1, i64::MAX - 1, 0]);
2577            v
2578        };
2579        for seed in [1u64, 7, 1234567, 0x9E3779B9, u64::MAX / 3] {
2580            let mut rng = Lcg(seed);
2581            let mut set = LogosI64Set::new();
2582            let mut oracle: HashSet<i64> = HashSet::new();
2583            for _ in 0..50_000 {
2584                let key = pool[(rng.next_u64() as usize) % pool.len()];
2585                if rng.next_u64() % 2 == 0 {
2586                    set.insert(key, 1);
2587                    oracle.insert(key);
2588                } else {
2589                    assert_eq!(
2590                        set.contains_key(&key),
2591                        oracle.contains(&key),
2592                        "contains disagreed for key {key} (seed {seed})"
2593                    );
2594                }
2595                assert_eq!(set.len(), oracle.len(), "len diverged (seed {seed})");
2596            }
2597            for &k in &pool {
2598                assert_eq!(set.contains_key(&k), oracle.contains(&k), "final contains key {k}");
2599            }
2600        }
2601    }
2602
2603    /// `LogosDivU64` (loop-invariant libdivide) must agree with the hardware
2604    /// `/` and `%` for EVERY (numerator, divisor) pair — exhaustively over small
2605    /// values (every divisor 1..=512 against numerators 0..=4096, which forces
2606    /// the power-of-two fast path, the 64-bit-magic path, AND the ADD_MARKER
2607    /// 65-bit-magic path), and under a wide fuzz that hammers the full `u64`
2608    /// range including `u64::MAX`, `2^63`, and the exact benchmark divisors. Any
2609    /// disagreement here is a miscompiled division — the test is the spec.
2610    #[test]
2611    fn divu64_matches_hardware_div_and_rem() {
2612        // Exhaustive small grid — divisor 1..=512, numerator 0..=4096.
2613        for d in 1u64..=512 {
2614            let m = LogosDivU64::new(d);
2615            for x in 0u64..=4096 {
2616                assert_eq!(m.div(x), x / d, "div({x}, {d})");
2617                assert_eq!(m.rem(x), x % d, "rem({x}, {d})");
2618            }
2619        }
2620
2621        // Boundary divisors (powers of two, primes, near-2^k, benchmark sizes)
2622        // crossed with boundary + fuzzed numerators across the full u64 range.
2623        let divisors: Vec<u64> = vec![
2624            1, 2, 3, 4, 5, 7, 8, 9, 10, 16, 31, 37, 41, 43, 47, 64, 100, 127, 128,
2625            1000, 1024, 65535, 65536, 65537, 1_000_000, 2_147_483_648, 3_000_000,
2626            5_000_000, 1_000_000_007, (1u64 << 62) - 1, 1u64 << 62, (1u64 << 63) - 1,
2627            1u64 << 63, u64::MAX - 1, u64::MAX,
2628        ];
2629        let mut rng = Lcg(0xDEAD_BEEF_CAFE_F00D);
2630        for &d in &divisors {
2631            let m = LogosDivU64::new(d);
2632            let mut numerators: Vec<u64> = vec![
2633                0, 1, 2, d.wrapping_sub(1), d, d.wrapping_add(1), d.wrapping_mul(2),
2634                i64::MAX as u64, 1u64 << 63, u64::MAX - 1, u64::MAX,
2635            ];
2636            for _ in 0..2000 {
2637                numerators.push(rng.next_u64());
2638            }
2639            for &x in &numerators {
2640                assert_eq!(m.div(x), x / d, "div({x}, {d}) [fuzz]");
2641                assert_eq!(m.rem(x), x % d, "rem({x}, {d}) [fuzz]");
2642            }
2643        }
2644    }
2645
2646    /// `i64::MIN` is a genuine, distinguishable key — present after insert,
2647    /// absent before, and independent of the rest of the table. (It is now an
2648    /// ordinary in-table key; the empty-slot sentinel is `0`.)
2649    #[test]
2650    fn i64map_sentinel_key_is_a_real_key() {
2651        let mut m = LogosI64Map::new();
2652        assert_eq!(m.get(&i64::MIN), None);
2653        assert!(!m.contains_key(&i64::MIN));
2654
2655        // Fill enough ordinary keys to force several resizes around it.
2656        for k in 0..1000i64 {
2657            m.insert(k, k * 3);
2658        }
2659        assert_eq!(m.get(&i64::MIN), None, "sentinel must stay absent");
2660
2661        m.insert(i64::MIN, 999);
2662        assert_eq!(m.get(&i64::MIN), Some(999));
2663        assert!(m.contains_key(&i64::MIN));
2664        // It does not perturb ordinary keys, nor they it.
2665        for k in 0..1000i64 {
2666            assert_eq!(m.get(&k), Some(k * 3));
2667        }
2668        m.insert(i64::MIN, -1);
2669        assert_eq!(m.get(&i64::MIN), Some(-1), "sentinel value overwrites");
2670        assert_eq!(m.len(), 1001, "sentinel counts once toward len");
2671    }
2672
2673    /// Overwriting an existing key replaces its value and never grows `len`.
2674    #[test]
2675    fn i64map_overwrite_preserves_len() {
2676        let mut m = LogosI64Map::new();
2677        for k in 0..100i64 {
2678            m.insert(k, 1);
2679        }
2680        assert_eq!(m.len(), 100);
2681        for k in 0..100i64 {
2682            m.insert(k, k * 10);
2683        }
2684        assert_eq!(m.len(), 100, "overwrites must not change len");
2685        for k in 0..100i64 {
2686            assert_eq!(m.get(&k), Some(k * 10));
2687        }
2688    }
2689
2690    /// `with_capacity` pre-sized then filled exactly to capacity reads back
2691    /// every entry (the headroom math must not under-allocate).
2692    #[test]
2693    fn i64map_with_capacity_fills_correctly() {
2694        for cap in [0usize, 1, 7, 8, 100, 1000] {
2695            let mut m = LogosI64Map::with_capacity(cap);
2696            let n = cap.max(1) as i64;
2697            for k in 0..n {
2698                m.insert(k, k + 7);
2699            }
2700            assert_eq!(m.len(), n as usize, "cap {cap}");
2701            for k in 0..n {
2702                assert_eq!(m.get(&k), Some(k + 7), "cap {cap} key {k}");
2703            }
2704            assert_eq!(m.get(&(n + 1)), None);
2705        }
2706    }
2707
2708    /// `Clone` has value semantics — mutating the original leaves the clone
2709    /// untouched (the whole point of selecting this map only when non-aliased).
2710    #[test]
2711    fn i64map_clone_is_independent() {
2712        let mut a = LogosI64Map::new();
2713        for k in 0..50i64 {
2714            a.insert(k, k);
2715        }
2716        let b = a.clone();
2717        for k in 0..50i64 {
2718            a.insert(k, k + 1000);
2719        }
2720        a.insert(100, 100);
2721        for k in 0..50i64 {
2722            assert_eq!(b.get(&k), Some(k), "clone must not see later mutations");
2723        }
2724        assert_eq!(b.get(&100), None, "clone must not gain new keys");
2725        assert_eq!(b.len(), 50);
2726    }
2727
2728    /// The key `0` is a genuine, distinguishable key — never confused with an
2729    /// empty slot — present after insert, absent before, and independent of the
2730    /// rest of the table across many resizes. With the `0` empty-slot sentinel
2731    /// the real key `0` lives outside the probe table; this pins that side path.
2732    #[test]
2733    fn i64map_zero_key_is_a_real_key() {
2734        let mut m = LogosI64Map::new();
2735        assert_eq!(m.get(&0), None);
2736        assert!(!m.contains_key(&0));
2737
2738        // Ordinary non-zero keys force several resizes around the zero key.
2739        for k in 1..=1000i64 {
2740            m.insert(k, k * 3);
2741        }
2742        assert_eq!(m.get(&0), None, "zero key must stay absent");
2743
2744        m.insert(0, 999);
2745        assert_eq!(m.get(&0), Some(999));
2746        assert!(m.contains_key(&0));
2747        for k in 1..=1000i64 {
2748            assert_eq!(m.get(&k), Some(k * 3), "ordinary keys undisturbed by zero key");
2749        }
2750        m.insert(0, -1);
2751        assert_eq!(m.get(&0), Some(-1), "zero key value overwrites");
2752        assert_eq!(m.len(), 1001, "zero key counts once toward len");
2753    }
2754
2755    /// The set analogue: `0` is a real member, distinct from empty slots, stable
2756    /// across resizes driven by non-zero members.
2757    #[test]
2758    fn i64set_zero_key_is_a_real_key() {
2759        let mut s = LogosI64Set::new();
2760        assert!(!s.contains_key(&0));
2761        for k in 1..=1000i64 {
2762            s.insert(k, 1);
2763        }
2764        assert!(!s.contains_key(&0), "zero key must stay absent");
2765        s.insert(0, 1);
2766        assert!(s.contains_key(&0));
2767        for k in 1..=1000i64 {
2768            assert!(s.contains_key(&k), "members undisturbed by zero key");
2769        }
2770        s.insert(0, 1);
2771        assert_eq!(s.len(), 1001, "zero key counts once toward len");
2772    }
2773
2774    /// A present key whose VALUE is `0` must read back as `Some(0)`, never `None`
2775    /// — emptiness is decided by the key lane, never the value lane (the classic
2776    /// open-addressing trap once the value array is zero-initialized via calloc).
2777    #[test]
2778    fn i64map_value_zero_distinct_from_absent() {
2779        let mut m = LogosI64Map::new();
2780        m.insert(5, 0);
2781        m.insert(7, 0);
2782        assert_eq!(m.get(&5), Some(0), "present key with value 0 is not absent");
2783        assert_eq!(m.get(&7), Some(0));
2784        assert_eq!(m.get(&6), None, "truly absent key is None");
2785
2786        // Same, with a pre-sized (calloc-zeroed) table and the zero key itself.
2787        let mut m2 = LogosI64Map::with_capacity(64);
2788        m2.insert(0, 0);
2789        m2.insert(3, 0);
2790        assert_eq!(m2.get(&0), Some(0), "zero key with zero value is present");
2791        assert_eq!(m2.get(&3), Some(0));
2792        assert_eq!(m2.get(&1), None);
2793    }
2794
2795    /// `i64::MIN` is an ORDINARY in-table key (no longer a sentinel): it coexists
2796    /// with `i64::MAX` and ordinary keys, each with a distinct value, across
2797    /// resizes, and overwrites like any other key.
2798    #[test]
2799    fn i64map_min_key_is_ordinary() {
2800        let mut m = LogosI64Map::new();
2801        m.insert(i64::MIN, 11);
2802        m.insert(i64::MAX, 22);
2803        for k in 1..=1000i64 {
2804            m.insert(k, k);
2805        }
2806        assert_eq!(m.get(&i64::MIN), Some(11));
2807        assert_eq!(m.get(&i64::MAX), Some(22));
2808        m.insert(i64::MIN, 33);
2809        assert_eq!(m.get(&i64::MIN), Some(33), "i64::MIN overwrites like any key");
2810        assert_eq!(m.get(&i64::MAX), Some(22), "and does not perturb i64::MAX");
2811        assert_eq!(m.len(), 1002, "i64::MIN + i64::MAX + 1000 ordinary keys");
2812    }
2813
2814    #[test]
2815    fn value_int_arithmetic() {
2816        assert_eq!(Value::Int(10) + Value::Int(3), Value::Int(13));
2817        assert_eq!(Value::Int(10) - Value::Int(3), Value::Int(7));
2818        assert_eq!(Value::Int(10) * Value::Int(3), Value::Int(30));
2819        assert_eq!(Value::Int(10) / Value::Int(3), Value::Int(3));
2820    }
2821
2822    #[test]
2823    fn value_float_arithmetic() {
2824        assert_eq!(Value::Float(2.5) + Value::Float(1.5), Value::Float(4.0));
2825        assert_eq!(Value::Float(5.0) - Value::Float(1.5), Value::Float(3.5));
2826        assert_eq!(Value::Float(2.0) * Value::Float(3.0), Value::Float(6.0));
2827        assert_eq!(Value::Float(7.0) / Value::Float(2.0), Value::Float(3.5));
2828    }
2829
2830    #[test]
2831    fn value_cross_type_promotion() {
2832        assert_eq!(Value::Int(2) + Value::Float(1.5), Value::Float(3.5));
2833        assert_eq!(Value::Float(2.5) + Value::Int(2), Value::Float(4.5));
2834        assert_eq!(Value::Int(3) * Value::Float(2.0), Value::Float(6.0));
2835        assert_eq!(Value::Float(6.0) / Value::Int(2), Value::Float(3.0));
2836    }
2837
2838    #[test]
2839    fn value_text_concat() {
2840        assert_eq!(
2841            Value::Text("hello".to_string()) + Value::Text(" world".to_string()),
2842            Value::Text("hello world".to_string())
2843        );
2844    }
2845
2846    #[test]
2847    #[should_panic(expected = "divide by zero")]
2848    fn value_div_by_zero_panics() {
2849        let _ = Value::Int(1) / Value::Int(0);
2850    }
2851
2852    #[test]
2853    #[should_panic(expected = "Cannot add")]
2854    fn value_incompatible_types_panic() {
2855        let _ = Value::Bool(true) + Value::Int(1);
2856    }
2857
2858    #[test]
2859    fn value_display() {
2860        assert_eq!(format!("{}", Value::Int(42)), "42");
2861        assert_eq!(format!("{}", Value::Float(3.14)), "3.14");
2862        assert_eq!(format!("{}", Value::Bool(true)), "true");
2863        assert_eq!(format!("{}", Value::Text("hi".to_string())), "hi");
2864        assert_eq!(format!("{}", Value::Char('a')), "a");
2865        assert_eq!(format!("{}", Value::Nothing), "nothing");
2866    }
2867
2868    #[test]
2869    fn value_from_conversions() {
2870        assert_eq!(Value::from(42i64), Value::Int(42));
2871        assert_eq!(Value::from(3.14f64), Value::Float(3.14));
2872        assert_eq!(Value::from(true), Value::Bool(true));
2873        assert_eq!(Value::from("hello"), Value::Text("hello".to_string()));
2874        assert_eq!(Value::from("hello".to_string()), Value::Text("hello".to_string()));
2875        assert_eq!(Value::from('x'), Value::Char('x'));
2876    }
2877
2878    /// Differential fuzz: a `LogosDenseI64Map` built over a proven window
2879    /// `[lo, lo+cap)` must agree with `std::collections::HashMap` on every
2880    /// `get`/`contains`/`len` — for keys IN the window (the only keys the
2881    /// soundness gate ever lets reach this representation), including in-window
2882    /// keys that are never inserted (which must read `None`), overwrites, and a
2883    /// negative offset `lo`. The window is the contract; we never index outside it.
2884    #[test]
2885    fn dense_i64map_matches_hashmap_oracle() {
2886        use std::collections::HashMap;
2887        // (lo, cap) windows: 0-based, 1-based (the collect shape), and negative.
2888        for &(lo, cap) in &[(0i64, 64usize), (1, 100), (-40, 80), (-1, 9), (1000, 50)] {
2889            for seed in [1u64, 7, 1234567, 0x9E3779B9, u64::MAX / 3] {
2890                let mut rng = Lcg(seed);
2891                let mut map = LogosDenseI64Map::with_bounds(lo, cap);
2892                let mut oracle: HashMap<i64, i64> = HashMap::new();
2893                for _ in 0..40_000 {
2894                    // Draw a key strictly inside the window [lo, lo+cap).
2895                    let key = lo + (rng.next_u64() % cap as u64) as i64;
2896                    match rng.next_u64() % 3 {
2897                        0 => {
2898                            let val = rng.next_u64() as i64;
2899                            map.insert(key, val);
2900                            oracle.insert(key, val);
2901                        }
2902                        1 => assert_eq!(
2903                            map.get(&key),
2904                            oracle.get(&key).copied(),
2905                            "get disagreed for key {key} (lo {lo} cap {cap} seed {seed})"
2906                        ),
2907                        _ => assert_eq!(
2908                            map.contains_key(&key),
2909                            oracle.contains_key(&key),
2910                            "contains disagreed for key {key} (lo {lo} cap {cap} seed {seed})"
2911                        ),
2912                    }
2913                    assert_eq!(map.len(), oracle.len(), "len diverged (lo {lo} cap {cap} seed {seed})");
2914                }
2915                // Final sweep: every in-window key reads identically, present or absent.
2916                for k in lo..lo + cap as i64 {
2917                    assert_eq!(
2918                        map.get(&k),
2919                        oracle.get(&k).copied(),
2920                        "final get key {k} (lo {lo} cap {cap})"
2921                    );
2922                }
2923            }
2924        }
2925    }
2926
2927    /// A never-inserted but in-range key reads `None` (the presence bitset is
2928    /// what distinguishes "stored 0" from "absent"), and an offset `lo` rebases
2929    /// correctly with no aliasing between neighbouring keys.
2930    #[test]
2931    fn dense_i64map_absent_in_range_key_is_none() {
2932        let mut m = LogosDenseI64Map::with_bounds(-5, 20); // window [-5, 15)
2933        assert_eq!(m.get(&3), None);
2934        m.insert(-5, 100);
2935        m.insert(14, 200);
2936        m.insert(0, 0); // value 0 is a real stored value, NOT "absent"
2937        assert_eq!(m.get(&-5), Some(100));
2938        assert_eq!(m.get(&14), Some(200));
2939        assert_eq!(m.get(&0), Some(0), "stored 0 must read as present");
2940        assert_eq!(m.get(&3), None, "never-inserted in-range key stays absent");
2941        assert_eq!(m.get(&-1), None);
2942        assert_eq!(m.len(), 3);
2943        // Overwrite preserves len; neighbours untouched.
2944        m.insert(0, 42);
2945        assert_eq!(m.get(&0), Some(42));
2946        assert_eq!(m.get(&-5), Some(100));
2947        assert_eq!(m.len(), 3, "overwrite must not change len");
2948    }
2949
2950    /// The presence-elided `LogosDenseI64MapNoPresence` is selected ONLY when the
2951    /// compiler proves every queried key was inserted (contiguous full coverage),
2952    /// so its `get` is a pure `Some(data[k-lo])` load. Under that regime it must
2953    /// agree with a `HashMap` for every covered key — including a negative `lo`.
2954    #[test]
2955    fn dense_i64map_nopresence_full_coverage() {
2956        for &(lo, cap) in &[(0i64, 64usize), (1, 1000), (-40, 80)] {
2957            let mut m = LogosDenseI64MapNoPresence::with_bounds(lo, cap);
2958            // Fully cover the window (the proven precondition for this type).
2959            for k in lo..lo + cap as i64 {
2960                m.insert(k, k.wrapping_mul(3).wrapping_add(7));
2961            }
2962            for k in lo..lo + cap as i64 {
2963                assert_eq!(
2964                    m.get(&k),
2965                    Some(k.wrapping_mul(3).wrapping_add(7)),
2966                    "nopresence get key {k} (lo {lo} cap {cap})"
2967                );
2968            }
2969            assert_eq!(m.len(), cap, "nopresence len (lo {lo} cap {cap})");
2970        }
2971    }
2972
2973    /// Differential fuzz for the dense SET sibling against `std::HashSet`.
2974    #[test]
2975    fn dense_i64set_matches_hashset_oracle() {
2976        use std::collections::HashSet;
2977        for &(lo, cap) in &[(0i64, 64usize), (1, 100), (-40, 80), (5, 5)] {
2978            for seed in [1u64, 7, 1234567, 0x9E3779B9, u64::MAX / 3] {
2979                let mut rng = Lcg(seed);
2980                let mut set = LogosDenseI64Set::with_bounds(lo, cap);
2981                let mut oracle: HashSet<i64> = HashSet::new();
2982                for _ in 0..40_000 {
2983                    let key = lo + (rng.next_u64() % cap as u64) as i64;
2984                    if rng.next_u64() % 2 == 0 {
2985                        set.insert(key, 1);
2986                        oracle.insert(key);
2987                    } else {
2988                        assert_eq!(
2989                            set.contains_key(&key),
2990                            oracle.contains(&key),
2991                            "contains disagreed for key {key} (lo {lo} cap {cap} seed {seed})"
2992                        );
2993                    }
2994                    assert_eq!(set.len(), oracle.len(), "len diverged (lo {lo} cap {cap} seed {seed})");
2995                }
2996                for k in lo..lo + cap as i64 {
2997                    assert_eq!(set.contains_key(&k), oracle.contains(&k), "final contains key {k}");
2998                }
2999            }
3000        }
3001    }
3002
3003    /// The exact `collect` benchmark shape: a 1-based window `[1, n]` allocated to
3004    /// capacity `n`, insert `i -> i*2`, then look every key up. The bound is
3005    /// `lo = 1`, so key `n` maps to index `n-1 < n` — the off-by-one that a naive
3006    /// `lo = 0` would blow. Both dense flavours must report all `n` found.
3007    #[test]
3008    fn dense_i64map_collect_benchmark_shape() {
3009        let n: i64 = 5000;
3010        // Presence-tracking flavour.
3011        let mut m = LogosDenseI64Map::with_bounds(1, n as usize);
3012        for i in 1..=n {
3013            m.insert(i, i << 1);
3014        }
3015        let found = (1..=n).filter(|&i| m.get(&i) == Some(i << 1)).count();
3016        assert_eq!(found as i64, n, "presence flavour must find every key");
3017        // Presence-elided flavour (proven full coverage).
3018        let mut mp = LogosDenseI64MapNoPresence::with_bounds(1, n as usize);
3019        for i in 1..=n {
3020            mp.insert(i, i << 1);
3021        }
3022        let found_np = (1..=n).filter(|&i| mp.get(&i) == Some(i << 1)).count();
3023        assert_eq!(found_np as i64, n, "nopresence flavour must find every key");
3024    }
3025
3026    /// Differential fuzz: a `LogosI32Map` driven by keys AND values inside `i32`
3027    /// range (the only inputs the narrowing gate ever sends it) must agree with a
3028    /// `HashMap` on every `get`/`contains`/`len` — across resizes, overwrites, the
3029    /// `0` sentinel path (the real key `0`), and `i32::MIN`/`i32::MAX` as ordinary
3030    /// in-table keys.
3031    #[test]
3032    fn i32map_matches_hashmap_oracle() {
3033        use std::collections::HashMap;
3034        let pool: Vec<i64> = {
3035            let mut v: Vec<i64> = (-40..40).collect();
3036            v.extend_from_slice(&[
3037                i32::MIN as i64, i32::MAX as i64, (i32::MIN + 1) as i64, (i32::MAX - 1) as i64, 0,
3038            ]);
3039            v
3040        };
3041        for seed in [1u64, 7, 1234567, 0x9E3779B9, u64::MAX / 3] {
3042            let mut rng = Lcg(seed);
3043            let mut map = LogosI32Map::new();
3044            let mut oracle: HashMap<i64, i64> = HashMap::new();
3045            for _ in 0..50_000 {
3046                let key = pool[(rng.next_u64() as usize) % pool.len()];
3047                match rng.next_u64() % 3 {
3048                    0 => {
3049                        // A value inside i32 range (what the narrowing proof guarantees).
3050                        let val = (rng.next_u64() as i32) as i64;
3051                        map.insert(key, val);
3052                        oracle.insert(key, val);
3053                    }
3054                    1 => assert_eq!(
3055                        map.get(&key),
3056                        oracle.get(&key).copied(),
3057                        "get disagreed for key {key} (seed {seed})"
3058                    ),
3059                    _ => assert_eq!(
3060                        map.contains_key(&key),
3061                        oracle.contains_key(&key),
3062                        "contains disagreed for key {key} (seed {seed})"
3063                    ),
3064                }
3065                assert_eq!(map.len(), oracle.len(), "len diverged (seed {seed})");
3066            }
3067            for &k in &pool {
3068                assert_eq!(map.get(&k), oracle.get(&k).copied(), "final get key {k}");
3069            }
3070        }
3071    }
3072
3073    /// `LogosI32Set` agrees with `std::HashSet` under fuzz over `i32`-range keys.
3074    #[test]
3075    fn i32set_matches_hashset_oracle() {
3076        use std::collections::HashSet;
3077        let pool: Vec<i64> = {
3078            let mut v: Vec<i64> = (-40..40).collect();
3079            v.extend_from_slice(&[
3080                i32::MIN as i64, i32::MAX as i64, (i32::MIN + 1) as i64, (i32::MAX - 1) as i64, 0,
3081            ]);
3082            v
3083        };
3084        for seed in [1u64, 7, 1234567, 0x9E3779B9, u64::MAX / 3] {
3085            let mut rng = Lcg(seed);
3086            let mut set = LogosI32Set::new();
3087            let mut oracle: HashSet<i64> = HashSet::new();
3088            for _ in 0..50_000 {
3089                let key = pool[(rng.next_u64() as usize) % pool.len()];
3090                if rng.next_u64() % 2 == 0 {
3091                    set.insert(key, 1);
3092                    oracle.insert(key);
3093                } else {
3094                    assert_eq!(
3095                        set.contains_key(&key),
3096                        oracle.contains(&key),
3097                        "contains disagreed for key {key} (seed {seed})"
3098                    );
3099                }
3100                assert_eq!(set.len(), oracle.len(), "len diverged (seed {seed})");
3101            }
3102            for &k in &pool {
3103                assert_eq!(set.contains_key(&k), oracle.contains(&k), "final contains key {k}");
3104            }
3105        }
3106    }
3107}
3108
3109// ===========================================================================
3110// LogosInt — the EXACT compiled integer (overflow ruling v2, stage 2)
3111// ===========================================================================
3112
3113/// The exact integer for GENERATED code: `i64` on the fast path, spilling to a
3114/// heap [`logicaffeine_base::BigInt`] the moment a value escapes 64 bits — the
3115/// AOT's mirror of the interpreter's Int→BigInt promotion (and of the JIT's
3116/// overflow side-exit). Every operation NORMALIZES: a big result that fits
3117/// `i64` downsizes to `Small`, so representation never leaks into `==`/`Ord`.
3118#[derive(Clone, Debug)]
3119pub enum LogosInt {
3120    Small(i64),
3121    Big(Box<logicaffeine_base::BigInt>),
3122}
3123
3124impl LogosInt {
3125    #[inline]
3126    pub fn from_i64(x: i64) -> Self {
3127        LogosInt::Small(x)
3128    }
3129
3130    pub fn from_big(b: logicaffeine_base::BigInt) -> Self {
3131        match b.to_i64() {
3132            Some(x) => LogosInt::Small(x),
3133            None => LogosInt::Big(Box::new(b)),
3134        }
3135    }
3136
3137    /// Parse a (possibly > 64-bit) decimal literal the codegen emitted.
3138    pub fn from_literal(s: &str) -> Self {
3139        if let Ok(x) = s.parse::<i64>() {
3140            return LogosInt::Small(x);
3141        }
3142        let (neg, digits) = match s.strip_prefix('-') {
3143            Some(d) => (true, d),
3144            None => (false, s),
3145        };
3146        let ten = logicaffeine_base::BigInt::from_i64(10);
3147        let mut acc = logicaffeine_base::BigInt::from_i64(0);
3148        for ch in digits.bytes() {
3149            debug_assert!(ch.is_ascii_digit(), "LogosInt literal digit");
3150            acc = acc.mul(&ten).add(&logicaffeine_base::BigInt::from_i64((ch - b'0') as i64));
3151        }
3152        if neg {
3153            acc = acc.negated();
3154        }
3155        LogosInt::from_big(acc)
3156    }
3157
3158    fn to_bigint(&self) -> logicaffeine_base::BigInt {
3159        match self {
3160            LogosInt::Small(x) => logicaffeine_base::BigInt::from_i64(*x),
3161            LogosInt::Big(b) => (**b).clone(),
3162        }
3163    }
3164
3165    /// The value as `i64` when it fits.
3166    pub fn to_i64(&self) -> Option<i64> {
3167        match self {
3168            LogosInt::Small(x) => Some(*x),
3169            LogosInt::Big(b) => b.to_i64(),
3170        }
3171    }
3172
3173    /// The value as `i64`, or a LOUD canonical error — for sinks that are
3174    /// structurally 64-bit (indices, sizes, native call arguments).
3175    #[inline]
3176    pub fn expect_i64(&self, what: &str) -> i64 {
3177        match self.to_i64() {
3178            Some(x) => x,
3179            None => panic!("Integer overflow: {self} does not fit a 64-bit {what}"),
3180        }
3181    }
3182
3183    #[inline]
3184    pub fn add(&self, rhs: &LogosInt) -> LogosInt {
3185        if let (LogosInt::Small(a), LogosInt::Small(b)) = (self, rhs) {
3186            if let Some(s) = a.checked_add(*b) {
3187                return LogosInt::Small(s);
3188            }
3189        }
3190        LogosInt::from_big(self.to_bigint().add(&rhs.to_bigint()))
3191    }
3192
3193    #[inline]
3194    pub fn sub(&self, rhs: &LogosInt) -> LogosInt {
3195        if let (LogosInt::Small(a), LogosInt::Small(b)) = (self, rhs) {
3196            if let Some(s) = a.checked_sub(*b) {
3197                return LogosInt::Small(s);
3198            }
3199        }
3200        LogosInt::from_big(self.to_bigint().sub(&rhs.to_bigint()))
3201    }
3202
3203    #[inline]
3204    pub fn mul(&self, rhs: &LogosInt) -> LogosInt {
3205        if let (LogosInt::Small(a), LogosInt::Small(b)) = (self, rhs) {
3206            if let Some(p) = a.checked_mul(*b) {
3207                return LogosInt::Small(p);
3208            }
3209        }
3210        LogosInt::from_big(self.to_bigint().mul(&rhs.to_bigint()))
3211    }
3212
3213    /// Truncating division; `Err` on a zero divisor (the interp's catchable
3214    /// "Division by zero"). `i64::MIN / -1` promotes exactly.
3215    #[inline]
3216    pub fn div(&self, rhs: &LogosInt) -> Result<LogosInt, String> {
3217        if let (LogosInt::Small(a), LogosInt::Small(b)) = (self, rhs) {
3218            if *b == 0 {
3219                return Err("Division by zero".to_string());
3220            }
3221            if let Some(q) = a.checked_div(*b) {
3222                return Ok(LogosInt::Small(q));
3223            }
3224        }
3225        match self.to_bigint().div_rem(&rhs.to_bigint()) {
3226            Some((q, _)) => Ok(LogosInt::from_big(q)),
3227            None => Err("Division by zero".to_string()),
3228        }
3229    }
3230
3231    /// Floor division — the quotient rounded toward NEGATIVE INFINITY (`-7 // 2 → -4`),
3232    /// the semantics of the `//` operator. Distinct from [`div`](Self::div), which
3233    /// truncates toward zero (`-7 / 2 → -3`); the two agree when the operands share a
3234    /// sign. Exact (promotes to `BigInt`); a zero divisor is a loud error.
3235    pub fn div_floor(&self, rhs: &LogosInt) -> Result<LogosInt, String> {
3236        if let (LogosInt::Small(a), LogosInt::Small(b)) = (self, rhs) {
3237            if *b == 0 {
3238                return Err("Division by zero".to_string());
3239            }
3240            // `i64::MIN / -1` overflows the Small path → fall through to the exact BigInt
3241            // branch. Otherwise floor = truncate, minus one when a nonzero remainder means
3242            // the true quotient sits below the truncated one (operands of opposite sign).
3243            if let (Some(q), Some(r)) = (a.checked_div(*b), a.checked_rem(*b)) {
3244                let floored = if r != 0 && (r < 0) != (*b < 0) { q - 1 } else { q };
3245                return Ok(LogosInt::Small(floored));
3246            }
3247        }
3248        match self.to_bigint().div_rem(&rhs.to_bigint()) {
3249            Some((q, r)) => {
3250                let floored = if !r.is_zero() && r.is_negative() != rhs.to_bigint().is_negative() {
3251                    q.sub(&logicaffeine_base::BigInt::from_i64(1))
3252                } else {
3253                    q
3254                };
3255                Ok(LogosInt::from_big(floored))
3256            }
3257            None => Err("Division by zero".to_string()),
3258        }
3259    }
3260
3261    /// Truncating remainder (sign of the dividend); `i64::MIN % -1` is 0.
3262    #[inline]
3263    pub fn rem(&self, rhs: &LogosInt) -> Result<LogosInt, String> {
3264        if let (LogosInt::Small(a), LogosInt::Small(b)) = (self, rhs) {
3265            if *b == 0 {
3266                return Err("Modulo by zero".to_string());
3267            }
3268            if let Some(r) = a.checked_rem(*b) {
3269                return Ok(LogosInt::Small(r));
3270            }
3271            return Ok(LogosInt::Small(0)); // MIN % -1
3272        }
3273        match self.to_bigint().div_rem(&rhs.to_bigint()) {
3274            Some((_, r)) => Ok(LogosInt::from_big(r)),
3275            None => Err("Modulo by zero".to_string()),
3276        }
3277    }
3278
3279    /// Exponentiation with an integer exponent. `Err` on a negative exponent
3280    /// (an Int can't hold the fractional result) or one too large to be a
3281    /// `u32` power; overflow of the i64 fast path promotes to `BigInt`
3282    /// exactly. Mirrors the interpreter's `int_power`.
3283    pub fn pow(&self, exp: &LogosInt) -> Result<LogosInt, String> {
3284        let e = match exp.to_i64() {
3285            Some(e) if e < 0 => {
3286                return Err("negative exponent on an integer (an Int can't hold a fraction — use a Float base)".to_string());
3287            }
3288            Some(e) => e,
3289            None => return Err("exponent too large".to_string()),
3290        };
3291        let e = u32::try_from(e).map_err(|_| "exponent too large".to_string())?;
3292        if let LogosInt::Small(b) = self {
3293            if let Some(r) = b.checked_pow(e) {
3294                return Ok(LogosInt::Small(r));
3295            }
3296        }
3297        Ok(LogosInt::from_big(self.to_bigint().pow(e)))
3298    }
3299
3300    pub fn neg(&self) -> LogosInt {
3301        match self {
3302            LogosInt::Small(x) => match x.checked_neg() {
3303                Some(n) => LogosInt::Small(n),
3304                None => LogosInt::from_big(self.to_bigint().negated()),
3305            },
3306            LogosInt::Big(b) => LogosInt::from_big(b.negated()),
3307        }
3308    }
3309}
3310
3311impl From<i64> for LogosInt {
3312    #[inline]
3313    fn from(x: i64) -> Self {
3314        LogosInt::Small(x)
3315    }
3316}
3317
3318impl PartialEq for LogosInt {
3319    fn eq(&self, other: &Self) -> bool {
3320        match (self, other) {
3321            // Normalization invariant: a Big never holds an i64-fitting value.
3322            (LogosInt::Small(a), LogosInt::Small(b)) => a == b,
3323            (LogosInt::Big(a), LogosInt::Big(b)) => **a == **b,
3324            _ => false,
3325        }
3326    }
3327}
3328impl Eq for LogosInt {}
3329
3330impl PartialEq<i64> for LogosInt {
3331    fn eq(&self, other: &i64) -> bool {
3332        matches!(self, LogosInt::Small(a) if a == other)
3333    }
3334}
3335impl PartialEq<LogosInt> for i64 {
3336    fn eq(&self, other: &LogosInt) -> bool {
3337        other == self
3338    }
3339}
3340
3341impl Ord for LogosInt {
3342    fn cmp(&self, other: &Self) -> core::cmp::Ordering {
3343        match (self, other) {
3344            (LogosInt::Small(a), LogosInt::Small(b)) => a.cmp(b),
3345            _ => self.to_bigint().cmp(&other.to_bigint()),
3346        }
3347    }
3348}
3349impl PartialOrd for LogosInt {
3350    fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
3351        Some(self.cmp(other))
3352    }
3353}
3354impl PartialOrd<i64> for LogosInt {
3355    fn partial_cmp(&self, other: &i64) -> Option<core::cmp::Ordering> {
3356        self.partial_cmp(&LogosInt::Small(*other))
3357    }
3358}
3359impl PartialOrd<LogosInt> for i64 {
3360    fn partial_cmp(&self, other: &LogosInt) -> Option<core::cmp::Ordering> {
3361        LogosInt::Small(*self).partial_cmp(other)
3362    }
3363}
3364
3365impl core::hash::Hash for LogosInt {
3366    fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
3367        // The unified numeric hash (mod 2^61−1) — coherent with equal i64s.
3368        match self {
3369            LogosInt::Small(x) => logicaffeine_base::numeric::numeric_hash_i64(*x).hash(state),
3370            LogosInt::Big(b) => logicaffeine_base::numeric::numeric_hash_bigint(b).hash(state),
3371        }
3372    }
3373}
3374
3375impl core::fmt::Display for LogosInt {
3376    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
3377        match self {
3378            LogosInt::Small(x) => write!(f, "{x}"),
3379            LogosInt::Big(b) => write!(f, "{b}"),
3380        }
3381    }
3382}
3383
3384#[cfg(test)]
3385mod logos_int_spec {
3386    use super::LogosInt;
3387
3388    #[test]
3389    fn overflow_promotes_and_downsizes_exactly() {
3390        let max = LogosInt::from_i64(i64::MAX);
3391        let one = LogosInt::from_i64(1);
3392        let big = max.add(&one);
3393        assert_eq!(big.to_string(), "9223372036854775808");
3394        assert!(matches!(big, LogosInt::Big(_)));
3395        // Coming back into range NORMALIZES to Small.
3396        let back = big.sub(&one);
3397        assert!(matches!(back, LogosInt::Small(_)));
3398        assert_eq!(back, i64::MAX);
3399    }
3400
3401    #[test]
3402    fn min_div_neg_one_is_exact() {
3403        let min = LogosInt::from_i64(i64::MIN);
3404        let neg1 = LogosInt::from_i64(-1);
3405        let q = min.div(&neg1).unwrap();
3406        assert_eq!(q.to_string(), "9223372036854775808");
3407        assert_eq!(min.rem(&neg1).unwrap(), 0i64);
3408        assert_eq!(min.neg().to_string(), "9223372036854775808");
3409    }
3410
3411    #[test]
3412    fn mul_overflow_matches_exact_value() {
3413        let max = LogosInt::from_i64(i64::MAX);
3414        let two = LogosInt::from_i64(2);
3415        assert_eq!(max.mul(&two).to_string(), "18446744073709551614");
3416    }
3417
3418    #[test]
3419    fn division_errors_are_canonical() {
3420        let one = LogosInt::from_i64(1);
3421        let zero = LogosInt::from_i64(0);
3422        assert_eq!(one.div(&zero).unwrap_err(), "Division by zero");
3423        assert_eq!(one.rem(&zero).unwrap_err(), "Modulo by zero");
3424    }
3425
3426    #[test]
3427    fn literal_roundtrip_beyond_64_bits() {
3428        let v = LogosInt::from_literal("18446744073709551614");
3429        assert_eq!(v.to_string(), "18446744073709551614");
3430        assert_eq!(LogosInt::from_literal("-42"), LogosInt::from_i64(-42));
3431    }
3432
3433    #[test]
3434    fn ordering_and_eq_cross_representation() {
3435        let max = LogosInt::from_i64(i64::MAX);
3436        let big = max.add(&LogosInt::from_i64(1));
3437        assert!(max < big);
3438        assert!(big > LogosInt::from_i64(0));
3439        assert!(LogosInt::from_i64(5) == 5i64);
3440        assert_ne!(big, LogosInt::from_i64(0));
3441    }
3442}