difftreelog
perf use prepared call for KeyF
in: master
9 files changed
crates/jrsonnet-evaluator/src/function/builtin.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/function/builtin.rs
+++ b/crates/jrsonnet-evaluator/src/function/builtin.rs
@@ -3,8 +3,8 @@
use jrsonnet_gcmodule::{cc_dyn, Trace, TraceBox};
use jrsonnet_parser::function::{FunctionSignature, ParamDefault, ParamName, ParamParse};
-use super::{arglike::ArgsLike, parse::parse_builtin_call, CallLocation};
-use crate::{Context, Result, Val};
+use super::CallLocation;
+use crate::{Result, Thunk, Val};
#[macro_export]
macro_rules! params {
@@ -34,8 +34,8 @@
self.0.params()
}
- fn call(&self, ctx: Context, loc: CallLocation<'_>, args: &dyn ArgsLike) -> Result<Val> {
- self.0.call(ctx, loc, args)
+ fn call(&self, loc: CallLocation<'_>, args: &[Option<Thunk<Val>>]) -> Result<Val> {
+ self.0.call(loc, args)
}
fn as_any(&self) -> &dyn Any {
@@ -52,7 +52,7 @@
/// Parameter names for named calls
fn params(&self) -> FunctionSignature;
/// Call the builtin
- fn call(&self, ctx: Context, loc: CallLocation<'_>, args: &dyn ArgsLike) -> Result<Val>;
+ fn call(&self, loc: CallLocation<'_>, args: &[Option<Thunk<Val>>]) -> Result<Val>;
fn as_any(&self) -> &dyn Any;
}
@@ -96,11 +96,10 @@
self.params.clone()
}
- fn call(&self, ctx: Context, _loc: CallLocation<'_>, args: &dyn ArgsLike) -> Result<Val> {
- let args = parse_builtin_call(ctx, self.params.clone(), args, true)?;
+ fn call(&self, _loc: CallLocation<'_>, args: &[Option<Thunk<Val>>]) -> Result<Val> {
let args = args
.into_iter()
- .map(|a| a.expect("legacy natives have no default params"))
+ .map(|a| a.as_ref().expect("legacy natives have no default params"))
.map(|a| a.evaluate())
.collect::<Result<Vec<Val>>>()?;
self.handler.call(&args)
crates/jrsonnet-evaluator/src/function/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/function/mod.rs
+++ b/crates/jrsonnet-evaluator/src/function/mod.rs
@@ -11,7 +11,8 @@
arglike::OptionalContext,
builtin::{Builtin, StaticBuiltin},
native::NativeDesc,
- parse::{parse_default_function_call, parse_function_call},
+ 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,
@@ -22,7 +23,9 @@
pub mod builtin;
pub mod native;
pub mod parse;
-pub mod prepared;
+mod prepared;
+
+pub use prepared::PreparedFuncVal;
pub use jrsonnet_parser::function::*;
@@ -173,7 +176,6 @@
tailstrict: bool,
) -> Result<Val> {
match self {
- Self::Id => ID.call(call_ctx, loc, args),
Self::Normal(func) => {
let body_ctx = func.call_body_context(call_ctx, args, tailstrict)?;
evaluate(body_ctx, &func.body)
@@ -184,8 +186,18 @@
}
thunk.evaluate()
}
- Self::StaticBuiltin(b) => b.call(call_ctx, loc, args),
- Self::Builtin(b) => b.call(call_ctx, loc, args),
+ Self::Id => {
+ let args = parse_builtin_call(call_ctx, ID.params(), args, tailstrict)?;
+ ID.call(loc, &args)
+ }
+ Self::StaticBuiltin(b) => {
+ let args = parse_builtin_call(call_ctx, b.params(), args, tailstrict)?;
+ b.call(loc, &args)
+ }
+ Self::Builtin(b) => {
+ let args = parse_builtin_call(call_ctx, b.params(), args, tailstrict)?;
+ b.call(loc, &args)
+ }
}
}
pub fn evaluate_simple<A: ArgsLike + OptionalContext>(
@@ -200,6 +212,41 @@
tailstrict,
)
}
+
+ pub(crate) fn evaluate_prepared(
+ &self,
+ prepared: &PreparedCall,
+ loc: CallLocation<'_>,
+ unnamed: &[Thunk<Val>],
+ named: &[Thunk<Val>],
+ _tailstrict: bool,
+ ) -> Result<Val> {
+ match self {
+ FuncVal::Id => {
+ let args = parse_prepared_builtin_call(prepared, ID.params(), unnamed, named)?;
+ ID.call(loc, &args)
+ }
+ FuncVal::Normal(func) => {
+ let body_ctx = parse_prepared_function_call(
+ func.ctx.clone(),
+ prepared,
+ &func.params,
+ unnamed,
+ named,
+ )?;
+ evaluate(body_ctx, &func.body)
+ }
+ FuncVal::Thunk(t) => t.evaluate(),
+ FuncVal::StaticBuiltin(b) => {
+ let args = parse_prepared_builtin_call(prepared, b.params(), unnamed, named)?;
+ b.call(loc, &args)
+ }
+ FuncVal::Builtin(b) => {
+ let args = parse_prepared_builtin_call(prepared, b.params(), unnamed, named)?;
+ b.call(loc, &args)
+ }
+ }
+ }
/// Convert jsonnet function to plain `Fn` value.
pub fn into_native<D: NativeDesc>(self) -> D::Value {
D::into_native(self)
crates/jrsonnet-evaluator/src/function/prepared.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/function/prepared.rs
+++ b/crates/jrsonnet-evaluator/src/function/prepared.rs
@@ -1,3 +1,6 @@
+use std::rc::Rc;
+
+use jrsonnet_gcmodule::{Acyclic, Trace};
use jrsonnet_parser::function::FunctionSignature;
use jrsonnet_parser::{ExprParams, IStr};
use rustc_hash::{FxHashMap, FxHashSet};
@@ -7,6 +10,34 @@
use crate::{bail, error::ErrorKind::*, Result};
use crate::{evaluate_named_param, Context, ContextBuilder, Pending, Thunk, Val};
+use super::{CallLocation, FuncVal};
+
+#[derive(Debug, Trace, Clone)]
+pub struct PreparedFuncVal {
+ fun: FuncVal,
+ prepared: Rc<PreparedCall>,
+}
+
+impl PreparedFuncVal {
+ pub fn new(fun: FuncVal, unnamed: usize, named: &[IStr]) -> Result<Self> {
+ let prepared = prepare_call(fun.params(), unnamed, named)?;
+ Ok(Self {
+ fun,
+ prepared: Rc::new(prepared),
+ })
+ }
+ pub fn call(
+ &self,
+ loc: CallLocation<'_>,
+ unnamed: &[Thunk<Val>],
+ named: &[Thunk<Val>],
+ ) -> Result<Val> {
+ self.fun
+ .evaluate_prepared(&self.prepared, loc, unnamed, named, false)
+ }
+}
+
+#[derive(Acyclic, Debug)]
pub struct PreparedCall {
// Param, named input.
named: Vec<(usize, usize)>,
crates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth1use std::string::String;23use proc_macro2::TokenStream;4use quote::{quote, quote_spanned};5use syn::{6 parenthesized,7 parse::{Parse, ParseStream},8 parse_macro_input,9 punctuated::Punctuated,10 spanned::Spanned,11 token::{self, Comma},12 Attribute, DeriveInput, Error, Expr, ExprClosure, FnArg, GenericArgument, Ident, ItemFn,13 LitStr, Pat, Path, PathArguments, Result, ReturnType, Token, Type,14};1516fn parse_attr<A: Parse, I>(attrs: &[Attribute], ident: I) -> Result<Option<A>>17where18 Ident: PartialEq<I>,19{20 let attrs = attrs21 .iter()22 .filter(|a| a.path().is_ident(&ident))23 .collect::<Vec<_>>();24 if attrs.len() > 1 {25 return Err(Error::new(26 attrs[1].span(),27 "this attribute may be specified only once",28 ));29 } else if attrs.is_empty() {30 return Ok(None);31 }32 let attr = attrs[0];33 let attr = attr.parse_args::<A>()?;3435 Ok(Some(attr))36}37fn remove_attr<I>(attrs: &mut Vec<Attribute>, ident: I)38where39 Ident: PartialEq<I>,40{41 attrs.retain(|a| !a.path().is_ident(&ident));42}4344fn path_is(path: &Path, needed: &str) -> bool {45 path.leading_colon.is_none()46 && !path.segments.is_empty()47 && path.segments.iter().last().unwrap().ident == needed48}4950fn type_is_path<'ty>(ty: &'ty Type, needed: &str) -> Option<&'ty PathArguments> {51 match ty {52 Type::Path(path) if path.qself.is_none() && path_is(&path.path, needed) => {53 let args = &path.path.segments.iter().last().unwrap().arguments;54 Some(args)55 }56 _ => None,57 }58}5960fn extract_type_from_option(ty: &Type) -> Result<Option<&Type>> {61 let Some(args) = type_is_path(ty, "Option") else {62 return Ok(None);63 };64 // It should have only on angle-bracketed param ("<String>"):65 let PathArguments::AngleBracketed(params) = args else {66 return Err(Error::new(args.span(), "missing option generic"));67 };68 let generic_arg = params.args.iter().next().unwrap();69 // This argument must be a type:70 let GenericArgument::Type(ty) = generic_arg else {71 return Err(Error::new(72 generic_arg.span(),73 "option generic should be a type",74 ));75 };76 Ok(Some(ty))77}7879struct Field {80 attrs: Vec<Attribute>,81 name: Ident,82 _colon: Token![:],83 ty: Type,84}85impl Parse for Field {86 fn parse(input: ParseStream) -> syn::Result<Self> {87 Ok(Self {88 attrs: input.call(Attribute::parse_outer)?,89 name: input.parse()?,90 _colon: input.parse()?,91 ty: input.parse()?,92 })93 }94}9596mod kw {97 syn::custom_keyword!(fields);98 syn::custom_keyword!(rename);99 syn::custom_keyword!(alias);100 syn::custom_keyword!(flatten);101 syn::custom_keyword!(add);102 syn::custom_keyword!(hide);103 syn::custom_keyword!(ok);104}105106struct BuiltinAttrs {107 fields: Vec<Field>,108}109impl Parse for BuiltinAttrs {110 fn parse(input: ParseStream) -> syn::Result<Self> {111 if input.is_empty() {112 return Ok(Self { fields: Vec::new() });113 }114 input.parse::<kw::fields>()?;115 let fields;116 parenthesized!(fields in input);117 let p = Punctuated::<Field, Comma>::parse_terminated(&fields)?;118 Ok(Self {119 fields: p.into_iter().collect(),120 })121 }122}123124enum Optionality {125 Required,126 Optional,127 Default(Expr),128}129130#[allow(clippy::large_enum_variant, reason = "this macro is not that hot for it to matter")]131enum ArgInfo {132 Normal {133 ty: Box<Type>,134 optionality: Optionality,135 name: Option<String>,136 cfg_attrs: Vec<Attribute>,137 },138 Lazy {139 is_option: bool,140 name: Option<String>,141 },142 Context,143 Location,144 This,145}146147impl ArgInfo {148 fn parse(name: &str, arg: &mut FnArg) -> Result<Self> {149 let FnArg::Typed(arg) = arg else {150 unreachable!()151 };152 let ident = match &arg.pat as &Pat {153 Pat::Ident(i) => Some(i.ident.clone()),154 _ => None,155 };156 let ty = &arg.ty;157 if type_is_path(ty, "Context").is_some() {158 return Ok(Self::Context);159 } else if type_is_path(ty, "CallLocation").is_some() {160 return Ok(Self::Location);161 } else if type_is_path(ty, "Thunk").is_some() {162 return Ok(Self::Lazy {163 is_option: false,164 name: ident.map(|v| v.to_string()),165 });166 }167168 match ty as &Type {169 Type::Reference(r) if type_is_path(&r.elem, name).is_some() => return Ok(Self::This),170 _ => {}171 }172173 let (optionality, ty) = if let Some(default) = parse_attr::<_, _>(&arg.attrs, "default")? {174 remove_attr(&mut arg.attrs, "default");175 (Optionality::Default(default), ty.clone())176 } else if let Some(ty) = extract_type_from_option(ty)? {177 if type_is_path(ty, "Thunk").is_some() {178 return Ok(Self::Lazy {179 is_option: true,180 name: ident.map(|v| v.to_string()),181 });182 }183184 (Optionality::Optional, Box::new(ty.clone()))185 } else {186 (Optionality::Required, ty.clone())187 };188189 let cfg_attrs = arg190 .attrs191 .iter()192 .filter(|a| a.path().is_ident("cfg"))193 .cloned()194 .collect();195196 Ok(Self::Normal {197 ty,198 optionality,199 name: ident.map(|v| v.to_string()),200 cfg_attrs,201 })202 }203}204205#[proc_macro_attribute]206pub fn builtin(207 attr: proc_macro::TokenStream,208 item: proc_macro::TokenStream,209) -> proc_macro::TokenStream {210 let attr = parse_macro_input!(attr as BuiltinAttrs);211 let item_fn = parse_macro_input!(item as ItemFn);212213 match builtin_inner(attr, item_fn) {214 Ok(v) => v.into(),215 Err(e) => e.into_compile_error().into(),216 }217}218219#[allow(clippy::too_many_lines)]220fn builtin_inner(attr: BuiltinAttrs, mut fun: ItemFn) -> syn::Result<TokenStream> {221 let ReturnType::Type(_, result) = &fun.sig.output else {222 return Err(Error::new(223 fun.sig.span(),224 "builtin should return something",225 ));226 };227228 let name = fun.sig.ident.to_string();229 let args = fun230 .sig231 .inputs232 .iter_mut()233 .map(|arg| ArgInfo::parse(&name, arg))234 .collect::<Result<Vec<_>>>()?;235236 let params_desc = args.iter().filter_map(|a| match a {237 ArgInfo::Normal {238 optionality,239 name,240 cfg_attrs,241 ..242 } => {243 let name = name244 .as_ref()245 .map_or_else(|| quote! {unnamed}, |n| quote! {named(#n)});246 let default = match optionality {247 Optionality::Required => quote!(ParamDefault::None),248 Optionality::Optional => quote!(ParamDefault::Exists),249 Optionality::Default(e) => quote!(ParamDefault::Literal(stringify!(#e))),250 };251 Some(quote! {252 #(#cfg_attrs)*253 [#name => #default],254 })255 }256 ArgInfo::Lazy { is_option, name } => {257 let name = name258 .as_ref()259 .map_or_else(|| quote! {unnamed}, |n| quote! {named(#n)});260 Some(quote! {261 [#name => ParamDefault::exists(#is_option)],262 })263 }264 ArgInfo::Context | ArgInfo::Location | ArgInfo::This => None,265 });266267 let mut id = 0usize;268 let pass = args269 .iter()270 .map(|a| match a {271 ArgInfo::Normal { .. } | ArgInfo::Lazy { .. } => {272 let cid = id;273 id += 1;274 (quote! {#cid}, a)275 }276 ArgInfo::Context | ArgInfo::Location | ArgInfo::This => {277 (quote! {compile_error!("should not use id")}, a)278 }279 })280 .map(|(id, a)| match a {281 ArgInfo::Normal {282 ty,283 optionality,284 name,285 cfg_attrs,286 } => {287 let name = name.as_ref().map_or("<unnamed>", String::as_str);288 let eval = quote! {jrsonnet_evaluator::in_description_frame(289 || format!("argument <{}> evaluation", #name),290 || <#ty>::from_untyped(value.evaluate()?),291 )?};292 let value = match optionality {293 Optionality::Required => quote! {{294 let value = parsed[#id].as_ref().expect("args shape is checked");295 #eval296 },},297 Optionality::Optional => quote! {if let Some(value) = &parsed[#id] {298 Some(#eval)299 } else {300 None301 },},302 Optionality::Default(expr) => quote! {if let Some(value) = &parsed[#id] {303 #eval304 } else {305 let v: #ty = #expr;306 v307 },},308 };309 quote! {310 #(#cfg_attrs)*311 #value312 }313 }314 ArgInfo::Lazy { is_option, .. } => {315 if *is_option {316 quote! {if let Some(value) = &parsed[#id] {317 Some(value.clone())318 } else {319 None320 },}321 } else {322 quote! {323 parsed[#id].as_ref().expect("args shape is correct").clone(),324 }325 }326 }327 ArgInfo::Context => quote! {ctx.clone(),},328 ArgInfo::Location => quote! {location,},329 ArgInfo::This => quote! {self,},330 });331332 let fields = attr.fields.iter().map(|field| {333 let attrs = &field.attrs;334 let name = &field.name;335 let ty = &field.ty;336 quote! {337 #(#attrs)*338 pub #name: #ty,339 }340 });341342 let name = &fun.sig.ident;343 let vis = &fun.vis;344 let static_ext = if attr.fields.is_empty() {345 quote! {346 impl #name {347 pub const INST: &'static dyn StaticBuiltin = &#name {};348 }349 impl StaticBuiltin for #name {}350 }351 } else {352 quote! {}353 };354 let static_derive_copy = if attr.fields.is_empty() {355 quote! {, Copy}356 } else {357 quote! {}358 };359360 Ok(quote! {361 #fun362363 #[doc(hidden)]364 #[allow(non_camel_case_types)]365 #[derive(Clone, jrsonnet_gcmodule::Trace #static_derive_copy)]366 #vis struct #name {367 #(#fields)*368 }369 const _: () = {370 use ::jrsonnet_evaluator::{371 State, Val,372 function::{builtin::{Builtin, StaticBuiltin}, FunctionSignature, ParamParse, ParamName, ParamDefault, CallLocation, ArgsLike, parse::parse_builtin_call},373 Result, Context, typed::Typed,374 parser::Span, params,375 };376 params!(377 #(#params_desc)*378 );379380 #static_ext381 impl Builtin for #name382 where383 Self: 'static384 {385 fn name(&self) -> &str {386 stringify!(#name)387 }388 fn params(&self) -> FunctionSignature {389 PARAMS.with(|p| p.clone())390 }391 #[allow(unused_variables)]392 fn call(&self, ctx: Context, location: CallLocation, args: &dyn ArgsLike) -> Result<Val> {393 let parsed = parse_builtin_call(ctx.clone(), self.params(), args, false)?;394395 let result: #result = #name(#(#pass)*);396 <_ as Typed>::into_result(result)397 }398 fn as_any(&self) -> &dyn ::std::any::Any {399 self400 }401 }402 };403 })404}405406#[derive(Default)]407#[allow(clippy::struct_excessive_bools)]408struct TypedAttr {409 rename: Option<String>,410 aliases: Vec<String>,411 flatten: bool,412 /// flatten(ok) strategy for flattened optionals413 /// field would be None in case of any parsing error (as in serde)414 flatten_ok: bool,415 // Should it be `field+:` instead of `field:`416 add: bool,417 // Should it be `field::` instead of `field:`418 hide: bool,419}420impl Parse for TypedAttr {421 fn parse(input: ParseStream) -> syn::Result<Self> {422 let mut out = Self::default();423 loop {424 let lookahead = input.lookahead1();425 if lookahead.peek(kw::rename) {426 input.parse::<kw::rename>()?;427 input.parse::<Token![=]>()?;428 let name = input.parse::<LitStr>()?;429 if out.rename.is_some() {430 return Err(Error::new(431 name.span(),432 "rename attribute may only be specified once",433 ));434 }435 out.rename = Some(name.value());436 } else if lookahead.peek(kw::alias) {437 input.parse::<kw::alias>()?;438 input.parse::<Token![=]>()?;439 let alias = input.parse::<LitStr>()?;440 out.aliases.push(alias.value());441 } else if lookahead.peek(kw::flatten) {442 input.parse::<kw::flatten>()?;443 out.flatten = true;444 if input.peek(token::Paren) {445 let content;446 parenthesized!(content in input);447 let lookahead = content.lookahead1();448 if lookahead.peek(kw::ok) {449 content.parse::<kw::ok>()?;450 out.flatten_ok = true;451 } else {452 return Err(lookahead.error());453 }454 }455 } else if lookahead.peek(kw::add) {456 input.parse::<kw::add>()?;457 out.add = true;458 } else if lookahead.peek(kw::hide) {459 input.parse::<kw::hide>()?;460 out.hide = true;461 } else if input.is_empty() {462 break;463 } else {464 return Err(lookahead.error());465 }466 if input.peek(Token![,]) {467 input.parse::<Token![,]>()?;468 } else {469 break;470 }471 }472 Ok(out)473 }474}475476struct TypedField {477 attr: TypedAttr,478 ident: Ident,479 ty: Type,480 is_option: bool,481 is_lazy: bool,482}483impl TypedField {484 fn parse(field: &syn::Field) -> Result<Self> {485 let attr = parse_attr::<TypedAttr, _>(&field.attrs, "typed")?.unwrap_or_default();486 let Some(ident) = field.ident.clone() else {487 return Err(Error::new(488 field.span(),489 "this field should appear in output object, but it has no visible name",490 ));491 };492 let (is_option, ty) = extract_type_from_option(&field.ty)?493 .map_or_else(|| (false, field.ty.clone()), |ty| (true, ty.clone()));494 if is_option && attr.flatten {495 if !attr.flatten_ok {496 return Err(Error::new(497 field.span(),498 "strategy should be set when flattening Option",499 ));500 }501 } else if attr.flatten_ok {502 return Err(Error::new(503 field.span(),504 "flatten(ok) is only useable on optional fields",505 ));506 }507508 let is_lazy = type_is_path(&ty, "Thunk").is_some();509510 Ok(Self {511 attr,512 ident,513 ty,514 is_option,515 is_lazy,516 })517 }518 /// None if this field is flattened in jsonnet output519 fn name(&self) -> Option<String> {520 if self.attr.flatten {521 return None;522 }523 Some(524 self.attr525 .rename526 .clone()527 .unwrap_or_else(|| self.ident.to_string()),528 )529 }530531 fn expand_field(&self) -> Option<TokenStream> {532 if self.is_option {533 return None;534 }535 let name = self.name()?;536 let ty = &self.ty;537 Some(quote! {538 (#name, <#ty as Typed>::TYPE)539 })540 }541542 fn expand_parse(&self) -> TokenStream {543 if self.is_option {544 self.expand_parse_optional()545 } else {546 self.expand_parse_mandatory()547 }548 }549550 fn expand_parse_optional(&self) -> TokenStream {551 let ident = &self.ident;552 let ty = &self.ty;553554 // optional flatten is handled in same way as serde555 if self.attr.flatten {556 return quote! {557 #ident: <#ty as TypedObj>::parse(&obj).ok(),558 };559 }560561 let name = self.name().unwrap();562 let aliases = &self.attr.aliases;563564 quote! {565 #ident: {566 let __value = if let Some(__v) = obj.get(#name.into())? {567 Some(__v)568 } #(else if let Some(__v) = obj.get(#aliases.into())? {569 Some(__v)570 })* else {571 None572 };573574 __value.map(<#ty as Typed>::from_untyped).transpose()?575 },576 }577 }578579 fn expand_parse_mandatory(&self) -> TokenStream {580 let ident = &self.ident;581 let ty = &self.ty;582583 // optional flatten is handled in same way as serde584 if self.attr.flatten {585 return quote! {586 #ident: <#ty as TypedObj>::parse(&obj)?,587 };588 }589590 let name = self.name().unwrap();591 let aliases = &self.attr.aliases;592593 let error_text = if aliases.is_empty() {594 // clippy does not understand name variable usage in quote! macro595 #[allow(clippy::redundant_clone)]596 name.clone()597 } else {598 format!("{name} (alias {})", aliases.join(", "))599 };600601 quote! {602 #ident: {603 let __value = if let Some(__v) = obj.get(#name.into())? {604 __v605 } #(else if let Some(__v) = obj.get(#aliases.into())? {606 __v607 })* else {608 return Err(ErrorKind::NoSuchField(#error_text.into(), vec![]).into());609 };610611 <#ty as Typed>::from_untyped(__value)?612 },613 }614 }615616 fn expand_serialize(&self) -> TokenStream {617 let ident = &self.ident;618 let ty = &self.ty;619 self.name().map_or_else(620 || {621 if self.is_option {622 quote! {623 if let Some(value) = self.#ident {624 <#ty as TypedObj>::serialize(value, out)?;625 }626 }627 } else {628 quote! {629 <#ty as TypedObj>::serialize(self.#ident, out)?;630 }631 }632 },633 |name| {634 let hide = if self.attr.hide {635 quote! {.hide()}636 } else {637 quote! {}638 };639 let add = if self.attr.add {640 quote! {.add()}641 } else {642 quote! {}643 };644 let value = if self.is_lazy {645 quote! {646 out.field(#name)647 #hide648 #add649 .try_thunk(<#ty as Typed>::into_lazy_untyped(value))?;650 }651 } else {652 quote! {653 out.field(#name)654 #hide655 #add656 .try_value(<#ty as Typed>::into_untyped(value)?)?;657 }658 };659 if self.is_option {660 quote! {661 if let Some(value) = self.#ident {662 #value663 }664 }665 } else {666 quote! {667 {668 let value = self.#ident;669 #value670 }671 }672 }673 },674 )675 }676}677678#[proc_macro_derive(Typed, attributes(typed))]679pub fn derive_typed(item: proc_macro::TokenStream) -> proc_macro::TokenStream {680 let input = parse_macro_input!(item as DeriveInput);681682 match derive_typed_inner(input) {683 Ok(v) => v.into(),684 Err(e) => e.to_compile_error().into(),685 }686}687688fn derive_typed_inner(input: DeriveInput) -> Result<TokenStream> {689 let syn::Data::Struct(data) = &input.data else {690 return Err(Error::new(input.span(), "only structs supported"));691 };692693 let ident = &input.ident;694 let fields = data695 .fields696 .iter()697 .map(TypedField::parse)698 .collect::<Result<Vec<_>>>()?;699700 let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();701702 let typed = {703 let fields = fields704 .iter()705 .filter_map(TypedField::expand_field)706 .collect::<Vec<_>>();707 quote! {708 impl #impl_generics Typed for #ident #ty_generics #where_clause {709 const TYPE: &'static ComplexValType = &ComplexValType::ObjectRef(&[710 #(#fields,)*711 ]);712713 fn from_untyped(value: Val) -> JrResult<Self> {714 let obj = value.as_obj().expect("shape is correct");715 Self::parse(&obj)716 }717718 fn into_untyped(value: Self) -> JrResult<Val> {719 let mut out = ObjValueBuilder::new();720 value.serialize(&mut out)?;721 Ok(Val::Obj(out.build()))722 }723724 }725 }726 };727728 let fields_parse = fields.iter().map(TypedField::expand_parse);729 let fields_serialize = fields730 .iter()731 .map(TypedField::expand_serialize)732 .collect::<Vec<_>>();733734 Ok(quote! {735 const _: () = {736 use ::jrsonnet_evaluator::{737 typed::{ComplexValType, Typed, TypedObj, CheckType},738 Val, State,739 error::{ErrorKind, Result as JrResult},740 ObjValueBuilder, ObjValue,741 };742743 #typed744745 impl #impl_generics TypedObj for #ident #ty_generics #where_clause {746 fn serialize(self, out: &mut ObjValueBuilder) -> JrResult<()> {747 #(#fields_serialize)*748749 Ok(())750 }751 fn parse(obj: &ObjValue) -> JrResult<Self> {752 Ok(Self {753 #(#fields_parse)*754 })755 }756 }757 };758 })759}760761struct FormatInput {762 formatting: LitStr,763 arguments: Vec<Expr>,764}765impl Parse for FormatInput {766 fn parse(input: ParseStream) -> Result<Self> {767 let formatting = input.parse()?;768 let mut arguments = Vec::new();769770 while input.peek(Token![,]) {771 input.parse::<Token![,]>()?;772 if input.is_empty() {773 // Trailing comma774 break;775 }776 let expr = input.parse()?;777 arguments.push(expr);778 }779780 if !input.is_empty() {781 return Err(syn::Error::new(input.span(), "unexpected trailing input"));782 }783784 Ok(Self {785 formatting,786 arguments,787 })788 }789}790fn is_format_str(i: &str) -> bool {791 let mut is_plain = true;792 // -1 = {793 // +1 = }794 let mut is_bracket = 0i8;795 for ele in i.chars() {796 match ele {797 '{' if is_bracket == -1 => {798 is_bracket = 0;799 }800 '}' if is_bracket == -1 => {801 is_plain = false;802 break;803 }804 '}' if is_bracket == 1 => {805 is_bracket = 0;806 }807 '{' if is_bracket == 1 => {808 is_plain = false;809 break;810 }811 '{' => {812 is_bracket = -1;813 }814 '}' => {815 is_bracket = 1;816 }817 _ if is_bracket != 0 => {818 is_plain = false;819 break;820 }821 _ => {}822 }823 }824 !is_plain || is_bracket != 0825}826impl FormatInput {827 fn expand(self) -> TokenStream {828 let format = self.formatting;829 if is_format_str(&format.value()) {830 let args = self.arguments;831 quote! {832 ::jrsonnet_evaluator::IStr::from(format!(#format #(, #args)*))833 }834 } else {835 if let Some(first) = self.arguments.first() {836 return syn::Error::new(837 first.span(),838 "string has no formatting codes, it should not have the arguments",839 )840 .into_compile_error();841 }842 quote! {843 ::jrsonnet_evaluator::IStr::from(#format)844 }845 }846 }847}848849/// `IStr` formatting helper850///851/// Using `format!("literal with no codes").into()` is slower than just `"literal with no codes".into()`852/// This macro looks for formatting codes in the input string, and uses853/// `format!()` only when necessary854#[proc_macro]855pub fn format_istr(input: proc_macro::TokenStream) -> proc_macro::TokenStream {856 let input = parse_macro_input!(input as FormatInput);857 input.expand().into()858}859860/// Create Thunk using closure syntax861#[proc_macro]862#[allow(non_snake_case)]863pub fn Thunk(input: proc_macro::TokenStream) -> proc_macro::TokenStream {864 let input = parse_macro_input!(input as ExprClosure);865866 let span = input.inputs.span();867 let move_check = input.capture.is_none().then(|| {868 quote_spanned! {span => {869 compile_error!("Thunk! needs to be called with move closure");870 }}871 });872873 let (env, closure, args) = syn_dissect_closure::split_env(input);874875 let trace_check = args.iter().map(|el| {876 let span = el.span();877 quote_spanned! {span => ::jrsonnet_evaluator::gc::assert_trace(&#el);}878 });879880 quote! {{881 #move_check882 #(#trace_check)*883 ::jrsonnet_evaluator::Thunk::new(::jrsonnet_evaluator::val::MemoizedClosureThunk::new(#env, #closure))884 }}.into()885}crates/jrsonnet-stdlib/src/keyf.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-stdlib/src/keyf.rs
@@ -0,0 +1,41 @@
+use jrsonnet_evaluator::function::{CallLocation, FuncVal, PreparedFuncVal};
+use jrsonnet_evaluator::typed::{ComplexValType, Typed, ValType};
+use jrsonnet_evaluator::{Error, Result, Thunk, Val};
+
+#[derive(Default, Clone)]
+pub enum KeyF {
+ #[default]
+ Identity,
+ Prepared(PreparedFuncVal),
+ PrepareFailure(Error),
+}
+impl KeyF {
+ pub fn is_identity(&self) -> bool {
+ matches!(self, Self::Identity)
+ }
+ fn new(val: FuncVal) -> Self {
+ if val.is_identity() {
+ Self::Identity
+ } else {
+ PreparedFuncVal::new(val, 1, &[]).map_or_else(Self::PrepareFailure, Self::Prepared)
+ }
+ }
+ pub fn eval(&self, val: impl Into<Thunk<Val>>) -> Result<Val> {
+ match self {
+ KeyF::Identity => val.into().evaluate(),
+ KeyF::Prepared(p) => p.call(CallLocation::native(), &[val.into()], &[]),
+ KeyF::PrepareFailure(e) => Err(e.clone()),
+ }
+ }
+}
+
+impl Typed for KeyF {
+ const TYPE: &'static ComplexValType = &ComplexValType::Simple(ValType::Func);
+ fn from_untyped(untyped: Val) -> Result<Self> {
+ FuncVal::from_untyped(untyped).map(Self::new)
+ }
+
+ fn into_untyped(_typed: Self) -> Result<Val> {
+ unreachable!("unused, todo: port split of Typed trait from #193")
+ }
+}
crates/jrsonnet-stdlib/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/lib.rs
+++ b/crates/jrsonnet-stdlib/src/lib.rs
@@ -50,6 +50,7 @@
mod sort;
mod strings;
mod types;
+mod keyf;
#[allow(clippy::too_many_lines)]
pub fn stdlib_uncached(settings: Cc<RefCell<Settings>>) -> ObjValue {
crates/jrsonnet-stdlib/src/sets.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/sets.rs
+++ b/crates/jrsonnet-stdlib/src/sets.rs
@@ -1,28 +1,23 @@
use std::cmp::Ordering;
use jrsonnet_evaluator::{
- function::{builtin, FuncVal},
- operator::evaluate_compare_op,
- val::ArrValue,
- Result, Thunk, Val,
+ function::builtin, operator::evaluate_compare_op, val::ArrValue, Result, Thunk, Val,
};
use jrsonnet_parser::BinaryOpType;
+use crate::keyf::KeyF;
+
#[builtin]
#[allow(non_snake_case)]
-pub fn builtin_set_member(x: Thunk<Val>, arr: ArrValue, keyF: Option<FuncVal>) -> Result<bool> {
+pub fn builtin_set_member(x: Thunk<Val>, arr: ArrValue, #[default] keyF: KeyF) -> Result<bool> {
let mut low = 0;
let mut high = arr.len();
- let keyF = keyF
- .unwrap_or(FuncVal::Id)
- .into_native::<((Thunk<Val>,), Val)>();
-
- let x = keyF(x)?;
+ let x = keyF.eval(x)?;
while low < high {
let middle = usize::midpoint(high, low);
- let comp = keyF(arr.get_lazy(middle).expect("in bounds"))?;
+ let comp = keyF.eval(arr.get_lazy(middle).expect("in bounds"))?;
match evaluate_compare_op(&comp, &x, BinaryOpType::Lt)? {
Ordering::Less => low = middle + 1,
Ordering::Equal => return Ok(true),
@@ -34,14 +29,11 @@
#[builtin]
#[allow(non_snake_case, clippy::redundant_closure)]
-pub fn builtin_set_inter(a: ArrValue, b: ArrValue, keyF: Option<FuncVal>) -> Result<ArrValue> {
+pub fn builtin_set_inter(a: ArrValue, b: ArrValue, #[default] keyF: KeyF) -> Result<ArrValue> {
let mut a = a.iter_lazy();
let mut b = b.iter_lazy();
- let keyF = keyF
- .unwrap_or(FuncVal::identity())
- .into_native::<((Thunk<Val>,), Val)>();
- let keyF = |v| keyF(v);
+ let keyF = |v| keyF.eval(v);
let mut av = a.next();
let mut bv = b.next();
@@ -73,14 +65,11 @@
#[builtin]
#[allow(non_snake_case, clippy::redundant_closure)]
-pub fn builtin_set_diff(a: ArrValue, b: ArrValue, keyF: Option<FuncVal>) -> Result<ArrValue> {
+pub fn builtin_set_diff(a: ArrValue, b: ArrValue, #[default] keyF: KeyF) -> Result<ArrValue> {
let mut a = a.iter_lazy();
let mut b = b.iter_lazy();
- let keyF = keyF
- .unwrap_or(FuncVal::identity())
- .into_native::<((Thunk<Val>,), Val)>();
- let keyF = |v| keyF(v);
+ let keyF = |v| keyF.eval(v);
let mut av = a.next();
let mut bv = b.next();
@@ -119,14 +108,11 @@
#[builtin]
#[allow(non_snake_case, clippy::redundant_closure)]
-pub fn builtin_set_union(a: ArrValue, b: ArrValue, keyF: Option<FuncVal>) -> Result<ArrValue> {
+pub fn builtin_set_union(a: ArrValue, b: ArrValue, #[default] keyF: KeyF) -> Result<ArrValue> {
let mut a = a.iter_lazy();
let mut b = b.iter_lazy();
- let keyF = keyF
- .unwrap_or(FuncVal::identity())
- .into_native::<((Thunk<Val>,), Val)>();
- let keyF = |v| keyF(v);
+ let keyF = |v| keyF.eval(v);
let mut av = a.next();
let mut bv = b.next();
crates/jrsonnet-stdlib/src/sort.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/sort.rs
+++ b/crates/jrsonnet-stdlib/src/sort.rs
@@ -4,14 +4,14 @@
use jrsonnet_evaluator::{
bail,
- function::{builtin, FuncVal},
+ function::builtin,
operator::evaluate_compare_op,
val::{equals, ArrValue},
Result, Thunk, Val,
};
use jrsonnet_parser::BinaryOpType;
-use crate::eval_on_empty;
+use crate::{eval_on_empty, keyf::KeyF};
#[derive(Copy, Clone)]
enum SortKeyType {
@@ -70,14 +70,11 @@
Ok(values)
}
-fn sort_keyf(values: ArrValue, keyf: FuncVal) -> Result<Vec<Thunk<Val>>> {
+fn sort_keyf(values: ArrValue, keyf: KeyF) -> Result<Vec<Thunk<Val>>> {
// Slow path, user provided key getter
let mut vk = Vec::with_capacity(values.len());
for value in values.iter_lazy() {
- vk.push((
- value.clone(),
- keyf.evaluate_simple(&(value.clone(),), false)?,
- ));
+ vk.push((value.clone(), keyf.eval(value)?));
}
let sort_type = get_sort_type(&vk, |v| &v.1)?;
match sort_type {
@@ -112,7 +109,7 @@
}
/// * `key_getter` - None, if identity sort required
-pub fn sort(values: ArrValue, key_getter: FuncVal) -> Result<ArrValue> {
+pub fn sort(values: ArrValue, key_getter: KeyF) -> Result<ArrValue> {
if values.len() <= 1 {
return Ok(values);
}
@@ -126,11 +123,7 @@
}
#[builtin]
-pub fn builtin_sort(
- arr: ArrValue,
-
- #[default(FuncVal::identity())] keyF: FuncVal,
-) -> Result<ArrValue> {
+pub fn builtin_sort(arr: ArrValue, #[default] keyF: KeyF) -> Result<ArrValue> {
super::sort::sort(arr, keyF)
}
@@ -147,14 +140,14 @@
Ok(out)
}
-fn uniq_keyf(arr: ArrValue, keyf: FuncVal) -> Result<Vec<Thunk<Val>>> {
+fn uniq_keyf(arr: ArrValue, keyf: KeyF) -> Result<Vec<Thunk<Val>>> {
let mut out = Vec::new();
let last_value = arr.get_lazy(0).unwrap();
- let mut last_key = keyf.evaluate_simple(&(last_value.clone(),), false)?;
+ let mut last_key = keyf.eval(last_value.clone())?;
out.push(last_value);
for next in arr.iter_lazy().skip(1) {
- let next_key = keyf.evaluate_simple(&(next.clone(),), false)?;
+ let next_key = keyf.eval(next.clone())?;
if !equals(&last_key, &next_key)? {
out.push(next.clone());
}
@@ -165,11 +158,7 @@
#[builtin]
#[allow(non_snake_case)]
-pub fn builtin_uniq(
- arr: ArrValue,
-
- #[default(FuncVal::identity())] keyF: FuncVal,
-) -> Result<ArrValue> {
+pub fn builtin_uniq(arr: ArrValue, #[default] keyF: KeyF) -> Result<ArrValue> {
if arr.len() <= 1 {
return Ok(arr);
}
@@ -184,11 +173,7 @@
#[builtin]
#[allow(non_snake_case)]
-pub fn builtin_set(
- arr: ArrValue,
-
- #[default(FuncVal::identity())] keyF: FuncVal,
-) -> Result<ArrValue> {
+pub fn builtin_set(arr: ArrValue, #[default] keyF: KeyF) -> Result<ArrValue> {
if arr.len() <= 1 {
return Ok(arr);
}
@@ -201,24 +186,16 @@
let arr = sort_keyf(arr, keyF.clone())?;
let arr = uniq_keyf(ArrValue::lazy(arr), keyF)?;
Ok(ArrValue::lazy(arr))
- }
-}
-
-fn eval_keyf(val: Val, key_f: Option<&FuncVal>) -> Result<Val> {
- if let Some(key_f) = key_f {
- key_f.evaluate_simple(&(val,), false)
- } else {
- Ok(val)
}
}
-fn array_top1(arr: ArrValue, key_f: Option<&FuncVal>, ordering: Ordering) -> Result<Val> {
+fn array_top1(arr: ArrValue, keyf: KeyF, ordering: Ordering) -> Result<Val> {
let mut iter = arr.iter();
let mut min = iter.next().expect("not empty")?;
- let mut min_key = eval_keyf(min.clone(), key_f)?;
+ let mut min_key = keyf.eval(Thunk::evaluated(min.clone()))?;
for item in iter {
let cur = item?;
- let cur_key = eval_keyf(cur.clone(), key_f)?;
+ let cur_key = keyf.eval(Thunk::evaluated(cur.clone()))?;
if evaluate_compare_op(&cur_key, &min_key, BinaryOpType::Lt)? == ordering {
min = cur;
min_key = cur_key;
@@ -230,22 +207,22 @@
#[builtin]
pub fn builtin_min_array(
arr: ArrValue,
- keyF: Option<FuncVal>,
+ #[default] keyF: KeyF,
onEmpty: Option<Thunk<Val>>,
) -> Result<Val> {
if arr.is_empty() {
return eval_on_empty(onEmpty);
}
- array_top1(arr, keyF.as_ref(), Ordering::Less)
+ array_top1(arr, keyF, Ordering::Less)
}
#[builtin]
pub fn builtin_max_array(
arr: ArrValue,
- keyF: Option<FuncVal>,
+ #[default] keyF: KeyF,
onEmpty: Option<Thunk<Val>>,
) -> Result<Val> {
if arr.is_empty() {
return eval_on_empty(onEmpty);
}
- array_top1(arr, keyF.as_ref(), Ordering::Greater)
+ array_top1(arr, keyF, Ordering::Greater)
}
tests/tests/builtin.rsdiffbeforeafterboth--- a/tests/tests/builtin.rs
+++ b/tests/tests/builtin.rs
@@ -18,8 +18,7 @@
#[test]
fn basic_function() -> Result<()> {
let a: a = a {};
- let v =
- u32::from_untyped(a.call(ContextBuilder::new().build(), CallLocation::native(), &())?)?;
+ let v = u32::from_untyped(a.call(CallLocation::native(), &[])?)?;
ensure_eq!(v, 1);
Ok(())