📜 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:
@@ -1,19 +1,39 @@
|
||||
use crate::eval::value::Value;
|
||||
|
||||
/// Errors that can occur running primitive operations in the evaluators.
|
||||
#[derive(Clone, Debug, PartialEq, thiserror::Error)]
|
||||
pub enum PrimOpError {
|
||||
#[error("Math error (underflow or overflow) computing {0} operator")]
|
||||
MathFailure(&'static str),
|
||||
/// This particular variant covers the case in which a primitive
|
||||
/// operator takes two arguments that are supposed to be the same,
|
||||
/// but they differ. (So, like, all the math operators.)
|
||||
#[error("Type mismatch ({1} vs {2}) computing {0} operator")]
|
||||
TypeMismatch(String, Value, Value),
|
||||
/// This variant covers when an operator must take a particular
|
||||
/// type, but the user has provided a different one.
|
||||
#[error("Bad type for operator {0}: {1}")]
|
||||
BadTypeFor(&'static str, Value),
|
||||
/// Probably obvious from the name, but just to be very clear: this
|
||||
/// happens when you pass three arguments to a two argument operator,
|
||||
/// etc. Technically that's a type error of some sort, but we split
|
||||
/// it out.
|
||||
#[error("Illegal number of arguments for {0}: {1} arguments found")]
|
||||
BadArgCount(String, usize),
|
||||
#[error("Unknown primitive operation {0}")]
|
||||
UnknownPrimOp(String),
|
||||
}
|
||||
|
||||
// Implementing primitives in an interpreter like this is *super* tedious,
|
||||
// and the only way to make it even somewhat manageable is to use macros.
|
||||
// This particular macro works for binary operations, and assumes that
|
||||
// you've already worked out that the `calculate` call provided two arguments.
|
||||
//
|
||||
// In those cases, it will rul the operations we know about, and error if
|
||||
// it doesn't.
|
||||
//
|
||||
// This macro then needs to be instantiated for every type, which is super
|
||||
// fun.
|
||||
macro_rules! run_op {
|
||||
($op: ident, $left: expr, $right: expr) => {
|
||||
match $op {
|
||||
@@ -23,15 +43,15 @@ macro_rules! run_op {
|
||||
.map(Into::into),
|
||||
"-" => $left
|
||||
.checked_sub($right)
|
||||
.ok_or(PrimOpError::MathFailure("+"))
|
||||
.ok_or(PrimOpError::MathFailure("-"))
|
||||
.map(Into::into),
|
||||
"*" => $left
|
||||
.checked_mul($right)
|
||||
.ok_or(PrimOpError::MathFailure("+"))
|
||||
.ok_or(PrimOpError::MathFailure("*"))
|
||||
.map(Into::into),
|
||||
"/" => $left
|
||||
.checked_div($right)
|
||||
.ok_or(PrimOpError::MathFailure("+"))
|
||||
.ok_or(PrimOpError::MathFailure("/"))
|
||||
.map(Into::into),
|
||||
_ => Err(PrimOpError::UnknownPrimOp($op.to_string())),
|
||||
}
|
||||
@@ -41,6 +61,8 @@ macro_rules! run_op {
|
||||
impl Value {
|
||||
fn binary_op(operation: &str, left: &Value, right: &Value) -> Result<Value, PrimOpError> {
|
||||
match left {
|
||||
// for now we only have one type, but in the future this is
|
||||
// going to be very irritating.
|
||||
Value::I64(x) => match right {
|
||||
Value::I64(y) => run_op!(operation, x, *y),
|
||||
// _ => Err(PrimOpError::TypeMismatch(
|
||||
@@ -52,6 +74,14 @@ impl Value {
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate the result of running the given primitive on the given arguments.
|
||||
///
|
||||
/// This can cause errors in a whole mess of ways, so be careful about your
|
||||
/// inputs. For example, addition only works when the two values have the exact
|
||||
/// same type, so expect an error if you try to do so. In addition, this
|
||||
/// implementation catches and raises an error on overflow or underflow, so
|
||||
/// its worth being careful to make sure that your inputs won't cause either
|
||||
/// condition.
|
||||
pub fn calculate(operation: &str, values: Vec<Value>) -> Result<Value, PrimOpError> {
|
||||
if values.len() == 2 {
|
||||
Value::binary_op(operation, &values[0], &values[1])
|
||||
|
||||
Reference in New Issue
Block a user