diff --git a/flake.lock b/flake.lock index bcf206d..e666a7d 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1736012469, - "narHash": "sha256-/qlNWm/IEVVH7GfgAIyP6EsVZI6zjAx1cV5zNyrs+rI=", + "lastModified": 1739020877, + "narHash": "sha256-mIvECo/NNdJJ/bXjNqIh8yeoSjVLAuDuTUzAo7dzs8Y=", "owner": "nixos", "repo": "nixpkgs", - "rev": "8f3e1f807051e32d8c95cd12b9b421623850a34d", + "rev": "a79cfe0ebd24952b580b1cf08cd906354996d547", "type": "github" }, "original": { diff --git a/src/environment.rs b/src/environment.rs new file mode 100644 index 0000000..19e47fa --- /dev/null +++ b/src/environment.rs @@ -0,0 +1,6 @@ +use std::{collections::HashMap, rc::Rc}; + +use crate::node::NodeEnum; + +pub type EnvironmentInternalSymbolKey = u16; +pub type Environment = HashMap>; diff --git a/src/main.rs b/src/main.rs index 193833e..6e3a82d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,18 @@ -use node::{add::Add, constant::Constant, subtract::Subtract, Environment, Node, NodeEnum}; +use std::io::{self, stdout, BufRead, Write}; mod node; +mod environment; #[cfg(test)] mod tests; fn main() { - let mut env = Environment::new(); - - let a = Constant::new(69.0).into(); - let b = Constant::new(420.0).into(); - let c = Constant::new(360.0).into(); - let d: NodeEnum = Subtract::new(Add::new(a,b).into(), c).into(); - - println!("d_0: {}", d); - println!("d_1: {}", d.evaluate(&mut env).unwrap()); + loop { + print!("> "); + let _ = stdout().flush(); + + let mut line = String::new(); + let stdin = io::stdin(); + stdin.lock().read_line(&mut line).unwrap(); + } } diff --git a/src/node/add.rs b/src/node/add.rs index 844ed6f..c16de87 100644 --- a/src/node/add.rs +++ b/src/node/add.rs @@ -1,34 +1,36 @@ -use super::{Node, NodeEnum, Precedence, constant::Constant}; +use std::rc::Rc; -#[derive(Clone, Debug, PartialEq)] +use super::{constant::Constant, Environment, Node, NodeEnum, Precedence}; + +#[derive(Clone, Debug, PartialEq, PartialOrd)] pub struct Add { - left: Box, - right: Box, + left: Rc, + right: Rc, } impl Node for Add { - fn evaluate(&self, env: &mut super::Environment) -> Result { + fn evaluate(&self, env: &mut Environment) -> Result, String> { let evaluated_left = self.left.evaluate(env)?; let evaluated_right = self.right.evaluate(env)?; - match (evaluated_left, evaluated_right) { + match (evaluated_left.as_ref(), evaluated_right.as_ref()) { (NodeEnum::Constant(a), NodeEnum::Constant(b)) => { - Ok(Constant::new(a.get_value() + b.get_value()).into()) + Ok(Rc::new(Constant::new(a.get_value() + b.get_value()).into())) } _ => Err(format!("Invalid Add operation: {:?}", self)), } } - fn to_string(&self) -> String { + fn as_string(&self, env: Option<&Environment>) -> String { let left_string = if self.left.precedence() <= self.precedence() { - format!("({})", self.left.to_string()) + format!("({})", self.left.as_string(env)) } else { - self.left.to_string() + self.left.as_string(env) }; let right_string = if self.right.precedence() <= self.precedence() { - format!("({})", self.right.to_string()) + format!("({})", self.right.as_string(env)) } else { - self.right.to_string() + self.right.as_string(env) }; format!("{}+{}", left_string, right_string) } @@ -41,8 +43,8 @@ impl Node for Add { impl Add { pub fn new(left: NodeEnum, right: NodeEnum) -> Self { Self { - left: Box::new(left), - right: Box::new(right), + left: Rc::new(left), + right: Rc::new(right), } } } diff --git a/src/node/assign.rs b/src/node/assign.rs new file mode 100644 index 0000000..c73e24c --- /dev/null +++ b/src/node/assign.rs @@ -0,0 +1,28 @@ +use std::rc::Rc; + +use super::{empty::Empty, symbol::Symbol, Environment, Node, NodeEnum, Precedence}; + +#[derive(Debug, Clone, PartialEq, PartialOrd)] +pub struct Assign { + left: Symbol, + right: Rc, +} + +impl Node for Assign { + fn evaluate(&self, env: &mut Environment) -> Result, String> { + env.insert(self.left.get_value(), self.right.clone()); + Ok(Empty::EMPTY.clone()) + } + + fn as_string(&self, env: Option<&Environment>) -> String { + format!( + "{} := {}", + self.left.as_string(env), + self.right.as_string(env) + ) + } + + fn precedence(&self) -> Precedence { + Precedence::Assign + } +} diff --git a/src/node/call.rs b/src/node/call.rs new file mode 100644 index 0000000..16c9f02 --- /dev/null +++ b/src/node/call.rs @@ -0,0 +1,60 @@ +use std::rc::Rc; + +use crate::environment::Environment; + +use super::{Node, NodeEnum, Precedence, symbol::Symbol}; + +#[derive(Debug, Clone, PartialEq, PartialOrd)] +pub struct Call { + left: Rc, + right: Vec>, +} + +impl Node for Call { + fn evaluate(&self, env: &mut Environment) -> Result, String> { + todo!(); + let function = match self.left.as_ref() { + NodeEnum::Symbol(symbol) => match symbol.evaluate(env)?.as_ref() { + _ => { + return Err(format!( + "Cannot call {} as a function", + symbol.as_string(Some(env)) + )); + } + + NodeEnum::Function(function) => function, + }, + NodeEnum::Function(function) => function, + NodeEnum::Call(call) => match call.evaluate(env)?.as_ref() { + NodeEnum::Function(function) => function, + + // FIXME: This might fail for long chains of calls + _ => { + return Err(format!( + "Cannot call {} as a function", + self.left.as_string(None) + )); + } + }, + _ => { + return Err(format!( + "Cannot call {} as a function", + self.left.as_string(None) + )); + } + }; + + // match function { + // + // } + todo!() + } + + fn as_string(&self, _: Option<&Environment>) -> String { + todo!() + } + + fn precedence(&self) -> Precedence { + Precedence::Call + } +} diff --git a/src/node/constant.rs b/src/node/constant.rs index f9d3465..c1dd79c 100644 --- a/src/node/constant.rs +++ b/src/node/constant.rs @@ -1,18 +1,18 @@ -use std::fmt::Display; +use std::rc::Rc; -use super::{Node, NodeEnum, Precedence}; +use super::{Environment, Node, Precedence}; -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, PartialOrd)] pub struct Constant { value: f64, } impl Node for Constant { - fn evaluate(&self, _: &mut super::Environment) -> Result { - Ok(self.clone().into()) + fn evaluate(&self, _: &mut super::Environment) -> Result, String> { + Ok(Rc::new(self.clone().into())) } - fn to_string(&self) -> String { + fn as_string(&self, _env: Option<&Environment>) -> String { self.value.to_string() } diff --git a/src/node/divide.rs b/src/node/divide.rs new file mode 100644 index 0000000..8be41f7 --- /dev/null +++ b/src/node/divide.rs @@ -0,0 +1,50 @@ +use std::rc::Rc; + +use super::{Node, NodeEnum, Precedence, constant::Constant}; + +#[derive(Clone, Debug, PartialEq, PartialOrd)] +pub struct Divide { + left: Rc, + right: Rc, +} + +impl Node for Divide { + fn evaluate(&self, env: &mut super::Environment) -> Result, String> { + let evaluated_left = self.left.evaluate(env)?; + let evaluated_right = self.right.evaluate(env)?; + + match (evaluated_left.as_ref(), evaluated_right.as_ref()) { + (NodeEnum::Constant(a), NodeEnum::Constant(b)) => { + Ok(Rc::new(Constant::new(a.get_value() / b.get_value()).into())) + } + _ => Err(format!("Invalid Divide operation: {:?}", self)), + } + } + + fn as_string(&self, env: Option<&super::Environment>) -> String { + let left_string = if self.left.precedence() <= self.precedence() { + format!("({})", self.left.as_string(env)) + } else { + self.left.as_string(env) + }; + let right_string = if self.right.precedence() <= self.precedence() { + format!("({})", self.right.as_string(env)) + } else { + self.right.as_string(env) + }; + format!("{}/{}", left_string, right_string) + } + + fn precedence(&self) -> Precedence { + Precedence::Factor + } +} + +impl Divide { + pub fn new(left: NodeEnum, right: NodeEnum) -> Self { + Self { + left: Rc::new(left), + right: Rc::new(right), + } + } +} diff --git a/src/node/empty.rs b/src/node/empty.rs new file mode 100644 index 0000000..a13607c --- /dev/null +++ b/src/node/empty.rs @@ -0,0 +1,23 @@ +use std::{rc::Rc, sync::LazyLock}; + +use super::{Environment, Node, NodeEnum, Precedence}; + +#[derive(Debug, Clone, PartialEq, PartialOrd)] +pub struct Empty; +impl Node for Empty { + fn evaluate(&self, _: &mut Environment) -> Result, String> { + Ok(Empty::EMPTY.clone()) + } + + fn as_string(&self, _: Option<&Environment>) -> String { + String::from("{{#VOID}}") + } + + fn precedence(&self) -> Precedence { + Precedence::Empty + } +} + +impl Empty { + pub const EMPTY: LazyLock> = LazyLock::new(|| Rc::new(Empty{}.into())); +} diff --git a/src/node/function.rs b/src/node/function.rs new file mode 100644 index 0000000..64d8bf4 --- /dev/null +++ b/src/node/function.rs @@ -0,0 +1,53 @@ +use std::rc::Rc; + +use crate::environment::Environment; + +use super::{Node, NodeEnum, Precedence, symbol::Symbol}; + +#[derive(Debug, Clone, PartialEq, PartialOrd)] +pub enum FunctionType { + Native( + &'static str, + fn(Vec>) -> Result, String>, + ), + UserFunction(Rc), +} + +#[derive(Debug, Clone, PartialEq, PartialOrd)] +pub struct Function { + function: FunctionType, + // TODO: Finish reasoning about whether or not this is + // the right way to implement functions + arguments: Vec, +} + +impl Node for Function { + fn evaluate(&self, _: &mut Environment) -> Result, String> { + Ok(Rc::new(self.clone().into())) + } + + fn as_string(&self, env: Option<&Environment>) -> String { + let args = self + .arguments + .iter() + .map(|x| Node::as_string(x, None)) + .reduce(|acc, e| format!("{acc}, {e}")) + .unwrap_or("()".to_owned()); + + match &self.function { + FunctionType::Native(name, _) => name.to_owned().to_owned(), + FunctionType::UserFunction(body) => format!("({args} -> {})", body.as_string(env)), + } + } + + fn precedence(&self) -> Precedence { + Precedence::Call + } +} + +impl Function { + // TODO: Implement new. + // pub fn new(t: FunctionType, ) -> Self { + // + // } +} diff --git a/src/node/mod.rs b/src/node/mod.rs index e6a46be..5df0df3 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -1,41 +1,71 @@ -use std::{cmp::Ordering, collections::HashMap, fmt::Display}; +use std::{collections::HashMap, fmt::Display, rc::Rc}; use add::Add; +use assign::Assign; +use call::Call; use constant::Constant; +use divide::Divide; +use empty::Empty; use enum_dispatch::enum_dispatch; +use function::Function; +use multiply::Multiply; +use node_ref::NodeRef; use subtract::Subtract; +use symbol::Symbol; + +use crate::environment::Environment; -pub mod constant; pub mod add; +pub mod constant; +pub mod divide; +pub mod multiply; pub mod subtract; +pub mod symbol; +mod node_ref; +mod assign; +mod empty; +mod function; +mod call; #[enum_dispatch] #[enum_dispatch(Debug, Clone, PartialEq, PartialOrd, ToString)] -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, PartialOrd)] pub enum NodeEnum { - Constant(Constant), - Add(Add), - Subtract(Subtract) + Constant, + Add, + Subtract, + Multiply, + Divide, + Symbol, + // NodeRef, // DEPRECATED, use Symbol + Assign, + Empty, + Function, + Call } #[derive(PartialEq, Eq, PartialOrd, Ord)] pub enum Precedence { + Empty, + Assign, + Compare, + Equality, Term, Factor, + Unary, + Call, Primary, } -pub type Environment = HashMap; - #[enum_dispatch(NodeEnum)] pub trait Node { - fn evaluate(&self, _: &mut Environment) -> Result; - fn to_string(&self) -> String; + fn evaluate(&self, _: &mut Environment) -> Result, String>; + fn as_string(&self, _: Option<&Environment>) -> String; fn precedence(&self) -> Precedence; } impl Display for NodeEnum { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", Node::to_string(self)) + write!(f, "{}", Node::as_string(self, None)) } } diff --git a/src/node/multiply.rs b/src/node/multiply.rs new file mode 100644 index 0000000..6304daf --- /dev/null +++ b/src/node/multiply.rs @@ -0,0 +1,50 @@ +use std::rc::Rc; + +use super::{Node, NodeEnum, Precedence, constant::Constant}; + +#[derive(Clone, Debug, PartialEq, PartialOrd)] +pub struct Multiply { + left: Rc, + right: Rc, +} + +impl Node for Multiply { + fn evaluate(&self, env: &mut super::Environment) -> Result, String> { + let evaluated_left = self.left.evaluate(env)?; + let evaluated_right = self.right.evaluate(env)?; + + match (evaluated_left.as_ref(), evaluated_right.as_ref()) { + (NodeEnum::Constant(a), NodeEnum::Constant(b)) => { + Ok(Rc::new(Constant::new(a.get_value() * b.get_value()).into())) + } + _ => Err(format!("Invalid Multiply operation: {:?}", self)), + } + } + + fn as_string(&self, env: Option<&super::Environment>) -> String { + let left_string = if self.left.precedence() <= self.precedence() { + format!("({})", self.left.as_string(env)) + } else { + self.left.as_string(env) + }; + let right_string = if self.right.precedence() <= self.precedence() { + format!("({})", self.right.as_string(env)) + } else { + self.right.as_string(env) + }; + format!("{}*{}", left_string, right_string) + } + + fn precedence(&self) -> Precedence { + Precedence::Factor + } +} + +impl Multiply { + pub fn new(left: NodeEnum, right: NodeEnum) -> Self { + Self { + left: Rc::new(left), + right: Rc::new(right), + } + } +} diff --git a/src/node/node_ref.rs b/src/node/node_ref.rs new file mode 100644 index 0000000..2ab92e1 --- /dev/null +++ b/src/node/node_ref.rs @@ -0,0 +1,30 @@ +use std::rc::Rc; + +use super::{Environment, Node, NodeEnum, Precedence}; + +#[derive(Debug, Clone, PartialEq, PartialOrd)] +pub struct NodeRef { + value: Rc, +} + +impl Node for NodeRef { + fn evaluate(&self, env: &mut Environment) -> Result, String> { + self.value.evaluate(env) + } + + fn as_string(&self, env: Option<&Environment>) -> String { + self.value.as_string(env) + } + + fn precedence(&self) -> Precedence { + self.value.precedence() + } +} + +impl NodeRef { + pub fn new(value: &Rc) -> Self { + Self { + value: value.clone(), + } + } +} diff --git a/src/node/subtract.rs b/src/node/subtract.rs index e4524b4..e5498b1 100644 --- a/src/node/subtract.rs +++ b/src/node/subtract.rs @@ -1,34 +1,36 @@ -use super::{Node, NodeEnum, Precedence, constant::Constant}; +use std::rc::Rc; -#[derive(Clone, Debug, PartialEq)] +use super::{constant::Constant, Environment, Node, NodeEnum, Precedence}; + +#[derive(Clone, Debug, PartialEq, PartialOrd)] pub struct Subtract { - left: Box, - right: Box, + left: Rc, + right: Rc, } impl Node for Subtract { - fn evaluate(&self, env: &mut super::Environment) -> Result { + fn evaluate(&self, env: &mut super::Environment) -> Result, String> { let evaluated_left = self.left.evaluate(env)?; let evaluated_right = self.right.evaluate(env)?; - match (evaluated_left, evaluated_right) { + match (evaluated_left.as_ref(), evaluated_right.as_ref()) { (NodeEnum::Constant(a), NodeEnum::Constant(b)) => { - Ok(Constant::new(a.get_value() - b.get_value()).into()) + Ok(Rc::new(Constant::new(a.get_value() - b.get_value()).into())) } _ => Err(format!("Invalid Add operation: {:?}", self)), } } - fn to_string(&self) -> String { + fn as_string(&self, env: Option<&Environment>) -> String { let left_string = if self.left.precedence() <= self.precedence() { - format!("({})", self.left.to_string()) + format!("({})", self.left.as_string(env)) } else { - self.left.to_string() + self.left.as_string(env) }; let right_string = if self.right.precedence() <= self.precedence() { - format!("({})", self.right.to_string()) + format!("({})", self.right.as_string(env)) } else { - self.right.to_string() + self.right.as_string(env) }; format!("{}-{}", left_string, right_string) } @@ -41,8 +43,8 @@ impl Node for Subtract { impl Subtract { pub fn new(left: NodeEnum, right: NodeEnum) -> Self { Self { - left: Box::new(left), - right: Box::new(right), + left: Rc::new(left), + right: Rc::new(right), } } } diff --git a/src/node/symbol.rs b/src/node/symbol.rs new file mode 100644 index 0000000..b987569 --- /dev/null +++ b/src/node/symbol.rs @@ -0,0 +1,86 @@ +use std::{ + collections::HashMap, + rc::Rc, + sync::{LazyLock, Mutex}, +}; + +use super::{Environment, Node, NodeEnum, Precedence, node_ref::NodeRef}; +use crate::environment::EnvironmentInternalSymbolKey; + +#[derive(Debug, Clone, PartialEq, PartialOrd)] +pub struct Symbol { + value: EnvironmentInternalSymbolKey, +} + +impl Node for Symbol { + fn evaluate(&self, env: &mut Environment) -> Result, String> { + if let Some(value) = env.get(&self.value) { + // TODO: Detect cyclic references and make sure they get + // deleted at some point + Ok(value.clone()) + } else { + Ok(Rc::new(self.clone().into())) + } + } + + fn as_string(&self, env: Option<&Environment>) -> String { + if let Some(env) = env { + if let Some(value) = env.get(&self.value) { + value.as_string(Some(env)) + } else { + Symbol::ID_TO_STR_MAP + .lock() + .unwrap() + .get(&self.value) + .unwrap_or(&format!("{{#SYMBOL {}}}", self.value)) + .clone() + } + } else { + Symbol::ID_TO_STR_MAP + .lock() + .unwrap() + .get(&self.value) + .unwrap_or(&format!("{{#SYMBOL {}}}", self.value)) + .clone() + } + } + + fn precedence(&self) -> Precedence { + Precedence::Primary + } +} + +impl Symbol { + const STR_TO_ID_MAP: LazyLock>> = + LazyLock::new(|| HashMap::new().into()); + const ID_TO_STR_MAP: LazyLock>> = + LazyLock::new(|| HashMap::new().into()); + + pub fn new(value: EnvironmentInternalSymbolKey) -> Self { + Self { value } + } + + pub fn new_from_str(str: String) -> Self { + if let Some(value) = Symbol::STR_TO_ID_MAP.lock().unwrap().get(str.as_str()) { + Self::new(value.clone()) + } else { + let id = Symbol::STR_TO_ID_MAP + .lock() + .unwrap() + .len() + .try_into() + .unwrap(); + Symbol::STR_TO_ID_MAP + .lock() + .unwrap() + .insert(str.clone(), id); + Symbol::ID_TO_STR_MAP.lock().unwrap().insert(id, str); + + Self::new(id) + } + } + + pub fn get_value(&self) -> EnvironmentInternalSymbolKey { + self.value + } +} diff --git a/src/tests/mod.rs b/src/tests/mod.rs index 0c981ff..9883234 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1,7 +1,10 @@ mod arithmetic { - use crate::node::{ - Environment, Node, NodeEnum, add::Add, constant::Constant, subtract::Subtract, - }; + use std::rc::Rc; + + use crate::{environment, node::{ + add::Add, constant::Constant, divide::Divide, multiply::Multiply, subtract::Subtract, Node, NodeEnum + }}; + use environment::Environment; use test_case::test_case; @@ -17,7 +20,7 @@ mod arithmetic { let b = Constant::new(b).into(); let d: NodeEnum = Add::new(a, b).into(); - let value = d.evaluate(&mut env).unwrap(); + let value = Rc::::try_unwrap(d.evaluate(&mut env).unwrap()).unwrap(); assert!( value == Constant::new(e).into(), @@ -40,7 +43,7 @@ mod arithmetic { let c = Constant::new(bb).into(); let d: NodeEnum = Subtract::new(Add::new(a, b).into(), c).into(); - let value = d.evaluate(&mut env).unwrap(); + let value = Rc::::try_unwrap(d.evaluate(&mut env).unwrap()).unwrap(); assert!( value == Constant::new(e).into(), @@ -55,28 +58,72 @@ mod arithmetic { #[test_case(5.0, -10.0, -50.0 ; "when right is negative")] #[test_case(-5.0, -10.0, 50.0 ; "when both are negative")] #[test_case(2734589235234.23, 0.0, 0.0 ; "when 0 is involved")] - fn multiplication(a: f64, b: f64, e: f64) {} + fn multiplication(a: f64, b: f64, e: f64) { + let mut env = Environment::new(); + + let a = Constant::new(a).into(); + let b = Constant::new(b).into(); + let d: NodeEnum = Multiply::new(a, b).into(); + + let value = Rc::::try_unwrap(d.evaluate(&mut env).unwrap()).unwrap(); + + assert!( + value == Constant::new(e).into(), + "Expected {} got {}", + e, + value + ) + } #[test_case(5.0, 10.0, 0.5 ; "when both are positive")] #[test_case(-5.0, 10.0, -0.5 ; "when left is negative")] #[test_case(5.0, -10.0, -0.5 ; "when right is negative")] #[test_case(-5.0, -10.0, 0.5 ; "when both are negative")] - fn division(a: f64, b: f64, e: f64) {} + fn division(a: f64, b: f64, e: f64) { + let mut env = Environment::new(); + + let a = Constant::new(a).into(); + let b = Constant::new(b).into(); + let d: NodeEnum = Divide::new(a, b).into(); + + let value = Rc::::try_unwrap(d.evaluate(&mut env).unwrap()).unwrap(); + + assert!( + value == Constant::new(e).into(), + "Expected {} got {}", + e, + value + ) + } +} + +mod functions { + #[test] + fn stringification() { + // let f = Function + } } mod expected_errors { use test_case::test_case; #[test_case(5.0, 0.0 ; "divide by zero")] - fn division(a: f64, b: f64) {} + fn division(a: f64, b: f64) { + let _ = a+b; + } } mod misc { - use crate::node::{ - Environment, Node, NodeEnum, add::Add, constant::Constant, subtract::Subtract, - }; + use test_case::test_case; - #[test_case(30, '+', 60, '-', 20, "(30+60)-20", "add and subtract")] - fn convert_to_string(a: into, op1: char, b: Into, op2: char, c: Into, e: String) { + #[test_case(30, '+', 60, '-', 20, "(30+60)-20" ; "add and subtract")] + fn convert_to_string( + a: impl Into, + op1: char, + b: impl Into, + op2: char, + c: impl Into, + e: &'static str, + ) { } }