📜 Add better documentation across the compiler. (#3)

These changes pay particular attention to API endpoints, to try to
ensure that any rustdocs generated are detailed and sensible. A good
next step, eventually, might be to include doctest examples, as well.
For the moment, it's not clear that they would provide a lot of value,
though.

In addition, this does a couple refactors to simplify the code base in
ways that make things clearer or, at least, briefer.
This commit is contained in:
2023-05-13 14:34:48 -05:00
parent f4594bf2cc
commit 1fbfd0c2d2
28 changed files with 1550 additions and 432 deletions

View File

@@ -1,3 +1,38 @@
//! 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 value;
@@ -9,6 +44,13 @@ pub use value::Value;
use crate::backend::BackendError;
/// 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 {
#[error(transparent)]
@@ -18,15 +60,15 @@ pub enum EvalError {
#[error(transparent)]
Backend(#[from] BackendError),
#[error("IO error: {0}")]
IO(String),
IO(#[from] std::io::Error),
#[error(transparent)]
Module(#[from] ModuleError),
}
impl From<std::io::Error> for EvalError {
fn from(value: std::io::Error) -> Self {
EvalError::IO(value.to_string())
}
#[error("Linker error: {0}")]
Linker(String),
#[error("Program exitted with status {0}")]
ExitCode(std::process::ExitStatus),
#[error("Unexpected output at runtime: {0}")]
RuntimeOutput(String),
}
impl PartialEq for EvalError {
@@ -48,7 +90,7 @@ impl PartialEq for EvalError {
},
EvalError::IO(a) => match other {
EvalError::IO(b) => a == b,
EvalError::IO(b) => a.to_string() == b.to_string(),
_ => false,
},
@@ -56,6 +98,21 @@ impl PartialEq for EvalError {
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,
},
}
}
}