todo: arbitrary ir

This commit is contained in:
2023-12-03 17:32:37 -08:00
parent 93cac44a99
commit 2c2268925a
16 changed files with 298 additions and 163 deletions

View File

@@ -4,19 +4,19 @@ use crate::eval::value::Value;
use super::primtype::{UnknownPrimType, ValuePrimitiveTypeError};
/// Errors that can occur running primitive operations in the evaluators.
#[derive(Clone, Debug, PartialEq, thiserror::Error)]
pub enum PrimOpError {
#[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, Value),
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),
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
@@ -36,6 +36,29 @@ pub enum PrimOpError {
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
@@ -59,8 +82,8 @@ macro_rules! run_op {
};
}
impl Value {
fn unary_op(operation: &str, value: &Value) -> Result<Value, PrimOpError> {
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())),
@@ -73,7 +96,11 @@ impl Value {
}
}
fn binary_op(operation: &str, left: &Value, right: &Value) -> Result<Value, PrimOpError> {
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),
@@ -139,7 +166,7 @@ impl Value {
right.clone(),
)),
},
Value::Function(_, _) => {
Value::Closure(_, _, _, _) | Value::Void => {
Err(PrimOpError::BadTypeFor(operation.to_string(), left.clone()))
}
}
@@ -153,7 +180,10 @@ impl Value {
/// 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> {
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]),