commit e1c73eb919e9ec1bd015191b3b30d790c60d90c6 Author: Oliver Date: Sun Nov 9 22:46:16 2025 -0700 init diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..9a30716 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,14 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "evalexpr" +version = "13.0.0" + +[[package]] +name = "sc_rs" +version = "0.1.0" +dependencies = [ + "evalexpr", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..7963f0d --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "sc_rs" +version = "0.1.0" +edition = "2024" + +[dependencies] +# evalexpr = "13.0.0" +evalexpr = { path='../evalexpr' } diff --git a/README.md b/README.md new file mode 100644 index 0000000..30bfd46 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# SC_RS + +Based loosely off sc-im, which has dumb keybinds and not many features. diff --git a/src/ctx.rs b/src/ctx.rs new file mode 100644 index 0000000..b630343 --- /dev/null +++ b/src/ctx.rs @@ -0,0 +1,154 @@ +use std::{collections::HashMap, rc::Rc}; + +use evalexpr::{error::EvalexprResultValue, *}; + +use crate::Grid; + +pub struct CallbackContext<'a, NumericTypes: EvalexprNumericTypes = DefaultNumericTypes> { + variables: Rc<&'a Grid>, + functions: HashMap>, + + /// True if builtin functions are disabled. + without_builtin_functions: bool, +} + +impl<'a, NumericTypes: EvalexprNumericTypes> CallbackContext<'a, NumericTypes> { + /// Constructs a `HashMapContext` with no mappings. + pub fn new(grid: Rc<&'a Grid>) -> Self { + Self { + variables: grid, + functions: Default::default(), + without_builtin_functions: false, + } + } + + /// Removes all variables from the context. + /// This allows to reuse the context without allocating a new HashMap. + /// + /// # Example + /// + /// ```rust + /// # use evalexpr::*; + /// + /// let mut context = HashMapContext::::new(); + /// context.set_value("abc".into(), "def".into()).unwrap(); + /// assert_eq!(context.get_value("abc"), Some(&("def".into()))); + /// context.clear_variables(); + /// assert_eq!(context.get_value("abc"), None); + /// ``` + pub fn clear_variables(&mut self) { + () + } + + /// Removes all functions from the context. + /// This allows to reuse the context without allocating a new HashMap. + pub fn clear_functions(&mut self) { + self.functions.clear() + } + + /// Removes all variables and functions from the context. + /// This allows to reuse the context without allocating a new HashMap. + /// + /// # Example + /// + /// ```rust + /// # use evalexpr::*; + /// + /// let mut context = HashMapContext::::new(); + /// context.set_value("abc".into(), "def".into()).unwrap(); + /// assert_eq!(context.get_value("abc"), Some(&("def".into()))); + /// context.clear(); + /// assert_eq!(context.get_value("abc"), None); + /// ``` + pub fn clear(&mut self) { + self.clear_variables(); + self.clear_functions(); + } +} + +impl<'a, NumericTypes: EvalexprNumericTypes> Context for CallbackContext<'a, NumericTypes> { + type NumericTypes = NumericTypes; + + fn get_value(&self, identifier: &str) -> Option<&Value> { + return Some(4.); + } + + fn call_function( + &self, + identifier: &str, + argument: &Value, + ) -> EvalexprResultValue { + todo!() + } + + fn are_builtin_functions_disabled(&self) -> bool { + self.without_builtin_functions + } + + fn set_builtin_functions_disabled( + &mut self, + disabled: bool, + ) -> EvalexprResult<(), NumericTypes> { + self.without_builtin_functions = disabled; + Ok(()) + } +} + +impl<'a, NumericTypes: EvalexprNumericTypes> ContextWithMutableVariables + for CallbackContext<'a, NumericTypes> +{ + fn set_value( + &mut self, + identifier: String, + value: Value, + ) -> EvalexprResult<(), NumericTypes> { + Ok(()) + } + + fn remove_value( + &mut self, + identifier: &str, + ) -> EvalexprResult>, Self::NumericTypes> { + // Removes a value from the `self.variables`, returning the value at the key if the key was previously in the map. + // Ok(self.variables.remove(identifier)) + todo!(); + } +} + +impl<'a, NumericTypes: EvalexprNumericTypes> ContextWithMutableFunctions + for CallbackContext<'a, NumericTypes> +{ + fn set_function( + &mut self, + identifier: String, + function: Function, + ) -> EvalexprResult<(), Self::NumericTypes> { + self.functions.insert(identifier, function); + Ok(()) + } +} + +impl<'b, NumericTypes: EvalexprNumericTypes> IterateVariablesContext for CallbackContext<'b, NumericTypes> { + type VariableIterator<'a> + = std::iter::Map< + std::collections::hash_map::Iter<'a, String, Value>, + fn((&String, &Value)) -> (String, Value), + > + where + Self: 'a; + type VariableNameIterator<'a> + = std::iter::Cloned>> + where + Self: 'a; + + fn iter_variables(&self) -> Self::VariableIterator<'_> { + todo!() + // self.variables.iter().map(|(string, value)| (string.clone(), value.clone())) + } + + fn iter_variable_names(&self) -> Self::VariableNameIterator<'_> { + todo!() + // self.variables.keys().cloned() + } +} + diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..b83f640 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,205 @@ +mod ctx; + +use std::rc::Rc; + +use evalexpr::*; + +// if this is very large at all it will overflow the stack +const LEN: usize = 100; + +struct Grid { + // a b c ... + // 0 + // 1 + // 2 + // ... + cells: [[Option>; LEN]; LEN], +} + +impl std::fmt::Debug for Grid { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Grid").field("cells", &"Too many to print").finish() + } +} + +impl Grid { + fn new() -> Self { + let b: [[Option>; LEN]; LEN] = + core::array::from_fn(|_| core::array::from_fn(|_| None)); + + Self { cells: b } + } + + fn eval(&self, mut eq: &str) -> f64 { + if eq.starts_with('=') { + eq = &eq[1..]; + } + + let mut ctx = ctx::CallbackContext::::new(Rc::new(self)); + // let mut ctx = HashMapContext::::new(); + + let val; + loop { + match eval_with_context(eq, &ctx) { + Ok(e) => { + val = e.as_float().expect("Should be float"); + break; + } + Err(e) => match e { + // TODO this is kinda a slow way to do this, the equation will get parsed + // multiple times. Might be good to modify the lib so that you can provide + // a callback for variables that are not found. + EvalexprError::VariableIdentifierNotFound(e) => { + panic!("Will not be able to parse this equation, cell {e} not found") + } + _ => panic!("{}", e), + }, + } + } + val + } + + fn parse_to_idx(i: &str) -> (usize, usize) { + let chars = i + .chars() + .take_while(|c| c.is_alphabetic()) + .collect::>(); + let nums = i + .chars() + .skip(chars.len()) + .take_while(|c| c.is_numeric()) + .collect::(); + + // get the x index from the chars + let x_idx = chars + .iter() + .enumerate() + .map(Self::char_to_idx) + .fold(0, |a, b| a + b); + + // get the y index from the numbers + let y_idx = nums + .parse::() + .expect("Got non-number character after sorting for just numeric characters"); + + (x_idx, y_idx) + } + + fn set_cell(&mut self, cell_id: &str, val: Box) { + let (x, y) = Self::parse_to_idx(cell_id); + // TODO check oob + self.cells[x][y] = Some(val); + } + + /// Get cells via text like: + /// A6 + /// F0 + fn get_cell(&self, cell_id: &str) -> &Option> { + let (x, y) = Self::parse_to_idx(cell_id); + // TODO check oob + &self.cells[x][y] + } + + // this function has unit tests + fn char_to_idx((idx, c): (usize, &char)) -> usize { + (c.to_ascii_lowercase() as usize - 97) + 26 * idx + } +} + +impl Default for Grid { + fn default() -> Self { + Self::new() + } +} + +trait Cell { + fn to_string(&self) -> String; + fn can_be_number(&self) -> bool; + fn as_num(&self) -> f32; + fn is_eq(&self) -> bool { + self.to_string().starts_with('=') + } +} + +impl Cell for f32 { + fn to_string(&self) -> String { + ToString::to_string(self) + } + + fn can_be_number(&self) -> bool { + true + } + + fn as_num(&self) -> f32 { + *self + } +} + +impl Cell for &str { + fn to_string(&self) -> String { + ToString::to_string(self) + } + + fn can_be_number(&self) -> bool { + // checking if the string is an equation + self.starts_with('=') + } + + fn as_num(&self) -> f32 { + unimplemented!("&str cannot be used in a numeric context") + } +} + +#[test] +fn test_math() { + let mut grid = Grid::new(); + grid.set_cell("A0", Box::new(2.)); + grid.set_cell("B0", Box::new(1.)); + grid.set_cell("C0", Box::new("=A0+B0")); + + assert_eq!(eval("1+2").unwrap(), Value::Int(3)); + + let disp = &grid.get_cell("C0"); + if let Some(inner) = disp { + if inner.is_eq() { + println!("{}", inner.to_string()); + let display = grid.eval(&inner.to_string()); + assert_eq!(display, 3.); + return; + } + } + panic!("Should've found the value and returned"); +} + +#[test] +fn test_cells() { + let mut grid = Grid::new(); + + assert!(&grid.cells[0][0].is_none()); + grid.set_cell("A0", Box::new("Hello")); + assert!(grid.get_cell("A0").is_some()); + + assert_eq!( + grid.get_cell("A0").as_ref().unwrap().to_string(), + String::from("Hello") + ); +} + +#[test] +fn c_to_i() { + assert_eq!(Grid::char_to_idx((0, &'a')), 0); + assert_eq!(Grid::char_to_idx((0, &'A')), 0); + assert_eq!(Grid::char_to_idx((0, &'z')), 25); + assert_eq!(Grid::char_to_idx((0, &'Z')), 25); + assert_eq!(Grid::char_to_idx((1, &'a')), 26); + + assert_eq!(Grid::parse_to_idx("A0"), (0, 0)); + assert_eq!(Grid::parse_to_idx("AA0"), (26, 0)); + assert_eq!(Grid::parse_to_idx("A1"), (0, 1)); + assert_eq!(Grid::parse_to_idx("A10"), (0, 10)); + assert_eq!(Grid::parse_to_idx("Aa10"), (26, 10)); +} + +fn main() { + println!("Only tests exist atm"); +}