thingymagick

This commit is contained in:
Snorre Ettrup Altschul 2025-02-12 01:50:26 +01:00
parent 84a4689a76
commit 5ab9cc93ac
16 changed files with 538 additions and 71 deletions

View file

@ -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": {

6
src/environment.rs Normal file
View file

@ -0,0 +1,6 @@
use std::{collections::HashMap, rc::Rc};
use crate::node::NodeEnum;
pub type EnvironmentInternalSymbolKey = u16;
pub type Environment = HashMap<EnvironmentInternalSymbolKey, Rc<NodeEnum>>;

View file

@ -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();
}
}

View file

@ -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<NodeEnum>,
right: Box<NodeEnum>,
left: Rc<NodeEnum>,
right: Rc<NodeEnum>,
}
impl Node for Add {
fn evaluate(&self, env: &mut super::Environment) -> Result<super::NodeEnum, String> {
fn evaluate(&self, env: &mut Environment) -> Result<Rc<NodeEnum>, 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),
}
}
}

28
src/node/assign.rs Normal file
View file

@ -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<NodeEnum>,
}
impl Node for Assign {
fn evaluate(&self, env: &mut Environment) -> Result<Rc<NodeEnum>, 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
}
}

60
src/node/call.rs Normal file
View file

@ -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<NodeEnum>,
right: Vec<Rc<NodeEnum>>,
}
impl Node for Call {
fn evaluate(&self, env: &mut Environment) -> Result<Rc<NodeEnum>, 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
}
}

View file

@ -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<super::NodeEnum, String> {
Ok(self.clone().into())
fn evaluate(&self, _: &mut super::Environment) -> Result<Rc<super::NodeEnum>, String> {
Ok(Rc::new(self.clone().into()))
}
fn to_string(&self) -> String {
fn as_string(&self, _env: Option<&Environment>) -> String {
self.value.to_string()
}

50
src/node/divide.rs Normal file
View file

@ -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<NodeEnum>,
right: Rc<NodeEnum>,
}
impl Node for Divide {
fn evaluate(&self, env: &mut super::Environment) -> Result<Rc<super::NodeEnum>, 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),
}
}
}

23
src/node/empty.rs Normal file
View file

@ -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<Rc<NodeEnum>, 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<Rc<NodeEnum>> = LazyLock::new(|| Rc::new(Empty{}.into()));
}

53
src/node/function.rs Normal file
View file

@ -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<Rc<NodeEnum>>) -> Result<Rc<NodeEnum>, String>,
),
UserFunction(Rc<NodeEnum>),
}
#[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<Symbol>,
}
impl Node for Function {
fn evaluate(&self, _: &mut Environment) -> Result<Rc<NodeEnum>, 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 {
//
// }
}

View file

@ -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<u16, NodeEnum>;
#[enum_dispatch(NodeEnum)]
pub trait Node {
fn evaluate(&self, _: &mut Environment) -> Result<NodeEnum, String>;
fn to_string(&self) -> String;
fn evaluate(&self, _: &mut Environment) -> Result<Rc<NodeEnum>, 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))
}
}

50
src/node/multiply.rs Normal file
View file

@ -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<NodeEnum>,
right: Rc<NodeEnum>,
}
impl Node for Multiply {
fn evaluate(&self, env: &mut super::Environment) -> Result<Rc<super::NodeEnum>, 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),
}
}
}

30
src/node/node_ref.rs Normal file
View file

@ -0,0 +1,30 @@
use std::rc::Rc;
use super::{Environment, Node, NodeEnum, Precedence};
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub struct NodeRef {
value: Rc<NodeEnum>,
}
impl Node for NodeRef {
fn evaluate(&self, env: &mut Environment) -> Result<Rc<NodeEnum>, 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<NodeEnum>) -> Self {
Self {
value: value.clone(),
}
}
}

View file

@ -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<NodeEnum>,
right: Box<NodeEnum>,
left: Rc<NodeEnum>,
right: Rc<NodeEnum>,
}
impl Node for Subtract {
fn evaluate(&self, env: &mut super::Environment) -> Result<super::NodeEnum, String> {
fn evaluate(&self, env: &mut super::Environment) -> Result<Rc<super::NodeEnum>, 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),
}
}
}

86
src/node/symbol.rs Normal file
View file

@ -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<Rc<NodeEnum>, 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<Mutex<HashMap<String, EnvironmentInternalSymbolKey>>> =
LazyLock::new(|| HashMap::new().into());
const ID_TO_STR_MAP: LazyLock<Mutex<HashMap<EnvironmentInternalSymbolKey, String>>> =
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
}
}

View file

@ -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::<NodeEnum>::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::<NodeEnum>::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::<NodeEnum>::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::<NodeEnum>::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<f64>, op1: char, b: Into<f64>, op2: char, c: Into<f64>, e: String) {
#[test_case(30, '+', 60, '-', 20, "(30+60)-20" ; "add and subtract")]
fn convert_to_string(
a: impl Into<f64>,
op1: char,
b: impl Into<f64>,
op2: char,
c: impl Into<f64>,
e: &'static str,
) {
}
}