use core::fmt;
use std::sync::Arc;
use bincode_next::{Decode, Encode};
#[derive(Clone, Copy, PartialEq, Eq, Hash, Encode, Decode)]
pub struct SymbolId(pub(crate) u32);
impl SymbolId {
#[must_use]
pub const fn new(id: u32) -> Self {
Self(id)
}
#[must_use]
pub const fn value(self) -> u32 {
self.0
}
}
impl fmt::Debug for SymbolId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SymbolId({})", self.0)
}
}
impl fmt::Display for SymbolId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "sym#{}", self.0)
}
}
impl fmt::Display for OpKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let s = match self {
Self::Add => "+",
Self::Sub => "-",
Self::Mul => "*",
Self::Div => "/",
Self::Pow => "^",
Self::Neg => "neg",
Self::Mod => "%",
};
f.write_str(s)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Encode, Decode)]
pub struct FnId(pub(crate) u32);
impl FnId {
pub const FIRST_USER: Self = Self(7);
#[must_use]
pub const fn from_op(op: OpKind) -> Self {
Self(op as u32)
}
#[must_use]
pub const fn to_op(self) -> Option<OpKind> {
match self.0 {
0 => Some(OpKind::Add),
1 => Some(OpKind::Sub),
2 => Some(OpKind::Mul),
3 => Some(OpKind::Div),
4 => Some(OpKind::Pow),
5 => Some(OpKind::Mod),
6 => Some(OpKind::Neg),
_ => None,
}
}
#[must_use]
pub const fn is_intrinsic(self) -> bool {
self.0 <= 6
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Encode, Decode)]
#[repr(u8)]
pub enum OpKind {
Add = 0,
Sub = 1,
Mul = 2,
Div = 3,
Pow = 4,
Mod = 5,
Neg = 6,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Encode, Decode)]
pub enum CtrlKind {
Select,
IfElse,
ForLoop,
}
#[derive(Debug, Clone, Copy, Encode, Decode)]
pub enum SymbolKind {
Variable(SymbolId),
Constant(f64),
Operator(OpKind),
Function(FnId),
ControlFlow(CtrlKind),
}
impl PartialEq for SymbolKind {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Variable(a), Self::Variable(b)) => a == b,
(Self::Constant(a), Self::Constant(b)) => a.to_bits() == b.to_bits(),
(Self::Operator(a), Self::Operator(b)) => a == b,
(Self::Function(a), Self::Function(b)) => a == b,
(Self::ControlFlow(a), Self::ControlFlow(b)) => a == b,
_ => false,
}
}
}
impl Eq for SymbolKind {}
impl std::hash::Hash for SymbolKind {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
core::mem::discriminant(self).hash(state);
match self {
Self::Variable(sym) => sym.hash(state),
Self::Constant(v) => v.to_bits().hash(state),
Self::Operator(op) => op.hash(state),
Self::Function(fn_id) => fn_id.hash(state),
Self::ControlFlow(ctrl) => ctrl.hash(state),
}
}
}
#[derive(Debug, Clone)]
pub struct SymbolRegistry {
names: Vec<Arc<str>>,
lookup: std::collections::HashMap<Arc<str>, SymbolId, rapidhash::fast::GlobalState>,
}
impl SymbolRegistry {
#[must_use]
pub fn new() -> Self {
Self {
names: Vec::new(),
lookup: std::collections::HashMap::default(),
}
}
pub fn intern(&mut self, name: &str) -> SymbolId {
if let Some(&id) = self.lookup.get(name) {
return id;
}
#[allow(clippy::cast_possible_truncation)]
let id = SymbolId(self.names.len() as u32);
let arc: Arc<str> = Arc::from(name); self.names.push(Arc::clone(&arc)); self.lookup.insert(arc, id);
id
}
#[must_use]
pub fn name(&self, id: SymbolId) -> Option<&str> {
self.names
.get(id.0 as usize)
.map(std::convert::AsRef::as_ref)
}
#[must_use]
pub fn lookup(&self, name: &str) -> Option<SymbolId> {
self.lookup.get(name).copied()
}
#[must_use]
pub fn lookup_bytes(&self, bytes: &[u8]) -> Option<SymbolId> {
core::str::from_utf8(bytes)
.ok()
.and_then(|s| self.lookup(s))
}
pub fn intern_bytes(&mut self, bytes: &[u8]) -> Option<SymbolId> {
let name = core::str::from_utf8(bytes).ok()?;
Some(self.intern(name))
}
#[must_use]
pub const fn len(&self) -> usize {
self.names.len()
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.names.is_empty()
}
}
impl Default for SymbolRegistry {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn intern_and_lookup() {
let mut reg = SymbolRegistry::new();
let x = reg.intern("x");
let y = reg.intern("y");
let x2 = reg.intern("x");
assert_eq!(x, x2, "re-interning should return same ID");
assert_ne!(x, y);
assert_eq!(reg.name(x), Some("x"));
assert_eq!(reg.name(y), Some("y"));
assert_eq!(reg.len(), 2);
}
#[test]
fn display_op_kind() {
assert_eq!(format!("{}", OpKind::Add), "+");
assert_eq!(format!("{}", OpKind::Pow), "^");
assert_eq!(format!("{}", OpKind::Neg), "neg");
}
#[test]
fn intern_scales_o1() {
let mut reg = SymbolRegistry::new();
for i in 0..1000 {
let name = format!("v{i}");
let id = reg.intern(&name);
assert_eq!(reg.lookup(&name), Some(id));
}
assert_eq!(reg.len(), 1000);
for i in 0..1000 {
let name = format!("v{i}");
let _ = reg.intern(&name);
}
assert_eq!(reg.len(), 1000);
}
}