196 lines
7.8 KiB
Rust
196 lines
7.8 KiB
Rust
use crate::eval::primtype::PrimitiveType;
|
|
use crate::eval::value::Value;
|
|
|
|
use super::primtype::{UnknownPrimType, ValuePrimitiveTypeError};
|
|
|
|
/// Errors that can occur running primitive operations in the evaluators.
|
|
#[derive(Clone, Debug, thiserror::Error)]
|
|
pub enum PrimOpError<IR> {
|
|
#[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<IR>, Value<IR>),
|
|
/// 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(String, Value<IR>),
|
|
/// 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),
|
|
#[error("Unsafe cast from {from} to {to}")]
|
|
UnsafeCast {
|
|
from: PrimitiveType,
|
|
to: PrimitiveType,
|
|
},
|
|
#[error(transparent)]
|
|
UnknownPrimType(#[from] UnknownPrimType),
|
|
#[error(transparent)]
|
|
ValuePrimitiveTypeError(#[from] ValuePrimitiveTypeError),
|
|
}
|
|
|
|
impl<IR1: Clone, IR2: Clone> PartialEq<PrimOpError<IR2>> for PrimOpError<IR1> {
|
|
fn eq(&self, other: &PrimOpError<IR2>) -> bool {
|
|
match (self, other) {
|
|
(PrimOpError::MathFailure(a), PrimOpError::MathFailure(b)) => a == b,
|
|
(PrimOpError::TypeMismatch(a, b, c), PrimOpError::TypeMismatch(x, y, z)) => {
|
|
a == x && b.strip() == y.strip() && c.strip() == z.strip()
|
|
}
|
|
(PrimOpError::BadTypeFor(a, b), PrimOpError::BadTypeFor(x, y)) => {
|
|
a == x && b.strip() == y.strip()
|
|
}
|
|
(PrimOpError::BadArgCount(a, b), PrimOpError::BadArgCount(x, y)) => a == x && b == y,
|
|
(PrimOpError::UnknownPrimOp(a), PrimOpError::UnknownPrimOp(x)) => a == x,
|
|
(
|
|
PrimOpError::UnsafeCast { from: a, to: b },
|
|
PrimOpError::UnsafeCast { from: x, to: y },
|
|
) => a == x && b == y,
|
|
(PrimOpError::UnknownPrimType(a), PrimOpError::UnknownPrimType(x)) => a == x,
|
|
(PrimOpError::ValuePrimitiveTypeError(a), PrimOpError::ValuePrimitiveTypeError(x)) => {
|
|
a == x
|
|
}
|
|
_ => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
// 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 {
|
|
"+" => Ok($left.wrapping_add($right).into()),
|
|
"-" => Ok($left.wrapping_sub($right).into()),
|
|
"*" => Ok($left.wrapping_mul($right).into()),
|
|
"/" if $right == 0 => Err(PrimOpError::MathFailure("/")),
|
|
"/" => Ok($left.wrapping_div($right).into()),
|
|
_ => Err(PrimOpError::UnknownPrimOp($op.to_string())),
|
|
}
|
|
};
|
|
}
|
|
|
|
impl<IR: Clone> Value<IR> {
|
|
fn unary_op(operation: &str, value: &Value<IR>) -> Result<Value<IR>, PrimOpError<IR>> {
|
|
match operation {
|
|
"-" => match value {
|
|
Value::I8(x) => Ok(Value::I8(x.wrapping_neg())),
|
|
Value::I16(x) => Ok(Value::I16(x.wrapping_neg())),
|
|
Value::I32(x) => Ok(Value::I32(x.wrapping_neg())),
|
|
Value::I64(x) => Ok(Value::I64(x.wrapping_neg())),
|
|
_ => Err(PrimOpError::BadTypeFor("-".to_string(), value.clone())),
|
|
},
|
|
_ => Err(PrimOpError::BadArgCount(operation.to_owned(), 1)),
|
|
}
|
|
}
|
|
|
|
fn binary_op(
|
|
operation: &str,
|
|
left: &Value<IR>,
|
|
right: &Value<IR>,
|
|
) -> Result<Value<IR>, PrimOpError<IR>> {
|
|
match left {
|
|
Value::I8(x) => match right {
|
|
Value::I8(y) => run_op!(operation, x, *y),
|
|
_ => Err(PrimOpError::TypeMismatch(
|
|
operation.to_string(),
|
|
left.clone(),
|
|
right.clone(),
|
|
)),
|
|
},
|
|
Value::I16(x) => match right {
|
|
Value::I16(y) => run_op!(operation, x, *y),
|
|
_ => Err(PrimOpError::TypeMismatch(
|
|
operation.to_string(),
|
|
left.clone(),
|
|
right.clone(),
|
|
)),
|
|
},
|
|
Value::I32(x) => match right {
|
|
Value::I32(y) => run_op!(operation, x, *y),
|
|
_ => Err(PrimOpError::TypeMismatch(
|
|
operation.to_string(),
|
|
left.clone(),
|
|
right.clone(),
|
|
)),
|
|
},
|
|
Value::I64(x) => match right {
|
|
Value::I64(y) => run_op!(operation, x, *y),
|
|
_ => Err(PrimOpError::TypeMismatch(
|
|
operation.to_string(),
|
|
left.clone(),
|
|
right.clone(),
|
|
)),
|
|
},
|
|
Value::U8(x) => match right {
|
|
Value::U8(y) => run_op!(operation, x, *y),
|
|
_ => Err(PrimOpError::TypeMismatch(
|
|
operation.to_string(),
|
|
left.clone(),
|
|
right.clone(),
|
|
)),
|
|
},
|
|
Value::U16(x) => match right {
|
|
Value::U16(y) => run_op!(operation, x, *y),
|
|
_ => Err(PrimOpError::TypeMismatch(
|
|
operation.to_string(),
|
|
left.clone(),
|
|
right.clone(),
|
|
)),
|
|
},
|
|
Value::U32(x) => match right {
|
|
Value::U32(y) => run_op!(operation, x, *y),
|
|
_ => Err(PrimOpError::TypeMismatch(
|
|
operation.to_string(),
|
|
left.clone(),
|
|
right.clone(),
|
|
)),
|
|
},
|
|
Value::U64(x) => match right {
|
|
Value::U64(y) => run_op!(operation, x, *y),
|
|
_ => Err(PrimOpError::TypeMismatch(
|
|
operation.to_string(),
|
|
left.clone(),
|
|
right.clone(),
|
|
)),
|
|
},
|
|
Value::Closure(_, _, _, _) | Value::Void => {
|
|
Err(PrimOpError::BadTypeFor(operation.to_string(), left.clone()))
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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<IR>>,
|
|
) -> Result<Value<IR>, PrimOpError<IR>> {
|
|
match values.len() {
|
|
1 => Value::unary_op(operation, &values[0]),
|
|
2 => Value::binary_op(operation, &values[0], &values[1]),
|
|
x => Err(PrimOpError::BadArgCount(operation.to_string(), x)),
|
|
}
|
|
}
|
|
}
|