make variables work in a way that makes sense

This commit is contained in:
mehbark 2026-01-07 08:53:32 -05:00
parent 8329545560
commit 67e79b18a8
6 changed files with 143 additions and 62 deletions

View file

@ -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
View 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
View 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,
},
}

View file

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

View file

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

View file

@ -7,7 +7,6 @@ mod eval;
mod map;
mod parser;
mod symbol;
mod val;
#[cfg(test)]
mod tests;