extern crate alloc;
use alloc::format;
use alloc::vec::Vec;
use crate::bytecode::GenericValue;
use crate::float::Float;
use crate::vm::VmError;
use crate::word::Word;
pub trait KeleusmaType<W: Word, F: Float>: Sized {
fn from_value(v: &GenericValue<W, F>) -> Result<Self, VmError>;
fn into_value(self) -> GenericValue<W, F>;
}
impl<W: Word, F: Float> KeleusmaType<W, F> for i64 {
fn from_value(v: &GenericValue<W, F>) -> Result<Self, VmError> {
match v {
GenericValue::Int(n) => Ok(W::to_i64(*n)),
other => Err(VmError::TypeError(format!(
"expected Word, got {}",
other.type_name()
))),
}
}
fn into_value(self) -> GenericValue<W, F> {
GenericValue::Int(W::from_i64_wrap(self))
}
}
impl<W: Word, F: Float> KeleusmaType<W, F> for u8 {
fn from_value(v: &GenericValue<W, F>) -> Result<Self, VmError> {
match v {
GenericValue::Byte(b) => Ok(*b),
other => Err(VmError::TypeError(format!(
"expected Byte, got {}",
other.type_name()
))),
}
}
fn into_value(self) -> GenericValue<W, F> {
GenericValue::Byte(self)
}
}
#[cfg(feature = "floats")]
impl<W: Word, F: Float> KeleusmaType<W, F> for f64 {
fn from_value(v: &GenericValue<W, F>) -> Result<Self, VmError> {
match v {
GenericValue::Float(f) => Ok(F::to_f64(*f)),
GenericValue::Int(n) => Ok(W::to_i64(*n) as f64),
other => Err(VmError::TypeError(format!(
"expected Float, got {}",
other.type_name()
))),
}
}
fn into_value(self) -> GenericValue<W, F> {
GenericValue::Float(F::from_f64(self))
}
}
impl<W: Word, F: Float> KeleusmaType<W, F> for bool {
fn from_value(v: &GenericValue<W, F>) -> Result<Self, VmError> {
match v {
GenericValue::Bool(b) => Ok(*b),
other => Err(VmError::TypeError(format!(
"expected bool, got {}",
other.type_name()
))),
}
}
fn into_value(self) -> GenericValue<W, F> {
GenericValue::Bool(self)
}
}
impl<W: Word, F: Float> KeleusmaType<W, F> for () {
fn from_value(v: &GenericValue<W, F>) -> Result<Self, VmError> {
match v {
GenericValue::Unit => Ok(()),
other => Err(VmError::TypeError(format!(
"expected unit, got {}",
other.type_name()
))),
}
}
fn into_value(self) -> GenericValue<W, F> {
GenericValue::Unit
}
}
impl<W: Word, F: Float, T: KeleusmaType<W, F>> KeleusmaType<W, F> for Option<T> {
fn from_value(v: &GenericValue<W, F>) -> Result<Self, VmError> {
match v {
GenericValue::None => Ok(Option::None),
other => {
T::from_value(other).map(Some)
}
}
}
fn into_value(self) -> GenericValue<W, F> {
match self {
Some(t) => t.into_value(),
Option::None => GenericValue::None,
}
}
}
impl<W: Word, F: Float, T: KeleusmaType<W, F> + Clone, const N: usize> KeleusmaType<W, F>
for [T; N]
{
fn from_value(v: &GenericValue<W, F>) -> Result<Self, VmError> {
match v {
GenericValue::Array(items) => {
if items.len() != N {
return Err(VmError::TypeError(format!(
"expected array of length {}, got {}",
N,
items.len()
)));
}
let mut converted: Vec<T> = Vec::with_capacity(N);
for item in items.iter() {
converted.push(T::from_value(item)?);
}
converted.try_into().map_err(|_| {
VmError::TypeError(format!("failed to convert array of length {}", N))
})
}
other => Err(VmError::TypeError(format!(
"expected array, got {}",
other.type_name()
))),
}
}
fn into_value(self) -> GenericValue<W, F> {
let items: Vec<GenericValue<W, F>> = self.into_iter().map(|t| t.into_value()).collect();
GenericValue::Array(items)
}
}
macro_rules! impl_tuple {
($($name:ident: $idx:tt),*) => {
impl<W: Word, FloatT: Float, $($name: KeleusmaType<W, FloatT>),*>
KeleusmaType<W, FloatT> for ($($name,)*)
{
#[allow(clippy::unused_unit, unused_assignments, non_snake_case)]
fn from_value(v: &GenericValue<W, FloatT>) -> Result<Self, VmError> {
match v {
GenericValue::Tuple(items) => {
let expected = [$(stringify!($name),)*].len();
if items.len() != expected {
return Err(VmError::TypeError(format!(
"expected tuple of arity {}, got {}",
expected,
items.len()
)));
}
Ok(($($name::from_value(&items[$idx])?,)*))
}
other => Err(VmError::TypeError(format!(
"expected tuple, got {}",
other.type_name()
))),
}
}
#[allow(non_snake_case)]
fn into_value(self) -> GenericValue<W, FloatT> {
let ($($name,)*) = self;
GenericValue::Tuple(::alloc::vec![$($name.into_value(),)*])
}
}
};
}
impl_tuple!(A: 0, B: 1);
impl_tuple!(A: 0, B: 1, C: 2);
impl_tuple!(A: 0, B: 1, C: 2, D: 3);
impl_tuple!(A: 0, B: 1, C: 2, D: 3, E: 4);
pub type BoxedNativeFn<W, F> = alloc::boxed::Box<
dyn for<'a> Fn(
&crate::vm::NativeCtx<'a>,
&[GenericValue<W, F>],
) -> Result<GenericValue<W, F>, VmError>,
>;
pub trait IntoNativeFn<W: Word, F: Float, Args, R> {
fn into_native_fn(self) -> BoxedNativeFn<W, F>;
}
pub trait IntoFallibleNativeFn<W: Word, F: Float, Args, R> {
fn into_native_fn(self) -> BoxedNativeFn<W, F>;
}
macro_rules! impl_into_native_fn {
($arity:expr; $($name:ident: $idx:tt),*) => {
impl<W: Word, FloatT: Float, Func, $($name,)* R>
IntoNativeFn<W, FloatT, ($($name,)*), R> for Func
where
Func: Fn($($name,)*) -> R + 'static,
$($name: KeleusmaType<W, FloatT>,)*
R: KeleusmaType<W, FloatT>,
{
#[allow(unused_variables, clippy::let_unit_value, non_snake_case)]
fn into_native_fn(self) -> BoxedNativeFn<W, FloatT> {
alloc::boxed::Box::new(
move |_ctx: &crate::vm::NativeCtx<'_>, args: &[GenericValue<W, FloatT>]|
-> Result<GenericValue<W, FloatT>, VmError> {
if args.len() != $arity {
return Err(VmError::NativeError(format!(
"native function expected {} argument(s), got {}",
$arity,
args.len()
)));
}
$(
let $name = <$name as KeleusmaType<W, FloatT>>::from_value(&args[$idx])?;
)*
Ok(self($($name,)*).into_value())
},
)
}
}
impl<W: Word, FloatT: Float, Func, $($name,)* R>
IntoFallibleNativeFn<W, FloatT, ($($name,)*), R> for Func
where
Func: Fn($($name,)*) -> Result<R, VmError> + 'static,
$($name: KeleusmaType<W, FloatT>,)*
R: KeleusmaType<W, FloatT>,
{
#[allow(unused_variables, clippy::let_unit_value, non_snake_case)]
fn into_native_fn(self) -> BoxedNativeFn<W, FloatT> {
alloc::boxed::Box::new(
move |_ctx: &crate::vm::NativeCtx<'_>, args: &[GenericValue<W, FloatT>]|
-> Result<GenericValue<W, FloatT>, VmError> {
if args.len() != $arity {
return Err(VmError::NativeError(format!(
"native function expected {} argument(s), got {}",
$arity,
args.len()
)));
}
$(
let $name = <$name as KeleusmaType<W, FloatT>>::from_value(&args[$idx])?;
)*
self($($name,)*).map(<R as KeleusmaType<W, FloatT>>::into_value)
},
)
}
}
};
}
impl_into_native_fn!(0;);
impl_into_native_fn!(1; A: 0);
impl_into_native_fn!(2; A: 0, B: 1);
impl_into_native_fn!(3; A: 0, B: 1, C: 2);
impl_into_native_fn!(4; A: 0, B: 1, C: 2, D: 3);
#[cfg(all(test, feature = "floats"))]
mod tests {
use super::*;
use crate::bytecode::Value;
#[test]
fn primitive_roundtrip() {
assert_eq!(
<i64 as KeleusmaType<i64, f64>>::from_value(&Value::Int(42)).unwrap(),
42
);
assert_eq!(
<f64 as KeleusmaType<i64, f64>>::from_value(&Value::Float(2.5)).unwrap(),
2.5
);
assert!(<bool as KeleusmaType<i64, f64>>::from_value(&Value::Bool(true)).unwrap());
<() as KeleusmaType<i64, f64>>::from_value(&Value::Unit).unwrap();
assert_eq!(
<i64 as KeleusmaType<i64, f64>>::into_value(42i64),
Value::Int(42)
);
assert_eq!(
<f64 as KeleusmaType<i64, f64>>::into_value(2.5f64),
Value::Float(2.5)
);
assert_eq!(
<bool as KeleusmaType<i64, f64>>::into_value(true),
Value::Bool(true)
);
assert_eq!(<() as KeleusmaType<i64, f64>>::into_value(()), Value::Unit);
}
#[test]
fn i64_to_f64_widening() {
assert_eq!(
<f64 as KeleusmaType<i64, f64>>::from_value(&Value::Int(7)).unwrap(),
7.0
);
}
#[test]
fn type_mismatch_errors() {
let err = <i64 as KeleusmaType<i64, f64>>::from_value(&Value::Bool(true)).unwrap_err();
match err {
VmError::TypeError(msg) => assert!(msg.contains("expected Word")),
other => panic!("expected TypeError, got {:?}", other),
}
}
#[test]
fn option_roundtrip() {
let some = <Option<i64> as KeleusmaType<i64, f64>>::into_value(Some(42i64));
assert_eq!(some, Value::Int(42));
let none = <Option<i64> as KeleusmaType<i64, f64>>::into_value(Option::<i64>::None);
assert_eq!(none, Value::None);
let recovered: Option<i64> =
<Option<i64> as KeleusmaType<i64, f64>>::from_value(&Value::Int(42)).unwrap();
assert_eq!(recovered, Some(42));
let recovered_none: Option<i64> =
<Option<i64> as KeleusmaType<i64, f64>>::from_value(&Value::None).unwrap();
assert_eq!(recovered_none, Option::None);
}
#[test]
fn tuple_roundtrip() {
let t = (1i64, 2.0f64, true);
let v = <(i64, f64, bool) as KeleusmaType<i64, f64>>::into_value(t);
match &v {
Value::Tuple(items) => assert_eq!(items.len(), 3),
other => panic!("expected tuple, got {:?}", other),
}
let r: (i64, f64, bool) =
<(i64, f64, bool) as KeleusmaType<i64, f64>>::from_value(&v).unwrap();
assert_eq!(r, (1, 2.0, true));
}
#[test]
fn array_roundtrip() {
let a: [i64; 3] = [10, 20, 30];
let v = <[i64; 3] as KeleusmaType<i64, f64>>::into_value(a);
let r: [i64; 3] = <[i64; 3] as KeleusmaType<i64, f64>>::from_value(&v).unwrap();
assert_eq!(r, [10, 20, 30]);
}
#[test]
fn array_length_mismatch() {
let v = Value::Array(::alloc::vec![Value::Int(1), Value::Int(2)]);
let err = <[i64; 3] as KeleusmaType<i64, f64>>::from_value(&v).unwrap_err();
match err {
VmError::TypeError(msg) => assert!(msg.contains("length")),
other => panic!("expected TypeError, got {:?}", other),
}
}
fn ctx(arena: &keleusma_arena::Arena) -> crate::vm::NativeCtx<'_> {
crate::vm::NativeCtx { arena }
}
#[test]
fn into_native_fn_arity_zero() {
let f = || 42i64;
let native = <_ as IntoNativeFn<i64, f64, (), i64>>::into_native_fn(f);
let arena = keleusma_arena::Arena::with_capacity(64);
let r = native(&ctx(&arena), &[]).unwrap();
assert_eq!(r, Value::Int(42));
}
#[test]
fn into_native_fn_arity_one() {
let f = |x: i64| x * 2;
let native = <_ as IntoNativeFn<i64, f64, (i64,), i64>>::into_native_fn(f);
let arena = keleusma_arena::Arena::with_capacity(64);
let r = native(&ctx(&arena), &[Value::Int(7)]).unwrap();
assert_eq!(r, Value::Int(14));
}
#[test]
fn into_native_fn_arity_two() {
let f = |a: i64, b: i64| a + b;
let native = <_ as IntoNativeFn<i64, f64, (i64, i64), i64>>::into_native_fn(f);
let arena = keleusma_arena::Arena::with_capacity(64);
let r = native(&ctx(&arena), &[Value::Int(3), Value::Int(4)]).unwrap();
assert_eq!(r, Value::Int(7));
}
#[test]
fn into_native_fn_arity_mismatch_errors() {
let f = |x: i64| x;
let native = <_ as IntoNativeFn<i64, f64, (i64,), i64>>::into_native_fn(f);
let arena = keleusma_arena::Arena::with_capacity(64);
let err = native(&ctx(&arena), &[Value::Int(1), Value::Int(2)]).unwrap_err();
match err {
VmError::NativeError(msg) => assert!(msg.contains("expected 1 argument")),
other => panic!("expected NativeError, got {:?}", other),
}
}
#[test]
fn into_fallible_native_fn_propagates_error() {
let f = |x: i64| -> Result<i64, VmError> {
if x == 0 {
Err(VmError::DivisionByZero)
} else {
Ok(100 / x)
}
};
let native = <_ as IntoFallibleNativeFn<i64, f64, (i64,), i64>>::into_native_fn(f);
let arena = keleusma_arena::Arena::with_capacity(64);
let r = native(&ctx(&arena), &[Value::Int(5)]).unwrap();
assert_eq!(r, Value::Int(20));
let err = native(&ctx(&arena), &[Value::Int(0)]).unwrap_err();
match err {
VmError::DivisionByZero => {}
other => panic!("expected DivisionByZero, got {:?}", other),
}
}
#[test]
fn type_error_message_contains_typename() {
let err = <i64 as KeleusmaType<i64, f64>>::from_value(&Value::Float(1.5)).unwrap_err();
match err {
VmError::TypeError(msg) => {
assert!(msg.contains("Float"), "got message: {}", msg)
}
other => panic!("expected TypeError, got {:?}", other),
}
}
}