From 84a4689a76acb414942be0664c2bbe18205f9d75 Mon Sep 17 00:00:00 2001 From: Snorre Date: Tue, 11 Feb 2025 19:27:56 +0100 Subject: [PATCH] initial commit --- .gitignore | 1 + Cargo.lock | 103 +++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 8 ++++ flake.lock | 48 ++++++++++++++++++++ flake.nix | 34 ++++++++++++++ src/main.rs | 18 ++++++++ src/node/add.rs | 48 ++++++++++++++++++++ src/node/constant.rs | 36 +++++++++++++++ src/node/mod.rs | 41 +++++++++++++++++ src/node/subtract.rs | 48 ++++++++++++++++++++ src/tests/mod.rs | 82 ++++++++++++++++++++++++++++++++++ 11 files changed, 467 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 src/main.rs create mode 100644 src/node/add.rs create mode 100644 src/node/constant.rs create mode 100644 src/node/mod.rs create mode 100644 src/node/subtract.rs create mode 100644 src/tests/mod.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..1277465 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,103 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "once_cell" +version = "1.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e" + +[[package]] +name = "openbirch-rs" +version = "0.1.0" +dependencies = [ + "enum_dispatch", + "test-case", +] + +[[package]] +name = "proc-macro2" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "test-case" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8" +dependencies = [ + "test-case-macros", +] + +[[package]] +name = "test-case-core" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "test-case-macros" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "test-case-core", +] + +[[package]] +name = "unicode-ident" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e6fe43e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "openbirch-rs" +version = "0.1.0" +edition = "2024" + +[dependencies] +enum_dispatch = "0.3.13" +test-case = "3.3.1" diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..bcf206d --- /dev/null +++ b/flake.lock @@ -0,0 +1,48 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1736012469, + "narHash": "sha256-/qlNWm/IEVVH7GfgAIyP6EsVZI6zjAx1cV5zNyrs+rI=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "8f3e1f807051e32d8c95cd12b9b421623850a34d", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs", + "rust-overlay": "rust-overlay" + } + }, + "rust-overlay": { + "inputs": { + "nixpkgs": [ + "nixpkgs" + ] + }, + "locked": { + "lastModified": 1736216977, + "narHash": "sha256-EMueGrzBpryM8mgOyoyJ7DdNRRk09ug1ggcLLp0WrCQ=", + "owner": "oxalica", + "repo": "rust-overlay", + "rev": "bbe7e4e7a70d235db4bbdcabbf8a2f6671881dd7", + "type": "github" + }, + "original": { + "owner": "oxalica", + "repo": "rust-overlay", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..9f72bb4 --- /dev/null +++ b/flake.nix @@ -0,0 +1,34 @@ +{ + description = "Openbirch rust flake"; + + inputs = { + nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable"; + rust-overlay = { + url = "github:oxalica/rust-overlay"; + inputs.nixpkgs.follows = "nixpkgs"; + }; + }; + + outputs = { self, nixpkgs, rust-overlay }: + let + # rust-overlay = import rust-overlay; + pkgs = import nixpkgs + { + system = "x86_64-linux"; + overlays = [ rust-overlay.overlays.default ]; + }; + in + { + devShells.x86_64-linux.default = pkgs.mkShell { + buildInputs = with pkgs; + [ + rust-bin.nightly.latest.default + rust-analyzer + ]; + + shell = '' + echo "Hello from nix dev shell" + ''; + }; + }; +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..193833e --- /dev/null +++ b/src/main.rs @@ -0,0 +1,18 @@ +use node::{add::Add, constant::Constant, subtract::Subtract, Environment, Node, NodeEnum}; + +mod node; + +#[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()); +} diff --git a/src/node/add.rs b/src/node/add.rs new file mode 100644 index 0000000..844ed6f --- /dev/null +++ b/src/node/add.rs @@ -0,0 +1,48 @@ +use super::{Node, NodeEnum, Precedence, constant::Constant}; + +#[derive(Clone, Debug, PartialEq)] +pub struct Add { + left: Box, + right: Box, +} + +impl Node for Add { + fn evaluate(&self, env: &mut super::Environment) -> Result { + let evaluated_left = self.left.evaluate(env)?; + let evaluated_right = self.right.evaluate(env)?; + + match (evaluated_left, evaluated_right) { + (NodeEnum::Constant(a), NodeEnum::Constant(b)) => { + Ok(Constant::new(a.get_value() + b.get_value()).into()) + } + _ => Err(format!("Invalid Add operation: {:?}", self)), + } + } + + fn to_string(&self) -> String { + let left_string = if self.left.precedence() <= self.precedence() { + format!("({})", self.left.to_string()) + } else { + self.left.to_string() + }; + let right_string = if self.right.precedence() <= self.precedence() { + format!("({})", self.right.to_string()) + } else { + self.right.to_string() + }; + format!("{}+{}", left_string, right_string) + } + + fn precedence(&self) -> Precedence { + Precedence::Term + } +} + +impl Add { + pub fn new(left: NodeEnum, right: NodeEnum) -> Self { + Self { + left: Box::new(left), + right: Box::new(right), + } + } +} diff --git a/src/node/constant.rs b/src/node/constant.rs new file mode 100644 index 0000000..f9d3465 --- /dev/null +++ b/src/node/constant.rs @@ -0,0 +1,36 @@ +use std::fmt::Display; + +use super::{Node, NodeEnum, Precedence}; + +#[derive(Clone, Debug, PartialEq)] +pub struct Constant { + value: f64, +} + +impl Node for Constant { + fn evaluate(&self, _: &mut super::Environment) -> Result { + Ok(self.clone().into()) + } + + fn to_string(&self) -> String { + self.value.to_string() + } + + fn precedence(&self) -> Precedence { + Precedence::Primary + } +} + +impl Constant { + pub fn new(value: f64) -> Self { + Self { value } + } + + pub fn get_value(&self) -> f64 { + self.value + } + + pub fn set_value(&mut self, value: f64) { + self.value = value; + } +} diff --git a/src/node/mod.rs b/src/node/mod.rs new file mode 100644 index 0000000..e6a46be --- /dev/null +++ b/src/node/mod.rs @@ -0,0 +1,41 @@ +use std::{cmp::Ordering, collections::HashMap, fmt::Display}; + +use add::Add; +use constant::Constant; +use enum_dispatch::enum_dispatch; +use subtract::Subtract; + +pub mod constant; +pub mod add; +pub mod subtract; + +#[enum_dispatch] +#[enum_dispatch(Debug, Clone, PartialEq, PartialOrd, ToString)] +#[derive(Debug, Clone, PartialEq)] +pub enum NodeEnum { + Constant(Constant), + Add(Add), + Subtract(Subtract) +} + +#[derive(PartialEq, Eq, PartialOrd, Ord)] +pub enum Precedence { + Term, + Factor, + Primary, +} + +pub type Environment = HashMap; + +#[enum_dispatch(NodeEnum)] +pub trait Node { + fn evaluate(&self, _: &mut Environment) -> Result; + fn to_string(&self) -> 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)) + } +} diff --git a/src/node/subtract.rs b/src/node/subtract.rs new file mode 100644 index 0000000..e4524b4 --- /dev/null +++ b/src/node/subtract.rs @@ -0,0 +1,48 @@ +use super::{Node, NodeEnum, Precedence, constant::Constant}; + +#[derive(Clone, Debug, PartialEq)] +pub struct Subtract { + left: Box, + right: Box, +} + +impl Node for Subtract { + fn evaluate(&self, env: &mut super::Environment) -> Result { + let evaluated_left = self.left.evaluate(env)?; + let evaluated_right = self.right.evaluate(env)?; + + match (evaluated_left, evaluated_right) { + (NodeEnum::Constant(a), NodeEnum::Constant(b)) => { + Ok(Constant::new(a.get_value() - b.get_value()).into()) + } + _ => Err(format!("Invalid Add operation: {:?}", self)), + } + } + + fn to_string(&self) -> String { + let left_string = if self.left.precedence() <= self.precedence() { + format!("({})", self.left.to_string()) + } else { + self.left.to_string() + }; + let right_string = if self.right.precedence() <= self.precedence() { + format!("({})", self.right.to_string()) + } else { + self.right.to_string() + }; + format!("{}-{}", left_string, right_string) + } + + fn precedence(&self) -> Precedence { + Precedence::Term + } +} + +impl Subtract { + pub fn new(left: NodeEnum, right: NodeEnum) -> Self { + Self { + left: Box::new(left), + right: Box::new(right), + } + } +} diff --git a/src/tests/mod.rs b/src/tests/mod.rs new file mode 100644 index 0000000..0c981ff --- /dev/null +++ b/src/tests/mod.rs @@ -0,0 +1,82 @@ +mod arithmetic { + use crate::node::{ + Environment, Node, NodeEnum, add::Add, constant::Constant, subtract::Subtract, + }; + + use test_case::test_case; + + #[test_case(69.0, 420.0, 489.0 ; "when both are positive")] + #[test_case(-2.0, -4.0, -6.0 ; "when both are negative")] + #[test_case(0.0, 0.0, 0.0 ; "when both are zero")] + #[test_case(f64::INFINITY, 0.0, f64::INFINITY ; "infinity")] + // #[test_case(f64::NAN, 0.0, f64::NAN ; "NaN")] // cant test NaN because NaN != NaN + fn addition(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 = Add::new(a, b).into(); + + let value = d.evaluate(&mut env).unwrap(); + + assert!( + value == Constant::new(e).into(), + "Expected {} got {}", + e, + value + ) + } + + #[test_case(69.0, 420.0, -351.0 ; "when both are positive")] + #[test_case(-2.0, -4.0, 2.0 ; "when both are negative")] + #[test_case(0.0, 0.0, 0.0 ; "when both are zero")] + #[test_case(f64::INFINITY, 0.0, f64::INFINITY ; "infinity")] + // #[test_case(f64::NAN, 0.0, f64::NAN ; "NaN")] // cant test NaN because NaN != NaN + fn subtraction(aa: f64, bb: f64, e: f64) { + let mut env = Environment::new(); + + let a = Constant::new(0.0).into(); + let b = Constant::new(aa).into(); + 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(); + + assert!( + value == Constant::new(e).into(), + "Expected {} got {}", + e, + value + ) + } + + #[test_case(5.0, 10.0, 50.0 ; "when both are positive")] + #[test_case(-5.0, 10.0, -50.0 ; "when left is negative")] + #[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) {} + + #[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) {} +} + +mod expected_errors { + use test_case::test_case; + + #[test_case(5.0, 0.0 ; "divide by zero")] + fn division(a: f64, b: f64) {} +} + +mod misc { + use crate::node::{ + Environment, Node, NodeEnum, add::Add, constant::Constant, subtract::Subtract, + }; + + #[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) { + } +}