--- a/crates/jrsonnet-evaluator/src/arr/mod.rs +++ b/crates/jrsonnet-evaluator/src/arr/mod.rs @@ -1,14 +1,15 @@ use std::{ any::Any, fmt::{self}, - num::NonZeroU32, rc::Rc, + num::NonZeroU32, + rc::Rc, }; use jrsonnet_gcmodule::{cc_dyn, Cc}; use jrsonnet_interner::IBytes; use jrsonnet_parser::{Expr, Spanned}; -use crate::{function::FuncVal, Context, Result, Thunk, Val}; +use crate::{typed::NativeFn, Context, Result, Thunk, Val}; mod spec; pub use spec::{ArrayLike, *}; @@ -61,13 +62,13 @@ } #[must_use] - pub fn map(self, mapper: FuncVal) -> Self { - Self::new(>::new(self, mapper)) + pub fn map(self, mapper: NativeFn!((Val) -> Val)) -> Self { + Self::new(::new(self, ArrayMapper::Plain(mapper))) } #[must_use] - pub fn map_with_index(self, mapper: FuncVal) -> Self { - Self::new(>::new(self, mapper)) + pub fn map_with_index(self, mapper: NativeFn!((u32, Val) -> Val)) -> Self { + Self::new(::new(self, ArrayMapper::WithIndex(mapper))) } pub fn filter(self, filter: impl Fn(&Val) -> Result) -> Result { --- a/crates/jrsonnet-evaluator/src/arr/spec.rs +++ b/crates/jrsonnet-evaluator/src/arr/spec.rs @@ -6,9 +6,11 @@ use jrsonnet_parser::{Expr, Spanned}; use super::ArrValue; +use crate::typed::NativeFn; +use crate::val::NumValue; use crate::{ - error::ErrorKind::InfiniteRecursionDetected, evaluate, function::FuncVal, typed::Typed, - val::ThunkValue, Context, Error, ObjValue, Result, Thunk, Val, + error::ErrorKind::InfiniteRecursionDetected, evaluate, typed::Typed, val::ThunkValue, Context, + Error, ObjValue, Result, Thunk, Val, }; pub trait ArrayLike: Any + Trace + Debug { @@ -404,14 +406,20 @@ } } +#[derive(Trace, Clone, Debug)] +pub enum ArrayMapper { + Plain(NativeFn!((Val) -> Val)), + WithIndex(NativeFn!((u32, Val) -> Val)), +} + #[derive(Trace, Debug, Clone)] -pub struct MappedArray { +pub struct MappedArray { inner: ArrValue, cached: Cc>>, - mapper: FuncVal, + mapper: ArrayMapper, } -impl MappedArray { - pub fn new(inner: ArrValue, mapper: FuncVal) -> Self { +impl MappedArray { + pub fn new(inner: ArrValue, mapper: ArrayMapper) -> Self { let len = inner.len(); Self { inner, @@ -420,14 +428,13 @@ } } fn evaluate(&self, index: usize, value: Val) -> Result { - if WITH_INDEX { - self.mapper.evaluate_simple(&(index, value), false) - } else { - self.mapper.evaluate_simple(&(value,), false) + match &self.mapper { + ArrayMapper::Plain(f) => f.call(value), + ArrayMapper::WithIndex(f) => f.call(index as u32, value), } } } -impl ArrayLike for MappedArray { +impl ArrayLike for MappedArray { fn len(&self) -> usize { self.cached.borrow().len() } @@ -468,11 +475,11 @@ } fn get_lazy(&self, index: usize) -> Option> { #[derive(Trace)] - struct MappedArrayThunk { - arr: MappedArray, + struct MappedArrayThunk { + arr: MappedArray, index: usize, } - impl ThunkValue for MappedArrayThunk { + impl ThunkValue for MappedArrayThunk { type Output = Val; fn get(&self) -> Result { --- a/crates/jrsonnet-evaluator/src/function/mod.rs +++ b/crates/jrsonnet-evaluator/src/function/mod.rs @@ -8,15 +8,13 @@ use jrsonnet_parser::{Destruct, Expr, ExprParams, Span, Spanned}; use self::{ - arglike::OptionalContext, builtin::{Builtin, StaticBuiltin}, - native::NativeDesc, parse::{parse_builtin_call, parse_default_function_call, parse_function_call}, prepared::{parse_prepared_builtin_call, parse_prepared_function_call, PreparedCall}, }; use crate::{ bail, error::ErrorKind::*, evaluate, evaluate_trivial, function::builtin::BuiltinFunc, Context, - ContextBuilder, Result, Thunk, Val, + Result, Thunk, Val, }; pub mod arglike; @@ -199,18 +197,6 @@ b.call(loc, &args) } } - } - pub fn evaluate_simple( - &self, - args: &A, - tailstrict: bool, - ) -> Result { - self.evaluate( - ContextBuilder::new().build(), - CallLocation::native(), - args, - tailstrict, - ) } pub(crate) fn evaluate_prepared( @@ -246,10 +232,6 @@ b.call(loc, &args) } } - } - /// Convert jsonnet function to plain `Fn` value. - pub fn into_native(self) -> D::Value { - D::into_native(self) } /// Is this function an indentity function. --- a/crates/jrsonnet-evaluator/src/function/native.rs +++ b/crates/jrsonnet-evaluator/src/function/native.rs @@ -1,43 +1,2 @@ -use super::{ - arglike::{ArgLike, OptionalContext}, - FuncVal, -}; -use crate::{typed::Typed, Result}; - -pub trait NativeDesc { - type Value; - fn into_native(val: FuncVal) -> Self::Value; -} -macro_rules! impl_native_desc { - ($($gen:ident)*) => { - impl<$($gen,)* O> NativeDesc for (($($gen,)*), O) - where - $($gen: ArgLike + OptionalContext,)* - O: Typed, - { - type Value = Box Result>; - - #[allow(non_snake_case)] - fn into_native(val: FuncVal) -> Self::Value { - Box::new(move |$($gen),*| { - let val = val.evaluate_simple( - &($($gen,)*), - false, - )?; - O::from_untyped(val) - }) - } - } - }; - ($($cur:ident)* @ $c:ident $($rest:ident)*) => { - impl_native_desc!($($cur)*); - impl_native_desc!($($cur)* $c @ $($rest)*); - }; - ($($cur:ident)* @) => { - impl_native_desc!($($cur)*); - } -} - -impl_native_desc! { - @ A B C D E F G H I J K L -} +use super::PreparedFuncVal; +use crate::{typed::Typed, CallLocation, Result, Thunk}; --- a/crates/jrsonnet-evaluator/src/typed/conversions.rs +++ b/crates/jrsonnet-evaluator/src/typed/conversions.rs @@ -8,7 +8,7 @@ use crate::{ arr::{ArrValue, BytesArray}, bail, - function::{native::NativeDesc, FuncDesc, FuncVal}, + function::{CallLocation, FuncDesc, FuncVal, PreparedFuncVal}, typed::CheckType, val::{IndexableVal, NumValue, StrValue, ThunkMapper}, ObjValue, ObjValueBuilder, Result, ResultExt, Thunk, Val, @@ -675,30 +675,65 @@ } } -pub struct NativeFn(D::Value); -impl Deref for NativeFn { - type Target = D::Value; +#[derive(Debug, Trace, Clone)] +pub struct NativeFn(pub(crate) PreparedFuncVal, PhantomData); +macro_rules! impl_native_desc { + ($i:expr; $($gen:ident)*) => { + impl<$($gen,)* O> NativeFn<($($gen,)* O,)> + where + $($gen: Typed,)* + O: Typed, + { + pub fn call( + &self, + $($gen: $gen,)* + ) -> Result { + let val = self.0.call( + CallLocation::native(), + &[$(Typed::into_lazy_untyped($gen),)*], + &[], + )?; + O::from_untyped(val) + } + } + impl<$($gen,)* O> Typed for NativeFn<($($gen,)* O,)> { + const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Func); + + fn into_untyped(_typed: Self) -> Result { + bail!("can only convert functions from jsonnet to native") + } - fn deref(&self) -> &Self::Target { - &self.0 + fn from_untyped(untyped: Val) -> Result { + let func = FuncVal::from_untyped(untyped)?; + Ok(Self( + PreparedFuncVal::new(func, $i, &[])?, + PhantomData, + )) + } + } + }; + ($i:expr; $($cur:ident)* @ $c:ident $($rest:ident)*) => { + impl_native_desc!($i; $($cur)*); + impl_native_desc!($i + 1; $($cur)* $c @ $($rest)*); + }; + ($i:expr; $($cur:ident)* @) => { + impl_native_desc!($i; $($cur)*); } } -impl Typed for NativeFn { - const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Func); - fn into_untyped(_typed: Self) -> Result { - bail!("can only convert functions from jsonnet to native") - } +impl_native_desc! { + 0; @ A B C D E F G H I J K L +} - fn from_untyped(untyped: Val) -> Result { - Ok(Self( - untyped - .as_func() - .expect("shape is checked") - .into_native::(), - )) +mod native_macro { + #[macro_export] + macro_rules! NativeFn { + (($($t:ty),* $(,)?) -> $res:ty) => { + NativeFn<($($t,)* $res)> + } } } +pub use crate::NativeFn; impl Typed for NumValue { const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Num); --- a/crates/jrsonnet-macros/src/lib.rs +++ b/crates/jrsonnet-macros/src/lib.rs @@ -3,7 +3,14 @@ use proc_macro2::TokenStream; use quote::{quote, quote_spanned}; use syn::{ - Attribute, DeriveInput, Error, Expr, ExprClosure, FnArg, GenericArgument, Ident, ItemFn, LitStr, Meta, Pat, Path, PathArguments, Result, ReturnType, Token, Type, parenthesized, parse::{Parse, ParseStream}, parse_macro_input, punctuated::Punctuated, spanned::Spanned, token::{self, Comma} + parenthesized, + parse::{Parse, ParseStream}, + parse_macro_input, + punctuated::Punctuated, + spanned::Spanned, + token::{self, Comma}, + Attribute, DeriveInput, Error, Expr, ExprClosure, FnArg, GenericArgument, Ident, ItemFn, + LitStr, Meta, Pat, Path, PathArguments, Result, ReturnType, Token, Type, }; fn try_parse_attr_noargs(attrs: &[Attribute], ident: I) -> Result --- a/crates/jrsonnet-stdlib/src/arrays.rs +++ b/crates/jrsonnet-stdlib/src/arrays.rs @@ -23,7 +23,8 @@ return Ok(ArrValue::empty()); } func.evaluate_trivial().map_or_else( - || Ok(ArrValue::range_exclusive(0, *sz).map(func)), + // TODO: Different mapped array impl avoiding allocating unnecessary vals + || Ok(ArrValue::range_exclusive(0, *sz).map(Typed::from_untyped(Val::Func(func))?)), |trivial| { let mut out = Vec::with_capacity(*sz as usize); for _ in 0..*sz { @@ -58,19 +59,22 @@ } #[builtin] -pub fn builtin_map(func: FuncVal, arr: IndexableVal) -> ArrValue { +pub fn builtin_map(func: NativeFn!((Val) -> Val), arr: IndexableVal) -> ArrValue { let arr = arr.to_array(); arr.map(func) } #[builtin] -pub fn builtin_map_with_index(func: FuncVal, arr: IndexableVal) -> ArrValue { +pub fn builtin_map_with_index(func: NativeFn!((u32, Val) -> Val), arr: IndexableVal) -> ArrValue { let arr = arr.to_array(); arr.map_with_index(func) } #[builtin] -pub fn builtin_map_with_key(func: FuncVal, obj: ObjValue) -> Result { +pub fn builtin_map_with_key( + func: NativeFn!((IStr, Val) -> Val), + obj: ObjValue, +) -> Result { let mut out = ObjValueBuilder::new(); for (k, v) in obj.iter( // Makes sense mapped object should be ordered the same way, should not break anything when the output is not ordered (the default). @@ -80,15 +84,14 @@ true, ) { let v = v?; - out.field(k.clone()) - .value(func.evaluate_simple(&(k, v), false)?); + out.field(k.clone()).value(func.call(k, v)?); } Ok(out.build()) } #[builtin] pub fn builtin_flatmap( - func: NativeFn<((Either![String, Val],), Val)>, + func: NativeFn!((Either![String, Val]) -> Val), arr: IndexableVal, ) -> Result { use std::fmt::Write; @@ -96,9 +99,9 @@ IndexableVal::Str(str) => { let mut out = String::new(); for c in str.chars() { - match func(Either2::A(c.to_string()))? { + match func.call(Either2::A(c.to_string()))? { Val::Str(o) => write!(out, "{o}").unwrap(), - Val::Null => {}, + Val::Null => {} _ => bail!("in std.join all items should be strings"), } } @@ -108,13 +111,13 @@ let mut out = Vec::new(); for el in a.iter() { let el = el?; - match func(Either2::B(el))? { + match func.call(Either2::B(el))? { Val::Arr(o) => { for oe in o.iter() { out.push(oe?); } } - Val::Null => {}, + Val::Null => {} _ => bail!("in std.join all items should be arrays"), } } @@ -123,32 +126,38 @@ } } +type FilterFunc = NativeFn!((Val) -> bool); + #[builtin] -pub fn builtin_filter(func: FuncVal, arr: ArrValue) -> Result { - arr.filter(|val| bool::from_untyped(func.evaluate_simple(&(val.clone(),), false)?)) +pub fn builtin_filter(func: FilterFunc, arr: ArrValue) -> Result { + arr.filter(|val| func.call(val.clone())) } #[builtin] pub fn builtin_filter_map( - filter_func: FuncVal, - map_func: FuncVal, + filter_func: FilterFunc, + map_func: NativeFn!((Val) -> Val), arr: ArrValue, ) -> Result { Ok(builtin_filter(filter_func, arr)?.map(map_func)) } #[builtin] -pub fn builtin_foldl(func: FuncVal, arr: Either![ArrValue, IStr], init: Val) -> Result { +pub fn builtin_foldl( + func: NativeFn!((Val, Either![Val, char]) -> Val), + arr: Either![ArrValue, IStr], + init: Val, +) -> Result { let mut acc = init; match arr { Either2::A(arr) => { for i in arr.iter() { - acc = func.evaluate_simple(&(acc, i?), false)?; + acc = func.call(acc, Either2::A(i?))?; } } Either2::B(arr) => { - for i in arr.chars() { - acc = func.evaluate_simple(&(acc, Val::string(i)), false)?; + for c in arr.chars() { + acc = func.call(acc, Either2::B(c))?; } } } @@ -156,17 +165,21 @@ } #[builtin] -pub fn builtin_foldr(func: FuncVal, arr: Either![ArrValue, IStr], init: Val) -> Result { +pub fn builtin_foldr( + func: NativeFn!((Either![Val, char], Val) -> Val), + arr: Either![ArrValue, IStr], + init: Val, +) -> Result { let mut acc = init; match arr { Either2::A(arr) => { for i in arr.iter().rev() { - acc = func.evaluate_simple(&(i?, acc), false)?; + acc = func.call(Either2::A(i?), acc)?; } } Either2::B(arr) => { - for i in arr.chars().rev() { - acc = func.evaluate_simple(&(Val::string(i), acc), false)?; + for c in arr.chars().rev() { + acc = func.call(Either2::B(c), acc)?; } } } --- a/crates/jrsonnet-stdlib/src/lib.rs +++ b/crates/jrsonnet-stdlib/src/lib.rs @@ -38,6 +38,7 @@ mod compat; mod encoding; mod hash; +mod keyf; mod manifest; mod math; mod misc; @@ -50,7 +51,6 @@ mod sort; mod strings; mod types; -mod keyf; #[allow(clippy::too_many_lines)] pub fn stdlib_uncached(settings: Cc>) -> ObjValue { --- a/tests/tests/as_native.rs +++ /dev/null @@ -1,22 +0,0 @@ -use jrsonnet_evaluator::{FileImportResolver, Result, State, trace::PathResolver}; -use jrsonnet_stdlib::ContextInitializer; - -mod common; - -#[test] -fn as_native() -> Result<()> { - let mut s = State::builder(); - s.context_initializer(ContextInitializer::new(PathResolver::new_cwd_fallback())) - .import_resolver(FileImportResolver::default()); - let s = s.build(); - - let val = s.evaluate_snippet("snip".to_owned(), r"function(a, b) a + b")?; - let func = val.as_func().expect("this is function"); - - let native = func.into_native::<((u32, u32), u32)>(); - - ensure_eq!(native(1, 2)?, 3); - ensure_eq!(native(3, 4)?, 7); - - Ok(()) -}