expr_solver/
symtable.rs

1//! Symbol table implementation.
2
3use crate::number::Number;
4use crate::symbol::{FuncError, Symbol, SymbolError};
5use std::borrow::Cow;
6
7/// Symbol table containing constants and functions.
8///
9/// The table stores mathematical constants like `pi` and functions like `sin`.
10/// Symbol lookups are case-insensitive.
11///
12/// # Examples
13///
14/// ```
15/// use expr_solver::{num, SymTable};
16///
17/// let mut table = SymTable::stdlib();
18/// table.add_const("x", num!(42), false).unwrap();
19/// ```
20#[derive(Debug, Default, Clone)]
21pub struct SymTable {
22    symbols: Vec<Symbol>,
23}
24
25impl SymTable {
26    /// Creates an empty symbol table.
27    pub fn new() -> Self {
28        Self::default()
29    }
30
31    /// Creates a symbol table from a vector of symbols.
32    /// This is used internally by the type-specific stdlib implementations.
33    pub(crate) fn from_symbols(symbols: Vec<Symbol>) -> Self {
34        Self { symbols }
35    }
36
37    /// Adds a constant to the table.
38    ///
39    /// Returns an error if a symbol with the same name already exists.
40    ///
41    /// # Parameters
42    /// - `name`: Constant name
43    /// - `value`: Constant value
44    /// - `local`: Whether this is a local constant (from let) or global (from stdlib)
45    pub fn add_const<S: Into<Cow<'static, str>>>(
46        &mut self,
47        name: S,
48        value: Number,
49        local: bool,
50    ) -> Result<&mut Self, SymbolError> {
51        let name = name.into();
52        if self.get_by_name(&name).is_ok() {
53            return Err(SymbolError::DuplicateSymbol(name.to_string()));
54        }
55        self.symbols.push(Symbol::Const {
56            name,
57            value,
58            description: None,
59            local,
60        });
61        Ok(self)
62    }
63
64    /// Adds a function to the table.
65    ///
66    /// # Parameters
67    /// - `name`: Function name
68    /// - `args`: Minimum number of arguments
69    /// - `variadic`: Whether the function accepts additional arguments
70    /// - `callback`: Function implementation
71    /// - `local`: Whether this is a local function (reserved for future) or global (from stdlib)
72    ///
73    /// Returns an error if a symbol with the same name already exists.
74    pub fn add_func<S: Into<Cow<'static, str>>>(
75        &mut self,
76        name: S,
77        args: usize,
78        variadic: bool,
79        callback: fn(&[Number]) -> Result<Number, FuncError>,
80        local: bool,
81    ) -> Result<&mut Self, SymbolError> {
82        let name = name.into();
83        if self.get_by_name(&name).is_ok() {
84            return Err(SymbolError::DuplicateSymbol(name.to_string()));
85        }
86        self.symbols.push(Symbol::Func {
87            name,
88            args,
89            variadic,
90            callback,
91            description: None,
92            local,
93        });
94        Ok(self)
95    }
96
97    /// Looks up a symbol by name (case-insensitive).
98    pub fn get_by_name(&self, name: &str) -> Result<&Symbol, SymbolError> {
99        self.symbols
100            .iter()
101            .find(|sym| sym.name().eq_ignore_ascii_case(name))
102            .ok_or_else(|| SymbolError::SymbolNotFound(name.to_string()))
103    }
104
105    /// Looks up a symbol by name and returns its index and reference (case-insensitive).
106    pub fn get_with_index(&self, name: &str) -> Result<(usize, &Symbol), SymbolError> {
107        self.symbols
108            .iter()
109            .enumerate()
110            .find(|(_, sym)| sym.name().eq_ignore_ascii_case(name))
111            .ok_or_else(|| SymbolError::SymbolNotFound(name.to_string()))
112    }
113
114    /// Returns a symbol by index.
115    pub fn get_by_index(&self, index: usize) -> Result<&Symbol, SymbolError> {
116        self.symbols
117            .get(index)
118            .ok_or_else(|| SymbolError::SymbolNotFound(index.to_string()))
119    }
120
121    /// Returns a symbol by index.
122    pub fn get_mut_by_index(&mut self, index: usize) -> Result<&mut Symbol, SymbolError> {
123        self.symbols
124            .get_mut(index)
125            .ok_or_else(|| SymbolError::SymbolNotFound(index.to_string()))
126    }
127
128    /// Returns an iterator over all symbols in the table.
129    pub fn symbols(&self) -> impl Iterator<Item = &Symbol> {
130        self.symbols.iter()
131    }
132}