#![cfg_attr(not(feature = "all-operators"), allow(dead_code, unused_imports))]
use datavalue::NumberValue;
use super::eager::Cx;
use crate::arena::{DataValue, data_to_str, truthy_arena};
use crate::{CompiledNode, Error, Result};
pub(crate) trait Extract<'a> {
type Raw;
type Out;
const SPAN: (u8, Option<u8>);
fn fetch(args: &'a [CompiledNode], i: usize, cx: &mut Cx<'_, 'a>) -> Result<Self::Raw>;
fn coerce(raw: Self::Raw, cx: &mut Cx<'_, 'a>) -> Result<Self::Out>;
}
pub(crate) trait Coerce<'a> {
type Out;
fn coerce(value: &'a DataValue<'a>, cx: &mut Cx<'_, 'a>) -> Result<Self::Out>;
}
impl<'a, T: Coerce<'a>> Extract<'a> for T {
type Raw = &'a DataValue<'a>;
type Out = T::Out;
const SPAN: (u8, Option<u8>) = (1, Some(1));
#[inline(always)]
fn fetch(args: &'a [CompiledNode], i: usize, cx: &mut Cx<'_, 'a>) -> Result<Self::Raw> {
cx.eval(&args[i])
}
#[inline(always)]
fn coerce(raw: Self::Raw, cx: &mut Cx<'_, 'a>) -> Result<Self::Out> {
<T as Coerce<'a>>::coerce(raw, cx)
}
}
pub(crate) struct Any;
impl<'a> Coerce<'a> for Any {
type Out = &'a DataValue<'a>;
#[inline(always)]
fn coerce(value: &'a DataValue<'a>, _cx: &mut Cx<'_, 'a>) -> Result<Self::Out> {
Ok(value)
}
}
pub(crate) struct Str;
impl<'a> Coerce<'a> for Str {
type Out = &'a str;
#[inline(always)]
fn coerce(value: &'a DataValue<'a>, cx: &mut Cx<'_, 'a>) -> Result<Self::Out> {
Ok(data_to_str(value, cx.arena))
}
}
pub(crate) struct Int;
impl<'a> Coerce<'a> for Int {
type Out = i64;
#[inline(always)]
fn coerce(value: &'a DataValue<'a>, _cx: &mut Cx<'_, 'a>) -> Result<Self::Out> {
value.as_i64().ok_or_else(Error::invalid_args)
}
}
pub(crate) struct StrictNum;
impl<'a> Coerce<'a> for StrictNum {
type Out = f64;
#[inline(always)]
fn coerce(value: &'a DataValue<'a>, _cx: &mut Cx<'_, 'a>) -> Result<Self::Out> {
match value {
DataValue::Number(n) => Ok(n.as_f64()),
DataValue::String(s) => crate::arena::parse_finite(s).ok_or_else(Error::invalid_args),
_ => Err(Error::invalid_args()),
}
}
}
pub(crate) struct Truthy;
impl<'a> Coerce<'a> for Truthy {
type Out = bool;
#[inline(always)]
fn coerce(value: &'a DataValue<'a>, cx: &mut Cx<'_, 'a>) -> Result<Self::Out> {
Ok(truthy_arena(value, cx.engine))
}
}
pub(crate) struct Obj;
impl<'a> Coerce<'a> for Obj {
type Out = &'a [(&'a str, DataValue<'a>)];
#[inline(always)]
fn coerce(value: &'a DataValue<'a>, _cx: &mut Cx<'_, 'a>) -> Result<Self::Out> {
match value {
DataValue::Object(pairs) => Ok(pairs),
_ => Err(Error::invalid_args()),
}
}
}
pub(crate) struct Nullable<T>(std::marker::PhantomData<T>);
impl<'a, T: Coerce<'a>> Coerce<'a> for Nullable<T> {
type Out = Option<T::Out>;
#[inline(always)]
fn coerce(value: &'a DataValue<'a>, cx: &mut Cx<'_, 'a>) -> Result<Self::Out> {
match value {
DataValue::Null => Ok(None),
value => T::coerce(value, cx).map(Some),
}
}
}
pub(crate) struct Opt<T>(std::marker::PhantomData<T>);
impl<'a, T: Coerce<'a>> Extract<'a> for Opt<T> {
type Raw = Option<&'a DataValue<'a>>;
type Out = Option<T::Out>;
const SPAN: (u8, Option<u8>) = (0, Some(1));
#[inline(always)]
fn fetch(args: &'a [CompiledNode], i: usize, cx: &mut Cx<'_, 'a>) -> Result<Self::Raw> {
match args.get(i) {
Some(node) => Ok(Some(cx.eval(node)?)),
None => Ok(None),
}
}
#[inline(always)]
fn coerce(raw: Self::Raw, cx: &mut Cx<'_, 'a>) -> Result<Self::Out> {
raw.map(|v| T::coerce(v, cx)).transpose()
}
}
pub(crate) struct Lenient<T>(std::marker::PhantomData<T>);
impl<'a, T: Coerce<'a>> Extract<'a> for Lenient<T> {
type Raw = Option<&'a DataValue<'a>>;
type Out = Option<T::Out>;
const SPAN: (u8, Option<u8>) = (0, Some(1));
#[inline(always)]
fn fetch(args: &'a [CompiledNode], i: usize, cx: &mut Cx<'_, 'a>) -> Result<Self::Raw> {
match args.get(i) {
Some(node) => Ok(Some(cx.eval(node)?)),
None => Ok(None),
}
}
#[inline(always)]
fn coerce(raw: Self::Raw, cx: &mut Cx<'_, 'a>) -> Result<Self::Out> {
Ok(raw.and_then(|v| T::coerce(v, cx).ok()))
}
}
pub(crate) struct Lazy;
impl<'a> Extract<'a> for Lazy {
type Raw = Option<&'a CompiledNode>;
type Out = Option<&'a CompiledNode>;
const SPAN: (u8, Option<u8>) = (0, Some(1));
#[inline(always)]
fn fetch(args: &'a [CompiledNode], i: usize, _cx: &mut Cx<'_, 'a>) -> Result<Self::Raw> {
Ok(args.get(i))
}
#[inline(always)]
fn coerce(raw: Self::Raw, _cx: &mut Cx<'_, 'a>) -> Result<Self::Out> {
Ok(raw)
}
}
pub(crate) struct Rest<T>(std::marker::PhantomData<T>);
impl<'a, T: Coerce<'a>> Extract<'a> for Rest<T> {
type Raw = &'a [CompiledNode];
type Out = RestArgs<'a, T>;
const SPAN: (u8, Option<u8>) = (0, None);
#[inline(always)]
fn fetch(args: &'a [CompiledNode], i: usize, _cx: &mut Cx<'_, 'a>) -> Result<Self::Raw> {
Ok(args.get(i..).unwrap_or(&[]))
}
#[inline(always)]
fn coerce(raw: Self::Raw, _cx: &mut Cx<'_, 'a>) -> Result<Self::Out> {
Ok(RestArgs {
nodes: raw,
_coerce: std::marker::PhantomData,
})
}
}
pub(crate) struct RestArgs<'a, T> {
nodes: &'a [CompiledNode],
_coerce: std::marker::PhantomData<T>,
}
impl<'a, T: Coerce<'a>> RestArgs<'a, T> {
#[inline(always)]
pub(crate) fn len(&self) -> usize {
self.nodes.len()
}
#[inline(always)]
pub(crate) fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
#[inline(always)]
pub(crate) fn get(&self, i: usize, cx: &mut Cx<'_, 'a>) -> Result<T::Out> {
let value = cx.eval(&self.nodes[i])?;
T::coerce(value, cx)
}
}
pub(crate) trait IntoValue<'a> {
fn into_value(self, cx: &mut Cx<'_, 'a>) -> &'a DataValue<'a>;
}
impl<'a> IntoValue<'a> for &'a DataValue<'a> {
#[inline(always)]
fn into_value(self, _cx: &mut Cx<'_, 'a>) -> &'a DataValue<'a> {
self
}
}
impl<'a> IntoValue<'a> for DataValue<'a> {
#[inline(always)]
fn into_value(self, cx: &mut Cx<'_, 'a>) -> &'a DataValue<'a> {
cx.alloc(self)
}
}
impl<'a> IntoValue<'a> for bool {
#[inline(always)]
fn into_value(self, _cx: &mut Cx<'_, 'a>) -> &'a DataValue<'a> {
crate::arena::singletons::singleton_bool(self)
}
}
impl<'a> IntoValue<'a> for &'a str {
#[inline(always)]
fn into_value(self, cx: &mut Cx<'_, 'a>) -> &'a DataValue<'a> {
if self.is_empty() {
crate::arena::singletons::singleton_empty_string()
} else {
cx.alloc(DataValue::String(self))
}
}
}
impl<'a> IntoValue<'a> for i64 {
#[inline(always)]
fn into_value(self, cx: &mut Cx<'_, 'a>) -> &'a DataValue<'a> {
match crate::arena::singletons::singleton_small_int(self) {
Some(v) => v,
None => cx.alloc(DataValue::from_i64(self)),
}
}
}
impl<'a> IntoValue<'a> for NumberValue {
#[inline(always)]
fn into_value(self, cx: &mut Cx<'_, 'a>) -> &'a DataValue<'a> {
cx.alloc(DataValue::Number(self))
}
}
impl<'a> IntoValue<'a> for f64 {
#[inline(always)]
fn into_value(self, cx: &mut Cx<'_, 'a>) -> &'a DataValue<'a> {
NumberValue::from_f64(self).into_value(cx)
}
}
impl<'a> IntoValue<'a> for bumpalo::collections::Vec<'a, DataValue<'a>> {
#[inline(always)]
fn into_value(self, cx: &mut Cx<'_, 'a>) -> &'a DataValue<'a> {
if self.is_empty() {
crate::arena::singletons::singleton_empty_array()
} else {
cx.alloc(DataValue::Array(self.into_bump_slice()))
}
}
}
impl<'a, T: IntoValue<'a>> IntoValue<'a> for Option<T> {
#[inline(always)]
fn into_value(self, cx: &mut Cx<'_, 'a>) -> &'a DataValue<'a> {
match self {
Some(v) => v.into_value(cx),
None => crate::arena::singletons::singleton_null(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Engine;
use crate::arena::ContextStack;
use bumpalo::Bump;
fn coerce<T>(value: &DataValue<'static>) -> Result<String>
where
T: for<'a> Coerce<'a>,
for<'a> <T as Coerce<'a>>::Out: std::fmt::Debug,
{
let engine = Engine::new();
let arena = Bump::new();
let root = arena.alloc(DataValue::Null);
let mut ctx = ContextStack::new(root, false);
let mut cx = Cx::new(&mut ctx, &engine, &arena);
let value: &DataValue<'_> = arena.alloc(*value);
T::coerce(value, &mut cx).map(|out| format!("{out:?}"))
}
#[test]
fn strict_num_takes_numbers_and_numeric_strings_only() {
assert_eq!(
coerce::<StrictNum>(&DataValue::from_i64(-3)).unwrap(),
"-3.0"
);
assert_eq!(
coerce::<StrictNum>(&DataValue::String("2.5")).unwrap(),
"2.5"
);
for bad in [
DataValue::String("x"),
DataValue::Bool(true),
DataValue::Null,
] {
assert!(coerce::<StrictNum>(&bad).is_err(), "{bad:?}");
}
}
#[test]
fn truthy_follows_the_engine_rule() {
assert_eq!(coerce::<Truthy>(&DataValue::from_i64(0)).unwrap(), "false");
assert_eq!(coerce::<Truthy>(&DataValue::String("a")).unwrap(), "true");
assert_eq!(coerce::<Truthy>(&DataValue::Array(&[])).unwrap(), "false");
}
#[test]
fn nullable_obj_admits_objects_and_null_only() {
assert_eq!(coerce::<Nullable<Obj>>(&DataValue::Null).unwrap(), "None");
assert_eq!(
coerce::<Nullable<Obj>>(&DataValue::Object(&[])).unwrap(),
"Some([])"
);
assert!(coerce::<Nullable<Obj>>(&DataValue::Array(&[])).is_err());
assert!(coerce::<Obj>(&DataValue::Null).is_err());
}
}