From ff3e2c83693822b0c5f4daebb2824c68a3cd339f Mon Sep 17 00:00:00 2001 From: Yaroslav Bolyukin Date: Sat, 15 Oct 2022 18:34:13 +0000 Subject: [PATCH] doc: review issues --- --- a/bindings/jsonnet/src/import.rs +++ b/bindings/jsonnet/src/import.rs @@ -93,7 +93,7 @@ /// # Safety /// -/// Caller should pass correct callback function +/// It should be safe to call `cb` using valid values with passed `ctx` #[no_mangle] pub unsafe extern "C" fn jsonnet_import_callback( vm: &State, @@ -109,10 +109,10 @@ /// # Safety /// -/// Caller should pass correct path: it should contain correct utf-8, and be \0-terminated +/// `path` should be a NUL-terminated string #[no_mangle] -pub unsafe extern "C" fn jsonnet_jpath_add(vm: &State, v: *const c_char) { - let cstr = CStr::from_ptr(v); +pub unsafe extern "C" fn jsonnet_jpath_add(vm: &State, path: *const c_char) { + let cstr = CStr::from_ptr(path); let path = PathBuf::from(cstr.to_str().unwrap()); let any_resolver = vm.import_resolver(); let resolver = any_resolver --- a/bindings/jsonnet/src/lib.rs +++ b/bindings/jsonnet/src/lib.rs @@ -41,17 +41,10 @@ let str = OsStr::from_bytes(input.to_bytes()); Cow::Borrowed(Path::new(str)) } - #[cfg(target_family = "windows")] + #[cfg(not(target_family = "unix"))] { - use std::os::windows::ffi::OsStringExt; - let str = input.to_str().expect("input is not utf8"); - let wide = str.encode_utf16().collect::>(); - let wide = OsString::from_wide(&wide); - Cow::Owned(PathBuf::new(wide)) - } - #[cfg(not(any(target_family = "unix", target_family = "windows")))] - { - compile_error!("unsupported os") + let string = input.to_str().expect("bad utf-8"); + Cow::Borrowed(string.as_ref()) } } @@ -62,9 +55,11 @@ let str = CString::new(input.as_os_str().as_bytes()).expect("input has zero byte in it"); Cow::Owned(str) } - #[cfg(not(any(target_family = "unix", target_family = "windows")))] + #[cfg(not(target_family = "unix"))] { - compile_error!("unsupported os") + let str = input.as_os_str().to_str().expect("bad utf-8"); + let cstr = CString::new(str).expect("input has NUL inside"); + Cow::Owned(cstr) } } @@ -169,7 +164,7 @@ /// /// # Safety /// -/// `filename` should be a \0-terminated string +/// `filename` should be a NUL-terminated string #[no_mangle] pub unsafe extern "C" fn jsonnet_evaluate_file( vm: &State, @@ -200,7 +195,7 @@ /// /// # Safety /// -/// `filename`, `snippet` should be a \0-terminated strings +/// `filename`, `snippet` should be a NUL-terminated strings #[no_mangle] pub unsafe extern "C" fn jsonnet_evaluate_snippet( vm: &State, --- a/bindings/jsonnet/src/native.rs +++ b/bindings/jsonnet/src/native.rs @@ -65,9 +65,9 @@ /// # Safety /// /// `vm` should be a vm allocated by `jsonnet_make` -/// `cb` should be a correct function pointer -/// `raw_params` should point to a NULL-terminated string array -/// `name`, `raw_params` elements should be a \0-terminated strings +/// `name` should be a NUL-terminated string +/// `cb` should be a function pointer +/// `raw_params` should point to a NULL-terminated array of NUL-terminated strings #[no_mangle] pub unsafe extern "C" fn jsonnet_native_callback( vm: &State, --- a/bindings/jsonnet/src/val_make.rs +++ b/bindings/jsonnet/src/val_make.rs @@ -12,7 +12,7 @@ /// /// # Safety /// -/// `v` should be a \0-terminated string +/// `v` should be a NUL-terminated string #[no_mangle] pub unsafe extern "C" fn jsonnet_json_make_string(_vm: &State, val: *const c_char) -> *mut Val { let val = CStr::from_ptr(val); --- a/bindings/jsonnet/src/val_modify.rs +++ b/bindings/jsonnet/src/val_modify.rs @@ -11,8 +11,8 @@ /// /// # Safety /// -/// `arr` should be correct pointer to array value allocated by make_array, or returned by other library call -/// `val` should be correct pointer to value allocated using this library +/// `arr` should be a pointer to array value allocated by make_array, or returned by other library call +/// `val` should be a pointer to value allocated using this library #[no_mangle] pub unsafe extern "C" fn jsonnet_json_array_append(_vm: &State, arr: &mut Val, val: &Val) { match arr { @@ -35,8 +35,8 @@ /// /// # Safety /// -/// `obj` should be a valid pointer to object value allocated by `make_object`, or returned by other library call -/// `name` should be \0-terminated string +/// `obj` should be a pointer to object value allocated by `make_object`, or returned by other library call +/// `name` should be NUL-terminated string #[no_mangle] pub unsafe extern "C" fn jsonnet_json_object_append( _vm: &State, --- a/bindings/jsonnet/src/vars_tlas.rs +++ b/bindings/jsonnet/src/vars_tlas.rs @@ -4,13 +4,13 @@ use jrsonnet_evaluator::State; -/// Bind a Jsonnet external var to the given string. +/// Binds a Jsonnet external variable to the given string. /// -/// Argument values are copied so memory should be managed by caller. +/// Argument values are copied so memory should be managed by the caller. /// /// # Safety /// -/// Caller should pass correct pointers as `name` and `code`, they need to be \0-terminated strings +/// `name`, `code` should be a NUL-terminated strings #[no_mangle] pub unsafe extern "C" fn jsonnet_ext_var(vm: &State, name: *const c_char, value: *const c_char) { let name = CStr::from_ptr(name); @@ -27,13 +27,13 @@ ) } -/// Bind a Jsonnet external var to the given code. +/// Binds a Jsonnet external variable to the given code. /// -/// Argument values are copied so memory should be managed by caller. +/// Argument values are copied so memory should be managed by the caller. /// /// # Safety /// -/// Caller should pass correct pointers as `name` and `code`, they need to be \0-terminated strings +/// `name`, `code` should be a NUL-terminated strings #[no_mangle] pub unsafe extern "C" fn jsonnet_ext_code(vm: &State, name: *const c_char, code: *const c_char) { let name = CStr::from_ptr(name); @@ -51,13 +51,13 @@ .expect("can't parse ext code") } -/// Bind a string top-level argument for a top-level parameter. +/// Binds a top-level string argument for a top-level parameter. /// -/// Argument values are copied so memory should be managed by caller. +/// Argument values are copied so memory should be managed by the caller. /// /// # Safety /// -/// Caller should pass correct pointers as `name` and `value`, they need to be \0-terminated strings +/// `name`, `value` should be a NUL-terminated strings #[no_mangle] pub unsafe extern "C" fn jsonnet_tla_var(vm: &State, name: *const c_char, value: *const c_char) { let name = CStr::from_ptr(name); @@ -68,13 +68,13 @@ ) } -/// Bind a code top-level argument for a top-level parameter. +/// Binds a top-level code argument for a top-level parameter. /// -/// Argument values are copied so memory should be managed by caller. +/// Argument values are copied so memory should be managed by the caller. /// /// # Safety /// -/// Caller should pass correct pointers as `name` and `code`, they need to be \0-terminated strings +/// `name`, `code` should be a NUL-terminated strings #[no_mangle] pub unsafe extern "C" fn jsonnet_tla_code(vm: &State, name: *const c_char, code: *const c_char) { let name = CStr::from_ptr(name); --- a/crates/jrsonnet-evaluator/src/ctx.rs +++ b/crates/jrsonnet-evaluator/src/ctx.rs @@ -20,6 +20,9 @@ } } +/// Context keeps information about current lexical code location +/// +/// This information includes local variables, top-level object (`$`), current object (`this`), and super object (`super`) #[derive(Debug, Clone, Trace)] pub struct Context(Cc); impl Context { @@ -160,8 +163,11 @@ extend: Some(parent), } } + /// # Panics + /// If `name` is already bound pub fn bind(&mut self, name: IStr, value: Thunk) -> &mut Self { - self.bindings.insert(name, value); + let old = self.bindings.insert(name, value); + assert!(old.is_none(), "variable bound twice in single context call"); self } pub fn build(self) -> Context { --- a/crates/jrsonnet-evaluator/src/error.rs +++ b/crates/jrsonnet-evaluator/src/error.rs @@ -67,7 +67,10 @@ type FunctionSignature = Vec<(Option, bool)>; +/// Possible errors +#[allow(missing_docs)] #[derive(Error, Debug, Clone, Trace)] +#[non_exhaustive] pub enum Error { #[error("intrinsic not found: {0}")] IntrinsicNotFound(IStr), @@ -217,9 +220,13 @@ } } +/// Single stack trace frame #[derive(Clone, Debug, Trace)] pub struct StackTraceElement { + /// Source of this frame + /// Some frames only act as description, without attached source pub location: Option, + /// Frame description pub desc: String, } #[derive(Debug, Clone, Trace)] --- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs +++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs @@ -269,9 +269,7 @@ } } let this = builder.build(); - let _ctx = ctx - .extend(GcHashMap::new(), None, None, Some(this.clone())) - .into_future(fctx); + fctx.fill(ctx.extend(GcHashMap::new(), None, None, Some(this.clone()))); Ok(this) } @@ -356,7 +354,7 @@ ctx: Context, value: &LocExpr, args: &ArgsDesc, - loc: CallLocation, + loc: CallLocation<'_>, tailstrict: bool, ) -> Result { let value = evaluate(s.clone(), ctx.clone(), value)?; @@ -602,7 +600,7 @@ } Slice(value, desc) => { fn parse_idx( - loc: CallLocation, + loc: CallLocation<'_>, s: State, ctx: &Context, expr: &Option, --- a/crates/jrsonnet-evaluator/src/function/builtin.rs +++ b/crates/jrsonnet-evaluator/src/function/builtin.rs @@ -24,7 +24,13 @@ /// Parameter names for named calls fn params(&self) -> &[BuiltinParam]; /// Call the builtin - fn call(&self, s: State, ctx: Context, loc: CallLocation, args: &dyn ArgsLike) -> Result; + fn call( + &self, + s: State, + ctx: Context, + loc: CallLocation<'_>, + args: &dyn ArgsLike, + ) -> Result; } pub trait StaticBuiltin: Builtin + Send + Sync @@ -70,7 +76,13 @@ &self.params } - fn call(&self, s: State, ctx: Context, _loc: CallLocation, args: &dyn ArgsLike) -> Result { + fn call( + &self, + s: State, + ctx: Context, + _loc: CallLocation<'_>, + args: &dyn ArgsLike, + ) -> Result { let args = parse_builtin_call(s.clone(), ctx, &self.params, args, true)?; let args = args .into_iter() --- a/crates/jrsonnet-evaluator/src/function/mod.rs +++ b/crates/jrsonnet-evaluator/src/function/mod.rs @@ -18,43 +18,50 @@ pub mod native; pub mod parse; +/// Function callsite location. +/// Either from other jsonnet code, specified by expression location, or from native (without location). #[derive(Clone, Copy)] pub struct CallLocation<'l>(pub Option<&'l ExprLocation>); impl<'l> CallLocation<'l> { + /// Construct new location for calls coming from specified jsonnet expression location. pub const fn new(loc: &'l ExprLocation) -> Self { Self(Some(loc)) } } impl CallLocation<'static> { + /// Construct new location for calls coming from native code. pub const fn native() -> Self { Self(None) } } -/// Function implemented in jsonnet +/// Represents Jsonnet function defined in code. #[derive(Debug, PartialEq, Trace)] pub struct FuncDesc { - /// In expressions like + /// # Example + /// + /// In expressions like this, deducted to `a`, unspecified otherwise. /// ```jsonnet /// local a = function() ... /// local a() ... /// { a: function() ... } /// { a() = ... } /// ``` - /// - /// Deducted to `a`, unspecified otherwise pub name: IStr, - /// Context, in which this function was evaluated + /// Context, in which this function was evaluated. /// - /// I.e in + /// # Example + /// In /// ```jsonnet /// local a = 2; /// function() ... /// ``` - /// context will contain `a` + /// context will contain `a`. pub ctx: Context, + /// Function parameter definition pub params: ParamsDesc, + /// Function body pub body: LocExpr, } impl FuncDesc { @@ -82,17 +89,17 @@ } } -/// Any possible function value, including plain functions and user-provided builtins +/// Represents a Jsonnet function value, including plain functions and user-provided builtins. #[allow(clippy::module_name_repetitions)] #[derive(Trace, Clone)] pub enum FuncVal { - /// std.id + /// Identity function, kept this way for comparsions. Id, - /// Plain function implemented in jsonnet + /// Plain function implemented in jsonnet. Normal(Cc), - /// Standard library function + /// Standard library function. StaticBuiltin(#[trace(skip)] &'static dyn StaticBuiltin), - /// User-provided function + /// User-provided function. Builtin(Cc>), } @@ -110,9 +117,7 @@ } impl FuncVal { - pub fn into_native(self) -> D::Value { - D::into_native(self) - } + /// Amount of non-default required arguments pub fn params_len(&self) -> usize { match self { Self::Id => 1, @@ -121,6 +126,7 @@ Self::Builtin(i) => i.params().iter().filter(|p| !p.has_default).count(), } } + /// Function name, as defined in code. pub fn name(&self) -> IStr { match self { Self::Id => "id".into(), @@ -129,11 +135,14 @@ Self::Builtin(builtin) => builtin.name().into(), } } + /// Call function using arguments evaluated in specified `call_ctx` [`Context`]. + /// + /// If `tailstrict` is specified - then arguments will be evaluated before being passed to function body. pub fn evaluate( &self, s: State, call_ctx: Context, - loc: CallLocation, + loc: CallLocation<'_>, args: &dyn ArgsLike, tailstrict: bool, ) -> Result { @@ -156,13 +165,22 @@ Self::Builtin(b) => b.call(s, call_ctx, loc, args), } } + /// Helper method, which calls [`Self::evaluate`] with sensible defaults for native code. pub fn evaluate_simple(&self, s: State, args: &dyn ArgsLike) -> Result { self.evaluate(s, Context::default(), CallLocation::native(), args, true) } + /// Convert jsonnet function to plain `Fn` value. + pub fn into_native(self) -> D::Value { + D::into_native(self) + } + /// Is this function an indentity function. + /// + /// Currently only works for builtin `std.id`, aka `Self::Id` value, `function(x) x` defined by jsonnet will not count as identity. pub const fn is_identity(&self) -> bool { matches!(self, Self::Id) } + /// Identity function value. pub const fn identity() -> Self { Self::Id } --- a/crates/jrsonnet-evaluator/src/gc.rs +++ b/crates/jrsonnet-evaluator/src/gc.rs @@ -22,7 +22,7 @@ } impl Trace for TraceBox { - fn trace(&self, tracer: &mut Tracer) { + fn trace(&self, tracer: &mut Tracer<'_>) { self.0.trace(tracer); } @@ -92,7 +92,7 @@ where V: Trace, { - fn trace(&self, tracer: &mut jrsonnet_gcmodule::Tracer) { + fn trace(&self, tracer: &mut Tracer<'_>) { for v in &self.0 { v.trace(tracer); } @@ -133,7 +133,7 @@ K: Trace, V: Trace, { - fn trace(&self, tracer: &mut jrsonnet_gcmodule::Tracer) { + fn trace(&self, tracer: &mut Tracer<'_>) { for (k, v) in &self.0 { k.trace(tracer); v.trace(tracer); --- a/crates/jrsonnet-evaluator/src/lib.rs +++ b/crates/jrsonnet-evaluator/src/lib.rs @@ -1,4 +1,18 @@ -#![warn(clippy::all, clippy::nursery, clippy::pedantic)] +//! jsonnet interpreter implementation + +#![deny(unsafe_op_in_unsafe_fn)] +#![warn( + clippy::all, + clippy::nursery, + clippy::pedantic, + // missing_docs, + elided_lifetimes_in_paths, + explicit_outlives_requirements, + noop_method_call, + single_use_lifetimes, + variant_size_differences, + rustdoc::all +)] #![allow( macro_expanded_macro_exports_accessed_by_absolute_paths, clippy::ptr_arg, @@ -67,23 +81,32 @@ use trace::{CompactFormat, TraceFormat}; pub use val::{ManifestFormat, Thunk, Val}; +/// Thunk without bound `super`/`this` +/// object inheritance may be overriden multiple times, and will be fixed only on field read pub trait Unbound: Trace { + /// Type of value after object context is bound type Bound; + /// Create value bound to specified object context fn bind(&self, s: State, sup: Option, this: Option) -> Result; } +/// Object fields may, or may not depend on `this`/`super`, this enum allows cheaper reuse of object-independent fields for native code +/// Standard jsonnet fields are always unbound #[derive(Clone, Trace)] -pub enum LazyBinding { - Bindable(Cc>>>), +pub enum MaybeUnbound { + /// Value needs to be bound to `this`/`super` + Unbound(Cc>>>), + /// Value is object-independent Bound(Thunk), } -impl Debug for LazyBinding { +impl Debug for MaybeUnbound { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "LazyBinding") + write!(f, "MaybeUnbound") } } -impl LazyBinding { +impl MaybeUnbound { + /// Attach object context to value, if required pub fn evaluate( &self, s: State, @@ -91,17 +114,19 @@ this: Option, ) -> Result> { match self { - Self::Bindable(v) => v.bind(s, sup, this), + Self::Unbound(v) => v.bind(s, sup, this), Self::Bound(v) => Ok(v.clone()), } } } -/// During import, this trait will be called to create initial context for file -/// It may initialize global variables, stdlib for example +/// During import, this trait will be called to create initial context for file. +/// It may initialize global variables, stdlib for example. pub trait ContextInitializer { + /// Initialize default file context. fn initialize(&self, state: State, for_file: Source) -> Context; - + /// Allows upcasting from abstract to concrete context initializer. + /// jrsonnet by itself doesn't use this method, it is allowed for it to panic. fn as_any(&self) -> &dyn Any; } @@ -116,6 +141,7 @@ } } +/// Dynamically reconfigurable evaluation settings pub struct EvaluationSettings { /// Limits recursion by limiting the number of stack frames pub max_stack: usize, @@ -401,7 +427,7 @@ /// Executes code creating a new stack frame pub fn push( &self, - e: CallLocation, + e: CallLocation<'_>, frame_desc: impl FnOnce() -> String, f: impl FnOnce() -> Result, ) -> Result { @@ -547,16 +573,13 @@ /// Internals impl State { - // fn data(&self) -> Ref { - // self.0.data.borrow() - // } - fn data_mut(&self) -> RefMut { + fn data_mut(&self) -> RefMut<'_, EvaluationData> { self.0.data.borrow_mut() } - pub fn settings(&self) -> Ref { + pub fn settings(&self) -> Ref<'_, EvaluationSettings> { self.0.settings.borrow() } - pub fn settings_mut(&self) -> RefMut { + pub fn settings_mut(&self) -> RefMut<'_, EvaluationSettings> { self.0.settings.borrow_mut() } } @@ -623,13 +646,13 @@ pub fn resolve(&self, path: impl AsRef) -> Result { self.import_resolver().resolve(path.as_ref()) } - pub fn import_resolver(&self) -> Ref { + pub fn import_resolver(&self) -> Ref<'_, dyn ImportResolver> { Ref::map(self.settings(), |s| &*s.import_resolver) } pub fn set_import_resolver(&self, resolver: Box) { self.settings_mut().import_resolver = resolver; } - pub fn context_initializer(&self) -> Ref { + pub fn context_initializer(&self) -> Ref<'_, dyn ContextInitializer> { Ref::map(self.settings(), |s| &*s.context_initializer) } @@ -640,7 +663,7 @@ self.settings_mut().manifest_format = format; } - pub fn trace_format(&self) -> Ref { + pub fn trace_format(&self) -> Ref<'_, dyn TraceFormat> { Ref::map(self.settings(), |s| &*s.trace_format) } pub fn set_trace_format(&self, format: Box) { --- a/crates/jrsonnet-evaluator/src/obj.rs +++ b/crates/jrsonnet-evaluator/src/obj.rs @@ -15,7 +15,7 @@ function::CallLocation, gc::{GcHashMap, GcHashSet, TraceBox}, operator::evaluate_add_op, - throw, LazyBinding, Result, State, Thunk, Unbound, Val, + throw, MaybeUnbound, Result, State, Thunk, Unbound, Val, }; #[cfg(not(feature = "exp-preserve-order"))] @@ -100,7 +100,7 @@ pub add: bool, pub visibility: Visibility, original_index: FieldIndex, - pub invoke: LazyBinding, + pub invoke: MaybeUnbound, pub location: Option, } @@ -208,7 +208,7 @@ new.insert(key, value); Self::new(Some(self), Cc::new(new), Cc::new(Vec::new())) } - pub fn extend_field(&mut self, name: IStr) -> ObjMemberBuilder { + pub fn extend_field(&mut self, name: IStr) -> ObjMemberBuilder> { ObjMemberBuilder::new(ExtendBuilder(self), name, FieldIndex::default()) } @@ -239,6 +239,8 @@ } /// Run callback for every field found in object + /// + /// Returns true if ended prematurely pub(crate) fn enum_fields( &self, depth: SuperDepth, @@ -500,7 +502,7 @@ self.assertions.push(assertion); self } - pub fn member(&mut self, name: IStr) -> ObjMemberBuilder { + pub fn member(&mut self, name: IStr) -> ObjMemberBuilder> { let field_index = self.next_field_index; self.next_field_index = self.next_field_index.next(); ObjMemberBuilder::new(ValueBuilder(self), name, field_index) @@ -558,7 +560,7 @@ self.location = Some(location); self } - fn build_member(self, binding: LazyBinding) -> (Kind, IStr, ObjMember) { + fn build_member(self, binding: MaybeUnbound) -> (Kind, IStr, ObjMember) { ( self.kind, self.name, @@ -574,18 +576,18 @@ } pub struct ValueBuilder<'v>(&'v mut ObjValueBuilder); -impl<'v> ObjMemberBuilder> { +impl ObjMemberBuilder> { pub fn value(self, s: State, value: Val) -> Result<()> { - self.binding(s, LazyBinding::Bound(Thunk::evaluated(value))) + self.binding(s, MaybeUnbound::Bound(Thunk::evaluated(value))) } pub fn bindable( self, s: State, bindable: TraceBox>>, ) -> Result<()> { - self.binding(s, LazyBinding::Bindable(Cc::new(bindable))) + self.binding(s, MaybeUnbound::Unbound(Cc::new(bindable))) } - pub fn binding(self, s: State, binding: LazyBinding) -> Result<()> { + pub fn binding(self, s: State, binding: MaybeUnbound) -> Result<()> { let (receiver, name, member) = self.build_member(binding); let location = member.location.clone(); let old = receiver.0.map.insert(name.clone(), member); @@ -601,14 +603,14 @@ } pub struct ExtendBuilder<'v>(&'v mut ObjValue); -impl<'v> ObjMemberBuilder> { +impl ObjMemberBuilder> { pub fn value(self, value: Val) { - self.binding(LazyBinding::Bound(Thunk::evaluated(value))); + self.binding(MaybeUnbound::Bound(Thunk::evaluated(value))); } pub fn bindable(self, bindable: TraceBox>>) { - self.binding(LazyBinding::Bindable(Cc::new(bindable))); + self.binding(MaybeUnbound::Unbound(Cc::new(bindable))); } - pub fn binding(self, binding: LazyBinding) { + pub fn binding(self, binding: MaybeUnbound) { let (receiver, name, member) = self.build_member(binding); let new = receiver.0.clone(); *receiver.0 = new.extend_with_raw_member(name, member); --- a/crates/jrsonnet-evaluator/src/stdlib/format.rs +++ b/crates/jrsonnet-evaluator/src/stdlib/format.rs @@ -36,7 +36,7 @@ type ParseResult<'t, T> = std::result::Result<(T, &'t str), FormatError>; -pub fn try_parse_mapping_key(str: &str) -> ParseResult<&str> { +pub fn try_parse_mapping_key(str: &str) -> ParseResult<'_, &str> { if str.is_empty() { return Err(TruncatedFormatCode); } @@ -96,7 +96,7 @@ pub sign: bool, } -pub fn try_parse_cflags(str: &str) -> ParseResult { +pub fn try_parse_cflags(str: &str) -> ParseResult<'_, CFlags> { if str.is_empty() { return Err(TruncatedFormatCode); } @@ -125,7 +125,7 @@ Star, Fixed(usize), } -pub fn try_parse_field_width(str: &str) -> ParseResult { +pub fn try_parse_field_width(str: &str) -> ParseResult<'_, Width> { if str.is_empty() { return Err(TruncatedFormatCode); } @@ -146,7 +146,7 @@ Ok((Width::Fixed(out), &str[digits..])) } -pub fn try_parse_precision(str: &str) -> ParseResult> { +pub fn try_parse_precision(str: &str) -> ParseResult<'_, Option> { if str.is_empty() { return Err(TruncatedFormatCode); } @@ -159,7 +159,7 @@ } // Only skips -pub fn try_parse_length_modifier(str: &str) -> ParseResult<()> { +pub fn try_parse_length_modifier(str: &str) -> ParseResult<'_, ()> { if str.is_empty() { return Err(TruncatedFormatCode); } @@ -191,7 +191,7 @@ caps: bool, } -pub fn parse_conversion_type(str: &str) -> ParseResult { +pub fn parse_conversion_type(str: &str) -> ParseResult<'_, ConvType> { if str.is_empty() { return Err(TruncatedFormatCode); } @@ -226,7 +226,7 @@ convtype: ConvTypeV, caps: bool, } -pub fn parse_code(str: &str) -> ParseResult { +pub fn parse_code(str: &str) -> ParseResult<'_, Code<'_>> { if str.is_empty() { return Err(TruncatedFormatCode); } @@ -255,7 +255,7 @@ String(&'s str), Code(Code<'s>), } -pub fn parse_codes(mut str: &str) -> Result> { +pub fn parse_codes(mut str: &str) -> Result>> { let mut bytes = str.as_bytes(); let mut out = vec![]; let mut offset = 0; @@ -475,7 +475,7 @@ s: State, out: &mut String, value: &Val, - code: &Code, + code: &Code<'_>, width: usize, precision: Option, ) -> Result<()> { --- a/crates/jrsonnet-evaluator/src/stdlib/manifest.rs +++ b/crates/jrsonnet-evaluator/src/stdlib/manifest.rs @@ -116,7 +116,7 @@ || { let value = obj.get(s.clone(), field.clone())?.unwrap(); manifest_json_ex_buf(s.clone(), &value, buf, cur_padding, options)?; - Ok(Val::Null) + Ok(()) }, )?; } --- a/crates/jrsonnet-evaluator/src/val.rs +++ b/crates/jrsonnet-evaluator/src/val.rs @@ -186,16 +186,26 @@ } } +/// Represents a Jsonnet array value. #[derive(Debug, Clone, Trace)] // may contrain other ArrValue #[trace(tracking(force))] pub enum ArrValue { + /// Layout optimized byte array. Bytes(#[trace(skip)] IBytes), + /// Every element is lazy evaluated. Lazy(Cc>>), + /// Every field is already evaluated. Eager(Cc>), + /// Concatenation of two arrays of any kind. Extended(Box<(Self, Self)>), + /// Represents a integer array in form `[start, start + 1, ... end - 1, end]`. + /// This kind of arrays is generated by `std.range(start, end)` call, and used for loops. Range(i32, i32), + /// Sliced array view. Slice(Box), + /// Reversed array view. + /// Returned by `std.reverse(other)` call Reversed(Box), } @@ -237,6 +247,7 @@ })) } + /// Array length. pub fn len(&self) -> usize { match self { Self::Bytes(i) => i.len(), @@ -249,10 +260,14 @@ } } + /// Is array contains no elements? pub fn is_empty(&self) -> bool { self.len() == 0 } + /// Get array element by index, evaluating it, if it is lazy. + /// + /// Returns `None` on out-of-bounds condition. pub fn get(&self, s: State, index: usize) -> Result> { match self { Self::Bytes(i) => i @@ -297,6 +312,9 @@ } } + /// Get array element by index, without evaluation. + /// + /// Returns `None` on out-of-bounds condition. pub fn get_lazy(&self, index: usize) -> Option> { match self { Self::Bytes(i) => i @@ -337,6 +355,7 @@ } } + /// Evaluate all array elements, returning new array. pub fn evaluated(&self, s: State) -> Result>> { Ok(match self { Self::Bytes(i) => { @@ -389,6 +408,7 @@ }) } + /// Iterate over elements, evaluating them. pub fn iter(&self, s: State) -> impl DoubleEndedIterator> + '_ { (0..self.len()).map(move |idx| match self { Self::Bytes(b) => Ok(Val::Num(f64::from(b[idx]))), @@ -400,6 +420,7 @@ }) } + /// Iterate over elements, returning lazy values. pub fn iter_lazy(&self) -> impl DoubleEndedIterator> + '_ { (0..self.len()).map(move |idx| match self { Self::Bytes(b) => Thunk::evaluated(Val::Num(f64::from(b[idx]))), @@ -411,11 +432,13 @@ }) } + /// Return a reversed view on current array. #[must_use] pub fn reversed(self) -> Self { Self::Reversed(Box::new(self)) } + /// Return a new array, produced by passing every element of current array to specified callback function. pub fn map(self, s: State, mapper: impl Fn(Val) -> Result) -> Result { let mut out = Vec::with_capacity(self.len()); @@ -426,6 +449,7 @@ Ok(Self::Eager(Cc::new(out))) } + /// Return a new array, produced from current array by removing every value, for which specified callback function returns false. pub fn filter(self, s: State, filter: impl Fn(&Val) -> Result) -> Result { let mut out = Vec::with_capacity(self.len()); @@ -460,12 +484,22 @@ } } +/// Represents a Jsonnet value, which can be spliced or indexed (string or array). #[allow(clippy::module_name_repetitions)] pub enum IndexableVal { + /// String. Str(IStr), + /// Array. Arr(ArrValue), } impl IndexableVal { + /// Slice the value. + /// + /// # Implementation + /// + /// For strings, will create a copy of specified interval. + /// + /// For arrays, nothing will be copied on this call, instead [`ArrValue::Slice`] view will be returned. pub fn slice( self, index: Option>, @@ -511,14 +545,24 @@ } } +/// Represents any valid Jsonnet value. #[derive(Debug, Clone, Trace)] pub enum Val { + /// Represents a Jsonnet boolean. Bool(bool), + /// Represents a Jsonnet null value. Null, + /// Represents a Jsonnet string. Str(IStr), + /// Represents a Jsonnet number. + /// Should be finite, and not NaN + /// This restriction isn't enforced by enum, as enum field can't be marked as private Num(f64), + /// Represents a Jsonnet array. Arr(ArrValue), + /// Represents a Jsonnet object. Obj(ObjValue), + /// Represents a Jsonnet function. Func(FuncVal), } --- a/crates/jrsonnet-stdlib/src/lib.rs +++ b/crates/jrsonnet-stdlib/src/lib.rs @@ -52,91 +52,88 @@ builder.with_super(eval); for (name, builtin) in [ - ("length".into(), builtin_length::INST), + ("length", builtin_length::INST), // Types - ("type".into(), builtin_type::INST), - ("isString".into(), builtin_is_string::INST), - ("isNumber".into(), builtin_is_number::INST), - ("isBoolean".into(), builtin_is_boolean::INST), - ("isObject".into(), builtin_is_object::INST), - ("isArray".into(), builtin_is_array::INST), - ("isFunction".into(), builtin_is_function::INST), + ("type", builtin_type::INST), + ("isString", builtin_is_string::INST), + ("isNumber", builtin_is_number::INST), + ("isBoolean", builtin_is_boolean::INST), + ("isObject", builtin_is_object::INST), + ("isArray", builtin_is_array::INST), + ("isFunction", builtin_is_function::INST), // Arrays - ("makeArray".into(), builtin_make_array::INST), - ("slice".into(), builtin_slice::INST), - ("map".into(), builtin_map::INST), - ("flatMap".into(), builtin_flatmap::INST), - ("filter".into(), builtin_filter::INST), - ("foldl".into(), builtin_foldl::INST), - ("foldr".into(), builtin_foldr::INST), - ("range".into(), builtin_range::INST), - ("join".into(), builtin_join::INST), - ("reverse".into(), builtin_reverse::INST), - ("any".into(), builtin_any::INST), - ("all".into(), builtin_all::INST), - ("member".into(), builtin_member::INST), - ("count".into(), builtin_count::INST), + ("makeArray", builtin_make_array::INST), + ("slice", builtin_slice::INST), + ("map", builtin_map::INST), + ("flatMap", builtin_flatmap::INST), + ("filter", builtin_filter::INST), + ("foldl", builtin_foldl::INST), + ("foldr", builtin_foldr::INST), + ("range", builtin_range::INST), + ("join", builtin_join::INST), + ("reverse", builtin_reverse::INST), + ("any", builtin_any::INST), + ("all", builtin_all::INST), + ("member", builtin_member::INST), + ("count", builtin_count::INST), // Math - ("modulo".into(), builtin_modulo::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), + ("modulo", builtin_modulo::INST), + ("floor", builtin_floor::INST), + ("ceil", builtin_ceil::INST), + ("log", builtin_log::INST), + ("pow", builtin_pow::INST), + ("sqrt", builtin_sqrt::INST), + ("sin", builtin_sin::INST), + ("cos", builtin_cos::INST), + ("tan", builtin_tan::INST), + ("asin", builtin_asin::INST), + ("acos", builtin_acos::INST), + ("atan", builtin_atan::INST), + ("exp", builtin_exp::INST), + ("mantissa", builtin_mantissa::INST), + ("exponent", builtin_exponent::INST), // Operator - ("mod".into(), builtin_mod::INST), - ("primitiveEquals".into(), builtin_primitive_equals::INST), - ("equals".into(), builtin_equals::INST), - ("format".into(), builtin_format::INST), + ("mod", builtin_mod::INST), + ("primitiveEquals", builtin_primitive_equals::INST), + ("equals", builtin_equals::INST), + ("format", builtin_format::INST), // Sort - ("sort".into(), builtin_sort::INST), + ("sort", builtin_sort::INST), // Hash - ("md5".into(), builtin_md5::INST), + ("md5", builtin_md5::INST), // Encoding - ("encodeUTF8".into(), builtin_encode_utf8::INST), - ("decodeUTF8".into(), builtin_decode_utf8::INST), - ("base64".into(), builtin_base64::INST), - ("base64Decode".into(), builtin_base64_decode::INST), - ( - "base64DecodeBytes".into(), - builtin_base64_decode_bytes::INST, - ), + ("encodeUTF8", builtin_encode_utf8::INST), + ("decodeUTF8", builtin_decode_utf8::INST), + ("base64", builtin_base64::INST), + ("base64Decode", builtin_base64_decode::INST), + ("base64DecodeBytes", builtin_base64_decode_bytes::INST), // Objects - ("objectFieldsEx".into(), builtin_object_fields_ex::INST), - ("objectHasEx".into(), builtin_object_has_ex::INST), + ("objectFieldsEx", builtin_object_fields_ex::INST), + ("objectHasEx", builtin_object_has_ex::INST), // Manifest - ("escapeStringJson".into(), builtin_escape_string_json::INST), - ("manifestJsonEx".into(), builtin_manifest_json_ex::INST), - ("manifestYamlDoc".into(), builtin_manifest_yaml_doc::INST), + ("escapeStringJson", builtin_escape_string_json::INST), + ("manifestJsonEx", builtin_manifest_json_ex::INST), + ("manifestYamlDoc", builtin_manifest_yaml_doc::INST), // Parsing - ("parseJson".into(), builtin_parse_json::INST), - ("parseYaml".into(), builtin_parse_yaml::INST), + ("parseJson", builtin_parse_json::INST), + ("parseYaml", builtin_parse_yaml::INST), // Misc - ("codepoint".into(), builtin_codepoint::INST), - ("substr".into(), builtin_substr::INST), - ("char".into(), builtin_char::INST), - ("strReplace".into(), builtin_str_replace::INST), - ("splitLimit".into(), builtin_splitlimit::INST), - ("asciiUpper".into(), builtin_ascii_upper::INST), - ("asciiLower".into(), builtin_ascii_lower::INST), - ("findSubstr".into(), builtin_find_substr::INST), - ("startsWith".into(), builtin_starts_with::INST), - ("endsWith".into(), builtin_ends_with::INST), + ("codepoint", builtin_codepoint::INST), + ("substr", builtin_substr::INST), + ("char", builtin_char::INST), + ("strReplace", builtin_str_replace::INST), + ("splitLimit", builtin_splitlimit::INST), + ("asciiUpper", builtin_ascii_upper::INST), + ("asciiLower", builtin_ascii_lower::INST), + ("findSubstr", builtin_find_substr::INST), + ("startsWith", builtin_starts_with::INST), + ("endsWith", builtin_ends_with::INST), ] .iter() .cloned() { builder - .member(name) + .member(name.into()) .hide() .value(s.clone(), Val::Func(FuncVal::StaticBuiltin(builtin))) .expect("no conflict"); --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -1 +1 @@ - +//! See tests/, suite/ and golden/ directories for tests -- gitstuff