diff --git a/examples/fizzbuzz.puyo b/examples/fizzbuzz.puyo index dea6828..7ff6bfb 100644 --- a/examples/fizzbuzz.puyo +++ b/examples/fizzbuzz.puyo @@ -35,13 +35,13 @@ fn fizzbuzz1([n]) ( nl(); ); -i := 1; - -loop [ - fizzbuzz1 i; - i := i + 1; - - if (i > 100) [ - exit 0; +# gotta predeclare the function so it can see itself +go := 0; +fn go([i max]) ( + if (i <= max) [ + fizzbuzz1(i); + go {^max} (i + 1); ] []; -]; +); + +go { ^max := 1000; } 1; diff --git a/src/eval/env.rs b/src/eval/env.rs new file mode 100644 index 0000000..cffbe52 --- /dev/null +++ b/src/eval/env.rs @@ -0,0 +1,45 @@ +use std::{cell::RefCell, collections::hash_map::Entry, rc::Rc}; + +use super::Val; +use crate::{map::Map, symbol::Symbol}; + +#[derive(Debug, Clone, Default)] +pub struct Env(Map>>); + +impl Env { + pub fn new() -> Self { + Self::default() + } + + pub fn get(&self, name: &str) -> Option { + self.0.get(name).map(|c| c.borrow().clone()) + } + + pub fn set(&mut self, name: Symbol, val: Val) { + match self.0.entry(name) { + Entry::Occupied(ent) => { + *ent.get().borrow_mut() = val; + } + Entry::Vacant(ent) => { + ent.insert(Rc::new(RefCell::new(val))); + } + } + } + + pub fn freeze(self) -> Map { + self.0 + .into_iter() + .map(|(k, v)| (k, Rc::unwrap_or_clone(v).into_inner())) + .collect() + } +} + +impl From> for Env { + fn from(value: Map) -> Self { + let map = value + .into_iter() + .map(|(k, v)| (k, Rc::new(RefCell::new(v)))) + .collect(); + Self(map) + } +} diff --git a/src/eval/error.rs b/src/eval/error.rs new file mode 100644 index 0000000..2a69f56 --- /dev/null +++ b/src/eval/error.rs @@ -0,0 +1,21 @@ +use super::Val; +use crate::symbol::Symbol; + +#[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), + #[error("")] + BadPrimArgType { + prim: &'static str, + arg: &'static str, + val: Val, + expected: &'static str, + }, +} diff --git a/src/eval.rs b/src/eval/mod.rs similarity index 73% rename from src/eval.rs rename to src/eval/mod.rs index 4d0ab4b..841fdca 100644 --- a/src/eval.rs +++ b/src/eval/mod.rs @@ -1,4 +1,6 @@ -// TODO: usable closures +mod env; +mod error; +mod val; use std::{ collections::HashMap, @@ -9,39 +11,30 @@ use std::{ 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 use env::Env; +pub use error::Error; +pub use val::Val; +use val::{Function, FunctionBody}; pub type Result = std::result::Result; #[allow(clippy::too_many_lines, reason = "you're right")] -fn eval(expr: &Ast, local_env: &mut Map, return_env: &mut Map) -> Result { +fn eval(expr: &Ast, local_env: &mut Env, return_env: &mut Env) -> 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()) + Ok(val) } Ident::Return(name) => { let Some(val) = local_env.get(name) else { return Err(Error::UndefinedReturnVariable(name.to_owned())); }; - Ok(val.clone()) + Ok(val) } }, @@ -49,10 +42,10 @@ fn eval(expr: &Ast, local_env: &mut Map, return_env: &mut Map) -> Resu let val = eval(ast, local_env, return_env)?; match ident { Ident::Local(name) => { - local_env.insert(name.clone(), val.clone()); + local_env.set(name.clone(), val.clone()); } Ident::Return(name) => { - return_env.insert(name.clone(), val.clone()); + return_env.set(name.clone(), val.clone()); } } Ok(val) @@ -80,18 +73,15 @@ fn eval(expr: &Ast, local_env: &mut Map, return_env: &mut Map) -> Resu Ast::Map(stmts) => { let mut local_env = local_env.clone(); - let mut return_env = HashMap::new(); + let mut return_env = Env::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(), - ); + return_env.set(name.to_owned(), local_env.get(name).unwrap_or_default()); } else { eval(stmt, &mut local_env, &mut return_env)?; } } - Ok(Val::Map(Box::new(return_env))) + Ok(Val::Map(Box::new(return_env.freeze()))) } Ast::App(f, x) => { @@ -152,34 +142,48 @@ fn stdenv() -> Map { res.insert( "println".to_owned(), - Val::prim(&["x"], &[], |local_env, _| println!("{}", local_env["x"])), + Val::prim(&["x"], &[], |local_env, _| { + println!("{}", local_env.get("x").unwrap()); + Ok(()) + }), ); res.insert( "print".to_owned(), - Val::prim(&["x"], &[], |local_env, _| print!("{}", local_env["x"])), + Val::prim(&["x"], &[], |local_env, _| { + print!("{}", local_env.get("x").unwrap()); + Ok(()) + }), ); res.insert( "put".to_owned(), Val::prim(&["x"], &[], |local_env, _| { - let Some(Val::Number(n)) = local_env.get("x") else { - panic!("where is it (put)"); + let val = local_env.get("x").unwrap(); + let Val::Number(n) = val else { + return Err(Error::BadPrimArgType { + prim: "put", + arg: "x", + val, + expected: "number", + }); }; #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] - let n = (n.abs() % 256.) as u8; + let n = (n.abs().floor() % 256.) as u8; io::stdout().write_all(&[n]).unwrap(); + Ok(()) }), ); res.insert( "if".to_owned(), Val::prim(&["cond", "then", "else"], &["x"], |loc, ret| { - if loc["cond"] == Val::Number(0.0) { - ret.insert("x".to_owned(), loc.get_mut("else").unwrap().run().unwrap()); + if loc.get("cond").unwrap() == Val::Number(0.0) { + ret.set("x".to_owned(), loc.get("else").unwrap().run()?); } else { - ret.insert("x".to_owned(), loc.get_mut("then").unwrap().run().unwrap()); + ret.set("x".to_owned(), loc.get("then").unwrap().run()?); } + Ok(()) }), ); @@ -187,7 +191,7 @@ fn stdenv() -> Map { "loop".to_owned(), Val::prim(&["body"], &[], |loc, _| { loop { - loc.get_mut("body").unwrap().run().unwrap(); + loc.get("body").unwrap().run()?; } }), ); @@ -195,8 +199,14 @@ fn stdenv() -> Map { res.insert( "exit".to_owned(), Val::prim(&["code"], &[], |loc, _| { - let Val::Number(n) = loc["code"] else { - panic!("exit code must be a number"); + let val = loc.get("code").unwrap(); + let Val::Number(n) = val else { + return Err(Error::BadPrimArgType { + prim: "exit", + arg: "code", + val, + expected: "integer in [0, 256)", + }); }; #[allow(clippy::cast_possible_truncation)] @@ -208,8 +218,8 @@ fn stdenv() -> Map { } pub fn run(program: &[Ast]) -> Result<()> { - let mut local_env = stdenv(); - let mut return_env = HashMap::new(); + let mut local_env = stdenv().into(); + let mut return_env = Env::new(); for stmt in program { eval(stmt, &mut local_env, &mut return_env)?; @@ -236,7 +246,7 @@ impl Function { return self.run().map(Some); }; - self.env.insert(var, x); + self.env.set(var, x); if self.inputs.is_empty() { self.run().map(Some) @@ -248,7 +258,7 @@ impl Function { self.inputs.retain(|name| !map.contains_key(name)); for (k, v) in map.into_iter() { - self.env.insert(k, v); + self.env.set(k, v); } if self.inputs.is_empty() { @@ -264,11 +274,13 @@ impl Function { let mut return_env = self.env.clone(); match &self.body { - FunctionBody::Prim(f) => f(&mut self.env, &mut return_env), + 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))) + Ok(Val::Map(Box::new(return_env.freeze()))) } } diff --git a/src/val.rs b/src/eval/val.rs similarity index 88% rename from src/val.rs rename to src/eval/val.rs index e39f4ad..faf5d80 100644 --- a/src/val.rs +++ b/src/eval/val.rs @@ -1,11 +1,12 @@ use std::{ cmp::{self, Ordering}, - collections::HashMap, fmt, ops, string::ToString, }; -use crate::{ast::Ast, map::Map, symbol::Symbol}; +use crate::{ast::Ast, eval, map::Map, symbol::Symbol}; + +use super::Env; #[derive(Clone, Debug)] pub enum Val { @@ -25,11 +26,14 @@ impl fmt::Display for Val { match self { Self::Number(n) => write!(f, "{n}"), // TODO: ok - Self::Function(fun) => write!( - f, - "fn ([{:?} {:?}]) ({:?})", - fun.inputs, fun.outputs, fun.body - ), + Self::Function(fun) => match &fun.body { + FunctionBody::Prim(prim) => { + write!(f, "fn ([{:?} {:?}]) ({prim:?})", fun.inputs, fun.outputs) + } + FunctionBody::Ast(ast) => { + write!(f, "fn ([{:?} {:?}]) ({ast})", fun.inputs, fun.outputs) + } + }, Self::Map(map) => write!(f, "{map:?}"), } } @@ -149,10 +153,10 @@ pub struct Function { pub(crate) inputs: Vec, pub(crate) outputs: Vec, pub(crate) body: FunctionBody, - pub(crate) env: Map, + pub(crate) env: Env, } -type PrimFn = fn(local_env: &mut Map, return_env: &mut Map); +type PrimFn = fn(local_env: &mut Env, return_env: &mut Env) -> eval::Result<()>; #[derive(Clone, Debug)] pub enum FunctionBody { @@ -166,7 +170,7 @@ impl Val { inputs: inputs.iter().rev().map(ToString::to_string).collect(), outputs: outputs.iter().rev().map(ToString::to_string).collect(), body: FunctionBody::Prim(run), - env: HashMap::new(), + env: Env::new(), })) } diff --git a/src/main.rs b/src/main.rs index 712f888..d1b28f1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,6 @@ mod eval; mod map; mod parser; mod symbol; -mod val; #[cfg(test)] mod tests;