make variables work in a way that makes sense
This commit is contained in:
parent
8329545560
commit
67e79b18a8
6 changed files with 143 additions and 62 deletions
|
|
@ -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;
|
||||
|
|
|
|||
45
src/eval/env.rs
Normal file
45
src/eval/env.rs
Normal file
|
|
@ -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<Rc<RefCell<Val>>>);
|
||||
|
||||
impl Env {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
|
||||
pub fn get(&self, name: &str) -> Option<Val> {
|
||||
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<Val> {
|
||||
self.0
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k, Rc::unwrap_or_clone(v).into_inner()))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Map<Val>> for Env {
|
||||
fn from(value: Map<Val>) -> Self {
|
||||
let map = value
|
||||
.into_iter()
|
||||
.map(|(k, v)| (k, Rc::new(RefCell::new(v))))
|
||||
.collect();
|
||||
Self(map)
|
||||
}
|
||||
}
|
||||
21
src/eval/error.rs
Normal file
21
src/eval/error.rs
Normal file
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
|
@ -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<T> = std::result::Result<T, Error>;
|
||||
|
||||
#[allow(clippy::too_many_lines, reason = "you're right")]
|
||||
fn eval(expr: &Ast, local_env: &mut Map<Val>, return_env: &mut Map<Val>) -> Result<Val> {
|
||||
fn eval(expr: &Ast, local_env: &mut Env, return_env: &mut Env) -> 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())
|
||||
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<Val>, return_env: &mut Map<Val>) -> 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<Val>, return_env: &mut Map<Val>) -> 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<Val> {
|
|||
|
||||
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<Val> {
|
|||
"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<Val> {
|
|||
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<Val> {
|
|||
}
|
||||
|
||||
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())))
|
||||
}
|
||||
}
|
||||
|
|
@ -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<Symbol>,
|
||||
pub(crate) outputs: Vec<Symbol>,
|
||||
pub(crate) body: FunctionBody,
|
||||
pub(crate) env: Map<Val>,
|
||||
pub(crate) env: Env,
|
||||
}
|
||||
|
||||
type PrimFn = fn(local_env: &mut Map<Val>, return_env: &mut Map<Val>);
|
||||
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(),
|
||||
}))
|
||||
}
|
||||
|
||||
|
|
@ -7,7 +7,6 @@ mod eval;
|
|||
mod map;
|
||||
mod parser;
|
||||
mod symbol;
|
||||
mod val;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
|
|
|||
Loading…
Reference in a new issue