mvp, buggy evaluation
This commit is contained in:
parent
b06f2f052f
commit
818f9afb3c
6 changed files with 321 additions and 19 deletions
21
Cargo.lock
generated
21
Cargo.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
202
src/eval.rs
Normal 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)))
|
||||
}
|
||||
}
|
||||
15
src/main.rs
15
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<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(())
|
||||
}
|
||||
|
|
|
|||
99
src/val.rs
99
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<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(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue