use std::collections::HashMap;
use crate::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct SymId(pub u32);
#[derive(Debug, Clone, Default)]
pub struct SymGen {
next: u32,
}
impl SymGen {
pub fn new() -> Self {
Self { next: 0 }
}
pub fn fresh(&mut self) -> SymId {
let id = SymId(self.next);
self.next += 1;
id
}
}
#[derive(Debug, Clone, Default)]
pub struct SymEnv {
map: HashMap<SymId, usize>,
}
impl SymEnv {
pub fn new() -> Self {
Self::default()
}
pub fn bind(&mut self, sym: SymId, value: usize) -> Result<()> {
match self.map.get(&sym) {
Some(&existing) if existing != value => Err(Error::Msg(format!(
"SymEnv: symbol {sym:?} already bound to {existing}; cannot rebind to {value} \
within a forward pass (symbols are write-once)",
))
.bt()),
_ => {
self.map.insert(sym, value);
Ok(())
}
}
}
pub fn get(&self, sym: SymId) -> Option<usize> {
self.map.get(&sym).copied()
}
pub fn is_bound(&self, sym: SymId) -> bool {
self.map.contains_key(&sym)
}
pub fn clear(&mut self) {
self.map.clear();
}
pub fn len(&self) -> usize {
self.map.len()
}
pub fn is_empty(&self) -> bool {
self.map.is_empty()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DynScalar {
Concrete(usize),
Sym(SymId),
}
impl DynScalar {
pub fn resolve(&self, env: &SymEnv) -> Option<usize> {
match self {
DynScalar::Concrete(v) => Some(*v),
DynScalar::Sym(s) => env.get(*s),
}
}
pub fn as_concrete(&self) -> Option<usize> {
match self {
DynScalar::Concrete(v) => Some(*v),
DynScalar::Sym(_) => None,
}
}
pub fn is_dynamic(&self) -> bool {
matches!(self, DynScalar::Sym(_))
}
}
impl From<usize> for DynScalar {
fn from(v: usize) -> Self {
DynScalar::Concrete(v)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn symgen_allocates_distinct_ids() {
let mut g = SymGen::new();
let a = g.fresh();
let b = g.fresh();
assert_ne!(a, b);
assert_eq!(a, SymId(0));
assert_eq!(b, SymId(1));
}
#[test]
fn symenv_bind_get_presence() {
let mut env = SymEnv::new();
let s = SymId(7);
assert!(!env.is_bound(s));
assert_eq!(env.get(s), None);
env.bind(s, 53).unwrap();
assert!(env.is_bound(s));
assert_eq!(env.get(s), Some(53));
}
#[test]
fn symenv_is_write_once() {
let mut env = SymEnv::new();
let s = SymId(1);
env.bind(s, 53).unwrap();
env.bind(s, 53).unwrap();
assert!(env.bind(s, 54).is_err());
assert_eq!(env.get(s), Some(53));
env.clear();
assert!(!env.is_bound(s));
}
#[test]
fn dynscalar_resolves_and_shares_value_by_sym() {
let mut env = SymEnv::new();
let s = SymId(3);
let a = DynScalar::Sym(s);
let b = DynScalar::Sym(s); let c = DynScalar::Concrete(99);
assert_eq!(a.resolve(&env), None, "unbound sym resolves to None");
env.bind(s, 42).unwrap();
assert_eq!(a.resolve(&env), Some(42));
assert_eq!(b.resolve(&env), Some(42));
assert_eq!(c.resolve(&env), Some(99));
assert_eq!(c.as_concrete(), Some(99));
assert_eq!(a.as_concrete(), None);
assert!(a.is_dynamic());
assert!(!c.is_dynamic());
assert_eq!(DynScalar::from(7usize), DynScalar::Concrete(7));
}
}