From 8f3ed48f89efa18f9d63c5268ca51f7a12a1e3e5 Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Mon, 27 Dec 2021 15:19:36 +0000 Subject: [PATCH] refactor: simplify and unify builtins --- --- a/cmds/jrsonnet/src/main.rs +++ b/cmds/jrsonnet/src/main.rs @@ -83,17 +83,16 @@ std::process::exit(0); }; - let success; - if let Some(size) = opts.debug.os_stack { - success = std::thread::Builder::new() + let success = if let Some(size) = opts.debug.os_stack { + std::thread::Builder::new() .stack_size(size * 1024 * 1024) .spawn(|| main_catch(opts)) .expect("new thread spawned") .join() - .expect("thread finished successfully"); + .expect("thread finished successfully") } else { - success = main_catch(opts) - } + main_catch(opts) + }; if !success { std::process::exit(1); } --- a/crates/jrsonnet-evaluator/src/builtin/format.rs +++ b/crates/jrsonnet-evaluator/src/builtin/format.rs @@ -577,10 +577,8 @@ } } ConvTypeV::Char => match value.clone() { - Val::Num(n) => tmp_out.push( - std::char::from_u32(n as u32) - .ok_or_else(|| InvalidUnicodeCodepointGot(n as u32))?, - ), + Val::Num(n) => tmp_out + .push(std::char::from_u32(n as u32).ok_or(InvalidUnicodeCodepointGot(n as u32))?), Val::Str(s) => { if s.chars().count() != 1 { throw!(RuntimeError( --- a/crates/jrsonnet-evaluator/src/builtin/mod.rs +++ b/crates/jrsonnet-evaluator/src/builtin/mod.rs @@ -1,3 +1,4 @@ +use crate::function::StaticBuiltin; use crate::typed::{Any, Either, Null, PositiveF64, VecVal, M1}; use crate::{self as jrsonnet_evaluator, ObjValue}; use crate::{ @@ -5,21 +6,16 @@ equals, error::{Error::*, Result}, operator::evaluate_mod_op, - primitive_equals, push_frame, throw, with_state, ArrValue, Context, FuncVal, - IndexableVal, Val, + primitive_equals, push_frame, throw, with_state, ArrValue, Context, FuncVal, IndexableVal, Val, }; use format::{format_arr, format_obj}; use gcmodule::Cc; use jrsonnet_interner::IStr; -use jrsonnet_parser::{ArgsDesc, ExprLocation}; +use jrsonnet_parser::ExprLocation; use serde::Deserialize; use serde_yaml::DeserializingQuirks; -use std::{ - collections::HashMap, - convert::{TryFrom, TryInto}, - path::PathBuf, - rc::Rc, -}; +use std::collections::HashMap; +use std::convert::{TryFrom, TryInto}; pub mod stdlib; pub use stdlib::*; @@ -32,7 +28,7 @@ pub fn std_format(str: IStr, vals: Val) -> Result { push_frame( - &ExprLocation(Rc::from(PathBuf::from("std.jsonnet")), 0, 0), + None, || format!("std.format of {}", str), || { Ok(match vals { @@ -75,86 +71,85 @@ )), } } - -type Builtin = fn(context: Context, loc: &ExprLocation, args: &ArgsDesc) -> Result; -type BuiltinsType = HashMap, Builtin>; +type BuiltinsType = HashMap; thread_local! { - static BUILTINS: BuiltinsType = { + pub static BUILTINS: BuiltinsType = { [ - ("length".into(), builtin_length as Builtin), - ("type".into(), builtin_type), - ("makeArray".into(), builtin_make_array), - ("codepoint".into(), builtin_codepoint), - ("objectFieldsEx".into(), builtin_object_fields_ex), - ("objectHasEx".into(), builtin_object_has_ex), - ("slice".into(), builtin_slice), - ("substr".into(), builtin_substr), - ("primitiveEquals".into(), builtin_primitive_equals), - ("equals".into(), builtin_equals), - ("modulo".into(), builtin_modulo), - ("mod".into(), builtin_mod), - ("floor".into(), builtin_floor), - ("ceil".into(), builtin_ceil), - ("log".into(), builtin_log), - ("pow".into(), builtin_pow), - ("sqrt".into(), builtin_sqrt), - ("sin".into(), builtin_sin), - ("cos".into(), builtin_cos), - ("tan".into(), builtin_tan), - ("asin".into(), builtin_asin), - ("acos".into(), builtin_acos), - ("atan".into(), builtin_atan), - ("exp".into(), builtin_exp), - ("mantissa".into(), builtin_mantissa), - ("exponent".into(), builtin_exponent), - ("extVar".into(), builtin_ext_var), - ("native".into(), builtin_native), - ("filter".into(), builtin_filter), - ("map".into(), builtin_map), - ("flatMap".into(), builtin_flatmap), - ("foldl".into(), builtin_foldl), - ("foldr".into(), builtin_foldr), - ("sortImpl".into(), builtin_sort_impl), - ("format".into(), builtin_format), - ("range".into(), builtin_range), - ("char".into(), builtin_char), - ("encodeUTF8".into(), builtin_encode_utf8), - ("decodeUTF8".into(), builtin_decode_utf8), - ("md5".into(), builtin_md5), - ("base64".into(), builtin_base64), - ("base64DecodeBytes".into(), builtin_base64_decode_bytes), - ("base64Decode".into(), builtin_base64_decode), - ("trace".into(), builtin_trace), - ("join".into(), builtin_join), - ("escapeStringJson".into(), builtin_escape_string_json), - ("manifestJsonEx".into(), builtin_manifest_json_ex), - ("manifestYamlDocImpl".into(), builtin_manifest_yaml_doc), - ("reverse".into(), builtin_reverse), - ("id".into(), builtin_id), - ("strReplace".into(), builtin_str_replace), - ("splitLimit".into(), builtin_splitlimit), - ("parseJson".into(), builtin_parse_json), - ("parseYaml".into(), builtin_parse_yaml), - ("asciiUpper".into(), builtin_ascii_upper), - ("asciiLower".into(), builtin_ascii_lower), - ("member".into(), builtin_member), - ("count".into(), builtin_count), + ("length".into(), builtin_length::INST), + ("type".into(), builtin_type::INST), + ("makeArray".into(), builtin_make_array::INST), + ("codepoint".into(), builtin_codepoint::INST), + ("objectFieldsEx".into(), builtin_object_fields_ex::INST), + ("objectHasEx".into(), builtin_object_has_ex::INST), + ("slice".into(), builtin_slice::INST), + ("substr".into(), builtin_substr::INST), + ("primitiveEquals".into(), builtin_primitive_equals::INST), + ("equals".into(), builtin_equals::INST), + ("modulo".into(), builtin_modulo::INST), + ("mod".into(), builtin_mod::INST), + ("floor".into(), builtin_floor::INST), + ("ceil".into(), builtin_ceil::INST), + ("log".into(), builtin_log::INST), + ("pow".into(), builtin_pow::INST), + ("sqrt".into(), builtin_sqrt::INST), + ("sin".into(), builtin_sin::INST), + ("cos".into(), builtin_cos::INST), + ("tan".into(), builtin_tan::INST), + ("asin".into(), builtin_asin::INST), + ("acos".into(), builtin_acos::INST), + ("atan".into(), builtin_atan::INST), + ("exp".into(), builtin_exp::INST), + ("mantissa".into(), builtin_mantissa::INST), + ("exponent".into(), builtin_exponent::INST), + ("extVar".into(), builtin_ext_var::INST), + ("native".into(), builtin_native::INST), + ("filter".into(), builtin_filter::INST), + ("map".into(), builtin_map::INST), + ("flatMap".into(), builtin_flatmap::INST), + ("foldl".into(), builtin_foldl::INST), + ("foldr".into(), builtin_foldr::INST), + ("sort".into(), builtin_sort::INST), + ("format".into(), builtin_format::INST), + ("range".into(), builtin_range::INST), + ("char".into(), builtin_char::INST), + ("encodeUTF8".into(), builtin_encode_utf8::INST), + ("decodeUTF8".into(), builtin_decode_utf8::INST), + ("md5".into(), builtin_md5::INST), + ("base64".into(), builtin_base64::INST), + ("base64DecodeBytes".into(), builtin_base64_decode_bytes::INST), + ("base64Decode".into(), builtin_base64_decode::INST), + ("trace".into(), builtin_trace::INST), + ("join".into(), builtin_join::INST), + ("escapeStringJson".into(), builtin_escape_string_json::INST), + ("manifestJsonEx".into(), builtin_manifest_json_ex::INST), + ("manifestYamlDoc".into(), builtin_manifest_yaml_doc::INST), + ("reverse".into(), builtin_reverse::INST), + ("id".into(), builtin_id::INST), + ("strReplace".into(), builtin_str_replace::INST), + ("splitLimit".into(), builtin_splitlimit::INST), + ("parseJson".into(), builtin_parse_json::INST), + ("parseYaml".into(), builtin_parse_yaml::INST), + ("asciiUpper".into(), builtin_ascii_upper::INST), + ("asciiLower".into(), builtin_ascii_lower::INST), + ("member".into(), builtin_member::INST), + ("count".into(), builtin_count::INST), ].iter().cloned().collect() }; } #[jrsonnet_macros::builtin] -fn builtin_length(x: Either>) -> Result { +fn builtin_length(x: Either>>>) -> Result { Ok(match x { Either::Left(x) => x.len(), Either::Right(Either::Left(x)) => x.0.len(), - Either::Right(Either::Right(x)) => x + Either::Right(Either::Right(Either::Left(x))) => x .fields_visibility() .into_iter() .filter(|(_k, v)| *v) .count(), + Either::Right(Either::Right(Either::Right(f))) => f.args_len(), }) } @@ -167,7 +162,7 @@ fn builtin_make_array(sz: usize, func: Cc) -> Result { let mut out = Vec::with_capacity(sz); for i in 0..sz { - out.push(func.evaluate_values(&[Val::Num(i as f64)])?) + out.push(func.evaluate_simple(&[i as f64].as_slice())?) } Ok(VecVal(out)) } @@ -354,12 +349,12 @@ #[jrsonnet_macros::builtin] fn builtin_filter(func: Cc, arr: ArrValue) -> Result { - arr.filter(|val| bool::try_from(func.evaluate_values(&[val.clone()])?)) + arr.filter(|val| bool::try_from(func.evaluate_simple(&[Any(val.clone())].as_slice())?)) } #[jrsonnet_macros::builtin] fn builtin_map(func: Cc, arr: ArrValue) -> Result { - arr.map(|val| func.evaluate_values(&[val])) + arr.map(|val| func.evaluate_simple(&[Any(val)].as_slice())) } #[jrsonnet_macros::builtin] @@ -368,7 +363,7 @@ IndexableVal::Str(s) => { let mut out = String::new(); for c in s.chars() { - match func.evaluate_values(&[Val::Str(c.to_string().into())])? { + match func.evaluate_simple(&[c.to_string()].as_slice())? { Val::Str(o) => out.push_str(&o), _ => throw!(RuntimeError( "in std.join all items should be strings".into() @@ -381,7 +376,7 @@ let mut out = Vec::new(); for el in a.iter() { let el = el?; - match func.evaluate_values(&[el])? { + match func.evaluate_simple(&[Any(el)].as_slice())? { Val::Arr(o) => { for oe in o.iter() { out.push(oe?) @@ -401,7 +396,7 @@ fn builtin_foldl(func: Cc, arr: ArrValue, init: Any) -> Result { let mut acc = init.0; for i in arr.iter() { - acc = func.evaluate_values(&[acc, i?])?; + acc = func.evaluate_simple(&[Any(acc), Any(i?)].as_slice())?; } Ok(Any(acc)) } @@ -410,18 +405,21 @@ fn builtin_foldr(func: Cc, arr: ArrValue, init: Any) -> Result { let mut acc = init.0; for i in arr.iter().rev() { - acc = func.evaluate_values(&[i?, acc])?; + acc = func.evaluate_simple(&[Any(i?), Any(acc)].as_slice())?; } Ok(Any(acc)) } #[jrsonnet_macros::builtin] #[allow(non_snake_case)] -fn builtin_sort_impl(arr: ArrValue, keyF: Cc) -> Result { +fn builtin_sort(arr: ArrValue, keyF: Option>) -> Result { if arr.len() <= 1 { return Ok(arr); } - Ok(ArrValue::Eager(sort::sort(arr.evaluated()?, &keyF)?)) + Ok(ArrValue::Eager(sort::sort( + arr.evaluated()?, + keyF.as_deref(), + )?)) } #[jrsonnet_macros::builtin] @@ -443,7 +441,7 @@ #[jrsonnet_macros::builtin] fn builtin_char(n: u32) -> Result { - Ok(std::char::from_u32(n as u32).ok_or_else(|| InvalidUnicodeCodepointGot(n as u32))?) + Ok(std::char::from_u32(n as u32).ok_or(InvalidUnicodeCodepointGot(n as u32))?) } #[jrsonnet_macros::builtin] @@ -466,16 +464,18 @@ } #[jrsonnet_macros::builtin] -fn builtin_trace(#[location] loc: &ExprLocation, str: IStr, rest: Any) -> Result { +fn builtin_trace(#[location] loc: Option<&ExprLocation>, str: IStr, rest: Any) -> Result { eprint!("TRACE:"); - with_state(|s| { - let locs = s.map_source_locations(&loc.0, &[loc.1]); - eprint!( - " {}:{}", - loc.0.file_name().unwrap().to_str().unwrap(), - locs[0].line - ); - }); + if let Some(loc) = loc { + with_state(|s| { + let locs = s.map_source_locations(&loc.0, &[loc.1]); + eprint!( + " {}:{}", + loc.0.file_name().unwrap().to_str().unwrap(), + locs[0].line + ); + }); + } eprintln!(" {}", str); Ok(rest) as Result } @@ -574,15 +574,19 @@ #[jrsonnet_macros::builtin] fn builtin_manifest_yaml_doc( value: Any, - indent_array_in_object: bool, - quote_keys: bool, + indent_array_in_object: Option, + quote_keys: Option, ) -> Result { manifest_yaml_ex( &value.0, &ManifestYamlOptions { padding: " ", - arr_element_padding: if indent_array_in_object { " " } else { "" }, - quote_keys, + arr_element_padding: if indent_array_in_object.unwrap_or(false) { + " " + } else { + "" + }, + quote_keys: quote_keys.unwrap_or(true), }, ) } @@ -648,15 +652,4 @@ } } Ok(count) -} - -pub fn call_builtin( - context: Context, - loc: &ExprLocation, - name: &str, - args: &ArgsDesc, -) -> Result { - BUILTINS - .with(|builtins| builtins.get(name).copied()) - .ok_or_else(|| IntrinsicNotFound(name.into()))?(context, loc, args) } --- a/crates/jrsonnet-evaluator/src/builtin/sort.rs +++ b/crates/jrsonnet-evaluator/src/builtin/sort.rs @@ -1,6 +1,8 @@ use crate::{ error::{Error, LocError, Result}, - throw, FuncVal, Val, + throw, + typed::Any, + FuncVal, Val, }; use gcmodule::{Cc, Trace}; @@ -59,42 +61,47 @@ Ok(sort_type) } -pub fn sort(values: Cc>, key_getter: &FuncVal) -> Result>> { +pub fn sort(values: Cc>, key_getter: Option<&FuncVal>) -> Result>> { if values.len() <= 1 { return Ok(values); } - if key_getter.is_ident() { - let mut mvalues = (*values).clone(); - let sort_type = get_sort_type(&mut mvalues, |k| k)?; + if let Some(key_getter) = key_getter { + // Slow path, user provided key getter + let mut vk = Vec::with_capacity(values.len()); + for value in values.iter() { + vk.push(( + value.clone(), + key_getter.evaluate_simple(&[Any(value.clone())].as_slice())?, + )); + } + let sort_type = get_sort_type(&mut vk, |v| &mut v.1)?; match sort_type { - SortKeyType::Number => mvalues.sort_by_key(|v| match v { - Val::Num(n) => NonNaNf64(*n), + SortKeyType::Number => vk.sort_by_key(|v| match v.1 { + Val::Num(n) => NonNaNf64(n), _ => unreachable!(), }), - SortKeyType::String => mvalues.sort_by_key(|v| match v { + SortKeyType::String => vk.sort_by_key(|v| match &v.1 { Val::Str(s) => s.clone(), _ => unreachable!(), }), SortKeyType::Unknown => unreachable!(), }; - Ok(Cc::new(mvalues)) + Ok(Cc::new(vk.into_iter().map(|v| v.0).collect())) } else { - let mut vk = Vec::with_capacity(values.len()); - for value in values.iter() { - vk.push((value.clone(), key_getter.evaluate_values(&[value.clone()])?)); - } - let sort_type = get_sort_type(&mut vk, |v| &mut v.1)?; + // Fast path, identity key getter + let mut mvalues = (*values).clone(); + let sort_type = get_sort_type(&mut mvalues, |k| k)?; match sort_type { - SortKeyType::Number => vk.sort_by_key(|v| match v.1 { - Val::Num(n) => NonNaNf64(n), + SortKeyType::Number => mvalues.sort_unstable_by_key(|v| match v { + Val::Num(n) => NonNaNf64(*n), _ => unreachable!(), }), - SortKeyType::String => vk.sort_by_key(|v| match &v.1 { + SortKeyType::String => mvalues.sort_unstable_by_key(|v| match v { Val::Str(s) => s.clone(), _ => unreachable!(), }), SortKeyType::Unknown => unreachable!(), }; - Ok(Cc::new(vk.into_iter().map(|v| v.0).collect())) + Ok(Cc::new(mvalues)) } } --- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs +++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs @@ -1,7 +1,7 @@ use std::convert::TryFrom; use crate::{ - builtin::std_slice, + builtin::{std_slice, BUILTINS}, error::Error::*, evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op}, gc::TraceBox, @@ -192,7 +192,7 @@ Ok(match field_name { jrsonnet_parser::FieldName::Fixed(n) => Some(n.clone()), jrsonnet_parser::FieldName::Dyn(expr) => push_frame( - &expr.1, + Some(&expr.1), || "evaluating field name".to_string(), || { let value = evaluate(context, expr)?; @@ -442,7 +442,7 @@ context: Context, value: &LocExpr, args: &ArgsDesc, - loc: &ExprLocation, + loc: Option<&ExprLocation>, tailstrict: bool, ) -> Result { let value = evaluate(context.clone(), value)?; @@ -463,13 +463,13 @@ let value = &assertion.0; let msg = &assertion.1; let assertion_result = push_frame( - &value.1, + Some(&value.1), || "assertion condition".to_owned(), || bool::try_from(evaluate(context.clone(), value)?), )?; if !assertion_result { push_frame( - &value.1, + Some(&value.1), || "assertion failure".to_owned(), || { if let Some(msg) = msg { @@ -519,7 +519,7 @@ BinaryOp(v1, o, v2) => evaluate_binary_op_special(context, v1, *o, v2)?, UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(context, v)?)?, Var(name) => push_frame( - loc, + Some(loc), || format!("variable <{}> access", name), || context.binding(name.clone())?.evaluate(), )?, @@ -528,7 +528,7 @@ (Val::Obj(v), Val::Str(s)) => { let sn = s.clone(); push_frame( - loc, + Some(loc), || format!("field <{}> access", sn), || { if let Some(v) = v.get(s.clone())? { @@ -624,17 +624,23 @@ &evaluate(context.clone(), s)?, &Val::Obj(evaluate_object(context, t)?), )?, - Apply(value, args, tailstrict) => evaluate_apply(context, value, args, loc, *tailstrict)?, + Apply(value, args, tailstrict) => { + evaluate_apply(context, value, args, Some(loc), *tailstrict)? + } Function(params, body) => { evaluate_method(context, "anonymous".into(), params.clone(), body.clone()) } - Intrinsic(name) => Val::Func(Cc::new(FuncVal::Intrinsic(name.clone()))), + Intrinsic(name) => Val::Func(Cc::new(FuncVal::StaticBuiltin( + BUILTINS + .with(|b| b.get(name).copied()) + .ok_or_else(|| IntrinsicNotFound(name.clone()))?, + ))), AssertExpr(assert, returned) => { evaluate_assert(context.clone(), assert)?; evaluate(context, returned)? } ErrorStmt(e) => push_frame( - loc, + Some(loc), || "error statement".to_owned(), || throw!(RuntimeError(IStr::try_from(evaluate(context, e)?)?,)), )?, @@ -644,7 +650,7 @@ cond_else, } => { if push_frame( - loc, + Some(loc), || "if condition".to_owned(), || bool::try_from(evaluate(context.clone(), &cond.0)?), )? { @@ -683,7 +689,7 @@ let mut import_location = tmp.to_path_buf(); import_location.pop(); push_frame( - loc, + Some(loc), || format!("import {:?}", path), || with_state(|s| s.import_file(&import_location, path)), )? --- a/crates/jrsonnet-evaluator/src/function.rs +++ b/crates/jrsonnet-evaluator/src/function.rs @@ -1,15 +1,16 @@ use crate::{ - error::Error::*, evaluate, evaluate_named, gc::TraceBox, throw, Context, FutureWrapper, - GcHashMap, LazyVal, LazyValValue, Result, Val, + error::{Error::*, LocError}, + evaluate, evaluate_named, + gc::TraceBox, + throw, + typed::Typed, + Context, FutureWrapper, GcHashMap, LazyVal, LazyValValue, Result, Val, }; use gcmodule::Trace; use jrsonnet_interner::IStr; -use jrsonnet_parser::{ArgsDesc, LocExpr, ParamsDesc}; -use std::collections::HashMap; +use jrsonnet_parser::{ArgsDesc, ExprLocation, LocExpr, ParamsDesc}; +use std::{borrow::Cow, collections::HashMap, convert::TryFrom}; -const NO_DEFAULT_CONTEXT: &str = - "no default context set for call with defined default parameter value"; - #[derive(Trace)] struct EvaluateLazyVal { context: Context, @@ -21,6 +22,248 @@ } } +pub trait ArgLike { + fn evaluate_arg(&self, ctx: Context, tailstrict: bool) -> Result; +} +impl ArgLike for &LocExpr { + fn evaluate_arg(&self, ctx: Context, tailstrict: bool) -> Result { + Ok(if tailstrict { + LazyVal::new_resolved(evaluate(ctx, self)?) + } else { + LazyVal::new(TraceBox(Box::new(EvaluateLazyVal { + context: ctx, + expr: (*self).clone(), + }))) + }) + } +} +impl ArgLike for T +where + T: Typed + Clone, + Val: TryFrom, +{ + fn evaluate_arg(&self, _ctx: Context, _tailstrict: bool) -> Result { + let val: Val = Val::try_from(self.clone())?; + Ok(LazyVal::new_resolved(val)) + } +} +pub enum TlaArg { + String(IStr), + Code(LocExpr), + Val(Val), +} +impl ArgLike for TlaArg { + fn evaluate_arg(&self, ctx: Context, tailstrict: bool) -> Result { + match self { + TlaArg::String(s) => Ok(LazyVal::new_resolved(Val::Str(s.clone()))), + TlaArg::Code(code) => Ok(if tailstrict { + LazyVal::new_resolved(evaluate(ctx, code)?) + } else { + LazyVal::new(TraceBox(Box::new(EvaluateLazyVal { + context: ctx, + expr: code.clone(), + }))) + }), + TlaArg::Val(val) => Ok(LazyVal::new_resolved(val.clone())), + } + } +} + +pub trait ArgsLike { + fn unnamed_len(&self) -> usize; + fn unnamed_iter( + &self, + ctx: Context, + tailstrict: bool, + handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>, + ) -> Result<()>; + fn named_iter( + &self, + ctx: Context, + tailstrict: bool, + handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>, + ) -> Result<()>; + fn named_names(&self, handler: &mut dyn FnMut(&IStr)); +} + +impl ArgsLike for ArgsDesc { + fn unnamed_len(&self) -> usize { + self.unnamed.len() + } + + fn unnamed_iter( + &self, + ctx: Context, + tailstrict: bool, + handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>, + ) -> Result<()> { + for (id, arg) in self.unnamed.iter().enumerate() { + handler( + id, + if tailstrict { + LazyVal::new_resolved(evaluate(ctx.clone(), arg)?) + } else { + LazyVal::new(TraceBox(Box::new(EvaluateLazyVal { + context: ctx.clone(), + expr: arg.clone(), + }))) + }, + )?; + } + Ok(()) + } + + fn named_iter( + &self, + ctx: Context, + tailstrict: bool, + handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>, + ) -> Result<()> { + for (name, arg) in self.named.iter() { + handler( + name, + if tailstrict { + LazyVal::new_resolved(evaluate(ctx.clone(), arg)?) + } else { + LazyVal::new(TraceBox(Box::new(EvaluateLazyVal { + context: ctx.clone(), + expr: arg.clone(), + }))) + }, + )?; + } + Ok(()) + } + + fn named_names(&self, handler: &mut dyn FnMut(&IStr)) { + for (name, _) in self.named.iter() { + handler(name) + } + } +} + +impl ArgsLike for [(IStr, A)] { + fn unnamed_len(&self) -> usize { + 0 + } + + fn unnamed_iter( + &self, + _ctx: Context, + _tailstrict: bool, + _handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>, + ) -> Result<()> { + Ok(()) + } + + fn named_iter( + &self, + ctx: Context, + tailstrict: bool, + handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>, + ) -> Result<()> { + for (name, val) in self.iter() { + handler(name, val.evaluate_arg(ctx.clone(), tailstrict)?)?; + } + Ok(()) + } + + fn named_names(&self, handler: &mut dyn FnMut(&IStr)) { + for (name, _) in self.iter() { + handler(name); + } + } +} + +impl ArgsLike for HashMap { + fn unnamed_len(&self) -> usize { + 0 + } + + fn unnamed_iter( + &self, + _ctx: Context, + _tailstrict: bool, + _handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>, + ) -> Result<()> { + Ok(()) + } + + fn named_iter( + &self, + ctx: Context, + tailstrict: bool, + handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>, + ) -> Result<()> { + for (name, value) in self.iter() { + handler(name, value.evaluate_arg(ctx.clone(), tailstrict)?)?; + } + Ok(()) + } + + fn named_names(&self, handler: &mut dyn FnMut(&IStr)) { + for (name, _) in self.iter() { + handler(name); + } + } +} + +impl ArgsLike for [A] { + fn unnamed_len(&self) -> usize { + self.len() + } + + fn unnamed_iter( + &self, + ctx: Context, + tailstrict: bool, + handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>, + ) -> Result<()> { + for (i, arg) in self.iter().enumerate() { + handler(i, arg.evaluate_arg(ctx.clone(), tailstrict)?)?; + } + Ok(()) + } + + fn named_iter( + &self, + _ctx: Context, + _tailstrict: bool, + _handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>, + ) -> Result<()> { + Ok(()) + } + + fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {} +} +impl ArgsLike for &[A] { + fn unnamed_len(&self) -> usize { + (*self).unnamed_len() + } + + fn unnamed_iter( + &self, + ctx: Context, + tailstrict: bool, + handler: &mut dyn FnMut(usize, LazyVal) -> Result<()>, + ) -> Result<()> { + (*self).unnamed_iter(ctx, tailstrict, handler) + } + + fn named_iter( + &self, + ctx: Context, + tailstrict: bool, + handler: &mut dyn FnMut(&IStr, LazyVal) -> Result<()>, + ) -> Result<()> { + (*self).named_iter(ctx, tailstrict, handler) + } + + fn named_names(&self, handler: &mut dyn FnMut(&IStr)) { + (*self).named_names(handler) + } +} + /// Creates correct [context](Context) for function body evaluation returning error on invalid call. /// /// ## Parameters @@ -33,55 +276,34 @@ ctx: Context, body_ctx: Context, params: &ParamsDesc, - args: &ArgsDesc, + args: &dyn ArgsLike, tailstrict: bool, ) -> Result { let mut passed_args = GcHashMap::with_capacity(params.len()); - if args.unnamed.len() > params.len() { + if args.unnamed_len() > params.len() { throw!(TooManyArgsFunctionHas(params.len())) } let mut filled_args = 0; - for (id, arg) in args.unnamed.iter().enumerate() { + args.unnamed_iter(ctx.clone(), tailstrict, &mut |id, arg| { let name = params[id].0.clone(); - passed_args.insert( - name, - if tailstrict { - LazyVal::new_resolved(evaluate(ctx.clone(), arg)?) - } else { - LazyVal::new(TraceBox(Box::new(EvaluateLazyVal { - context: ctx.clone(), - expr: arg.clone(), - }))) - }, - ); + passed_args.insert(name, arg); filled_args += 1; - } + Ok(()) + })?; - for (name, value) in args.named.iter() { + args.named_iter(ctx, tailstrict, &mut |name, value| { // FIXME: O(n) for arg existence check if !params.iter().any(|p| &p.0 == name) { throw!(UnknownFunctionParameter((name as &str).to_owned())); } - if passed_args - .insert( - name.clone(), - if tailstrict { - LazyVal::new_resolved(evaluate(ctx.clone(), value)?) - } else { - LazyVal::new(TraceBox(Box::new(EvaluateLazyVal { - context: ctx.clone(), - expr: value.clone(), - }))) - }, - ) - .is_some() - { + if passed_args.insert(name.clone(), value).is_some() { throw!(BindingParameterASecondTime(name.clone())); } filled_args += 1; - } + Ok(()) + })?; if filled_args < params.len() { // Some args are unset, but maybe we have defaults for them @@ -123,8 +345,14 @@ // Some args still wasn't filled if filled_args != params.len() { - for param in params.iter().skip(args.unnamed.len()) { - if !args.named.iter().any(|a| a.0 == param.0) { + for param in params.iter().skip(args.unnamed_len()) { + let mut found = false; + args.named_names(&mut |name| { + if name == ¶m.0 { + found = true; + } + }); + if !found { throw!(FunctionParameterNotBoundInCall(param.0.clone())); } } @@ -141,12 +369,33 @@ } } -#[derive(Clone, Copy)] +type BuiltinParamName = Cow<'static, str>; + +#[derive(Clone)] pub struct BuiltinParam { - pub name: &'static str, + pub name: BuiltinParamName, pub has_default: bool, } +pub trait Builtin: Trace { + fn name(&self) -> &str; + fn params(&self) -> &[BuiltinParam]; + fn call( + &self, + context: Context, + loc: Option<&ExprLocation>, + args: &dyn ArgsLike, + ) -> Result; +} + +pub trait StaticBuiltin: Builtin + Send + Sync +where + Self: 'static, +{ + // In impl, to make it object safe: + // const INST: &'static Self; +} + /// You shouldn't probally use this function, use jrsonnet_macros::builtin instead /// /// ## Parameters @@ -154,58 +403,38 @@ /// * `params`: function parameters' definition /// * `args`: passed function arguments /// * `tailstrict`: if set to `true` function arguments are eagerly executed, otherwise - lazily -pub fn parse_builtin_call<'k>( +pub fn parse_builtin_call( ctx: Context, - params: &'static [BuiltinParam], - args: &'k ArgsDesc, + params: &[BuiltinParam], + args: &dyn ArgsLike, tailstrict: bool, -) -> Result> { +) -> Result> { let mut passed_args = GcHashMap::with_capacity(params.len()); - if args.unnamed.len() > params.len() { + if args.unnamed_len() > params.len() { throw!(TooManyArgsFunctionHas(params.len())) } let mut filled_args = 0; - for (id, arg) in args.unnamed.iter().enumerate() { - let name = params[id].name; - passed_args.insert( - name, - if tailstrict { - LazyVal::new_resolved(evaluate(ctx.clone(), arg)?) - } else { - LazyVal::new(TraceBox(Box::new(EvaluateLazyVal { - context: ctx.clone(), - expr: arg.clone(), - }))) - }, - ); + args.unnamed_iter(ctx.clone(), tailstrict, &mut |id, arg| { + let name = params[id].name.clone(); + passed_args.insert(name, arg); filled_args += 1; - } + Ok(()) + })?; - for (name, value) in args.named.iter() { + args.named_iter(ctx, tailstrict, &mut |name, arg| { // FIXME: O(n) for arg existence check - if !params.iter().any(|p| p.name == name as &str) { - throw!(UnknownFunctionParameter((name as &str).to_owned())); - } - if passed_args - .insert( - name, - if tailstrict { - LazyVal::new_resolved(evaluate(ctx.clone(), value)?) - } else { - LazyVal::new(TraceBox(Box::new(EvaluateLazyVal { - context: ctx.clone(), - expr: value.clone(), - }))) - }, - ) - .is_some() - { + let p = params + .iter() + .find(|p| p.name == name as &str) + .ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?; + if passed_args.insert(p.name.clone(), arg).is_some() { throw!(BindingParameterASecondTime(name.clone())); } filled_args += 1; - } + Ok(()) + })?; if filled_args < params.len() { for param in params.iter().filter(|p| p.has_default) { @@ -217,97 +446,19 @@ // Some args still wasn't filled if filled_args != params.len() { - for param in params.iter().skip(args.unnamed.len()) { - if !args.named.iter().any(|a| &a.0 as &str == param.name) { - throw!(FunctionParameterNotBoundInCall(param.name.into())); + for param in params.iter().skip(args.unnamed_len()) { + let mut found = false; + args.named_names(&mut |name| { + if name as &str == ¶m.name as &str { + found = true; + } + }); + if !found { + throw!(FunctionParameterNotBoundInCall(param.name.clone().into())); } } unreachable!(); } } Ok(passed_args) -} - -pub fn parse_function_call_map( - ctx: Context, - body_ctx: Option, - params: &ParamsDesc, - args: &HashMap, - tailstrict: bool, -) -> Result { - let mut out = GcHashMap::with_capacity(params.len()); - let mut positioned_args = vec![None; params.0.len()]; - for (name, val) in args.iter() { - let idx = params - .iter() - .position(|p| *p.0 == **name) - .ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?; - - if idx >= params.len() { - throw!(TooManyArgsFunctionHas(params.len())); - } - if positioned_args[idx].is_some() { - throw!(BindingParameterASecondTime(params[idx].0.clone())); - } - positioned_args[idx] = Some(val.clone()); - } - // Fill defaults - for (id, p) in params.iter().enumerate() { - let val = if let Some(arg) = positioned_args[id].take() { - LazyVal::new_resolved(arg) - } else if let Some(default) = &p.1 { - if tailstrict { - LazyVal::new_resolved(evaluate( - body_ctx.clone().expect(NO_DEFAULT_CONTEXT), - default, - )?) - } else { - let body_ctx = body_ctx.clone(); - let default = default.clone(); - #[derive(Trace)] - struct EvaluateLazyVal { - body_ctx: Option, - default: LocExpr, - } - impl LazyValValue for EvaluateLazyVal { - fn get(self: Box) -> Result { - evaluate( - self.body_ctx.clone().expect(NO_DEFAULT_CONTEXT), - &self.default, - ) - } - } - LazyVal::new(TraceBox(Box::new(EvaluateLazyVal { body_ctx, default }))) - } - } else { - throw!(FunctionParameterNotBoundInCall(p.0.clone())); - }; - out.insert(p.0.clone(), val); - } - - Ok(body_ctx.unwrap_or(ctx).extend(out, None, None, None)) -} - -pub fn place_args(body_ctx: Context, params: &ParamsDesc, args: &[Val]) -> Result { - let mut out = GcHashMap::with_capacity(params.len()); - let mut positioned_args = vec![None; params.0.len()]; - for (id, arg) in args.iter().enumerate() { - if id >= params.len() { - throw!(TooManyArgsFunctionHas(params.len())); - } - positioned_args[id] = Some(arg); - } - // Fill defaults - for (id, p) in params.iter().enumerate() { - let val = if let Some(arg) = &positioned_args[id] { - (*arg).clone() - } else if let Some(default) = &p.1 { - evaluate(body_ctx.clone(), default)? - } else { - throw!(FunctionParameterNotBoundInCall(p.0.clone())); - }; - out.insert(p.0.clone(), LazyVal::new_resolved(val)); - } - - Ok(body_ctx.extend(out, None, None, None)) } --- a/crates/jrsonnet-evaluator/src/lib.rs +++ b/crates/jrsonnet-evaluator/src/lib.rs @@ -25,6 +25,7 @@ use error::{Error::*, LocError, Result, StackTraceElement}; pub use evaluate::*; pub use function::parse_function_call; +use function::TlaArg; use gc::{GcHashMap, TraceBox}; use gcmodule::{Cc, Trace}; pub use import::*; @@ -77,7 +78,7 @@ /// Used for ext.native pub ext_natives: HashMap>, /// TLA vars - pub tla_vars: HashMap, + pub tla_vars: HashMap, /// Global variables are inserted in default context pub globals: HashMap, /// Used to resolve file locations/contents @@ -174,7 +175,7 @@ EVAL_STATE.with(|s| f(s.borrow().as_ref().unwrap())) } pub(crate) fn push_frame( - e: &ExprLocation, + e: Option<&ExprLocation>, frame_desc: impl FnOnce() -> String, f: impl FnOnce() -> Result, ) -> Result { @@ -203,24 +204,21 @@ impl EvaluationState { /// Parses and adds file as loaded - pub fn add_file(&self, path: Rc, source_code: IStr) -> Result<()> { - self.add_parsed_file( - path.clone(), - source_code.clone(), - parse( - &source_code, - &ParserSettings { - file_name: path.clone(), - }, - ) - .map_err(|error| ImportSyntaxError { - error: Box::new(error), - path: path.to_owned(), - source_code, - })?, - )?; + pub fn add_file(&self, path: Rc, source_code: IStr) -> Result { + let parsed = parse( + &source_code, + &ParserSettings { + file_name: path.clone(), + }, + ) + .map_err(|error| ImportSyntaxError { + error: Box::new(error), + path: path.to_owned(), + source_code: source_code.clone(), + })?; + self.add_parsed_file(path, source_code, parsed.clone())?; - Ok(()) + Ok(parsed) } pub fn reset_evaluation_state(&self, name: &Path) { @@ -341,7 +339,7 @@ /// Executes code creating a new stack frame pub fn push( &self, - e: &ExprLocation, + e: Option<&ExprLocation>, frame_desc: impl FnOnce() -> String, f: impl FnOnce() -> Result, ) -> Result { @@ -364,7 +362,7 @@ } if let Err(mut err) = result { err.trace_mut().0.push(StackTraceElement { - location: Some(e.clone()), + location: e.cloned(), desc: frame_desc(), }); return Err(err); @@ -506,8 +504,9 @@ Val::Func(func) => push_description_frame( || "during TLA call".to_owned(), || { - func.evaluate_map( + func.evaluate( self.create_default_context(), + None, &self.settings().tla_vars, true, ) @@ -581,15 +580,20 @@ } pub fn add_tla(&self, name: IStr, value: Val) { - self.settings_mut().tla_vars.insert(name, value); + self.settings_mut() + .tla_vars + .insert(name, TlaArg::Val(value)); } pub fn add_tla_str(&self, name: IStr, value: IStr) { - self.add_tla(name, Val::Str(value)); + self.settings_mut() + .tla_vars + .insert(name, TlaArg::String(value)); } pub fn add_tla_code(&self, name: IStr, code: IStr) -> Result<()> { - let value = - self.evaluate_snippet_raw(PathBuf::from(format!("tla_code {}", name)).into(), code)?; - self.add_tla(name, value); + let parsed = self.add_file(PathBuf::from(format!("tla_code {}", name)).into(), code)?; + self.settings_mut() + .tla_vars + .insert(name, TlaArg::Code(parsed)); Ok(()) } @@ -668,11 +672,11 @@ state.run_in_state(|| { state .push( - &ExprLocation(PathBuf::from("test1.jsonnet").into(), 10, 20), + Some(&ExprLocation(PathBuf::from("test1.jsonnet").into(), 10, 20)), || "outer".to_owned(), || { state.push( - &ExprLocation(PathBuf::from("test2.jsonnet").into(), 30, 40), + Some(&ExprLocation(PathBuf::from("test2.jsonnet").into(), 30, 40)), || "inner".to_owned(), || Err(RuntimeError("".into()).into()), )?; @@ -975,15 +979,8 @@ fn parse_json() { assert_json!( r#"std.parseJson('{"a": -1,"b": 1,"c": 3.141,"d": []}')"#, - r#"{"a": -1,"b": 1,"c": 3.141,"d": []}"# - ); - // TODO: this should in fact fail as is no proper JSON syntax - assert_json!( - r#"std.parseJson("{a:-1, b:1, c:3.141, d:[]}")"#, r#"{"a": -1,"b": 1,"c": 3.141,"d": []}"# ); - // TODO: this is also no valid JSON - assert_json!(r#"std.parseJson('local x = 2; x * x')"#, r#"4"#); } #[test] --- a/crates/jrsonnet-evaluator/src/native.rs +++ b/crates/jrsonnet-evaluator/src/native.rs @@ -8,6 +8,7 @@ use std::path::Path; use std::rc::Rc; +#[deprecated(note = "Use builtins instead")] pub trait NativeCallbackHandler: Trace { fn call(&self, from: Rc, args: &[Val]) -> Result; } --- a/crates/jrsonnet-evaluator/src/typed/conversions.rs +++ b/crates/jrsonnet-evaluator/src/typed/conversions.rs @@ -37,7 +37,7 @@ .into() )) } - Ok(n as $ty) + Ok(n as Self) } _ => unreachable!(), } @@ -249,6 +249,7 @@ /// To be used in Vec /// Regular Val can't be used here, because it has wrong TryFrom::Error type +#[derive(Clone)] pub struct Any(pub Val); impl Typed for Any { --- a/crates/jrsonnet-evaluator/src/val.rs +++ b/crates/jrsonnet-evaluator/src/val.rs @@ -1,24 +1,20 @@ use crate::{ - builtin::{ - call_builtin, - manifest::{ - manifest_json_ex, manifest_yaml_ex, ManifestJsonOptions, ManifestType, - ManifestYamlOptions, - }, + builtin::manifest::{ + manifest_json_ex, manifest_yaml_ex, ManifestJsonOptions, ManifestType, ManifestYamlOptions, }, cc_ptr_eq, error::{Error::*, LocError}, evaluate, - function::{parse_function_call, parse_function_call_map, place_args}, + function::{parse_function_call, ArgsLike, Builtin, StaticBuiltin}, gc::TraceBox, native::NativeCallback, throw, Context, ObjValue, Result, }; use gcmodule::{Cc, Trace}; use jrsonnet_interner::IStr; -use jrsonnet_parser::{ArgsDesc, ExprLocation, LocExpr, ParamsDesc}; +use jrsonnet_parser::{ExprLocation, LocExpr, ParamsDesc}; use jrsonnet_types::ValType; -use std::{cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc}; +use std::{cell::RefCell, fmt::Debug, rc::Rc}; pub trait LazyValValue: Trace { fn get(self: Box) -> Result; @@ -41,6 +37,10 @@ pub fn new_resolved(val: Val) -> Self { Self(Cc::new(RefCell::new(LazyValInternals::Computed(val)))) } + pub fn force(&self) -> Result<()> { + self.evaluate()?; + Ok(()) + } pub fn evaluate(&self) -> Result { match &*self.0.borrow() { LazyValInternals::Computed(v) => return Ok(v.clone()), @@ -86,42 +86,63 @@ pub body: LocExpr, } -#[derive(Debug, Trace)] +#[derive(Trace)] pub enum FuncVal { /// Plain function implemented in jsonnet Normal(FuncDesc), /// Standard library function - Intrinsic(IStr), + StaticBuiltin(#[skip_trace] &'static dyn StaticBuiltin), + + Builtin(TraceBox), /// Library functions implemented in native NativeExt(IStr, Cc), } +impl Debug for FuncVal { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Normal(arg0) => f.debug_tuple("Normal").field(arg0).finish(), + Self::StaticBuiltin(arg0) => f.debug_tuple("Intrinsic").field(&arg0.name()).finish(), + Self::Builtin(arg0) => f.debug_tuple("Intrinsic").field(&arg0.name()).finish(), + Self::NativeExt(arg0, arg1) => { + f.debug_tuple("NativeExt").field(arg0).field(arg1).finish() + } + } + } +} + impl PartialEq for FuncVal { fn eq(&self, other: &Self) -> bool { match (self, other) { (Self::Normal(a), Self::Normal(b)) => a == b, - (Self::Intrinsic(an), Self::Intrinsic(bn)) => an == bn, + (Self::StaticBuiltin(an), Self::StaticBuiltin(bn)) => std::ptr::eq(*an, *bn), (Self::NativeExt(an, _), Self::NativeExt(bn, _)) => an == bn, (..) => false, } } } impl FuncVal { - pub fn is_ident(&self) -> bool { - matches!(&self, Self::Intrinsic(n) if n as &str == "id") + pub fn args_len(&self) -> usize { + match self { + Self::Normal(n) => n.params.iter().filter(|p| p.1.is_none()).count(), + Self::StaticBuiltin(i) => i.params().iter().filter(|p| !p.has_default).count(), + Self::Builtin(i) => i.params().iter().filter(|p| !p.has_default).count(), + Self::NativeExt(_, n) => n.params.iter().filter(|p| p.1.is_none()).count(), + } } pub fn name(&self) -> IStr { match self { Self::Normal(normal) => normal.name.clone(), - Self::Intrinsic(name) => format!("std.{}", name).into(), + Self::StaticBuiltin(builtin) => builtin.name().into(), + Self::Builtin(builtin) => builtin.name().into(), Self::NativeExt(n, _) => format!("native.{}", n).into(), } } pub fn evaluate( &self, call_ctx: Context, - loc: &ExprLocation, - args: &ArgsDesc, + loc: Option<&ExprLocation>, + args: &dyn ArgsLike, tailstrict: bool, ) -> Result { match self { @@ -135,7 +156,8 @@ )?; evaluate(ctx, &func.body) } - Self::Intrinsic(name) => call_builtin(call_ctx, loc, name, args), + Self::StaticBuiltin(name) => name.call(call_ctx, loc, args), + Self::Builtin(b) => b.call(call_ctx, loc, args), Self::NativeExt(_name, handler) => { let args = parse_function_call(call_ctx, Context::new(), &handler.params, args, true)?; @@ -143,42 +165,12 @@ for p in handler.params.0.iter() { out_args.push(args.binding(p.0.clone())?.evaluate()?); } - Ok(handler.call(loc.0.clone(), &out_args)?) - } - } - } - - pub fn evaluate_map( - &self, - call_ctx: Context, - args: &HashMap, - tailstrict: bool, - ) -> Result { - match self { - Self::Normal(func) => { - let ctx = parse_function_call_map( - call_ctx, - Some(func.ctx.clone()), - &func.params, - args, - tailstrict, - )?; - evaluate(ctx, &func.body) + Ok(handler.call(loc.expect("todo").0.clone(), &out_args)?) } - Self::Intrinsic(_) => todo!(), - Self::NativeExt(_, _) => todo!(), } } - - pub fn evaluate_values(&self, args: &[Val]) -> Result { - match self { - Self::Normal(func) => { - let ctx = place_args(func.ctx.clone(), &func.params, args)?; - evaluate(ctx, &func.body) - } - Self::Intrinsic(_) => todo!(), - Self::NativeExt(_, _) => todo!(), - } + pub fn evaluate_simple(&self, args: &dyn ArgsLike) -> Result { + self.evaluate(Context::default(), None, args, true) } } --- a/crates/jrsonnet-interner/src/lib.rs +++ b/crates/jrsonnet-interner/src/lib.rs @@ -2,6 +2,7 @@ use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; use std::{ + borrow::Cow, cell::RefCell, fmt::{self, Display}, hash::{BuildHasherDefault, Hash, Hasher}, @@ -90,6 +91,12 @@ } } +impl<'i> From> for IStr { + fn from(c: Cow<'i, str>) -> Self { + (&c as &str).into() + } +} + impl Serialize for IStr { fn serialize(&self, serializer: S) -> Result where --- a/crates/jrsonnet-macros/src/lib.rs +++ b/crates/jrsonnet-macros/src/lib.rs @@ -1,11 +1,50 @@ -use proc_macro2::Span; use quote::quote; -use syn::{parse_macro_input, FnArg, Ident, ItemFn, Pat, PatType}; +use syn::{ + parse_macro_input, FnArg, GenericArgument, ItemFn, Pat, PatType, Path, PathArguments, Type, +}; fn is_location_arg(t: &PatType) -> bool { t.attrs.iter().any(|a| a.path.is_ident("location")) } +trait RetainHad { + fn retain_had(&mut self, h: impl FnMut(&T) -> bool) -> bool; +} +impl RetainHad for Vec { + fn retain_had(&mut self, h: impl FnMut(&T) -> bool) -> bool { + let before = self.len(); + self.retain(h); + let after = self.len(); + before != after + } +} + +fn extract_type_from_option(ty: &Type) -> Option<&Type> { + fn path_is_option(path: &Path) -> bool { + path.leading_colon.is_none() + && path.segments.len() == 1 + && path.segments.iter().next().unwrap().ident == "Option" + } + + match ty { + Type::Path(typepath) if typepath.qself.is_none() && path_is_option(&typepath.path) => { + // Get the first segment of the path (there is only one, in fact: "Option"): + let type_params = &typepath.path.segments.iter().next().unwrap().arguments; + // It should have only on angle-bracketed param (""): + let generic_arg = match type_params { + PathArguments::AngleBracketed(params) => params.args.iter().next().unwrap(), + _ => panic!("missing option generic"), + }; + // This argument must be a type: + match generic_arg { + GenericArgument::Type(ty) => Some(ty), + _ => panic!("option generic should be a type"), + } + } + _ => None, + } +} + #[proc_macro_attribute] pub fn builtin( _attr: proc_macro::TokenStream, @@ -33,11 +72,10 @@ Pat::Ident(i) => i.ident.to_string(), _ => panic!("only idents supported yet"), }; - // TODO: Check if ty == Option<_> - let optional = false; + let optional = extract_type_from_option(&t.ty).is_some(); quote! { BuiltinParam { - name: #ident, + name: std::borrow::Cow::Borrowed(#ident), has_default: #optional, } } @@ -53,10 +91,7 @@ FnArg::Typed(t) => t, }) .map(|t| { - let count_before = t.attrs.len(); - t.attrs.retain(|a| !a.path.is_ident("location")); - let count_after = t.attrs.len(); - let is_location = count_before != count_after; + let is_location = t.attrs.retain_had(|a| !a.path.is_ident("location")); if is_location { quote! {{ loc @@ -67,38 +102,68 @@ _ => panic!("only idents supported yet"), }; let ty = &t.ty; - quote! {{ - let value = parsed.get(#ident).unwrap(); + if let Some(opt_ty) = extract_type_from_option(&t.ty) { + quote! {{ + if let Some(value) = parsed.get(#ident) { + Some(jrsonnet_evaluator::push_description_frame( + || format!("argument <{}> evaluation", #ident), + || <#opt_ty>::try_from(value.evaluate()?), + )?) + } else { + None + } + }} + } else { + quote! {{ + let value = parsed.get(#ident).unwrap(); - jrsonnet_evaluator::push_description_frame( - || format!("argument <{}> evaluation", #ident), - || <#ty>::try_from(value.evaluate()?), - )? - }} + jrsonnet_evaluator::push_description_frame( + || format!("argument <{}> evaluation", #ident), + || <#ty>::try_from(value.evaluate()?), + )? + }} + } } - }).collect::>(); - - let inner_name = Ident::new("inner", Span::call_site()); - let mut inner_fun = fun.clone(); - inner_fun.sig.ident = inner_name.clone(); + }) + .collect::>(); - let attrs = &fun.attrs; + let name = &fun.sig.ident; let vis = &fun.vis; - let name = &fun.sig.ident; (quote! { - #(#attrs)* - #vis fn #name(context: Context, loc: &ExprLocation, args: &ArgsDesc) -> Result { - #inner_fun - use jrsonnet_evaluator::function::BuiltinParam; + #fun + #[doc(hidden)] + #[allow(non_camel_case_types)] + #[derive(Clone, Copy, gcmodule::Trace)] + #vis struct #name {} + const _: () = { + use jrsonnet_evaluator::function::{Builtin, StaticBuiltin, BuiltinParam, ArgsLike}; const PARAMS: &'static [BuiltinParam] = &[ #(#params),* ]; - let parsed = jrsonnet_evaluator::function::parse_builtin_call(context, &PARAMS, args, false)?; - let result: #result = #inner_name(#(#args),*); - let result = result?; - result.try_into() - } + impl #name { + pub const INST: &'static dyn StaticBuiltin = &#name {}; + } + impl StaticBuiltin for #name {} + impl Builtin for #name + where + Self: 'static + { + fn name(&self) -> &str { + stringify!(#name) + } + fn params(&self) -> &[BuiltinParam] { + PARAMS + } + fn call(&self, context: Context, loc: Option<&ExprLocation>, args: &dyn ArgsLike) -> Result { + let parsed = jrsonnet_evaluator::function::parse_builtin_call(context, &PARAMS, args, false)?; + + let result: #result = #name(#(#args),*); + let result = result?; + result.try_into() + } + } + }; }) .into() } --- a/crates/jrsonnet-parser/src/lib.rs +++ b/crates/jrsonnet-parser/src/lib.rs @@ -40,7 +40,7 @@ / "#" (!eol()[_])* eol() rule single_whitespace() = quiet!{([' ' | '\r' | '\n' | '\t'] / comment())} / expected!("") - rule _() = single_whitespace()* + rule _() = quiet!{([' ' | '\r' | '\n' | '\t']+) / comment()}* / expected!("") /// For comma-delimited elements rule comma() = quiet!{_ "," _} / expected!("") @@ -305,6 +305,14 @@ pub fn parse(str: &str, settings: &ParserSettings) -> Result { jsonnet_parser::jsonnet(str, settings) } +/// Used for importstr values +pub fn string_to_expr(str: IStr, settings: &ParserSettings) -> LocExpr { + let len = str.len(); + LocExpr( + Rc::new(Expr::Str(str)), + ExprLocation(settings.file_name.clone(), 0, len), + ) +} #[cfg(test)] pub mod tests { --- a/crates/jrsonnet-stdlib/src/std.jsonnet +++ b/crates/jrsonnet-stdlib/src/std.jsonnet @@ -375,9 +375,7 @@ manifestJsonEx:: $intrinsic(manifestJsonEx), - manifestYamlDocImpl:: $intrinsic(manifestYamlDocImpl), - - manifestYamlDoc(value, indent_array_in_object=false, quote_keys=true):: std.manifestYamlDocImpl(value, indent_array_in_object, quote_keys), + manifestYamlDoc:: $intrinsic(manifestYamlDoc), manifestYamlStream(value, indent_array_in_object=false, c_document_end=true):: if !std.isArray(value) then @@ -443,10 +441,7 @@ reverse:: $intrinsic(reverse), - sortImpl:: $intrinsic(sortImpl), - - sort(arr, keyF=id):: - std.sortImpl(arr, keyF), + sort:: $intrinsic(sort), uniq(arr, keyF=id):: local f(a, b) = -- gitstuff