backtraced errors and primitive funcs

This commit is contained in:
me 2025-12-20 00:36:15 +02:00
parent ad06f7f616
commit b9488506ce
9 changed files with 174 additions and 54 deletions

View file

@ -12,7 +12,7 @@ test:
.PHONY: run .PHONY: run
run: run:
cargo run -- game.ayin RUST_BACKTRACE=1 cargo run -- game.ayin
.PHONY: review .PHONY: review
review: review:

View file

@ -10,8 +10,8 @@ let update = fn(state, input) {
return state; return state;
}; };
let draw = fn(frame, state) { let draw = fn(state) {
frame.clear(0,0,0); frame_clear(0,0,0);
}; };
let migrate = fn(state) { let migrate = fn(state) {

View file

@ -71,6 +71,7 @@ pub enum Value {
Label(Label), Label(Label),
Ref(Ref), Ref(Ref),
Closure { env: EnvName, expr: Box<Expr> }, Closure { env: EnvName, expr: Box<Expr> },
PrimitiveFunc(Name),
} }
/// A mutable variable. /// A mutable variable.
@ -91,7 +92,7 @@ pub struct Arg {
} }
/// A symbol. /// A symbol.
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone)] #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone)]
pub struct Name(pub String); pub struct Name(pub String);
/// A field. /// A field.

View file

@ -1,6 +1,7 @@
//! Interpreter for Ayin. //! Interpreter for Ayin.
use super::types::*; use super::types::*;
use std::backtrace::Backtrace;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use crate::ast; use crate::ast;
@ -9,8 +10,8 @@ pub fn global_env_name() -> ast::EnvName {
ast::EnvName("global".to_string()) ast::EnvName("global".to_string())
} }
pub fn setup(program: ast::Program) -> Result<State, Error> { pub fn setup(program: ast::Program, prim_funcs: PrimitiveFuncs) -> Result<State, Error> {
let mut state = State::new(global_env_name().0.clone()); let mut state = State::new(global_env_name().0.clone(), prim_funcs);
defs_to_env(program.0, &global_env_name(), &mut state)?; defs_to_env(program.0, &global_env_name(), &mut state)?;
Ok(state) Ok(state)
@ -39,7 +40,7 @@ pub fn run(
func: ast::Name, func: ast::Name,
args: Vec<ast::Expr>, args: Vec<ast::Expr>,
) -> Result<ast::Value, Error> { ) -> Result<ast::Value, Error> {
let mut state = setup(program)?; let mut state = setup(program, PrimitiveFuncs::new(vec![]))?;
interpret(&mut state, func, args) interpret(&mut state, func, args)
} }
@ -89,7 +90,7 @@ fn eval_expr(expr_env: &Env, state: &mut State, expr: &ast::Expr) -> Result<ast:
eval_expr(expr_env, state, r#else) eval_expr(expr_env, state, r#else)
} }
} }
v => Err(Error::NotABoolean(v)), v => Err(Error::NotABoolean(v, Backtrace::capture())),
}, },
ast::Expr::Func(func) => { ast::Expr::Func(func) => {
let env_name = state.generate_env(expr_env.clone())?; let env_name = state.generate_env(expr_env.clone())?;
@ -113,12 +114,12 @@ fn eval_expr(expr_env: &Env, state: &mut State, expr: &ast::Expr) -> Result<ast:
})? { })? {
Ok(last) Ok(last)
} else { } else {
Err(Error::LastStatementNotAnExpr) Err(Error::LastStatementNotAnExpr(Backtrace::capture()))
} }
} }
ast::Expr::Access { expr, field } => match eval_expr(expr_env, state, expr)? { ast::Expr::Access { expr, field } => match eval_expr(expr_env, state, expr)? {
ast::Value::Record(record) => Ok(state.variables.get(record.get(&field)?).clone()), ast::Value::Record(record) => Ok(state.variables.get(record.get(&field)?).clone()),
v => Err(Error::NotARecord(v)), v => Err(Error::NotARecord(v, Backtrace::capture())),
}, },
ast::Expr::Var(var) => { ast::Expr::Var(var) => {
let value = expr_env.get(var)?.clone(); let value = expr_env.get(var)?.clone();
@ -131,7 +132,7 @@ fn eval_expr(expr_env: &Env, state: &mut State, expr: &ast::Expr) -> Result<ast:
let mut closure_env: Env = state.envs.get(&env)?.clone(); let mut closure_env: Env = state.envs.get(&env)?.clone();
if func.args.len() != args.len() { if func.args.len() != args.len() {
Err(Error::ArgumentsMismatch)?; Err(Error::ArgumentsMismatch(Backtrace::capture()))?;
} }
for (arg, e) in func.args.into_iter().zip(args.iter()) { for (arg, e) in func.args.into_iter().zip(args.iter()) {
@ -140,9 +141,27 @@ fn eval_expr(expr_env: &Env, state: &mut State, expr: &ast::Expr) -> Result<ast:
} }
eval_expr(&closure_env, state, &func.body) eval_expr(&closure_env, state, &func.body)
} }
e => Err(Error::NotAFunction(e)), e => Err(Error::NotAFunction(e, Backtrace::capture())),
}, },
e => Err(Error::NotAFunction(ast::Expr::Value(e))), ast::Value::PrimitiveFunc(func) => {
let mut argsvec = vec![];
for arg in args.iter() {
let evalled = eval_expr(&expr_env, state, arg)?;
argsvec.push(evalled);
}
if let Some(func) = state.primitive_funcs.get(&func) {
Ok(func(argsvec))
} else {
Err(Error::NotAFunction(
ast::Expr::Value(ast::Value::PrimitiveFunc(func)),
Backtrace::capture(),
))
}
}
e => Err(Error::NotAFunction(
ast::Expr::Value(e),
Backtrace::capture(),
)),
}, },
ast::Expr::Value(v) => match v { ast::Expr::Value(v) => match v {
ast::Value::Closure { expr, env } => { ast::Value::Closure { expr, env } => {
@ -159,7 +178,7 @@ fn eval_expr(expr_env: &Env, state: &mut State, expr: &ast::Expr) -> Result<ast:
state.variables.set(reference, rhs.clone()); state.variables.set(reference, rhs.clone());
Ok(rhs) Ok(rhs)
} }
v => Err(Error::NotAReference(v.clone())), v => Err(Error::NotAReference(v.clone(), Backtrace::capture())),
}, },
/* /*
_ => { _ => {
@ -200,12 +219,12 @@ fn eval_expr_shallow(
let result = expr_env.get(&var)?; let result = expr_env.get(&var)?;
match result { match result {
ast::Value::Ref(_) => Ok(result.clone()), ast::Value::Ref(_) => Ok(result.clone()),
v => Err(Error::NotAReference(v.clone())), v => Err(Error::NotAReference(v.clone(), Backtrace::capture())),
} }
} }
ast::Expr::Access { expr, field } => match eval_expr(expr_env, state, expr)? { ast::Expr::Access { expr, field } => match eval_expr(expr_env, state, expr)? {
ast::Value::Record(record) => Ok(ast::Value::Ref(record.get(&field)?.clone())), ast::Value::Record(record) => Ok(ast::Value::Ref(record.get(&field)?.clone())),
v => Err(Error::NotARecord(v)), v => Err(Error::NotARecord(v, Backtrace::capture())),
}, },
_ => eval_expr(expr_env, state, expr), _ => eval_expr(expr_env, state, expr),
} }
@ -385,7 +404,10 @@ mod tests {
] ]
.into(); .into();
let result = run(program, "main".into(), vec![]); let result = run(program, "main".into(), vec![]);
assert_eq!(result, Err(Error::DuplicateNames("main".into()))); assert_eq!(
result,
Err(Error::DuplicateNames("main".into(), Backtrace::capture()))
);
} }
#[test] #[test]
@ -396,7 +418,10 @@ mod tests {
] ]
.into(); .into();
let result = run(program, "main".into(), vec![]); let result = run(program, "main".into(), vec![]);
assert_eq!(result, Err(Error::NotARecord(0.into()))); assert_eq!(
result,
Err(Error::NotARecord(0.into(), Backtrace::capture()))
);
} }
#[test] #[test]
fn fun_call_not_a_function() { fn fun_call_not_a_function() {
@ -406,6 +431,9 @@ mod tests {
] ]
.into(); .into();
let result = run(program, "main".into(), vec![]); let result = run(program, "main".into(), vec![]);
assert_eq!(result, Err(Error::NotAFunction(0.into()))); assert_eq!(
result,
Err(Error::NotAFunction(0.into(), Backtrace::capture()))
);
} }
} }

View file

@ -1,39 +1,73 @@
//! Types used in the interpreter. //! Types used in the interpreter.
use crate::ast; use crate::ast;
use std::backtrace::Backtrace;
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::collections::HashMap; use std::collections::HashMap;
#[derive(PartialEq, Debug, thiserror::Error)] #[derive(Debug)]
pub enum Error { pub enum Error {
#[error("Closure generation failed. Duplicate env names: {0:?}")] DuplicateEnvNames(ast::EnvName, Backtrace),
DuplicateEnvNames(ast::EnvName), DuplicateNames(ast::Name, Backtrace),
#[error("Duplicate names: {0:?}")] NameNotFound(ast::Name, Backtrace),
DuplicateNames(ast::Name), FieldNotFound(ast::Label, Backtrace),
#[error("Name not found: {0:?}")] EnvNotFound(ast::EnvName, Backtrace),
NameNotFound(ast::Name), LastStatementNotAnExpr(Backtrace),
#[error("Field not found: {0:?}")] NotAFunction(ast::Expr, Backtrace),
FieldNotFound(ast::Label), NotARecord(ast::Value, Backtrace),
#[error("Env not found: {0:?}")] NotAReference(ast::Value, Backtrace),
EnvNotFound(ast::EnvName), NotABoolean(ast::Value, Backtrace),
#[error("Last statement is not an expression")] ArgumentsMismatch(Backtrace),
LastStatementNotAnExpr, }
#[error("Not a function {0:?}")] impl PartialEq for Error {
NotAFunction(ast::Expr), fn eq(&self, other: &Self) -> bool {
#[error("Not a record {0:?}")] match (self, other) {
NotARecord(ast::Value), (Error::DuplicateEnvNames(a1, _), Error::DuplicateEnvNames(a2, _)) => a1 == a2,
#[error("Not a reference {0:?}")] (Error::DuplicateNames(a1, _), Error::DuplicateNames(a2, _)) => a1 == a2,
NotAReference(ast::Value), (Error::NameNotFound(a1, _), Error::NameNotFound(a2, _)) => a1 == a2,
#[error("Not a boolean {0:?}")] (Error::FieldNotFound(a1, _), Error::FieldNotFound(a2, _)) => a1 == a2,
NotABoolean(ast::Value), (Error::EnvNotFound(a1, _), Error::EnvNotFound(a2, _)) => a1 == a2,
#[error("Arguments mismatch")] (Error::LastStatementNotAnExpr(_), Error::LastStatementNotAnExpr(_)) => true,
ArgumentsMismatch,
(Error::NotAFunction(a1, _), Error::NotAFunction(a2, _)) => a1 == a2,
(Error::NotARecord(a1, _), Error::NotARecord(a2, _)) => a1 == a2,
(Error::NotAReference(a1, _), Error::NotAReference(a2, _)) => a1 == a2,
(Error::NotABoolean(a1, _), Error::NotABoolean(a2, _)) => a1 == a2,
(Error::ArgumentsMismatch(_), Error::ArgumentsMismatch(_)) => true,
_ => false,
}
}
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Error::DuplicateEnvNames(a, b) => {
write!(
f,
"Closure generation failed. Duplicate env names: {a:?}\n{b}"
)
}
Error::DuplicateNames(a, b) => write!(f, "Duplicate names: {a:?}\n{b}"),
Error::NameNotFound(a, b) => write!(f, "Name not found: {a:?}\n{b}"),
Error::FieldNotFound(a, b) => write!(f, "Field not found: {a:?}\n{b}"),
Error::EnvNotFound(a, b) => write!(f, "Env not found: {a:?}\n{b}"),
Error::LastStatementNotAnExpr(a) => {
write!(f, "Last statement is not an expression\n{a}")
}
Error::NotAFunction(a, b) => write!(f, "Not a function {a:?}\n{b}"),
Error::NotARecord(a, b) => write!(f, "Not a record {a:?}\n{b}"),
Error::NotAReference(a, b) => write!(f, "Not a reference {a:?}\n{b}"),
Error::NotABoolean(a, b) => write!(f, "Not a boolean {a:?}\n{b}"),
Error::ArgumentsMismatch(a) => write!(f, "Arguments mismatch\n{a}"),
}
}
} }
impl From<ast::Error> for Error { impl From<ast::Error> for Error {
fn from(error: ast::Error) -> Error { fn from(error: ast::Error) -> Error {
match error { match error {
ast::Error::LabelNotFound(l) => Error::FieldNotFound(l), ast::Error::LabelNotFound(l) => Error::FieldNotFound(l, Backtrace::capture()),
} }
} }
} }
@ -57,6 +91,26 @@ pub struct State {
env_namer: Namer, env_namer: Namer,
pub envs: Envs, pub envs: Envs,
pub variables: Variables, pub variables: Variables,
pub primitive_funcs: PrimitiveFuncs,
}
pub struct PrimitiveFuncs {
pub map: HashMap<ast::Name, PrimFunc>,
}
pub type PrimFunc = fn(Vec<ast::Value>) -> ast::Value;
impl PrimitiveFuncs {
pub fn new(prims: Vec<(&str, PrimFunc)>) -> Self {
let mut map: HashMap<ast::Name, PrimFunc> = HashMap::new();
for (key, func) in prims.into_iter() {
map.insert(key.into(), func);
}
PrimitiveFuncs { map }
}
pub fn get(&self, name: &ast::Name) -> Option<&PrimFunc> {
self.map.get(name)
}
} }
pub struct Variables { pub struct Variables {
@ -84,12 +138,13 @@ impl Variables {
} }
} }
impl State { impl State {
pub fn new(name: String) -> State { pub fn new(name: String, primitive_funcs: PrimitiveFuncs) -> State {
State { State {
name, name,
env_namer: Namer::new(), env_namer: Namer::new(),
envs: Envs(BTreeMap::new()), envs: Envs(BTreeMap::new()),
variables: Variables::new(), variables: Variables::new(),
primitive_funcs,
} }
} }
pub fn generate_env(&mut self, env: Env) -> Result<ast::EnvName, Error> { pub fn generate_env(&mut self, env: Env) -> Result<ast::EnvName, Error> {
@ -113,14 +168,16 @@ impl Env {
} }
} }
pub fn get(&self, name: &ast::Name) -> Result<&ast::Value, Error> { pub fn get(&self, name: &ast::Name) -> Result<&ast::Value, Error> {
self.env.get(name).ok_or(Error::NameNotFound(name.clone())) self.env
.get(name)
.ok_or(Error::NameNotFound(name.clone(), Backtrace::capture()))
} }
pub fn insert(&mut self, name: ast::Name, value: ast::Value) { pub fn insert(&mut self, name: ast::Name, value: ast::Value) {
self.env.insert(name.clone(), value); self.env.insert(name.clone(), value);
} }
pub fn insert_nodup(&mut self, name: &ast::Name, value: ast::Value) -> Result<(), Error> { pub fn insert_nodup(&mut self, name: &ast::Name, value: ast::Value) -> Result<(), Error> {
if self.env.insert(name.clone(), value).is_some() { if self.env.insert(name.clone(), value).is_some() {
Err(Error::DuplicateNames(name.clone())) Err(Error::DuplicateNames(name.clone(), Backtrace::capture()))
} else { } else {
Ok(()) Ok(())
} }
@ -134,16 +191,16 @@ impl Envs {
pub fn get(&self, env_name: &ast::EnvName) -> Result<&Env, Error> { pub fn get(&self, env_name: &ast::EnvName) -> Result<&Env, Error> {
self.0 self.0
.get(env_name) .get(env_name)
.ok_or(Error::EnvNotFound(env_name.clone())) .ok_or(Error::EnvNotFound(env_name.clone(), Backtrace::capture()))
} }
pub fn get_mut(&mut self, env_name: &ast::EnvName) -> Result<&mut Env, Error> { pub fn get_mut(&mut self, env_name: &ast::EnvName) -> Result<&mut Env, Error> {
self.0 self.0
.get_mut(env_name) .get_mut(env_name)
.ok_or(Error::EnvNotFound(env_name.clone())) .ok_or(Error::EnvNotFound(env_name.clone(), Backtrace::capture()))
} }
pub fn insert(&mut self, name: &ast::EnvName, env: Env) -> Result<(), Error> { pub fn insert(&mut self, name: &ast::EnvName, env: Env) -> Result<(), Error> {
if self.0.insert(name.clone(), env).is_some() { if self.0.insert(name.clone(), env).is_some() {
Err(Error::DuplicateEnvNames(name.clone())) Err(Error::DuplicateEnvNames(name.clone(), Backtrace::capture()))
} else { } else {
Ok(()) Ok(())
} }

View file

@ -29,7 +29,7 @@ async fn main() {
loop { loop {
let events = fetch_events(&mut state); let events = fetch_events(&mut state);
update(&mut state, events); update(&mut state, events);
draw(&state); draw(&mut state);
next_frame().await; next_frame().await;
} }
} }

View file

@ -1,3 +1,5 @@
pub mod primitive_functions;
pub mod runtime; pub mod runtime;
pub use runtime::*; pub use runtime::*;

View file

@ -0,0 +1,22 @@
use crate::ast;
use crate::interpret::PrimitiveFuncs;
use macroquad::prelude::*;
pub fn primitive_funcs() -> PrimitiveFuncs {
PrimitiveFuncs::new(vec![("frame_clear", clear)])
}
fn clear(args: Vec<ast::Value>) -> ast::Value {
let r = to_u32(args.get(0));
let g = to_u32(args.get(1));
let b = to_u32(args.get(2));
clear_background(Color::from_rgba(r, g, b, 255));
ast::UNIT_VALUE
}
fn to_u32(value: Option<&ast::Value>) -> u8 {
match value {
Some(ast::Value::Int(i)) => *i as u8,
_ => 0,
}
}

View file

@ -1,3 +1,4 @@
use super::primitive_functions::primitive_funcs;
use super::types::*; use super::types::*;
use crate::ast; use crate::ast;
use crate::ast::helpers; use crate::ast::helpers;
@ -8,11 +9,11 @@ use macroquad::prelude::*;
pub async fn setup(code: ast::Program) -> State { pub async fn setup(code: ast::Program) -> State {
let font = load_ttf_font("./assets/fonts/monogram.ttf").await.unwrap(); let font = load_ttf_font("./assets/fonts/monogram.ttf").await.unwrap();
let mut state = match interpret::setup(code) { let mut state = match interpret::setup(code, primitive_funcs()) {
Ok(state) => state, Ok(state) => state,
Err(err) => { Err(err) => {
println!("Error: {}", err); println!("Error: {}", err);
interpret::State::new("game".into()) interpret::State::new("game".into(), primitive_funcs())
} }
}; };
let game_state = match interpret::interpret(&mut state, "setup".into(), vec![]) { let game_state = match interpret::interpret(&mut state, "setup".into(), vec![]) {
@ -32,7 +33,7 @@ pub async fn setup(code: ast::Program) -> State {
} }
pub async fn migrate(mut state: State, code: ast::Program) -> State { pub async fn migrate(mut state: State, code: ast::Program) -> State {
match interpret::setup(code) { match interpret::setup(code, primitive_funcs()) {
Err(err) => { Err(err) => {
println!("Error: {}", err); println!("Error: {}", err);
state state
@ -136,7 +137,7 @@ pub fn fetch_events(state: &mut State) -> Input {
input input
} }
pub fn draw(state: &State) { pub fn draw(state: &mut State) {
clear_background(Color::from_hex(0x081829)); clear_background(Color::from_hex(0x081829));
set_default_camera(); set_default_camera();
@ -151,6 +152,15 @@ pub fn draw(state: &State) {
set_default_camera(); set_default_camera();
match interpret::interpret(
&mut state.state,
"draw".into(),
vec![ast::Expr::Value(state.game_state.clone())],
) {
Ok(_) => {}
Err(err) => println!("Error: {}", err),
}
println!( println!(
"{:#?}", "{:#?}",
interpret::value_to_stadnalone_expr(&state.state, state.game_state.clone()) interpret::value_to_stadnalone_expr(&state.state, state.game_state.clone())