todo: arbitrary ir
This commit is contained in:
@@ -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]),
|
||||
|
||||
@@ -39,11 +39,12 @@ pub enum ValuePrimitiveTypeError {
|
||||
CannotConvertFunction(Option<String>),
|
||||
}
|
||||
|
||||
impl<'a> TryFrom<&'a Value> for PrimitiveType {
|
||||
impl<'a, IR> TryFrom<&'a Value<IR>> for PrimitiveType {
|
||||
type Error = ValuePrimitiveTypeError;
|
||||
|
||||
fn try_from(value: &'a Value) -> Result<Self, Self::Error> {
|
||||
fn try_from(value: &'a Value<IR>) -> Result<Self, Self::Error> {
|
||||
match value {
|
||||
Value::Void => Ok(PrimitiveType::Void),
|
||||
Value::I8(_) => Ok(PrimitiveType::I8),
|
||||
Value::I16(_) => Ok(PrimitiveType::I16),
|
||||
Value::I32(_) => Ok(PrimitiveType::I32),
|
||||
@@ -52,9 +53,9 @@ impl<'a> TryFrom<&'a Value> for PrimitiveType {
|
||||
Value::U16(_) => Ok(PrimitiveType::U16),
|
||||
Value::U32(_) => Ok(PrimitiveType::U32),
|
||||
Value::U64(_) => Ok(PrimitiveType::U64),
|
||||
Value::Function(name, _) => {
|
||||
Err(ValuePrimitiveTypeError::CannotConvertFunction(name.clone()))
|
||||
}
|
||||
Value::Closure(name, _, _, _) => Err(ValuePrimitiveTypeError::CannotConvertFunction(
|
||||
name.as_ref().map(|x| (**x).clone()),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -147,7 +148,7 @@ impl PrimitiveType {
|
||||
/// type to the target type. (So, for example, "1i64" is a number that could
|
||||
/// work as a "u64", but since negative numbers wouldn't work, a cast from
|
||||
/// "1i64" to "u64" will fail.)
|
||||
pub fn safe_cast(&self, source: &Value) -> Result<Value, PrimOpError> {
|
||||
pub fn safe_cast<IR>(&self, source: &Value<IR>) -> Result<Value<IR>, PrimOpError<IR>> {
|
||||
match (self, source) {
|
||||
(PrimitiveType::U8, Value::U8(x)) => Ok(Value::U8(*x)),
|
||||
(PrimitiveType::U16, Value::U8(x)) => Ok(Value::U16(*x as u16)),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::EvalError;
|
||||
use crate::util::scoped_map::ScopedMap;
|
||||
use internment::ArcIntern;
|
||||
use std::fmt;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// Values in the interpreter.
|
||||
///
|
||||
@@ -8,7 +8,8 @@ use std::rc::Rc;
|
||||
/// are almost entirely identical. However, it's nice to have them separated
|
||||
/// by type so that we don't mix them up.
|
||||
#[derive(Clone)]
|
||||
pub enum Value {
|
||||
pub enum Value<IR> {
|
||||
Void,
|
||||
I8(i8),
|
||||
I16(i16),
|
||||
I32(i32),
|
||||
@@ -17,14 +18,44 @@ pub enum Value {
|
||||
U16(u16),
|
||||
U32(u32),
|
||||
U64(u64),
|
||||
Function(
|
||||
Option<String>,
|
||||
Rc<dyn Fn(Vec<Value>) -> Result<Value, EvalError>>,
|
||||
Closure(
|
||||
Option<ArcIntern<String>>,
|
||||
ScopedMap<ArcIntern<String>, Value<IR>>,
|
||||
Vec<ArcIntern<String>>,
|
||||
IR,
|
||||
),
|
||||
}
|
||||
|
||||
fn format_value(value: &Value, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
impl<IR: Clone> Value<IR> {
|
||||
/// Given a Value associated with some expression type, just strip out
|
||||
/// the expressions and replace them with unit.
|
||||
///
|
||||
/// Doing this transformation will likely make this value useless for
|
||||
/// computation, but is very useful in allowing equivalence checks.
|
||||
pub fn strip(&self) -> Value<()> {
|
||||
match self {
|
||||
Value::Void => Value::Void,
|
||||
Value::U8(x) => Value::U8(*x),
|
||||
Value::U16(x) => Value::U16(*x),
|
||||
Value::U32(x) => Value::U32(*x),
|
||||
Value::U64(x) => Value::U64(*x),
|
||||
Value::I8(x) => Value::I8(*x),
|
||||
Value::I16(x) => Value::I16(*x),
|
||||
Value::I32(x) => Value::I32(*x),
|
||||
Value::I64(x) => Value::I64(*x),
|
||||
Value::Closure(name, env, args, _) => {
|
||||
let new_env = env
|
||||
.clone()
|
||||
.map_values(|x| x.strip());
|
||||
Value::Closure(name.clone(), new_env, args.clone(), ())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn format_value<IR>(value: &Value<IR>, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match value {
|
||||
Value::Void => write!(f, "<void>"),
|
||||
Value::I8(x) => write!(f, "{}i8", x),
|
||||
Value::I16(x) => write!(f, "{}i16", x),
|
||||
Value::I32(x) => write!(f, "{}i32", x),
|
||||
@@ -33,26 +64,27 @@ fn format_value(value: &Value, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
Value::U16(x) => write!(f, "{}u16", x),
|
||||
Value::U32(x) => write!(f, "{}u32", x),
|
||||
Value::U64(x) => write!(f, "{}u64", x),
|
||||
Value::Function(Some(name), _) => write!(f, "<function {}>", name),
|
||||
Value::Function(None, _) => write!(f, "<function>"),
|
||||
Value::Closure(Some(name), _, _, _) => write!(f, "<function {}>", name),
|
||||
Value::Closure(None, _, _, _) => write!(f, "<function>"),
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Value {
|
||||
impl<IR> fmt::Debug for Value<IR> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
format_value(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Value {
|
||||
impl<IR> fmt::Display for Value<IR> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
format_value(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Value {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
impl<IR1, IR2> PartialEq<Value<IR2>> for Value<IR1> {
|
||||
fn eq(&self, other: &Value<IR2>) -> bool {
|
||||
match self {
|
||||
Value::Void => matches!(other, Value::Void),
|
||||
Value::I8(x) => match other {
|
||||
Value::I8(y) => x == y,
|
||||
_ => false,
|
||||
@@ -85,58 +117,54 @@ impl PartialEq for Value {
|
||||
Value::U64(y) => x == y,
|
||||
_ => false,
|
||||
},
|
||||
Value::Function(Some(x), _) => match other {
|
||||
Value::Function(Some(y), _) => x == y,
|
||||
_ => false,
|
||||
},
|
||||
Value::Function(None, _) => false,
|
||||
Value::Closure(_, _, _, _) => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i8> for Value {
|
||||
impl<IR> From<i8> for Value<IR> {
|
||||
fn from(value: i8) -> Self {
|
||||
Value::I8(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i16> for Value {
|
||||
impl<IR> From<i16> for Value<IR> {
|
||||
fn from(value: i16) -> Self {
|
||||
Value::I16(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i32> for Value {
|
||||
impl<IR> From<i32> for Value<IR> {
|
||||
fn from(value: i32) -> Self {
|
||||
Value::I32(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<i64> for Value {
|
||||
impl<IR> From<i64> for Value<IR> {
|
||||
fn from(value: i64) -> Self {
|
||||
Value::I64(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u8> for Value {
|
||||
impl<IR> From<u8> for Value<IR> {
|
||||
fn from(value: u8) -> Self {
|
||||
Value::U8(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u16> for Value {
|
||||
impl<IR> From<u16> for Value<IR> {
|
||||
fn from(value: u16) -> Self {
|
||||
Value::U16(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u32> for Value {
|
||||
impl<IR> From<u32> for Value<IR> {
|
||||
fn from(value: u32) -> Self {
|
||||
Value::U32(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for Value {
|
||||
impl<IR> From<u64> for Value<IR> {
|
||||
fn from(value: u64) -> Self {
|
||||
Value::U64(value)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user