mvp, buggy evaluation

This commit is contained in:
mehbark 2026-01-07 00:26:18 -05:00
parent b06f2f052f
commit 818f9afb3c
6 changed files with 321 additions and 19 deletions

21
Cargo.lock generated
View file

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

View file

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

View file

@ -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),

202
src/eval.rs Normal file
View file

@ -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<T> = std::result::Result<T, Error>;
fn eval(expr: &Ast, local_env: &mut Map<Val>, return_env: &mut Map<Val>) -> Result<Val> {
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<Val> {
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<Option<Val>> {
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<Val> {
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)))
}
}

View file

@ -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<dyn Error>> {
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(())
}

View file

@ -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<Symbol>,
outputs: Vec<Symbol>,
run: Box<dyn FnMut(Map<Val>) -> Val>,
},
Map(Map<Val>),
Function(Box<Function>),
Map(Box<Map<Val>>),
}
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<Symbol>,
pub(crate) outputs: Vec<Symbol>,
pub(crate) body: FunctionBody,
pub(crate) env: Map<Val>,
}
type PrimFn = fn(local_env: &mut Map<Val>, return_env: &mut Map<Val>);
#[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(),
}))
}
}