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
run:
cargo run -- game.ayin
RUST_BACKTRACE=1 cargo run -- game.ayin
.PHONY: review
review:

View file

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

View file

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

View file

@ -1,6 +1,7 @@
//! Interpreter for Ayin.
use super::types::*;
use std::backtrace::Backtrace;
use std::collections::BTreeMap;
use crate::ast;
@ -9,8 +10,8 @@ pub fn global_env_name() -> ast::EnvName {
ast::EnvName("global".to_string())
}
pub fn setup(program: ast::Program) -> Result<State, Error> {
let mut state = State::new(global_env_name().0.clone());
pub fn setup(program: ast::Program, prim_funcs: PrimitiveFuncs) -> Result<State, Error> {
let mut state = State::new(global_env_name().0.clone(), prim_funcs);
defs_to_env(program.0, &global_env_name(), &mut state)?;
Ok(state)
@ -39,7 +40,7 @@ pub fn run(
func: ast::Name,
args: Vec<ast::Expr>,
) -> Result<ast::Value, Error> {
let mut state = setup(program)?;
let mut state = setup(program, PrimitiveFuncs::new(vec![]))?;
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)
}
}
v => Err(Error::NotABoolean(v)),
v => Err(Error::NotABoolean(v, Backtrace::capture())),
},
ast::Expr::Func(func) => {
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)
} else {
Err(Error::LastStatementNotAnExpr)
Err(Error::LastStatementNotAnExpr(Backtrace::capture()))
}
}
ast::Expr::Access { expr, field } => match eval_expr(expr_env, state, expr)? {
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) => {
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();
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()) {
@ -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)
}
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::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());
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)?;
match result {
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::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),
}
@ -385,7 +404,10 @@ mod tests {
]
.into();
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]
@ -396,7 +418,10 @@ mod tests {
]
.into();
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]
fn fun_call_not_a_function() {
@ -406,6 +431,9 @@ mod tests {
]
.into();
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.
use crate::ast;
use std::backtrace::Backtrace;
use std::collections::BTreeMap;
use std::collections::HashMap;
#[derive(PartialEq, Debug, thiserror::Error)]
#[derive(Debug)]
pub enum Error {
#[error("Closure generation failed. Duplicate env names: {0:?}")]
DuplicateEnvNames(ast::EnvName),
#[error("Duplicate names: {0:?}")]
DuplicateNames(ast::Name),
#[error("Name not found: {0:?}")]
NameNotFound(ast::Name),
#[error("Field not found: {0:?}")]
FieldNotFound(ast::Label),
#[error("Env not found: {0:?}")]
EnvNotFound(ast::EnvName),
#[error("Last statement is not an expression")]
LastStatementNotAnExpr,
#[error("Not a function {0:?}")]
NotAFunction(ast::Expr),
#[error("Not a record {0:?}")]
NotARecord(ast::Value),
#[error("Not a reference {0:?}")]
NotAReference(ast::Value),
#[error("Not a boolean {0:?}")]
NotABoolean(ast::Value),
#[error("Arguments mismatch")]
ArgumentsMismatch,
DuplicateEnvNames(ast::EnvName, Backtrace),
DuplicateNames(ast::Name, Backtrace),
NameNotFound(ast::Name, Backtrace),
FieldNotFound(ast::Label, Backtrace),
EnvNotFound(ast::EnvName, Backtrace),
LastStatementNotAnExpr(Backtrace),
NotAFunction(ast::Expr, Backtrace),
NotARecord(ast::Value, Backtrace),
NotAReference(ast::Value, Backtrace),
NotABoolean(ast::Value, Backtrace),
ArgumentsMismatch(Backtrace),
}
impl PartialEq for Error {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Error::DuplicateEnvNames(a1, _), Error::DuplicateEnvNames(a2, _)) => a1 == a2,
(Error::DuplicateNames(a1, _), Error::DuplicateNames(a2, _)) => a1 == a2,
(Error::NameNotFound(a1, _), Error::NameNotFound(a2, _)) => a1 == a2,
(Error::FieldNotFound(a1, _), Error::FieldNotFound(a2, _)) => a1 == a2,
(Error::EnvNotFound(a1, _), Error::EnvNotFound(a2, _)) => a1 == a2,
(Error::LastStatementNotAnExpr(_), Error::LastStatementNotAnExpr(_)) => true,
(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 {
fn from(error: ast::Error) -> 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,
pub envs: Envs,
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 {
@ -84,12 +138,13 @@ impl Variables {
}
}
impl State {
pub fn new(name: String) -> State {
pub fn new(name: String, primitive_funcs: PrimitiveFuncs) -> State {
State {
name,
env_namer: Namer::new(),
envs: Envs(BTreeMap::new()),
variables: Variables::new(),
primitive_funcs,
}
}
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> {
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) {
self.env.insert(name.clone(), value);
}
pub fn insert_nodup(&mut self, name: &ast::Name, value: ast::Value) -> Result<(), Error> {
if self.env.insert(name.clone(), value).is_some() {
Err(Error::DuplicateNames(name.clone()))
Err(Error::DuplicateNames(name.clone(), Backtrace::capture()))
} else {
Ok(())
}
@ -134,16 +191,16 @@ impl Envs {
pub fn get(&self, env_name: &ast::EnvName) -> Result<&Env, Error> {
self.0
.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> {
self.0
.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> {
if self.0.insert(name.clone(), env).is_some() {
Err(Error::DuplicateEnvNames(name.clone()))
Err(Error::DuplicateEnvNames(name.clone(), Backtrace::capture()))
} else {
Ok(())
}

View file

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

View file

@ -1,3 +1,5 @@
pub mod primitive_functions;
pub mod 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 crate::ast;
use crate::ast::helpers;
@ -8,11 +9,11 @@ use macroquad::prelude::*;
pub async fn setup(code: ast::Program) -> State {
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,
Err(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![]) {
@ -32,7 +33,7 @@ pub async fn setup(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) => {
println!("Error: {}", err);
state
@ -136,7 +137,7 @@ pub fn fetch_events(state: &mut State) -> Input {
input
}
pub fn draw(state: &State) {
pub fn draw(state: &mut State) {
clear_background(Color::from_hex(0x081829));
set_default_camera();
@ -151,6 +152,15 @@ pub fn draw(state: &State) {
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!(
"{:#?}",
interpret::value_to_stadnalone_expr(&state.state, state.game_state.clone())