173 lines
6.5 KiB
Rust
173 lines
6.5 KiB
Rust
//! Helpful functions for evaluating NGR programs.
|
|
//!
|
|
//! Look, this is a compiler, and so you might be asking why it has a bunch of
|
|
//! stuff in it to help with writing interpreters. Well, the answer is simple:
|
|
//! testing. It's really nice to know that if you start with a program that
|
|
//! does a thing, and then you muck with it, you end up with a program that does
|
|
//! the exact same thing. If you talk to people who think about language
|
|
//! semantics, they'll call this "observational equivalence": maybe the two
|
|
//! programs don't do 100% the same things in the same order, but you shouldn't
|
|
//! be able to observe the difference ... at least, not without a stopwatch,
|
|
//! memory profilers, etc.
|
|
//!
|
|
//! The actual evaluators for our various syntaxes are hidden in `eval` functions
|
|
//! of the various ASTs. It's nice to have them "next to" the syntax that way, so
|
|
//! that we just edit stuff in one part of the source tree at a time. This module,
|
|
//! then, just contains some things that are generally helpful across all the
|
|
//! interpreters we've written.
|
|
//!
|
|
//! In particular, this module helps with:
|
|
//!
|
|
//! * Defining a common error type -- [`EvalError`] -- that we can reasonably
|
|
//! compare. It's nice to compare errors, here, because we want to know that
|
|
//! if a program used to fail, it will still fail after we change it, and
|
|
//! fail in the exact same way.
|
|
//! * Defining a notion of a binding environment: [`EvalEnvironment`]. This
|
|
//! will help us keep track of variables bound in our program, as we run it.
|
|
//! * Defining a notion of a runtime value: [`Value`]. Yes, this is the
|
|
//! umpteenth time that we're re-defining basically the same enumeration
|
|
//! with exactly the same name, but it's nice to have it separated so that
|
|
//! we don't confuse them.
|
|
//! * Finally, this module implements all of our primitive functions, as the
|
|
//! [`Value::calculate`] function. This is just a nice abstraction boundary,
|
|
//! because the implementation of some parts of these primitives is really
|
|
//! awful to look at.
|
|
//!
|
|
//mod env;
|
|
mod primop;
|
|
mod primtype;
|
|
mod value;
|
|
|
|
use cranelift_module::ModuleError;
|
|
use internment::ArcIntern;
|
|
pub use primop::PrimOpError;
|
|
pub use primtype::PrimitiveType;
|
|
pub use value::Value;
|
|
|
|
use crate::backend::BackendError;
|
|
|
|
use self::primtype::UnknownPrimType;
|
|
|
|
/// All of the errors that can happen trying to evaluate an NGR program.
|
|
///
|
|
/// This is yet another standard [`thiserror::Error`] type, but with the
|
|
/// caveat that it implements [`PartialEq`] even though some of its
|
|
/// constituent members don't. It does so through the very sketchy mechanism
|
|
/// of converting those errors to strings and then seeing if they're the
|
|
/// same.
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum EvalError<IR> {
|
|
#[error(transparent)]
|
|
PrimOp(#[from] PrimOpError<IR>),
|
|
#[error(transparent)]
|
|
Backend(#[from] BackendError),
|
|
#[error("IO error: {0}")]
|
|
IO(#[from] std::io::Error),
|
|
#[error(transparent)]
|
|
Module(#[from] ModuleError),
|
|
#[error("Linker error: {0}")]
|
|
Linker(String),
|
|
#[error("Program exitted with status {0}")]
|
|
ExitCode(std::process::ExitStatus),
|
|
#[error("Unexpected output at runtime: {0}")]
|
|
RuntimeOutput(String),
|
|
#[error("Cannot cast to function type: {0}")]
|
|
CastToFunction(String),
|
|
#[error(transparent)]
|
|
UnknownPrimType(#[from] UnknownPrimType),
|
|
#[error("Variable lookup failed for {1} at {0:?}")]
|
|
LookupFailed(crate::syntax::Location, String),
|
|
#[error("Attempted to call something that wasn't a function at {0:?} (it was a {1})")]
|
|
NotAFunction(crate::syntax::Location, Value<IR>),
|
|
#[error("Wrong argument call for function ({1:?}) at {0:?}; expected {2}, saw {3}")]
|
|
WrongArgCount(
|
|
crate::syntax::Location,
|
|
Option<ArcIntern<String>>,
|
|
usize,
|
|
usize,
|
|
),
|
|
#[error("Value has no fields {1} (attempt to get field {2} at {0:?})")]
|
|
NoFieldForValue(crate::syntax::Location, Value<IR>, ArcIntern<String>),
|
|
#[error("Bad field {2} for structure {1:?} at {0:?}")]
|
|
BadFieldForStructure(
|
|
crate::syntax::Location,
|
|
Option<ArcIntern<String>>,
|
|
ArcIntern<String>,
|
|
),
|
|
}
|
|
|
|
impl<IR1: Clone, IR2: Clone> PartialEq<EvalError<IR1>> for EvalError<IR2> {
|
|
fn eq(&self, other: &EvalError<IR1>) -> bool {
|
|
match self {
|
|
EvalError::LookupFailed(a, b) => match other {
|
|
EvalError::LookupFailed(x, y) => a == x && b == y,
|
|
_ => false,
|
|
},
|
|
EvalError::PrimOp(a) => match other {
|
|
EvalError::PrimOp(b) => a == b,
|
|
_ => false,
|
|
},
|
|
|
|
EvalError::Backend(a) => match other {
|
|
EvalError::Backend(b) => a == b,
|
|
_ => false,
|
|
},
|
|
|
|
EvalError::IO(a) => match other {
|
|
EvalError::IO(b) => a.to_string() == b.to_string(),
|
|
_ => false,
|
|
},
|
|
|
|
EvalError::Module(a) => match other {
|
|
EvalError::Module(b) => a.to_string() == b.to_string(),
|
|
_ => false,
|
|
},
|
|
|
|
EvalError::Linker(a) => match other {
|
|
EvalError::Linker(b) => a == b,
|
|
_ => false,
|
|
},
|
|
|
|
EvalError::ExitCode(a) => match other {
|
|
EvalError::ExitCode(b) => a == b,
|
|
_ => false,
|
|
},
|
|
|
|
EvalError::RuntimeOutput(a) => match other {
|
|
EvalError::RuntimeOutput(b) => a == b,
|
|
_ => false,
|
|
},
|
|
|
|
EvalError::CastToFunction(a) => match other {
|
|
EvalError::CastToFunction(b) => a == b,
|
|
_ => false,
|
|
},
|
|
|
|
EvalError::UnknownPrimType(a) => match other {
|
|
EvalError::UnknownPrimType(b) => a == b,
|
|
_ => false,
|
|
},
|
|
|
|
EvalError::NotAFunction(a, b) => match other {
|
|
EvalError::NotAFunction(x, y) => a == x && b == y,
|
|
_ => false,
|
|
},
|
|
|
|
EvalError::WrongArgCount(a, b, c, d) => match other {
|
|
EvalError::WrongArgCount(w, x, y, z) => a == w && b == x && c == y && d == z,
|
|
_ => false,
|
|
},
|
|
|
|
EvalError::NoFieldForValue(a, b, c) => match other {
|
|
EvalError::NoFieldForValue(x, y, z) => a == x && b == y && c == z,
|
|
_ => false,
|
|
},
|
|
|
|
EvalError::BadFieldForStructure(a, b, c) => match other {
|
|
EvalError::BadFieldForStructure(x, y, z) => a == x && b == y && c == z,
|
|
_ => false,
|
|
},
|
|
}
|
|
}
|
|
}
|