Skip to main content

oxilean_kernel/ffi/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use crate::{Expr, Name};
6use std::collections::HashMap;
7
8/// A window iterator that yields overlapping windows of size `n`.
9#[allow(dead_code)]
10pub struct WindowIterator<'a, T> {
11    pub(super) data: &'a [T],
12    pub(super) pos: usize,
13    pub(super) window: usize,
14}
15#[allow(dead_code)]
16impl<'a, T> WindowIterator<'a, T> {
17    /// Creates a new window iterator.
18    pub fn new(data: &'a [T], window: usize) -> Self {
19        Self {
20            data,
21            pos: 0,
22            window,
23        }
24    }
25}
26/// A min-heap implemented as a binary heap.
27#[allow(dead_code)]
28pub struct MinHeap<T: Ord> {
29    data: Vec<T>,
30}
31#[allow(dead_code)]
32impl<T: Ord> MinHeap<T> {
33    /// Creates a new empty min-heap.
34    pub fn new() -> Self {
35        Self { data: Vec::new() }
36    }
37    /// Inserts an element.
38    pub fn push(&mut self, val: T) {
39        self.data.push(val);
40        self.sift_up(self.data.len() - 1);
41    }
42    /// Removes and returns the minimum element.
43    pub fn pop(&mut self) -> Option<T> {
44        if self.data.is_empty() {
45            return None;
46        }
47        let n = self.data.len();
48        self.data.swap(0, n - 1);
49        let min = self.data.pop();
50        if !self.data.is_empty() {
51            self.sift_down(0);
52        }
53        min
54    }
55    /// Returns a reference to the minimum element.
56    pub fn peek(&self) -> Option<&T> {
57        self.data.first()
58    }
59    /// Returns the number of elements.
60    pub fn len(&self) -> usize {
61        self.data.len()
62    }
63    /// Returns `true` if empty.
64    pub fn is_empty(&self) -> bool {
65        self.data.is_empty()
66    }
67    fn sift_up(&mut self, mut i: usize) {
68        while i > 0 {
69            let parent = (i - 1) / 2;
70            if self.data[i] < self.data[parent] {
71                self.data.swap(i, parent);
72                i = parent;
73            } else {
74                break;
75            }
76        }
77    }
78    fn sift_down(&mut self, mut i: usize) {
79        let n = self.data.len();
80        loop {
81            let left = 2 * i + 1;
82            let right = 2 * i + 2;
83            let mut smallest = i;
84            if left < n && self.data[left] < self.data[smallest] {
85                smallest = left;
86            }
87            if right < n && self.data[right] < self.data[smallest] {
88                smallest = right;
89            }
90            if smallest == i {
91                break;
92            }
93            self.data.swap(i, smallest);
94            i = smallest;
95        }
96    }
97}
98/// FFI safety level for external functions.
99#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
100pub enum FfiSafety {
101    /// Safe to call from safe Rust code.
102    Safe,
103    /// Must be called from unsafe blocks.
104    Unsafe,
105    /// System call (platform-specific behavior).
106    System,
107}
108/// A counter that can measure elapsed time between snapshots.
109#[allow(dead_code)]
110pub struct Stopwatch {
111    start: crate::wall_clock::Instant,
112    splits: Vec<f64>,
113}
114#[allow(dead_code)]
115impl Stopwatch {
116    /// Creates and starts a new stopwatch.
117    pub fn start() -> Self {
118        Self {
119            start: crate::wall_clock::Instant::now(),
120            splits: Vec::new(),
121        }
122    }
123    /// Records a split time (elapsed since start).
124    pub fn split(&mut self) {
125        self.splits.push(self.elapsed_ms());
126    }
127    /// Returns total elapsed milliseconds since start.
128    pub fn elapsed_ms(&self) -> f64 {
129        self.start.elapsed().as_secs_f64() * 1000.0
130    }
131    /// Returns all recorded split times.
132    pub fn splits(&self) -> &[f64] {
133        &self.splits
134    }
135    /// Returns the number of splits.
136    pub fn num_splits(&self) -> usize {
137        self.splits.len()
138    }
139}
140/// A hierarchical configuration tree.
141#[allow(dead_code)]
142pub struct ConfigNode {
143    key: String,
144    value: Option<String>,
145    children: Vec<ConfigNode>,
146}
147#[allow(dead_code)]
148impl ConfigNode {
149    /// Creates a leaf config node with a value.
150    pub fn leaf(key: impl Into<String>, value: impl Into<String>) -> Self {
151        Self {
152            key: key.into(),
153            value: Some(value.into()),
154            children: Vec::new(),
155        }
156    }
157    /// Creates a section node with children.
158    pub fn section(key: impl Into<String>) -> Self {
159        Self {
160            key: key.into(),
161            value: None,
162            children: Vec::new(),
163        }
164    }
165    /// Adds a child node.
166    pub fn add_child(&mut self, child: ConfigNode) {
167        self.children.push(child);
168    }
169    /// Returns the key.
170    pub fn key(&self) -> &str {
171        &self.key
172    }
173    /// Returns the value, or `None` for section nodes.
174    pub fn value(&self) -> Option<&str> {
175        self.value.as_deref()
176    }
177    /// Returns the number of children.
178    pub fn num_children(&self) -> usize {
179        self.children.len()
180    }
181    /// Looks up a dot-separated path.
182    pub fn lookup(&self, path: &str) -> Option<&str> {
183        let mut parts = path.splitn(2, '.');
184        let head = parts.next()?;
185        let tail = parts.next();
186        if head != self.key {
187            return None;
188        }
189        match tail {
190            None => self.value.as_deref(),
191            Some(rest) => self.children.iter().find_map(|c| c.lookup_relative(rest)),
192        }
193    }
194    fn lookup_relative(&self, path: &str) -> Option<&str> {
195        let mut parts = path.splitn(2, '.');
196        let head = parts.next()?;
197        let tail = parts.next();
198        if head != self.key {
199            return None;
200        }
201        match tail {
202            None => self.value.as_deref(),
203            Some(rest) => self.children.iter().find_map(|c| c.lookup_relative(rest)),
204        }
205    }
206}
207/// A sparse vector: stores only non-default elements.
208#[allow(dead_code)]
209pub struct SparseVec<T: Default + Clone + PartialEq> {
210    entries: std::collections::HashMap<usize, T>,
211    default_: T,
212    logical_len: usize,
213}
214#[allow(dead_code)]
215impl<T: Default + Clone + PartialEq> SparseVec<T> {
216    /// Creates a new sparse vector with logical length `len`.
217    pub fn new(len: usize) -> Self {
218        Self {
219            entries: std::collections::HashMap::new(),
220            default_: T::default(),
221            logical_len: len,
222        }
223    }
224    /// Sets element at `idx`.
225    pub fn set(&mut self, idx: usize, val: T) {
226        if val == self.default_ {
227            self.entries.remove(&idx);
228        } else {
229            self.entries.insert(idx, val);
230        }
231    }
232    /// Gets element at `idx`.
233    pub fn get(&self, idx: usize) -> &T {
234        self.entries.get(&idx).unwrap_or(&self.default_)
235    }
236    /// Returns the logical length.
237    pub fn len(&self) -> usize {
238        self.logical_len
239    }
240    /// Returns whether the collection is empty.
241    pub fn is_empty(&self) -> bool {
242        self.len() == 0
243    }
244    /// Returns the number of non-default elements.
245    pub fn nnz(&self) -> usize {
246        self.entries.len()
247    }
248}
249/// A simple mutable key-value store for test fixtures.
250#[allow(dead_code)]
251pub struct Fixture {
252    data: std::collections::HashMap<String, String>,
253}
254#[allow(dead_code)]
255impl Fixture {
256    /// Creates an empty fixture.
257    pub fn new() -> Self {
258        Self {
259            data: std::collections::HashMap::new(),
260        }
261    }
262    /// Sets a key.
263    pub fn set(&mut self, key: impl Into<String>, val: impl Into<String>) {
264        self.data.insert(key.into(), val.into());
265    }
266    /// Gets a value.
267    pub fn get(&self, key: &str) -> Option<&str> {
268        self.data.get(key).map(|s| s.as_str())
269    }
270    /// Returns the number of entries.
271    pub fn len(&self) -> usize {
272        self.data.len()
273    }
274    /// Returns whether the collection is empty.
275    pub fn is_empty(&self) -> bool {
276        self.len() == 0
277    }
278}
279/// A write-once cell.
280#[allow(dead_code)]
281pub struct WriteOnce<T> {
282    value: std::cell::Cell<Option<T>>,
283}
284#[allow(dead_code)]
285impl<T: Copy> WriteOnce<T> {
286    /// Creates an empty write-once cell.
287    pub fn new() -> Self {
288        Self {
289            value: std::cell::Cell::new(None),
290        }
291    }
292    /// Writes a value.  Returns `false` if already written.
293    pub fn write(&self, val: T) -> bool {
294        if self.value.get().is_some() {
295            return false;
296        }
297        self.value.set(Some(val));
298        true
299    }
300    /// Returns the value if written.
301    pub fn read(&self) -> Option<T> {
302        self.value.get()
303    }
304    /// Returns `true` if the value has been written.
305    pub fn is_written(&self) -> bool {
306        self.value.get().is_some()
307    }
308}
309/// A reusable scratch buffer for path computations.
310#[allow(dead_code)]
311pub struct PathBuf {
312    components: Vec<String>,
313}
314#[allow(dead_code)]
315impl PathBuf {
316    /// Creates a new empty path buffer.
317    pub fn new() -> Self {
318        Self {
319            components: Vec::new(),
320        }
321    }
322    /// Pushes a component.
323    pub fn push(&mut self, comp: impl Into<String>) {
324        self.components.push(comp.into());
325    }
326    /// Pops the last component.
327    pub fn pop(&mut self) {
328        self.components.pop();
329    }
330    /// Returns the current path as a `/`-separated string.
331    pub fn as_str(&self) -> String {
332        self.components.join("/")
333    }
334    /// Returns the depth of the path.
335    pub fn depth(&self) -> usize {
336        self.components.len()
337    }
338    /// Clears the path.
339    pub fn clear(&mut self) {
340        self.components.clear();
341    }
342}
343/// Represents a rewrite rule `lhs → rhs`.
344#[allow(dead_code)]
345#[allow(missing_docs)]
346pub struct RewriteRule {
347    /// The name of the rule.
348    pub name: String,
349    /// A string representation of the LHS pattern.
350    pub lhs: String,
351    /// A string representation of the RHS.
352    pub rhs: String,
353    /// Whether this is a conditional rule (has side conditions).
354    pub conditional: bool,
355}
356#[allow(dead_code)]
357impl RewriteRule {
358    /// Creates an unconditional rewrite rule.
359    pub fn unconditional(
360        name: impl Into<String>,
361        lhs: impl Into<String>,
362        rhs: impl Into<String>,
363    ) -> Self {
364        Self {
365            name: name.into(),
366            lhs: lhs.into(),
367            rhs: rhs.into(),
368            conditional: false,
369        }
370    }
371    /// Creates a conditional rewrite rule.
372    pub fn conditional(
373        name: impl Into<String>,
374        lhs: impl Into<String>,
375        rhs: impl Into<String>,
376    ) -> Self {
377        Self {
378            name: name.into(),
379            lhs: lhs.into(),
380            rhs: rhs.into(),
381            conditional: true,
382        }
383    }
384    /// Returns a textual representation.
385    pub fn display(&self) -> String {
386        format!("{}: {} → {}", self.name, self.lhs, self.rhs)
387    }
388}
389/// A simple decision tree node for rule dispatching.
390#[allow(dead_code)]
391#[allow(missing_docs)]
392pub enum DecisionNode {
393    /// A leaf with an action string.
394    Leaf(String),
395    /// An interior node: check `key` equals `val` → `yes_branch`, else `no_branch`.
396    Branch {
397        key: String,
398        val: String,
399        yes_branch: Box<DecisionNode>,
400        no_branch: Box<DecisionNode>,
401    },
402}
403#[allow(dead_code)]
404impl DecisionNode {
405    /// Evaluates the decision tree with the given context.
406    pub fn evaluate(&self, ctx: &std::collections::HashMap<String, String>) -> &str {
407        match self {
408            DecisionNode::Leaf(action) => action.as_str(),
409            DecisionNode::Branch {
410                key,
411                val,
412                yes_branch,
413                no_branch,
414            } => {
415                let actual = ctx.get(key).map(|s| s.as_str()).unwrap_or("");
416                if actual == val.as_str() {
417                    yes_branch.evaluate(ctx)
418                } else {
419                    no_branch.evaluate(ctx)
420                }
421            }
422        }
423    }
424    /// Returns the depth of the decision tree.
425    pub fn depth(&self) -> usize {
426        match self {
427            DecisionNode::Leaf(_) => 0,
428            DecisionNode::Branch {
429                yes_branch,
430                no_branch,
431                ..
432            } => 1 + yes_branch.depth().max(no_branch.depth()),
433        }
434    }
435}
436/// A fixed-size sliding window that computes a running sum.
437#[allow(dead_code)]
438pub struct SlidingSum {
439    window: Vec<f64>,
440    capacity: usize,
441    pos: usize,
442    sum: f64,
443    count: usize,
444}
445#[allow(dead_code)]
446impl SlidingSum {
447    /// Creates a sliding sum with the given window size.
448    pub fn new(capacity: usize) -> Self {
449        Self {
450            window: vec![0.0; capacity],
451            capacity,
452            pos: 0,
453            sum: 0.0,
454            count: 0,
455        }
456    }
457    /// Adds a value to the window, removing the oldest if full.
458    pub fn push(&mut self, val: f64) {
459        let oldest = self.window[self.pos];
460        self.sum -= oldest;
461        self.sum += val;
462        self.window[self.pos] = val;
463        self.pos = (self.pos + 1) % self.capacity;
464        if self.count < self.capacity {
465            self.count += 1;
466        }
467    }
468    /// Returns the current window sum.
469    pub fn sum(&self) -> f64 {
470        self.sum
471    }
472    /// Returns the window mean, or `None` if empty.
473    pub fn mean(&self) -> Option<f64> {
474        if self.count == 0 {
475            None
476        } else {
477            Some(self.sum / self.count as f64)
478        }
479    }
480    /// Returns the current window size (number of valid elements).
481    pub fn count(&self) -> usize {
482        self.count
483    }
484}
485/// FFI values at runtime.
486#[derive(Clone, Debug, PartialEq)]
487pub enum FfiValue {
488    /// Boolean value.
489    Bool(bool),
490    /// Unsigned integer (up to 64-bit).
491    UInt(u64),
492    /// Signed integer (up to 64-bit).
493    Int(i64),
494    /// Floating point number.
495    Float(f64),
496    /// String value.
497    Str(String),
498    /// Byte array.
499    Bytes(Vec<u8>),
500    /// Unit value.
501    Unit,
502}
503impl FfiValue {
504    /// Try to convert an OxiLean expression to an FFI value.
505    pub fn try_from_expr(expr: &Expr, ty: &FfiType) -> Result<Self, FfiError> {
506        match (expr, ty) {
507            (Expr::Lit(crate::Literal::Nat(n)), FfiType::UInt64) => match n.to_u64() {
508                Some(v) => Ok(FfiValue::UInt(v)),
509                None => Err(FfiError::ValueOutOfRange(format!(
510                    "u64 range exceeded: {}",
511                    n
512                ))),
513            },
514            (Expr::Lit(crate::Literal::Nat(n)), FfiType::UInt32) => {
515                match n.to_u64().filter(|v| *v <= u32::MAX as u64) {
516                    Some(v) => Ok(FfiValue::UInt(v)),
517                    None => Err(FfiError::ValueOutOfRange(format!(
518                        "u32 range exceeded: {}",
519                        n
520                    ))),
521                }
522            }
523            (Expr::Lit(crate::Literal::Nat(n)), FfiType::Int64) => {
524                match n.to_u64().filter(|v| *v <= i64::MAX as u64) {
525                    Some(v) => Ok(FfiValue::Int(v as i64)),
526                    None => Err(FfiError::ValueOutOfRange(format!(
527                        "i64 range exceeded: {}",
528                        n
529                    ))),
530                }
531            }
532            (Expr::Lit(crate::Literal::Str(s)), FfiType::String) => Ok(FfiValue::Str(s.clone())),
533            _ => Err(FfiError::TypeMismatch(format!(
534                "Cannot convert {:?} to {:?}",
535                expr, ty
536            ))),
537        }
538    }
539    /// Convert an FFI value to an OxiLean expression.
540    pub fn to_expr(&self) -> Expr {
541        match self {
542            FfiValue::Bool(b) => Expr::Const(
543                if *b {
544                    Name::str("True")
545                } else {
546                    Name::str("False")
547                },
548                vec![],
549            ),
550            FfiValue::UInt(n) => Expr::Lit(crate::Literal::nat(*n)),
551            FfiValue::Int(n) => {
552                if *n >= 0 {
553                    Expr::Lit(crate::Literal::nat(*n as u64))
554                } else {
555                    Expr::Const(Name::str("Int.neg"), vec![])
556                }
557            }
558            FfiValue::Float(f) => Expr::Lit(crate::Literal::Str(f.to_string())),
559            FfiValue::Str(s) => Expr::Lit(crate::Literal::Str(s.clone())),
560            FfiValue::Bytes(bs) => Expr::Lit(crate::Literal::Str(
561                bs.iter().map(|b| format!("{:02x}", b)).collect::<String>(),
562            )),
563            FfiValue::Unit => Expr::Sort(crate::Level::zero()),
564        }
565    }
566}
567/// A generic counter that tracks min/max/sum for statistical summaries.
568#[allow(dead_code)]
569pub struct StatSummary {
570    count: u64,
571    sum: f64,
572    min: f64,
573    max: f64,
574}
575#[allow(dead_code)]
576impl StatSummary {
577    /// Creates an empty summary.
578    pub fn new() -> Self {
579        Self {
580            count: 0,
581            sum: 0.0,
582            min: f64::INFINITY,
583            max: f64::NEG_INFINITY,
584        }
585    }
586    /// Records a sample.
587    pub fn record(&mut self, val: f64) {
588        self.count += 1;
589        self.sum += val;
590        if val < self.min {
591            self.min = val;
592        }
593        if val > self.max {
594            self.max = val;
595        }
596    }
597    /// Returns the mean, or `None` if no samples.
598    pub fn mean(&self) -> Option<f64> {
599        if self.count == 0 {
600            None
601        } else {
602            Some(self.sum / self.count as f64)
603        }
604    }
605    /// Returns the minimum, or `None` if no samples.
606    pub fn min(&self) -> Option<f64> {
607        if self.count == 0 {
608            None
609        } else {
610            Some(self.min)
611        }
612    }
613    /// Returns the maximum, or `None` if no samples.
614    pub fn max(&self) -> Option<f64> {
615        if self.count == 0 {
616            None
617        } else {
618            Some(self.max)
619        }
620    }
621    /// Returns the count of recorded samples.
622    pub fn count(&self) -> u64 {
623        self.count
624    }
625}
626/// FFI-compatible types.
627///
628/// Represents types that can be marshalled across FFI boundaries.
629#[derive(Clone, PartialEq, Eq, Hash, Debug)]
630pub enum FfiType {
631    /// Boolean type (maps to C bool or Rust bool).
632    Bool,
633    /// 8-bit unsigned integer.
634    UInt8,
635    /// 16-bit unsigned integer.
636    UInt16,
637    /// 32-bit unsigned integer.
638    UInt32,
639    /// 64-bit unsigned integer.
640    UInt64,
641    /// 8-bit signed integer.
642    Int8,
643    /// 16-bit signed integer.
644    Int16,
645    /// 32-bit signed integer.
646    Int32,
647    /// 64-bit signed integer.
648    Int64,
649    /// 32-bit floating point.
650    Float32,
651    /// 64-bit floating point.
652    Float64,
653    /// String (null-terminated in C).
654    String,
655    /// Byte array.
656    ByteArray,
657    /// Unit type (void).
658    Unit,
659    /// Pointer to another type.
660    Ptr(Box<FfiType>),
661    /// Function pointer.
662    Fn(Vec<FfiType>, Box<FfiType>),
663    /// OxiLean-specific opaque type.
664    OxiLean(String),
665}
666impl FfiType {
667    /// Check if this type can be safely passed across FFI boundaries.
668    pub fn is_ffi_safe(&self) -> bool {
669        match self {
670            FfiType::Bool
671            | FfiType::UInt8
672            | FfiType::UInt16
673            | FfiType::UInt32
674            | FfiType::UInt64
675            | FfiType::Int8
676            | FfiType::Int16
677            | FfiType::Int32
678            | FfiType::Int64
679            | FfiType::Float32
680            | FfiType::Float64
681            | FfiType::String
682            | FfiType::ByteArray
683            | FfiType::Unit => true,
684            FfiType::Ptr(inner) => inner.is_ffi_safe(),
685            FfiType::Fn(params, ret) => params.iter().all(|t| t.is_ffi_safe()) && ret.is_ffi_safe(),
686            FfiType::OxiLean(_) => false,
687        }
688    }
689    /// Get the size in bytes of this type (if known).
690    pub fn size_bytes(&self) -> Option<usize> {
691        match self {
692            FfiType::Bool => Some(1),
693            FfiType::UInt8 | FfiType::Int8 => Some(1),
694            FfiType::UInt16 | FfiType::Int16 => Some(2),
695            FfiType::UInt32 | FfiType::Int32 => Some(4),
696            FfiType::UInt64 | FfiType::Int64 => Some(8),
697            FfiType::Float32 => Some(4),
698            FfiType::Float64 => Some(8),
699            FfiType::Unit => Some(0),
700            FfiType::Ptr(_) => Some(std::mem::size_of::<*const ()>()),
701            FfiType::String | FfiType::ByteArray => None,
702            FfiType::Fn(_, _) => Some(std::mem::size_of::<*const ()>()),
703            FfiType::OxiLean(_) => None,
704        }
705    }
706}
707/// A pair of `StatSummary` values tracking before/after a transformation.
708#[allow(dead_code)]
709pub struct TransformStat {
710    before: StatSummary,
711    after: StatSummary,
712}
713#[allow(dead_code)]
714impl TransformStat {
715    /// Creates a new transform stat recorder.
716    pub fn new() -> Self {
717        Self {
718            before: StatSummary::new(),
719            after: StatSummary::new(),
720        }
721    }
722    /// Records a before value.
723    pub fn record_before(&mut self, v: f64) {
724        self.before.record(v);
725    }
726    /// Records an after value.
727    pub fn record_after(&mut self, v: f64) {
728        self.after.record(v);
729    }
730    /// Returns the mean reduction ratio (after/before).
731    pub fn mean_ratio(&self) -> Option<f64> {
732        let b = self.before.mean()?;
733        let a = self.after.mean()?;
734        if b.abs() < f64::EPSILON {
735            return None;
736        }
737        Some(a / b)
738    }
739}
740/// A pool of reusable string buffers.
741#[allow(dead_code)]
742pub struct StringPool {
743    free: Vec<String>,
744}
745#[allow(dead_code)]
746impl StringPool {
747    /// Creates a new empty string pool.
748    pub fn new() -> Self {
749        Self { free: Vec::new() }
750    }
751    /// Takes a string from the pool (may be empty).
752    pub fn take(&mut self) -> String {
753        self.free.pop().unwrap_or_default()
754    }
755    /// Returns a string to the pool.
756    pub fn give(&mut self, mut s: String) {
757        s.clear();
758        self.free.push(s);
759    }
760    /// Returns the number of free strings in the pool.
761    pub fn free_count(&self) -> usize {
762        self.free.len()
763    }
764}
765/// A simple key-value store backed by a sorted Vec for small maps.
766#[allow(dead_code)]
767pub struct SmallMap<K: Ord + Clone, V: Clone> {
768    entries: Vec<(K, V)>,
769}
770#[allow(dead_code)]
771impl<K: Ord + Clone, V: Clone> SmallMap<K, V> {
772    /// Creates a new empty small map.
773    pub fn new() -> Self {
774        Self {
775            entries: Vec::new(),
776        }
777    }
778    /// Inserts or replaces the value for `key`.
779    pub fn insert(&mut self, key: K, val: V) {
780        match self.entries.binary_search_by_key(&&key, |(k, _)| k) {
781            Ok(i) => self.entries[i].1 = val,
782            Err(i) => self.entries.insert(i, (key, val)),
783        }
784    }
785    /// Returns the value for `key`, or `None`.
786    pub fn get(&self, key: &K) -> Option<&V> {
787        self.entries
788            .binary_search_by_key(&key, |(k, _)| k)
789            .ok()
790            .map(|i| &self.entries[i].1)
791    }
792    /// Returns the number of entries.
793    pub fn len(&self) -> usize {
794        self.entries.len()
795    }
796    /// Returns `true` if empty.
797    pub fn is_empty(&self) -> bool {
798        self.entries.is_empty()
799    }
800    /// Returns all keys.
801    pub fn keys(&self) -> Vec<&K> {
802        self.entries.iter().map(|(k, _)| k).collect()
803    }
804    /// Returns all values.
805    pub fn values(&self) -> Vec<&V> {
806        self.entries.iter().map(|(_, v)| v).collect()
807    }
808}
809/// A tagged union for representing a simple two-case discriminated union.
810#[allow(dead_code)]
811pub enum Either2<A, B> {
812    /// The first alternative.
813    First(A),
814    /// The second alternative.
815    Second(B),
816}
817#[allow(dead_code)]
818impl<A, B> Either2<A, B> {
819    /// Returns `true` if this is the first alternative.
820    pub fn is_first(&self) -> bool {
821        matches!(self, Either2::First(_))
822    }
823    /// Returns `true` if this is the second alternative.
824    pub fn is_second(&self) -> bool {
825        matches!(self, Either2::Second(_))
826    }
827    /// Returns the first value if present.
828    pub fn first(self) -> Option<A> {
829        match self {
830            Either2::First(a) => Some(a),
831            _ => None,
832        }
833    }
834    /// Returns the second value if present.
835    pub fn second(self) -> Option<B> {
836        match self {
837            Either2::Second(b) => Some(b),
838            _ => None,
839        }
840    }
841    /// Maps over the first alternative.
842    pub fn map_first<C, F: FnOnce(A) -> C>(self, f: F) -> Either2<C, B> {
843        match self {
844            Either2::First(a) => Either2::First(f(a)),
845            Either2::Second(b) => Either2::Second(b),
846        }
847    }
848}
849/// A simple directed acyclic graph.
850#[allow(dead_code)]
851pub struct SimpleDag {
852    /// `edges[i]` is the list of direct successors of node `i`.
853    edges: Vec<Vec<usize>>,
854}
855#[allow(dead_code)]
856impl SimpleDag {
857    /// Creates a DAG with `n` nodes and no edges.
858    pub fn new(n: usize) -> Self {
859        Self {
860            edges: vec![Vec::new(); n],
861        }
862    }
863    /// Adds an edge from `from` to `to`.
864    pub fn add_edge(&mut self, from: usize, to: usize) {
865        if from < self.edges.len() {
866            self.edges[from].push(to);
867        }
868    }
869    /// Returns the successors of `node`.
870    pub fn successors(&self, node: usize) -> &[usize] {
871        self.edges.get(node).map(|v| v.as_slice()).unwrap_or(&[])
872    }
873    /// Returns `true` if `from` can reach `to` via DFS.
874    pub fn can_reach(&self, from: usize, to: usize) -> bool {
875        let mut visited = vec![false; self.edges.len()];
876        self.dfs(from, to, &mut visited)
877    }
878    fn dfs(&self, cur: usize, target: usize, visited: &mut Vec<bool>) -> bool {
879        if cur == target {
880            return true;
881        }
882        if cur >= visited.len() || visited[cur] {
883            return false;
884        }
885        visited[cur] = true;
886        for &next in self.successors(cur) {
887            if self.dfs(next, target, visited) {
888                return true;
889            }
890        }
891        false
892    }
893    /// Returns the topological order of nodes, or `None` if a cycle is detected.
894    pub fn topological_sort(&self) -> Option<Vec<usize>> {
895        let n = self.edges.len();
896        let mut in_degree = vec![0usize; n];
897        for succs in &self.edges {
898            for &s in succs {
899                if s < n {
900                    in_degree[s] += 1;
901                }
902            }
903        }
904        let mut queue: std::collections::VecDeque<usize> =
905            (0..n).filter(|&i| in_degree[i] == 0).collect();
906        let mut order = Vec::new();
907        while let Some(node) = queue.pop_front() {
908            order.push(node);
909            for &s in self.successors(node) {
910                if s < n {
911                    in_degree[s] -= 1;
912                    if in_degree[s] == 0 {
913                        queue.push_back(s);
914                    }
915                }
916            }
917        }
918        if order.len() == n {
919            Some(order)
920        } else {
921            None
922        }
923    }
924    /// Returns the number of nodes.
925    pub fn num_nodes(&self) -> usize {
926        self.edges.len()
927    }
928}
929/// FFI function signature.
930#[derive(Clone, Debug)]
931pub struct FfiSignature {
932    /// Parameter types.
933    pub params: Vec<FfiType>,
934    /// Return type.
935    pub ret_type: Box<FfiType>,
936}
937impl FfiSignature {
938    /// Create a new FFI signature.
939    pub fn new(params: Vec<FfiType>, ret_type: Box<FfiType>) -> Self {
940        FfiSignature { params, ret_type }
941    }
942    /// Validate the signature for FFI safety.
943    pub fn validate(&self) -> Result<(), FfiError> {
944        for param in &self.params {
945            if !param.is_ffi_safe() {
946                return Err(FfiError::InvalidSignature(format!(
947                    "Unsafe parameter type: {}",
948                    param
949                )));
950            }
951        }
952        if !self.ret_type.is_ffi_safe() {
953            return Err(FfiError::InvalidSignature(format!(
954                "Unsafe return type: {}",
955                self.ret_type
956            )));
957        }
958        Ok(())
959    }
960    /// Check if this signature is valid for a given expression type.
961    pub fn matches_expr(&self, expr: &Expr) -> bool {
962        matches!(expr, Expr::Const(_, _))
963    }
964}
965/// A non-empty list (at least one element guaranteed).
966#[allow(dead_code)]
967pub struct NonEmptyVec<T> {
968    head: T,
969    tail: Vec<T>,
970}
971#[allow(dead_code)]
972impl<T> NonEmptyVec<T> {
973    /// Creates a non-empty vec with a single element.
974    pub fn singleton(val: T) -> Self {
975        Self {
976            head: val,
977            tail: Vec::new(),
978        }
979    }
980    /// Pushes an element.
981    pub fn push(&mut self, val: T) {
982        self.tail.push(val);
983    }
984    /// Returns a reference to the first element.
985    pub fn first(&self) -> &T {
986        &self.head
987    }
988    /// Returns a reference to the last element.
989    pub fn last(&self) -> &T {
990        self.tail.last().unwrap_or(&self.head)
991    }
992    /// Returns the number of elements.
993    pub fn len(&self) -> usize {
994        1 + self.tail.len()
995    }
996    /// Returns whether the collection is empty.
997    pub fn is_empty(&self) -> bool {
998        self.len() == 0
999    }
1000    /// Returns all elements as a Vec.
1001    pub fn to_vec(&self) -> Vec<&T> {
1002        let mut v = vec![&self.head];
1003        v.extend(self.tail.iter());
1004        v
1005    }
1006}
1007/// A simple stack-based calculator for arithmetic expressions.
1008#[allow(dead_code)]
1009pub struct StackCalc {
1010    stack: Vec<i64>,
1011}
1012#[allow(dead_code)]
1013impl StackCalc {
1014    /// Creates a new empty calculator.
1015    pub fn new() -> Self {
1016        Self { stack: Vec::new() }
1017    }
1018    /// Pushes an integer literal.
1019    pub fn push(&mut self, n: i64) {
1020        self.stack.push(n);
1021    }
1022    /// Replaces the top two values, `b` on top of `a`, with `op(a, b)` and
1023    /// returns it.  Returns `None`, leaving the stack unchanged, if it holds
1024    /// fewer than two values.
1025    fn apply_binary(&mut self, op: impl FnOnce(i64, i64) -> i64) -> Option<i64> {
1026        let (rest_len, a, b) = match self.stack[..] {
1027            [ref rest @ .., a, b] => (rest.len(), a, b),
1028            _ => return None,
1029        };
1030        let value = op(a, b);
1031        self.stack.truncate(rest_len);
1032        self.stack.push(value);
1033        Some(value)
1034    }
1035    /// Adds the top two values and returns the sum, which replaces them.
1036    /// Returns `None`, leaving the stack unchanged, if it holds fewer than
1037    /// two values.
1038    pub fn add(&mut self) -> Option<i64> {
1039        self.apply_binary(|a, b| a + b)
1040    }
1041    /// Subtracts top from second and returns the difference, which replaces
1042    /// them.  Returns `None`, leaving the stack unchanged, if it holds fewer
1043    /// than two values.
1044    pub fn sub(&mut self) -> Option<i64> {
1045        self.apply_binary(|a, b| a - b)
1046    }
1047    /// Multiplies the top two values and returns the product, which replaces
1048    /// them.  Returns `None`, leaving the stack unchanged, if it holds fewer
1049    /// than two values.
1050    pub fn mul(&mut self) -> Option<i64> {
1051        self.apply_binary(|a, b| a * b)
1052    }
1053    /// Peeks the top value.
1054    pub fn peek(&self) -> Option<i64> {
1055        self.stack.last().copied()
1056    }
1057    /// Returns the stack depth.
1058    pub fn depth(&self) -> usize {
1059        self.stack.len()
1060    }
1061}
1062/// A label set for a graph node.
1063#[allow(dead_code)]
1064pub struct LabelSet {
1065    labels: Vec<String>,
1066}
1067#[allow(dead_code)]
1068impl LabelSet {
1069    /// Creates a new empty label set.
1070    pub fn new() -> Self {
1071        Self { labels: Vec::new() }
1072    }
1073    /// Adds a label (deduplicates).
1074    pub fn add(&mut self, label: impl Into<String>) {
1075        let s = label.into();
1076        if !self.labels.contains(&s) {
1077            self.labels.push(s);
1078        }
1079    }
1080    /// Returns `true` if `label` is present.
1081    pub fn has(&self, label: &str) -> bool {
1082        self.labels.iter().any(|l| l == label)
1083    }
1084    /// Returns the count of labels.
1085    pub fn count(&self) -> usize {
1086        self.labels.len()
1087    }
1088    /// Returns all labels.
1089    pub fn all(&self) -> &[String] {
1090        &self.labels
1091    }
1092}
1093/// A versioned record that stores a history of values.
1094#[allow(dead_code)]
1095pub struct VersionedRecord<T: Clone> {
1096    /// Every earlier value, oldest first: version `n` is `earlier[n]`.
1097    earlier: Vec<T>,
1098    /// The latest value (version `earlier.len()`), declared last so it drops last.
1099    current: T,
1100}
1101#[allow(dead_code)]
1102impl<T: Clone> VersionedRecord<T> {
1103    /// Creates a new record with an initial value.
1104    pub fn new(initial: T) -> Self {
1105        Self {
1106            earlier: Vec::new(),
1107            current: initial,
1108        }
1109    }
1110    /// Updates the record with a new version.
1111    pub fn update(&mut self, val: T) {
1112        let previous = std::mem::replace(&mut self.current, val);
1113        self.earlier.push(previous);
1114    }
1115    /// Returns the current (latest) value.
1116    pub fn current(&self) -> &T {
1117        &self.current
1118    }
1119    /// Returns the value at version `n` (0-indexed), or `None`.
1120    pub fn at_version(&self, n: usize) -> Option<&T> {
1121        match self.earlier.get(n) {
1122            Some(value) => Some(value),
1123            None if n == self.earlier.len() => Some(&self.current),
1124            None => None,
1125        }
1126    }
1127    /// Returns the version number of the current value.
1128    pub fn version(&self) -> usize {
1129        self.earlier.len()
1130    }
1131    /// Returns `true` if more than one version exists.
1132    pub fn has_history(&self) -> bool {
1133        !self.earlier.is_empty()
1134    }
1135}
1136/// A set of rewrite rules.
1137#[allow(dead_code)]
1138pub struct RewriteRuleSet {
1139    rules: Vec<RewriteRule>,
1140}
1141#[allow(dead_code)]
1142impl RewriteRuleSet {
1143    /// Creates an empty rule set.
1144    pub fn new() -> Self {
1145        Self { rules: Vec::new() }
1146    }
1147    /// Adds a rule.
1148    pub fn add(&mut self, rule: RewriteRule) {
1149        self.rules.push(rule);
1150    }
1151    /// Returns the number of rules.
1152    pub fn len(&self) -> usize {
1153        self.rules.len()
1154    }
1155    /// Returns `true` if the set is empty.
1156    pub fn is_empty(&self) -> bool {
1157        self.rules.is_empty()
1158    }
1159    /// Returns all conditional rules.
1160    pub fn conditional_rules(&self) -> Vec<&RewriteRule> {
1161        self.rules.iter().filter(|r| r.conditional).collect()
1162    }
1163    /// Returns all unconditional rules.
1164    pub fn unconditional_rules(&self) -> Vec<&RewriteRule> {
1165        self.rules.iter().filter(|r| !r.conditional).collect()
1166    }
1167    /// Looks up a rule by name.
1168    pub fn get(&self, name: &str) -> Option<&RewriteRule> {
1169        self.rules.iter().find(|r| r.name == name)
1170    }
1171}
1172/// External function declaration.
1173#[derive(Clone, Debug)]
1174pub struct ExternDecl {
1175    /// Function name in OxiLean.
1176    pub name: Name,
1177    /// Type expression of the function.
1178    pub type_expr: Expr,
1179    /// Library name (e.g., "libc", "libm").
1180    pub lib_name: String,
1181    /// C symbol name.
1182    pub symbol_name: String,
1183    /// Safety level.
1184    pub safety: FfiSafety,
1185    /// Calling convention.
1186    pub calling_convention: CallingConvention,
1187    /// FFI signature.
1188    pub signature: FfiSignature,
1189}
1190impl ExternDecl {
1191    /// Create a new external declaration.
1192    pub fn new(
1193        name: Name,
1194        type_expr: Expr,
1195        lib_name: String,
1196        symbol_name: String,
1197        safety: FfiSafety,
1198        calling_convention: CallingConvention,
1199        signature: FfiSignature,
1200    ) -> Self {
1201        ExternDecl {
1202            name,
1203            type_expr,
1204            lib_name,
1205            symbol_name,
1206            safety,
1207            calling_convention,
1208            signature,
1209        }
1210    }
1211    /// Validate this declaration.
1212    pub fn validate(&self) -> Result<(), FfiError> {
1213        self.signature.validate()?;
1214        if self.lib_name.is_empty() {
1215            return Err(FfiError::InvalidSignature(
1216                "Library name cannot be empty".to_string(),
1217            ));
1218        }
1219        if self.symbol_name.is_empty() {
1220            return Err(FfiError::InvalidSignature(
1221                "Symbol name cannot be empty".to_string(),
1222            ));
1223        }
1224        Ok(())
1225    }
1226}
1227/// Built-in external functions.
1228pub struct BuiltinExterns;
1229impl BuiltinExterns {
1230    /// Register built-in external functions.
1231    pub fn register_builtins(registry: &mut ExternRegistry) -> Result<(), FfiError> {
1232        Self::register_io(registry)?;
1233        Self::register_string(registry)?;
1234        Self::register_arithmetic(registry)?;
1235        Ok(())
1236    }
1237    /// Register I/O external functions.
1238    pub(crate) fn register_io(registry: &mut ExternRegistry) -> Result<(), FfiError> {
1239        registry.register(ExternDecl::new(
1240            Name::str("builtin_print"),
1241            Expr::Const(Name::str("String"), vec![]),
1242            "libc".to_string(),
1243            "puts".to_string(),
1244            FfiSafety::System,
1245            CallingConvention::C,
1246            FfiSignature::new(vec![FfiType::String], Box::new(FfiType::Int32)),
1247        ))?;
1248        registry.register(ExternDecl::new(
1249            Name::str("builtin_print_int"),
1250            Expr::Const(Name::str("Nat"), vec![]),
1251            "libc".to_string(),
1252            "printf".to_string(),
1253            FfiSafety::System,
1254            CallingConvention::C,
1255            FfiSignature::new(
1256                vec![FfiType::String, FfiType::UInt64],
1257                Box::new(FfiType::Int32),
1258            ),
1259        ))?;
1260        Ok(())
1261    }
1262    /// Register string external functions.
1263    pub(crate) fn register_string(registry: &mut ExternRegistry) -> Result<(), FfiError> {
1264        registry.register(ExternDecl::new(
1265            Name::str("builtin_strlen"),
1266            Expr::Const(Name::str("String"), vec![]),
1267            "libc".to_string(),
1268            "strlen".to_string(),
1269            FfiSafety::Unsafe,
1270            CallingConvention::C,
1271            FfiSignature::new(vec![FfiType::String], Box::new(FfiType::UInt64)),
1272        ))?;
1273        registry.register(ExternDecl::new(
1274            Name::str("builtin_strcmp"),
1275            Expr::Const(Name::str("String"), vec![]),
1276            "libc".to_string(),
1277            "strcmp".to_string(),
1278            FfiSafety::Unsafe,
1279            CallingConvention::C,
1280            FfiSignature::new(
1281                vec![FfiType::String, FfiType::String],
1282                Box::new(FfiType::Int32),
1283            ),
1284        ))?;
1285        Ok(())
1286    }
1287    /// Register arithmetic external functions.
1288    pub(crate) fn register_arithmetic(registry: &mut ExternRegistry) -> Result<(), FfiError> {
1289        registry.register(ExternDecl::new(
1290            Name::str("builtin_abs"),
1291            Expr::Const(Name::str("Int"), vec![]),
1292            "libc".to_string(),
1293            "abs".to_string(),
1294            FfiSafety::Safe,
1295            CallingConvention::C,
1296            FfiSignature::new(vec![FfiType::Int32], Box::new(FfiType::Int32)),
1297        ))?;
1298        registry.register(ExternDecl::new(
1299            Name::str("builtin_sqrt"),
1300            Expr::Const(Name::str("Float"), vec![]),
1301            "libm".to_string(),
1302            "sqrt".to_string(),
1303            FfiSafety::Safe,
1304            CallingConvention::C,
1305            FfiSignature::new(vec![FfiType::Float64], Box::new(FfiType::Float64)),
1306        ))?;
1307        Ok(())
1308    }
1309}
1310/// A version record for an external library.
1311#[derive(Clone, Debug, PartialEq, Eq)]
1312pub struct LibraryVersion {
1313    /// Library name.
1314    pub name: String,
1315    /// Major version.
1316    pub major: u32,
1317    /// Minor version.
1318    pub minor: u32,
1319    /// Patch version.
1320    pub patch: u32,
1321}
1322impl LibraryVersion {
1323    /// Create a new version.
1324    pub fn new(name: &str, major: u32, minor: u32, patch: u32) -> Self {
1325        Self {
1326            name: name.to_string(),
1327            major,
1328            minor,
1329            patch,
1330        }
1331    }
1332    /// Whether this version is at least as recent as the given version.
1333    pub fn at_least(&self, major: u32, minor: u32, patch: u32) -> bool {
1334        (self.major, self.minor, self.patch) >= (major, minor, patch)
1335    }
1336}
1337/// Metadata about a foreign symbol.
1338#[derive(Clone, Debug)]
1339pub struct SymbolMetadata {
1340    /// Symbol name.
1341    pub symbol: String,
1342    /// Library containing the symbol.
1343    pub library: String,
1344    /// Whether the symbol is weak (optional link).
1345    pub weak: bool,
1346    /// Whether the symbol is thread-local.
1347    pub thread_local: bool,
1348}
1349impl SymbolMetadata {
1350    /// Create a new symbol metadata.
1351    pub fn new(symbol: &str, library: &str) -> Self {
1352        Self {
1353            symbol: symbol.to_string(),
1354            library: library.to_string(),
1355            weak: false,
1356            thread_local: false,
1357        }
1358    }
1359    /// Mark this symbol as weak.
1360    pub fn with_weak(mut self) -> Self {
1361        self.weak = true;
1362        self
1363    }
1364    /// Mark this symbol as thread-local.
1365    pub fn with_thread_local(mut self) -> Self {
1366        self.thread_local = true;
1367        self
1368    }
1369}
1370/// A type-erased function pointer with arity tracking.
1371#[allow(dead_code)]
1372pub struct RawFnPtr {
1373    /// The raw function pointer (stored as usize for type erasure).
1374    ptr: usize,
1375    arity: usize,
1376    name: String,
1377}
1378#[allow(dead_code)]
1379impl RawFnPtr {
1380    /// Creates a new raw function pointer descriptor.
1381    pub fn new(ptr: usize, arity: usize, name: impl Into<String>) -> Self {
1382        Self {
1383            ptr,
1384            arity,
1385            name: name.into(),
1386        }
1387    }
1388    /// Returns the arity.
1389    pub fn arity(&self) -> usize {
1390        self.arity
1391    }
1392    /// Returns the name.
1393    pub fn name(&self) -> &str {
1394        &self.name
1395    }
1396    /// Returns the raw pointer value.
1397    pub fn raw(&self) -> usize {
1398        self.ptr
1399    }
1400}
1401/// A dependency closure builder (transitive closure via BFS).
1402#[allow(dead_code)]
1403pub struct TransitiveClosure {
1404    adj: Vec<Vec<usize>>,
1405    n: usize,
1406}
1407#[allow(dead_code)]
1408impl TransitiveClosure {
1409    /// Creates a transitive closure builder for `n` nodes.
1410    pub fn new(n: usize) -> Self {
1411        Self {
1412            adj: vec![Vec::new(); n],
1413            n,
1414        }
1415    }
1416    /// Adds a direct edge.
1417    pub fn add_edge(&mut self, from: usize, to: usize) {
1418        if from < self.n {
1419            self.adj[from].push(to);
1420        }
1421    }
1422    /// Computes all nodes reachable from `start` (including `start`).
1423    pub fn reachable_from(&self, start: usize) -> Vec<usize> {
1424        let mut visited = vec![false; self.n];
1425        let mut queue = std::collections::VecDeque::new();
1426        queue.push_back(start);
1427        while let Some(node) = queue.pop_front() {
1428            if node >= self.n || visited[node] {
1429                continue;
1430            }
1431            visited[node] = true;
1432            for &next in &self.adj[node] {
1433                queue.push_back(next);
1434            }
1435        }
1436        (0..self.n).filter(|&i| visited[i]).collect()
1437    }
1438    /// Returns `true` if `from` can transitively reach `to`.
1439    pub fn can_reach(&self, from: usize, to: usize) -> bool {
1440        self.reachable_from(from).contains(&to)
1441    }
1442}
1443/// A flat list of substitution pairs `(from, to)`.
1444#[allow(dead_code)]
1445pub struct FlatSubstitution {
1446    pairs: Vec<(String, String)>,
1447}
1448#[allow(dead_code)]
1449impl FlatSubstitution {
1450    /// Creates an empty substitution.
1451    pub fn new() -> Self {
1452        Self { pairs: Vec::new() }
1453    }
1454    /// Adds a pair.
1455    pub fn add(&mut self, from: impl Into<String>, to: impl Into<String>) {
1456        self.pairs.push((from.into(), to.into()));
1457    }
1458    /// Applies all substitutions to `s` (leftmost-first order).
1459    pub fn apply(&self, s: &str) -> String {
1460        let mut result = s.to_string();
1461        for (from, to) in &self.pairs {
1462            result = result.replace(from.as_str(), to.as_str());
1463        }
1464        result
1465    }
1466    /// Returns the number of pairs.
1467    pub fn len(&self) -> usize {
1468        self.pairs.len()
1469    }
1470    /// Returns `true` if empty.
1471    pub fn is_empty(&self) -> bool {
1472        self.pairs.is_empty()
1473    }
1474}
1475/// Registry for external function declarations.
1476pub struct ExternRegistry {
1477    /// Map from (lib_name, symbol_name) to ExternDecl.
1478    decls: HashMap<(String, String), ExternDecl>,
1479    /// Map from OxiLean name to (lib_name, symbol_name).
1480    name_map: HashMap<String, (String, String)>,
1481}
1482impl ExternRegistry {
1483    /// Create a new empty registry.
1484    pub fn new() -> Self {
1485        ExternRegistry {
1486            decls: HashMap::new(),
1487            name_map: HashMap::new(),
1488        }
1489    }
1490    /// Register an external function declaration.
1491    pub fn register(&mut self, decl: ExternDecl) -> Result<(), FfiError> {
1492        decl.validate()?;
1493        let key = (decl.lib_name.clone(), decl.symbol_name.clone());
1494        if self.decls.contains_key(&key) {
1495            return Err(FfiError::DuplicateSymbol(format!(
1496                "{}::{}",
1497                decl.lib_name, decl.symbol_name
1498            )));
1499        }
1500        let name_str = decl.name.to_string();
1501        if self.name_map.contains_key(&name_str) {
1502            return Err(FfiError::DuplicateSymbol(name_str));
1503        }
1504        self.name_map.insert(name_str, key.clone());
1505        self.decls.insert(key, decl);
1506        Ok(())
1507    }
1508    /// Look up a declaration by OxiLean name.
1509    pub fn lookup(&self, name: &Name) -> Result<&ExternDecl, FfiError> {
1510        let name_str = name.to_string();
1511        let key = self
1512            .name_map
1513            .get(&name_str)
1514            .ok_or_else(|| FfiError::SymbolNotFound(name_str.clone()))?;
1515        self.decls
1516            .get(key)
1517            .ok_or(FfiError::SymbolNotFound(name_str))
1518    }
1519    /// Look up a declaration by symbol name.
1520    pub fn lookup_by_symbol(
1521        &self,
1522        lib_name: &str,
1523        symbol_name: &str,
1524    ) -> Result<&ExternDecl, FfiError> {
1525        let key = (lib_name.to_string(), symbol_name.to_string());
1526        self.decls
1527            .get(&key)
1528            .ok_or_else(|| FfiError::SymbolNotFound(format!("{}::{}", lib_name, symbol_name)))
1529    }
1530    /// Validate all registered declarations.
1531    pub fn validate_all(&self) -> Result<(), FfiError> {
1532        for decl in self.decls.values() {
1533            decl.validate()?;
1534        }
1535        Ok(())
1536    }
1537    /// Get all registered declarations.
1538    pub fn all_decls(&self) -> impl Iterator<Item = &ExternDecl> {
1539        self.decls.values()
1540    }
1541    /// Get the number of registered declarations.
1542    pub fn count(&self) -> usize {
1543        self.decls.len()
1544    }
1545}
1546/// A token bucket rate limiter.
1547#[allow(dead_code)]
1548pub struct TokenBucket {
1549    capacity: u64,
1550    tokens: u64,
1551    refill_per_ms: u64,
1552    last_refill: crate::wall_clock::Instant,
1553}
1554#[allow(dead_code)]
1555impl TokenBucket {
1556    /// Creates a new token bucket.
1557    pub fn new(capacity: u64, refill_per_ms: u64) -> Self {
1558        Self {
1559            capacity,
1560            tokens: capacity,
1561            refill_per_ms,
1562            last_refill: crate::wall_clock::Instant::now(),
1563        }
1564    }
1565    /// Attempts to consume `n` tokens.  Returns `true` on success.
1566    pub fn try_consume(&mut self, n: u64) -> bool {
1567        self.refill();
1568        if self.tokens >= n {
1569            self.tokens -= n;
1570            true
1571        } else {
1572            false
1573        }
1574    }
1575    fn refill(&mut self) {
1576        let now = crate::wall_clock::Instant::now();
1577        let elapsed_ms = now.duration_since(self.last_refill).as_millis() as u64;
1578        if elapsed_ms > 0 {
1579            let new_tokens = elapsed_ms * self.refill_per_ms;
1580            self.tokens = (self.tokens + new_tokens).min(self.capacity);
1581            self.last_refill = now;
1582        }
1583    }
1584    /// Returns the number of currently available tokens.
1585    pub fn available(&self) -> u64 {
1586        self.tokens
1587    }
1588    /// Returns the bucket capacity.
1589    pub fn capacity(&self) -> u64 {
1590        self.capacity
1591    }
1592}
1593/// A trie-based prefix counter.
1594#[allow(dead_code)]
1595pub struct PrefixCounter {
1596    children: std::collections::HashMap<char, PrefixCounter>,
1597    count: usize,
1598}
1599#[allow(dead_code)]
1600impl PrefixCounter {
1601    /// Creates an empty prefix counter.
1602    pub fn new() -> Self {
1603        Self {
1604            children: std::collections::HashMap::new(),
1605            count: 0,
1606        }
1607    }
1608    /// Records a string.
1609    pub fn record(&mut self, s: &str) {
1610        self.count += 1;
1611        let mut node = self;
1612        for c in s.chars() {
1613            node = node.children.entry(c).or_default();
1614            node.count += 1;
1615        }
1616    }
1617    /// Returns how many strings have been recorded that start with `prefix`.
1618    pub fn count_with_prefix(&self, prefix: &str) -> usize {
1619        let mut node = self;
1620        for c in prefix.chars() {
1621            match node.children.get(&c) {
1622                Some(n) => node = n,
1623                None => return 0,
1624            }
1625        }
1626        node.count
1627    }
1628}
1629/// A mutable reference stack for tracking the current "focus" in a tree traversal.
1630#[allow(dead_code)]
1631pub struct FocusStack<T> {
1632    items: Vec<T>,
1633}
1634#[allow(dead_code)]
1635impl<T> FocusStack<T> {
1636    /// Creates an empty focus stack.
1637    pub fn new() -> Self {
1638        Self { items: Vec::new() }
1639    }
1640    /// Focuses on `item`.
1641    pub fn focus(&mut self, item: T) {
1642        self.items.push(item);
1643    }
1644    /// Blurs (pops) the current focus.
1645    pub fn blur(&mut self) -> Option<T> {
1646        self.items.pop()
1647    }
1648    /// Returns the current focus, or `None`.
1649    pub fn current(&self) -> Option<&T> {
1650        self.items.last()
1651    }
1652    /// Returns the focus depth.
1653    pub fn depth(&self) -> usize {
1654        self.items.len()
1655    }
1656    /// Returns `true` if there is no current focus.
1657    pub fn is_empty(&self) -> bool {
1658        self.items.is_empty()
1659    }
1660}
1661/// Calling convention for external functions.
1662#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
1663pub enum CallingConvention {
1664    /// Rust calling convention.
1665    Rust,
1666    /// C calling convention (platform-dependent).
1667    C,
1668    /// System calling convention.
1669    System,
1670}
1671/// FFI-related errors.
1672#[derive(Clone, PartialEq, Eq, Hash, Debug)]
1673pub enum FfiError {
1674    /// Symbol not found in registry.
1675    SymbolNotFound(String),
1676    /// Library not found.
1677    LibraryNotFound(String),
1678    /// Type mismatch in conversion.
1679    TypeMismatch(String),
1680    /// Value out of range.
1681    ValueOutOfRange(String),
1682    /// Invalid function signature.
1683    InvalidSignature(String),
1684    /// Duplicate symbol registration.
1685    DuplicateSymbol(String),
1686    /// Validation failed.
1687    ValidationFailed(String),
1688}
1689/// A manifest of external libraries required by a compiled OxiLean module.
1690#[derive(Clone, Debug, Default)]
1691pub struct LibraryManifest {
1692    /// Required libraries.
1693    pub entries: Vec<LibraryVersion>,
1694}
1695impl LibraryManifest {
1696    /// Create an empty manifest.
1697    pub fn new() -> Self {
1698        Self::default()
1699    }
1700    /// Add a required library.
1701    pub fn require(&mut self, lib: LibraryVersion) {
1702        self.entries.push(lib);
1703    }
1704    /// Check if a library (by name) is required.
1705    pub fn requires_lib(&self, name: &str) -> bool {
1706        self.entries.iter().any(|l| l.name == name)
1707    }
1708    /// Number of required libraries.
1709    pub fn len(&self) -> usize {
1710        self.entries.len()
1711    }
1712    /// Whether the manifest is empty.
1713    pub fn is_empty(&self) -> bool {
1714        self.entries.is_empty()
1715    }
1716}