From 818f9afb3c68f38c971812d5a24818ff98fe09ac Mon Sep 17 00:00:00 2001 From: mehbark Date: Wed, 7 Jan 2026 00:26:18 -0500 Subject: [PATCH] mvp, buggy evaluation --- Cargo.lock | 21 ++++++ Cargo.toml | 1 + src/ast.rs | 2 +- src/eval.rs | 202 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 15 ++-- src/val.rs | 99 +++++++++++++++++++++---- 6 files changed, 321 insertions(+), 19 deletions(-) create mode 100644 src/eval.rs diff --git a/Cargo.lock b/Cargo.lock index 903555b..1eb80dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -249,6 +249,7 @@ dependencies = [ "chumsky", "logos", "pretty_assertions", + "thiserror", ] [[package]] @@ -369,6 +370,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-ident" version = "1.0.22" diff --git a/Cargo.toml b/Cargo.toml index 1f26477..8407a69 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ edition = "2024" ariadne = { version = "0.6.0", features = ["auto-color"]} chumsky = { version = "0.12.0", features = ["pratt"] } logos = "0.16.0" +thiserror = "2.0.17" [dev-dependencies] pretty_assertions = "1.4.1" diff --git a/src/ast.rs b/src/ast.rs index 0dccc70..b1d671b 100644 --- a/src/ast.rs +++ b/src/ast.rs @@ -50,7 +50,7 @@ impl fmt::Display for BinOp { } // TODO: great display repr for this for testing (yes it's allowed) -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, PartialOrd)] pub enum Ast { /// `x`, `^x` Var(Ident), diff --git a/src/eval.rs b/src/eval.rs new file mode 100644 index 0000000..53f40d4 --- /dev/null +++ b/src/eval.rs @@ -0,0 +1,202 @@ +use std::collections::HashMap; + +use crate::{ + ast::{Ast, BinOp, Ident}, + map::Map, + symbol::Symbol, + val::{Function, FunctionBody, Val}, +}; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("`{0}` is not in the local environment")] + UndefinedLocalVariable(Symbol), + #[error("`{0}` is not in the return environment")] + UndefinedReturnVariable(Symbol), + #[error("`{0:?}` is not a function")] + CalledNonFunction(Val), + #[error("tried to get `{1:?}` from `{0:?}` (which is not a map)")] + AccessedNonMap(Val, Symbol), +} + +pub type Result = std::result::Result; + +fn eval(expr: &Ast, local_env: &mut Map, return_env: &mut Map) -> Result { + match expr { + Ast::Var(ident) => match ident { + Ident::Local(name) => { + let Some(val) = local_env.get(name) else { + return Err(Error::UndefinedLocalVariable(name.to_owned())); + }; + Ok(val.clone()) + } + Ident::Return(name) => { + let Some(val) = local_env.get(name) else { + return Err(Error::UndefinedReturnVariable(name.to_owned())); + }; + Ok(val.clone()) + } + }, + + Ast::Set(ident, ast) => { + let val = eval(ast, local_env, return_env)?; + match ident { + Ident::Local(name) => { + local_env.insert(name.clone(), val.clone()); + } + Ident::Return(name) => { + return_env.insert(name.clone(), val.clone()); + } + } + Ok(val) + } + + Ast::Fn { + inputs, + outputs, + body, + } => { + let mut inputs = inputs.clone(); + inputs.reverse(); + let mut outputs = outputs.clone(); + outputs.reverse(); + + Ok(Val::Function(Box::new(Function { + inputs, + outputs, + env: local_env.clone(), + body: FunctionBody::Ast(Ast::Block(body.clone())), + }))) + } + + Ast::Map(stmts) => { + let mut local_env = local_env.clone(); + let mut return_env = HashMap::new(); + for stmt in stmts { + if let Ast::Var(Ident::Return(name)) = stmt { + return_env.insert( + name.to_owned(), + local_env.get(name).cloned().unwrap_or_default(), + ); + } else { + eval(stmt, &mut local_env, &mut return_env)?; + } + } + Ok(Val::Map(Box::new(return_env))) + } + + Ast::App(f, x) => { + let f = eval(f, local_env, return_env)?; + let x = eval(x, local_env, return_env)?; + let Val::Function(mut f) = f else { + return Err(Error::CalledNonFunction(f)); + }; + if let Some(result) = f.call(x)? { + Ok(result) + } else { + Ok(Val::Function(f)) + } + } + + Ast::BinOp(lhs, op, rhs) => { + let lhs = eval(lhs, local_env, return_env)?; + let rhs = eval(rhs, local_env, return_env)?; + Ok(match op { + BinOp::Add => lhs + rhs, + BinOp::Sub => lhs - rhs, + BinOp::Mul => lhs * rhs, + BinOp::Div => lhs / rhs, + _ => todo!(), + }) + } + + Ast::Num(n) => Ok(Val::Number(*n)), + + Ast::Block(stmts) => { + let Some(last) = stmts.last() else { + return Ok(Val::Number(0.0)); + }; + for stmt in &stmts[..stmts.len() - 1] { + eval(stmt, local_env, return_env)?; + } + eval(last, local_env, return_env) + } + + Ast::Access(ast, name) => { + let val = eval(ast, local_env, return_env)?; + let Val::Map(map) = val else { + return Err(Error::AccessedNonMap(val, name.clone())); + }; + Ok(map.get(name).cloned().unwrap_or_default()) + } + } +} + +fn stdenv() -> Map { + let mut res = HashMap::new(); + + res.insert("pi".to_owned(), Val::Number(std::f64::consts::PI)); + + res.insert( + "println".to_owned(), + Val::prim(&["x"], &[], |local_env, _| println!("{:?}", local_env["x"])), + ); + + res +} + +pub fn run(program: &[Ast]) -> Result<()> { + let mut local_env = stdenv(); + let mut return_env = HashMap::new(); + + for stmt in program { + eval(stmt, &mut local_env, &mut return_env)?; + } + + Ok(()) +} + +impl Function { + pub fn call(&mut self, val: Val) -> Result> { + match val { + x @ (Val::Number(_) | Val::Function { .. }) => { + let Some(var) = self.inputs.pop() else { + return self.run().map(Some); + }; + + self.env.insert(var, x); + + if self.inputs.is_empty() { + self.run().map(Some) + } else { + Ok(None) + } + } + Val::Map(map) => { + self.inputs.retain(|name| !map.contains_key(name)); + + for (k, v) in map.into_iter() { + self.env.insert(k, v); + } + + if self.inputs.is_empty() { + self.run().map(Some) + } else { + Ok(None) + } + } + } + } + + pub fn run(&mut self) -> Result { + let mut return_env = self.env.clone(); + + match &self.body { + FunctionBody::Prim(f) => f(&mut self.env, &mut return_env), + FunctionBody::Ast(ast) => { + eval(ast, &mut self.env, &mut return_env)?; + } + } + Ok(Val::Map(Box::new(return_env))) + } +} diff --git a/src/main.rs b/src/main.rs index 7cdd562..0b5e244 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,8 +1,9 @@ -use std::env; +use std::{env, error::Error}; -use crate::parser::parse; +use crate::{eval::run, parser::parse}; mod ast; +mod eval; mod map; mod parser; mod symbol; @@ -11,10 +12,14 @@ mod val; #[cfg(test)] mod tests; -fn main() { +fn main() -> Result<(), Box> { let src = &env::args().nth(1).expect("give me an argument now"); let res = parse(src); - for stmt in res { - println!("{stmt};"); + eprintln!("-- ast"); + for stmt in &res { + eprintln!("{stmt};"); } + eprintln!("-- /ast"); + run(&res)?; + Ok(()) } diff --git a/src/val.rs b/src/val.rs index 12fb230..3bf6017 100644 --- a/src/val.rs +++ b/src/val.rs @@ -1,24 +1,30 @@ -use std::{fmt, ops}; +use std::{collections::HashMap, fmt, ops, string::ToString}; -use crate::{map::Map, symbol::Symbol}; +use crate::{ast::Ast, map::Map, symbol::Symbol}; +#[derive(Clone)] pub enum Val { Number(f64), - Function { - inputs: Vec, - outputs: Vec, - run: Box) -> Val>, - }, - Map(Map), + Function(Box), + Map(Box>), +} + +impl Default for Val { + fn default() -> Self { + Val::Number(0.0) + } } impl fmt::Debug for Val { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Number(n) => write!(f, "{n:?}"), - Self::Function { - inputs, outputs, .. - } => write!(f, "fn([{}] [{}])", inputs.join(" "), outputs.join(" ")), + // TODO: ok + Self::Function(fun) => write!( + f, + "fn ([{:?} {:?}]) ({:?})", + fun.inputs, fun.outputs, fun.body + ), Self::Map(map) => write!(f, "{map:?}"), } } @@ -30,9 +36,9 @@ impl ops::Add for Val { fn add(self, rhs: Self) -> Self::Output { match (self, rhs) { (Val::Number(a), Val::Number(b)) => Val::Number(a + b), - (f @ Val::Function { .. }, g @ Val::Function { .. }) => todo!(), + // (f @ Val::Function { .. }, g @ Val::Function { .. }) => todo!(), (Val::Map(mut m1), Val::Map(m2)) => { - for (k, v) in m2 { + for (k, v) in m2.into_iter() { m1.insert(k, v); } Val::Map(m1) @@ -41,3 +47,70 @@ impl ops::Add for Val { } } } + +impl ops::Sub for Val { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + match (self, rhs) { + (Val::Number(a), Val::Number(b)) => Val::Number(a - b), + (Val::Map(mut m1), Val::Map(m2)) => { + for k in m2.keys() { + m1.remove(k); + } + Val::Map(m1) + } + (lhs, rhs) => panic!("how do i sub these {lhs:?} {rhs:?}"), + } + } +} + +impl ops::Mul for Val { + type Output = Self; + + fn mul(self, rhs: Self) -> Self::Output { + match (self, rhs) { + (Val::Number(a), Val::Number(b)) => Val::Number(a * b), + (lhs, rhs) => panic!("how do i mul these {lhs:?} {rhs:?}"), + } + } +} + +impl ops::Div for Val { + type Output = Self; + + fn div(self, rhs: Self) -> Self::Output { + match (self, rhs) { + (Val::Number(a), Val::Number(b)) => Val::Number(a / b), + (lhs, rhs) => panic!("how do i div these {lhs:?} {rhs:?}"), + } + } +} + +#[derive(Debug, Clone)] +pub struct Function { + /// Inputs in reverse order (fn ([a b c])) -> vec![c b a] + pub(crate) inputs: Vec, + pub(crate) outputs: Vec, + pub(crate) body: FunctionBody, + pub(crate) env: Map, +} + +type PrimFn = fn(local_env: &mut Map, return_env: &mut Map); + +#[derive(Clone, Debug)] +pub enum FunctionBody { + Prim(PrimFn), + Ast(Ast), +} + +impl Val { + pub fn prim(inputs: &[&str], outputs: &[&str], run: PrimFn) -> Val { + Self::Function(Box::new(Function { + inputs: inputs.iter().map(ToString::to_string).collect(), + outputs: outputs.iter().map(ToString::to_string).collect(), + body: FunctionBody::Prim(run), + env: HashMap::new(), + })) + } +}