difftreelog
doc: review issues
in: master
19 files changed
bindings/jsonnet/src/import.rsdiffbeforeafterboth--- 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
bindings/jsonnet/src/lib.rsdiffbeforeafterboth--- 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::<Vec<_>>();
- 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,
bindings/jsonnet/src/native.rsdiffbeforeafterboth--- 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,
bindings/jsonnet/src/val_make.rsdiffbeforeafterboth--- 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);
bindings/jsonnet/src/val_modify.rsdiffbeforeafterboth--- 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,
bindings/jsonnet/src/vars_tlas.rsdiffbeforeafterboth--- 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);
crates/jrsonnet-evaluator/src/ctx.rsdiffbeforeafterboth--- 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<ContextInternals>);
impl Context {
@@ -160,8 +163,11 @@
extend: Some(parent),
}
}
+ /// # Panics
+ /// If `name` is already bound
pub fn bind(&mut self, name: IStr, value: Thunk<Val>) -> &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 {
crates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -67,7 +67,10 @@
type FunctionSignature = Vec<(Option<IStr>, 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<ExprLocation>,
+ /// Frame description
pub desc: String,
}
#[derive(Debug, Clone, Trace)]
crates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth--- 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<Val> {
let value = evaluate(s.clone(), ctx.clone(), value)?;
@@ -602,7 +600,7 @@
}
Slice(value, desc) => {
fn parse_idx<T: Typed>(
- loc: CallLocation,
+ loc: CallLocation<'_>,
s: State,
ctx: &Context,
expr: &Option<LocExpr>,
crates/jrsonnet-evaluator/src/function/builtin.rsdiffbeforeafterboth--- 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<Val>;
+ fn call(
+ &self,
+ s: State,
+ ctx: Context,
+ loc: CallLocation<'_>,
+ args: &dyn ArgsLike,
+ ) -> Result<Val>;
}
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<Val> {
+ fn call(
+ &self,
+ s: State,
+ ctx: Context,
+ _loc: CallLocation<'_>,
+ args: &dyn ArgsLike,
+ ) -> Result<Val> {
let args = parse_builtin_call(s.clone(), ctx, &self.params, args, true)?;
let args = args
.into_iter()
crates/jrsonnet-evaluator/src/function/mod.rsdiffbeforeafterboth--- 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<FuncDesc>),
- /// Standard library function
+ /// Standard library function.
StaticBuiltin(#[trace(skip)] &'static dyn StaticBuiltin),
- /// User-provided function
+ /// User-provided function.
Builtin(Cc<TraceBox<dyn Builtin>>),
}
@@ -110,9 +117,7 @@
}
impl FuncVal {
- pub fn into_native<D: NativeDesc>(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<Val> {
@@ -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<Val> {
self.evaluate(s, Context::default(), CallLocation::native(), args, true)
}
+ /// Convert jsonnet function to plain `Fn` value.
+ pub fn into_native<D: NativeDesc>(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
}
crates/jrsonnet-evaluator/src/gc.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/gc.rs
+++ b/crates/jrsonnet-evaluator/src/gc.rs
@@ -22,7 +22,7 @@
}
impl<T: ?Sized + Trace> Trace for TraceBox<T> {
- 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);
crates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth1#![warn(clippy::all, clippy::nursery, clippy::pedantic)]2#![allow(3 macro_expanded_macro_exports_accessed_by_absolute_paths,4 clippy::ptr_arg,5 // Too verbose6 clippy::must_use_candidate,7 // A lot of functions pass around errors thrown by code8 clippy::missing_errors_doc,9 // A lot of pointers have interior Rc10 clippy::needless_pass_by_value,11 // Its fine12 clippy::wildcard_imports,13 clippy::enum_glob_use,14 clippy::module_name_repetitions,15 // TODO: fix individual issues, however this works as intended almost everywhere16 clippy::cast_precision_loss,17 clippy::cast_possible_wrap,18 clippy::cast_possible_truncation,19 clippy::cast_sign_loss,20 // False positives21 // https://github.com/rust-lang/rust-clippy/issues/690222 clippy::use_self,23 // https://github.com/rust-lang/rust-clippy/issues/853924 clippy::iter_with_drain,25)]2627// For jrsonnet-macros28extern crate self as jrsonnet_evaluator;2930mod ctx;31mod dynamic;32pub mod error;33mod evaluate;34pub mod function;35pub mod gc;36mod import;37mod integrations;38mod map;39mod obj;40pub mod stdlib;41pub mod trace;42pub mod typed;43pub mod val;4445use std::{46 any::Any,47 cell::{Ref, RefCell, RefMut},48 collections::HashMap,49 fmt::{self, Debug},50 path::Path,51 rc::Rc,52};5354pub use ctx::*;55pub use dynamic::*;56use error::{Error::*, LocError, Result, StackTraceElement};57pub use evaluate::*;58use function::{CallLocation, TlaArg};59use gc::{GcHashMap, TraceBox};60use hashbrown::hash_map::RawEntryMut;61pub use import::*;62use jrsonnet_gcmodule::{Cc, Trace};63pub use jrsonnet_interner::{IBytes, IStr};64pub use jrsonnet_parser as parser;65use jrsonnet_parser::*;66pub use obj::*;67use trace::{CompactFormat, TraceFormat};68pub use val::{ManifestFormat, Thunk, Val};6970pub trait Unbound: Trace {71 type Bound;72 fn bind(&self, s: State, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Self::Bound>;73}7475#[derive(Clone, Trace)]76pub enum LazyBinding {77 Bindable(Cc<TraceBox<dyn Unbound<Bound = Thunk<Val>>>>),78 Bound(Thunk<Val>),79}8081impl Debug for LazyBinding {82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {83 write!(f, "LazyBinding")84 }85}86impl LazyBinding {87 pub fn evaluate(88 &self,89 s: State,90 sup: Option<ObjValue>,91 this: Option<ObjValue>,92 ) -> Result<Thunk<Val>> {93 match self {94 Self::Bindable(v) => v.bind(s, sup, this),95 Self::Bound(v) => Ok(v.clone()),96 }97 }98}99100/// During import, this trait will be called to create initial context for file101/// It may initialize global variables, stdlib for example102pub trait ContextInitializer {103 fn initialize(&self, state: State, for_file: Source) -> Context;104105 fn as_any(&self) -> &dyn Any;106}107108/// Context initializer which adds nothing.109pub struct DummyContextInitializer;110impl ContextInitializer for DummyContextInitializer {111 fn initialize(&self, _state: State, _for_file: Source) -> Context {112 Context::default()113 }114 fn as_any(&self) -> &dyn Any {115 self116 }117}118119pub struct EvaluationSettings {120 /// Limits recursion by limiting the number of stack frames121 pub max_stack: usize,122 /// Limits amount of stack trace items preserved123 pub max_trace: usize,124 /// TLA vars125 pub tla_vars: HashMap<IStr, TlaArg>,126 /// Context initializer, which will be used for imports and everything127 /// [`NoopContextInitializer`] is used by default, most likely you want to have `jrsonnet-stdlib`128 pub context_initializer: Box<dyn ContextInitializer>,129 /// Used to resolve file locations/contents130 pub import_resolver: Box<dyn ImportResolver>,131 /// Used in manifestification functions132 pub manifest_format: ManifestFormat,133 /// Used for bindings134 pub trace_format: Box<dyn TraceFormat>,135}136impl Default for EvaluationSettings {137 fn default() -> Self {138 Self {139 max_stack: 200,140 max_trace: 20,141 context_initializer: Box::new(DummyContextInitializer),142 tla_vars: HashMap::default(),143 import_resolver: Box::new(DummyImportResolver),144 manifest_format: ManifestFormat::Json {145 padding: 4,146 #[cfg(feature = "exp-preserve-order")]147 preserve_order: false,148 },149 trace_format: Box::new(CompactFormat {150 padding: 4,151 resolver: trace::PathResolver::Absolute,152 }),153 }154 }155}156157#[derive(Default)]158struct EvaluationData {159 /// Used for stack overflow detection, stacktrace is populated on unwind160 stack_depth: usize,161 /// Updated every time stack entry is popt162 stack_generation: usize,163164 breakpoints: Breakpoints,165166 /// Contains file source codes and evaluation results for imports and pretty-printed stacktraces167 files: GcHashMap<SourcePath, FileData>,168}169struct FileData {170 string: Option<IStr>,171 bytes: Option<IBytes>,172 parsed: Option<LocExpr>,173 evaluated: Option<Val>,174175 evaluating: bool,176}177impl FileData {178 fn new_string(data: IStr) -> Self {179 Self {180 string: Some(data),181 bytes: None,182 parsed: None,183 evaluated: None,184 evaluating: false,185 }186 }187 fn new_bytes(data: IBytes) -> Self {188 Self {189 string: None,190 bytes: Some(data),191 parsed: None,192 evaluated: None,193 evaluating: false,194 }195 }196}197198#[allow(clippy::type_complexity)]199pub struct Breakpoint {200 loc: ExprLocation,201 collected: RefCell<HashMap<usize, (usize, Vec<Result<Val>>)>>,202}203#[derive(Default)]204struct Breakpoints(Vec<Rc<Breakpoint>>);205impl Breakpoints {206 fn insert(207 &self,208 stack_depth: usize,209 stack_generation: usize,210 loc: &ExprLocation,211 result: Result<Val>,212 ) -> Result<Val> {213 if self.0.is_empty() {214 return result;215 }216 for item in &self.0 {217 if item.loc.belongs_to(loc) {218 let mut collected = item.collected.borrow_mut();219 let (depth, vals) = collected.entry(stack_generation).or_default();220 if stack_depth > *depth {221 vals.clear();222 }223 vals.push(result.clone());224 }225 }226 result227 }228}229230#[derive(Default)]231pub struct EvaluationStateInternals {232 /// Internal state233 data: RefCell<EvaluationData>,234 /// Settings, safe to change at runtime235 settings: RefCell<EvaluationSettings>,236}237238/// Maintains stack trace and import resolution239#[derive(Default, Clone)]240pub struct State(Rc<EvaluationStateInternals>);241242impl State {243 /// Should only be called with path retrieved from [`resolve_path`], may panic otherwise244 pub fn import_resolved_str(&self, path: SourcePath) -> Result<IStr> {245 let mut data = self.data_mut();246 let mut file = data.files.raw_entry_mut().from_key(&path);247248 let file = match file {249 RawEntryMut::Occupied(ref mut d) => d.get_mut(),250 RawEntryMut::Vacant(v) => {251 let data = self.settings().import_resolver.load_file_contents(&path)?;252 v.insert(253 path.clone(),254 FileData::new_string(255 std::str::from_utf8(&data)256 .map_err(|_| ImportBadFileUtf8(path.clone()))?257 .into(),258 ),259 )260 .1261 }262 };263 if let Some(str) = &file.string {264 return Ok(str.clone());265 }266 if file.string.is_none() {267 file.string = Some(268 file.bytes269 .as_ref()270 .expect("either string or bytes should be set")271 .clone()272 .cast_str()273 .ok_or_else(|| ImportBadFileUtf8(path.clone()))?,274 );275 }276 Ok(file.string.as_ref().expect("just set").clone())277 }278 /// Should only be called with path retrieved from [`resolve_path`], may panic otherwise279 pub fn import_resolved_bin(&self, path: SourcePath) -> Result<IBytes> {280 let mut data = self.data_mut();281 let mut file = data.files.raw_entry_mut().from_key(&path);282283 let file = match file {284 RawEntryMut::Occupied(ref mut d) => d.get_mut(),285 RawEntryMut::Vacant(v) => {286 let data = self.settings().import_resolver.load_file_contents(&path)?;287 v.insert(path.clone(), FileData::new_bytes(data.as_slice().into()))288 .1289 }290 };291 if let Some(str) = &file.bytes {292 return Ok(str.clone());293 }294 if file.bytes.is_none() {295 file.bytes = Some(296 file.string297 .as_ref()298 .expect("either string or bytes should be set")299 .clone()300 .cast_bytes(),301 );302 }303 Ok(file.bytes.as_ref().expect("just set").clone())304 }305 /// Should only be called with path retrieved from [`resolve_path`], may panic otherwise306 pub fn import_resolved(&self, path: SourcePath) -> Result<Val> {307 let mut data = self.data_mut();308 let mut file = data.files.raw_entry_mut().from_key(&path);309310 let file = match file {311 RawEntryMut::Occupied(ref mut d) => d.get_mut(),312 RawEntryMut::Vacant(v) => {313 let data = self.settings().import_resolver.load_file_contents(&path)?;314 v.insert(315 path.clone(),316 FileData::new_string(317 std::str::from_utf8(&data)318 .map_err(|_| ImportBadFileUtf8(path.clone()))?319 .into(),320 ),321 )322 .1323 }324 };325 if let Some(val) = &file.evaluated {326 return Ok(val.clone());327 }328 if file.string.is_none() {329 file.string = Some(330 std::str::from_utf8(331 file.bytes332 .as_ref()333 .expect("either string or bytes should be set"),334 )335 .map_err(|_| ImportBadFileUtf8(path.clone()))?336 .into(),337 );338 }339 let code = file.string.as_ref().expect("just set");340 let file_name = Source::new(path.clone(), code.clone());341 if file.parsed.is_none() {342 file.parsed = Some(343 jrsonnet_parser::parse(344 code,345 &ParserSettings {346 file_name: file_name.clone(),347 },348 )349 .map_err(|e| ImportSyntaxError {350 path: file_name.clone(),351 error: Box::new(e),352 })?,353 );354 }355 let parsed = file.parsed.as_ref().expect("just set").clone();356 if file.evaluating {357 throw!(InfiniteRecursionDetected)358 }359 file.evaluating = true;360 // Dropping file here, as it borrows data, which may be used in evaluation361 drop(data);362 let res = evaluate(363 self.clone(),364 self.create_default_context(file_name),365 &parsed,366 );367368 let mut data = self.data_mut();369 let mut file = data.files.raw_entry_mut().from_key(&path);370371 let file = match file {372 RawEntryMut::Occupied(ref mut d) => d.get_mut(),373 RawEntryMut::Vacant(_) => unreachable!("this file was just here!"),374 };375 file.evaluating = false;376 match res {377 Ok(v) => {378 file.evaluated = Some(v.clone());379 Ok(v)380 }381 Err(e) => Err(e),382 }383 }384385 /// Has same semantics as `import 'path'` called from `from` file386 pub fn import_from(&self, from: &SourcePath, path: &str) -> Result<Val> {387 let resolved = self.resolve_from(from, path)?;388 self.import_resolved(resolved)389 }390 pub fn import(&self, path: impl AsRef<Path>) -> Result<Val> {391 let resolved = self.resolve(path)?;392 self.import_resolved(resolved)393 }394395 /// Creates context with all passed global variables396 pub fn create_default_context(&self, source: Source) -> Context {397 let context_initializer = &self.settings().context_initializer;398 context_initializer.initialize(self.clone(), source)399 }400401 /// Executes code creating a new stack frame402 pub fn push<T>(403 &self,404 e: CallLocation,405 frame_desc: impl FnOnce() -> String,406 f: impl FnOnce() -> Result<T>,407 ) -> Result<T> {408 {409 let mut data = self.data_mut();410 let stack_depth = &mut data.stack_depth;411 if *stack_depth > self.max_stack() {412 // Error creation uses data, so i drop guard here413 drop(data);414 throw!(StackOverflow);415 }416 *stack_depth += 1;417 }418 let result = f();419 {420 let mut data = self.data_mut();421 data.stack_depth -= 1;422 data.stack_generation += 1;423 }424 if let Err(mut err) = result {425 err.trace_mut().0.push(StackTraceElement {426 location: e.0.cloned(),427 desc: frame_desc(),428 });429 return Err(err);430 }431 result432 }433434 /// Executes code creating a new stack frame435 pub fn push_val(436 &self,437 e: &ExprLocation,438 frame_desc: impl FnOnce() -> String,439 f: impl FnOnce() -> Result<Val>,440 ) -> Result<Val> {441 {442 let mut data = self.data_mut();443 let stack_depth = &mut data.stack_depth;444 if *stack_depth > self.max_stack() {445 // Error creation uses data, so i drop guard here446 drop(data);447 throw!(StackOverflow);448 }449 *stack_depth += 1;450 }451 let mut result = f();452 {453 let mut data = self.data_mut();454 data.stack_depth -= 1;455 data.stack_generation += 1;456 result = data457 .breakpoints458 .insert(data.stack_depth, data.stack_generation, e, result);459 }460 if let Err(mut err) = result {461 err.trace_mut().0.push(StackTraceElement {462 location: Some(e.clone()),463 desc: frame_desc(),464 });465 return Err(err);466 }467 result468 }469 /// Executes code creating a new stack frame470 pub fn push_description<T>(471 &self,472 frame_desc: impl FnOnce() -> String,473 f: impl FnOnce() -> Result<T>,474 ) -> Result<T> {475 {476 let mut data = self.data_mut();477 let stack_depth = &mut data.stack_depth;478 if *stack_depth > self.max_stack() {479 // Error creation uses data, so i drop guard here480 drop(data);481 throw!(StackOverflow);482 }483 *stack_depth += 1;484 }485 let result = f();486 {487 let mut data = self.data_mut();488 data.stack_depth -= 1;489 data.stack_generation += 1;490 }491 if let Err(mut err) = result {492 err.trace_mut().0.push(StackTraceElement {493 location: None,494 desc: frame_desc(),495 });496 return Err(err);497 }498 result499 }500501 /// # Panics502 /// In case of formatting failure503 pub fn stringify_err(&self, e: &LocError) -> String {504 let mut out = String::new();505 self.settings()506 .trace_format507 .write_trace(&mut out, self, e)508 .unwrap();509 out510 }511512 pub fn manifest(&self, val: Val) -> Result<IStr> {513 self.push_description(514 || "manifestification".to_string(),515 || val.manifest(self.clone(), &self.manifest_format()),516 )517 }518 pub fn manifest_multi(&self, val: Val) -> Result<Vec<(IStr, IStr)>> {519 val.manifest_multi(self.clone(), &self.manifest_format())520 }521 pub fn manifest_stream(&self, val: Val) -> Result<Vec<IStr>> {522 val.manifest_stream(self.clone(), &self.manifest_format())523 }524525 /// If passed value is function then call with set TLA526 pub fn with_tla(&self, val: Val) -> Result<Val> {527 Ok(match val {528 Val::Func(func) => self.push_description(529 || "during TLA call".to_owned(),530 || {531 func.evaluate(532 self.clone(),533 self.create_default_context(Source::new_virtual(534 "<tla>".into(),535 IStr::empty(),536 )),537 CallLocation::native(),538 &self.settings().tla_vars,539 true,540 )541 },542 )?,543 v => v,544 })545 }546}547548/// Internals549impl State {550 // fn data(&self) -> Ref<EvaluationData> {551 // self.0.data.borrow()552 // }553 fn data_mut(&self) -> RefMut<EvaluationData> {554 self.0.data.borrow_mut()555 }556 pub fn settings(&self) -> Ref<EvaluationSettings> {557 self.0.settings.borrow()558 }559 pub fn settings_mut(&self) -> RefMut<EvaluationSettings> {560 self.0.settings.borrow_mut()561 }562}563564/// Raw methods evaluate passed values but don't perform TLA execution565impl State {566 /// Parses and evaluates the given snippet567 pub fn evaluate_snippet(&self, name: impl Into<IStr>, code: impl Into<IStr>) -> Result<Val> {568 let code = code.into();569 let source = Source::new_virtual(name.into(), code.clone());570 let parsed = jrsonnet_parser::parse(571 &code,572 &ParserSettings {573 file_name: source.clone(),574 },575 )576 .map_err(|e| ImportSyntaxError {577 path: source.clone(),578 error: Box::new(e),579 })?;580 evaluate(self.clone(), self.create_default_context(source), &parsed)581 }582}583584/// Settings utilities585impl State {586 pub fn add_tla(&self, name: IStr, value: Val) {587 self.settings_mut()588 .tla_vars589 .insert(name, TlaArg::Val(value));590 }591 pub fn add_tla_str(&self, name: IStr, value: IStr) {592 self.settings_mut()593 .tla_vars594 .insert(name, TlaArg::String(value));595 }596 pub fn add_tla_code(&self, name: IStr, code: &str) -> Result<()> {597 let source_name = format!("<top-level-arg:{name}>");598 let source = Source::new_virtual(source_name.into(), code.into());599 let parsed = jrsonnet_parser::parse(600 code,601 &ParserSettings {602 file_name: source.clone(),603 },604 )605 .map_err(|e| ImportSyntaxError {606 path: source,607 error: Box::new(e),608 })?;609 self.settings_mut()610 .tla_vars611 .insert(name, TlaArg::Code(parsed));612 Ok(())613 }614615 // Only panics in case of [`ImportResolver`] contract violation616 #[allow(clippy::missing_panics_doc)]617 pub fn resolve_from(&self, from: &SourcePath, path: &str) -> Result<SourcePath> {618 self.import_resolver().resolve_from(from, path.as_ref())619 }620621 // Only panics in case of [`ImportResolver`] contract violation622 #[allow(clippy::missing_panics_doc)]623 pub fn resolve(&self, path: impl AsRef<Path>) -> Result<SourcePath> {624 self.import_resolver().resolve(path.as_ref())625 }626 pub fn import_resolver(&self) -> Ref<dyn ImportResolver> {627 Ref::map(self.settings(), |s| &*s.import_resolver)628 }629 pub fn set_import_resolver(&self, resolver: Box<dyn ImportResolver>) {630 self.settings_mut().import_resolver = resolver;631 }632 pub fn context_initializer(&self) -> Ref<dyn ContextInitializer> {633 Ref::map(self.settings(), |s| &*s.context_initializer)634 }635636 pub fn manifest_format(&self) -> ManifestFormat {637 self.settings().manifest_format.clone()638 }639 pub fn set_manifest_format(&self, format: ManifestFormat) {640 self.settings_mut().manifest_format = format;641 }642643 pub fn trace_format(&self) -> Ref<dyn TraceFormat> {644 Ref::map(self.settings(), |s| &*s.trace_format)645 }646 pub fn set_trace_format(&self, format: Box<dyn TraceFormat>) {647 self.settings_mut().trace_format = format;648 }649650 pub fn max_trace(&self) -> usize {651 self.settings().max_trace652 }653 pub fn set_max_trace(&self, trace: usize) {654 self.settings_mut().max_trace = trace;655 }656657 pub fn max_stack(&self) -> usize {658 self.settings().max_stack659 }660 pub fn set_max_stack(&self, trace: usize) {661 self.settings_mut().max_stack = trace;662 }663}1//! jsonnet interpreter implementation23#![deny(unsafe_op_in_unsafe_fn)]4#![warn(5 clippy::all,6 clippy::nursery,7 clippy::pedantic,8 // missing_docs,9 elided_lifetimes_in_paths,10 explicit_outlives_requirements,11 noop_method_call,12 single_use_lifetimes,13 variant_size_differences,14 rustdoc::all15)]16#![allow(17 macro_expanded_macro_exports_accessed_by_absolute_paths,18 clippy::ptr_arg,19 // Too verbose20 clippy::must_use_candidate,21 // A lot of functions pass around errors thrown by code22 clippy::missing_errors_doc,23 // A lot of pointers have interior Rc24 clippy::needless_pass_by_value,25 // Its fine26 clippy::wildcard_imports,27 clippy::enum_glob_use,28 clippy::module_name_repetitions,29 // TODO: fix individual issues, however this works as intended almost everywhere30 clippy::cast_precision_loss,31 clippy::cast_possible_wrap,32 clippy::cast_possible_truncation,33 clippy::cast_sign_loss,34 // False positives35 // https://github.com/rust-lang/rust-clippy/issues/690236 clippy::use_self,37 // https://github.com/rust-lang/rust-clippy/issues/853938 clippy::iter_with_drain,39)]4041// For jrsonnet-macros42extern crate self as jrsonnet_evaluator;4344mod ctx;45mod dynamic;46pub mod error;47mod evaluate;48pub mod function;49pub mod gc;50mod import;51mod integrations;52mod map;53mod obj;54pub mod stdlib;55pub mod trace;56pub mod typed;57pub mod val;5859use std::{60 any::Any,61 cell::{Ref, RefCell, RefMut},62 collections::HashMap,63 fmt::{self, Debug},64 path::Path,65 rc::Rc,66};6768pub use ctx::*;69pub use dynamic::*;70use error::{Error::*, LocError, Result, StackTraceElement};71pub use evaluate::*;72use function::{CallLocation, TlaArg};73use gc::{GcHashMap, TraceBox};74use hashbrown::hash_map::RawEntryMut;75pub use import::*;76use jrsonnet_gcmodule::{Cc, Trace};77pub use jrsonnet_interner::{IBytes, IStr};78pub use jrsonnet_parser as parser;79use jrsonnet_parser::*;80pub use obj::*;81use trace::{CompactFormat, TraceFormat};82pub use val::{ManifestFormat, Thunk, Val};8384/// Thunk without bound `super`/`this`85/// object inheritance may be overriden multiple times, and will be fixed only on field read86pub trait Unbound: Trace {87 /// Type of value after object context is bound88 type Bound;89 /// Create value bound to specified object context90 fn bind(&self, s: State, sup: Option<ObjValue>, this: Option<ObjValue>) -> Result<Self::Bound>;91}9293/// Object fields may, or may not depend on `this`/`super`, this enum allows cheaper reuse of object-independent fields for native code94/// Standard jsonnet fields are always unbound95#[derive(Clone, Trace)]96pub enum MaybeUnbound {97 /// Value needs to be bound to `this`/`super`98 Unbound(Cc<TraceBox<dyn Unbound<Bound = Thunk<Val>>>>),99 /// Value is object-independent100 Bound(Thunk<Val>),101}102103impl Debug for MaybeUnbound {104 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {105 write!(f, "MaybeUnbound")106 }107}108impl MaybeUnbound {109 /// Attach object context to value, if required110 pub fn evaluate(111 &self,112 s: State,113 sup: Option<ObjValue>,114 this: Option<ObjValue>,115 ) -> Result<Thunk<Val>> {116 match self {117 Self::Unbound(v) => v.bind(s, sup, this),118 Self::Bound(v) => Ok(v.clone()),119 }120 }121}122123/// During import, this trait will be called to create initial context for file.124/// It may initialize global variables, stdlib for example.125pub trait ContextInitializer {126 /// Initialize default file context.127 fn initialize(&self, state: State, for_file: Source) -> Context;128 /// Allows upcasting from abstract to concrete context initializer.129 /// jrsonnet by itself doesn't use this method, it is allowed for it to panic.130 fn as_any(&self) -> &dyn Any;131}132133/// Context initializer which adds nothing.134pub struct DummyContextInitializer;135impl ContextInitializer for DummyContextInitializer {136 fn initialize(&self, _state: State, _for_file: Source) -> Context {137 Context::default()138 }139 fn as_any(&self) -> &dyn Any {140 self141 }142}143144/// Dynamically reconfigurable evaluation settings145pub struct EvaluationSettings {146 /// Limits recursion by limiting the number of stack frames147 pub max_stack: usize,148 /// Limits amount of stack trace items preserved149 pub max_trace: usize,150 /// TLA vars151 pub tla_vars: HashMap<IStr, TlaArg>,152 /// Context initializer, which will be used for imports and everything153 /// [`NoopContextInitializer`] is used by default, most likely you want to have `jrsonnet-stdlib`154 pub context_initializer: Box<dyn ContextInitializer>,155 /// Used to resolve file locations/contents156 pub import_resolver: Box<dyn ImportResolver>,157 /// Used in manifestification functions158 pub manifest_format: ManifestFormat,159 /// Used for bindings160 pub trace_format: Box<dyn TraceFormat>,161}162impl Default for EvaluationSettings {163 fn default() -> Self {164 Self {165 max_stack: 200,166 max_trace: 20,167 context_initializer: Box::new(DummyContextInitializer),168 tla_vars: HashMap::default(),169 import_resolver: Box::new(DummyImportResolver),170 manifest_format: ManifestFormat::Json {171 padding: 4,172 #[cfg(feature = "exp-preserve-order")]173 preserve_order: false,174 },175 trace_format: Box::new(CompactFormat {176 padding: 4,177 resolver: trace::PathResolver::Absolute,178 }),179 }180 }181}182183#[derive(Default)]184struct EvaluationData {185 /// Used for stack overflow detection, stacktrace is populated on unwind186 stack_depth: usize,187 /// Updated every time stack entry is popt188 stack_generation: usize,189190 breakpoints: Breakpoints,191192 /// Contains file source codes and evaluation results for imports and pretty-printed stacktraces193 files: GcHashMap<SourcePath, FileData>,194}195struct FileData {196 string: Option<IStr>,197 bytes: Option<IBytes>,198 parsed: Option<LocExpr>,199 evaluated: Option<Val>,200201 evaluating: bool,202}203impl FileData {204 fn new_string(data: IStr) -> Self {205 Self {206 string: Some(data),207 bytes: None,208 parsed: None,209 evaluated: None,210 evaluating: false,211 }212 }213 fn new_bytes(data: IBytes) -> Self {214 Self {215 string: None,216 bytes: Some(data),217 parsed: None,218 evaluated: None,219 evaluating: false,220 }221 }222}223224#[allow(clippy::type_complexity)]225pub struct Breakpoint {226 loc: ExprLocation,227 collected: RefCell<HashMap<usize, (usize, Vec<Result<Val>>)>>,228}229#[derive(Default)]230struct Breakpoints(Vec<Rc<Breakpoint>>);231impl Breakpoints {232 fn insert(233 &self,234 stack_depth: usize,235 stack_generation: usize,236 loc: &ExprLocation,237 result: Result<Val>,238 ) -> Result<Val> {239 if self.0.is_empty() {240 return result;241 }242 for item in &self.0 {243 if item.loc.belongs_to(loc) {244 let mut collected = item.collected.borrow_mut();245 let (depth, vals) = collected.entry(stack_generation).or_default();246 if stack_depth > *depth {247 vals.clear();248 }249 vals.push(result.clone());250 }251 }252 result253 }254}255256#[derive(Default)]257pub struct EvaluationStateInternals {258 /// Internal state259 data: RefCell<EvaluationData>,260 /// Settings, safe to change at runtime261 settings: RefCell<EvaluationSettings>,262}263264/// Maintains stack trace and import resolution265#[derive(Default, Clone)]266pub struct State(Rc<EvaluationStateInternals>);267268impl State {269 /// Should only be called with path retrieved from [`resolve_path`], may panic otherwise270 pub fn import_resolved_str(&self, path: SourcePath) -> Result<IStr> {271 let mut data = self.data_mut();272 let mut file = data.files.raw_entry_mut().from_key(&path);273274 let file = match file {275 RawEntryMut::Occupied(ref mut d) => d.get_mut(),276 RawEntryMut::Vacant(v) => {277 let data = self.settings().import_resolver.load_file_contents(&path)?;278 v.insert(279 path.clone(),280 FileData::new_string(281 std::str::from_utf8(&data)282 .map_err(|_| ImportBadFileUtf8(path.clone()))?283 .into(),284 ),285 )286 .1287 }288 };289 if let Some(str) = &file.string {290 return Ok(str.clone());291 }292 if file.string.is_none() {293 file.string = Some(294 file.bytes295 .as_ref()296 .expect("either string or bytes should be set")297 .clone()298 .cast_str()299 .ok_or_else(|| ImportBadFileUtf8(path.clone()))?,300 );301 }302 Ok(file.string.as_ref().expect("just set").clone())303 }304 /// Should only be called with path retrieved from [`resolve_path`], may panic otherwise305 pub fn import_resolved_bin(&self, path: SourcePath) -> Result<IBytes> {306 let mut data = self.data_mut();307 let mut file = data.files.raw_entry_mut().from_key(&path);308309 let file = match file {310 RawEntryMut::Occupied(ref mut d) => d.get_mut(),311 RawEntryMut::Vacant(v) => {312 let data = self.settings().import_resolver.load_file_contents(&path)?;313 v.insert(path.clone(), FileData::new_bytes(data.as_slice().into()))314 .1315 }316 };317 if let Some(str) = &file.bytes {318 return Ok(str.clone());319 }320 if file.bytes.is_none() {321 file.bytes = Some(322 file.string323 .as_ref()324 .expect("either string or bytes should be set")325 .clone()326 .cast_bytes(),327 );328 }329 Ok(file.bytes.as_ref().expect("just set").clone())330 }331 /// Should only be called with path retrieved from [`resolve_path`], may panic otherwise332 pub fn import_resolved(&self, path: SourcePath) -> Result<Val> {333 let mut data = self.data_mut();334 let mut file = data.files.raw_entry_mut().from_key(&path);335336 let file = match file {337 RawEntryMut::Occupied(ref mut d) => d.get_mut(),338 RawEntryMut::Vacant(v) => {339 let data = self.settings().import_resolver.load_file_contents(&path)?;340 v.insert(341 path.clone(),342 FileData::new_string(343 std::str::from_utf8(&data)344 .map_err(|_| ImportBadFileUtf8(path.clone()))?345 .into(),346 ),347 )348 .1349 }350 };351 if let Some(val) = &file.evaluated {352 return Ok(val.clone());353 }354 if file.string.is_none() {355 file.string = Some(356 std::str::from_utf8(357 file.bytes358 .as_ref()359 .expect("either string or bytes should be set"),360 )361 .map_err(|_| ImportBadFileUtf8(path.clone()))?362 .into(),363 );364 }365 let code = file.string.as_ref().expect("just set");366 let file_name = Source::new(path.clone(), code.clone());367 if file.parsed.is_none() {368 file.parsed = Some(369 jrsonnet_parser::parse(370 code,371 &ParserSettings {372 file_name: file_name.clone(),373 },374 )375 .map_err(|e| ImportSyntaxError {376 path: file_name.clone(),377 error: Box::new(e),378 })?,379 );380 }381 let parsed = file.parsed.as_ref().expect("just set").clone();382 if file.evaluating {383 throw!(InfiniteRecursionDetected)384 }385 file.evaluating = true;386 // Dropping file here, as it borrows data, which may be used in evaluation387 drop(data);388 let res = evaluate(389 self.clone(),390 self.create_default_context(file_name),391 &parsed,392 );393394 let mut data = self.data_mut();395 let mut file = data.files.raw_entry_mut().from_key(&path);396397 let file = match file {398 RawEntryMut::Occupied(ref mut d) => d.get_mut(),399 RawEntryMut::Vacant(_) => unreachable!("this file was just here!"),400 };401 file.evaluating = false;402 match res {403 Ok(v) => {404 file.evaluated = Some(v.clone());405 Ok(v)406 }407 Err(e) => Err(e),408 }409 }410411 /// Has same semantics as `import 'path'` called from `from` file412 pub fn import_from(&self, from: &SourcePath, path: &str) -> Result<Val> {413 let resolved = self.resolve_from(from, path)?;414 self.import_resolved(resolved)415 }416 pub fn import(&self, path: impl AsRef<Path>) -> Result<Val> {417 let resolved = self.resolve(path)?;418 self.import_resolved(resolved)419 }420421 /// Creates context with all passed global variables422 pub fn create_default_context(&self, source: Source) -> Context {423 let context_initializer = &self.settings().context_initializer;424 context_initializer.initialize(self.clone(), source)425 }426427 /// Executes code creating a new stack frame428 pub fn push<T>(429 &self,430 e: CallLocation<'_>,431 frame_desc: impl FnOnce() -> String,432 f: impl FnOnce() -> Result<T>,433 ) -> Result<T> {434 {435 let mut data = self.data_mut();436 let stack_depth = &mut data.stack_depth;437 if *stack_depth > self.max_stack() {438 // Error creation uses data, so i drop guard here439 drop(data);440 throw!(StackOverflow);441 }442 *stack_depth += 1;443 }444 let result = f();445 {446 let mut data = self.data_mut();447 data.stack_depth -= 1;448 data.stack_generation += 1;449 }450 if let Err(mut err) = result {451 err.trace_mut().0.push(StackTraceElement {452 location: e.0.cloned(),453 desc: frame_desc(),454 });455 return Err(err);456 }457 result458 }459460 /// Executes code creating a new stack frame461 pub fn push_val(462 &self,463 e: &ExprLocation,464 frame_desc: impl FnOnce() -> String,465 f: impl FnOnce() -> Result<Val>,466 ) -> Result<Val> {467 {468 let mut data = self.data_mut();469 let stack_depth = &mut data.stack_depth;470 if *stack_depth > self.max_stack() {471 // Error creation uses data, so i drop guard here472 drop(data);473 throw!(StackOverflow);474 }475 *stack_depth += 1;476 }477 let mut result = f();478 {479 let mut data = self.data_mut();480 data.stack_depth -= 1;481 data.stack_generation += 1;482 result = data483 .breakpoints484 .insert(data.stack_depth, data.stack_generation, e, result);485 }486 if let Err(mut err) = result {487 err.trace_mut().0.push(StackTraceElement {488 location: Some(e.clone()),489 desc: frame_desc(),490 });491 return Err(err);492 }493 result494 }495 /// Executes code creating a new stack frame496 pub fn push_description<T>(497 &self,498 frame_desc: impl FnOnce() -> String,499 f: impl FnOnce() -> Result<T>,500 ) -> Result<T> {501 {502 let mut data = self.data_mut();503 let stack_depth = &mut data.stack_depth;504 if *stack_depth > self.max_stack() {505 // Error creation uses data, so i drop guard here506 drop(data);507 throw!(StackOverflow);508 }509 *stack_depth += 1;510 }511 let result = f();512 {513 let mut data = self.data_mut();514 data.stack_depth -= 1;515 data.stack_generation += 1;516 }517 if let Err(mut err) = result {518 err.trace_mut().0.push(StackTraceElement {519 location: None,520 desc: frame_desc(),521 });522 return Err(err);523 }524 result525 }526527 /// # Panics528 /// In case of formatting failure529 pub fn stringify_err(&self, e: &LocError) -> String {530 let mut out = String::new();531 self.settings()532 .trace_format533 .write_trace(&mut out, self, e)534 .unwrap();535 out536 }537538 pub fn manifest(&self, val: Val) -> Result<IStr> {539 self.push_description(540 || "manifestification".to_string(),541 || val.manifest(self.clone(), &self.manifest_format()),542 )543 }544 pub fn manifest_multi(&self, val: Val) -> Result<Vec<(IStr, IStr)>> {545 val.manifest_multi(self.clone(), &self.manifest_format())546 }547 pub fn manifest_stream(&self, val: Val) -> Result<Vec<IStr>> {548 val.manifest_stream(self.clone(), &self.manifest_format())549 }550551 /// If passed value is function then call with set TLA552 pub fn with_tla(&self, val: Val) -> Result<Val> {553 Ok(match val {554 Val::Func(func) => self.push_description(555 || "during TLA call".to_owned(),556 || {557 func.evaluate(558 self.clone(),559 self.create_default_context(Source::new_virtual(560 "<tla>".into(),561 IStr::empty(),562 )),563 CallLocation::native(),564 &self.settings().tla_vars,565 true,566 )567 },568 )?,569 v => v,570 })571 }572}573574/// Internals575impl State {576 fn data_mut(&self) -> RefMut<'_, EvaluationData> {577 self.0.data.borrow_mut()578 }579 pub fn settings(&self) -> Ref<'_, EvaluationSettings> {580 self.0.settings.borrow()581 }582 pub fn settings_mut(&self) -> RefMut<'_, EvaluationSettings> {583 self.0.settings.borrow_mut()584 }585}586587/// Raw methods evaluate passed values but don't perform TLA execution588impl State {589 /// Parses and evaluates the given snippet590 pub fn evaluate_snippet(&self, name: impl Into<IStr>, code: impl Into<IStr>) -> Result<Val> {591 let code = code.into();592 let source = Source::new_virtual(name.into(), code.clone());593 let parsed = jrsonnet_parser::parse(594 &code,595 &ParserSettings {596 file_name: source.clone(),597 },598 )599 .map_err(|e| ImportSyntaxError {600 path: source.clone(),601 error: Box::new(e),602 })?;603 evaluate(self.clone(), self.create_default_context(source), &parsed)604 }605}606607/// Settings utilities608impl State {609 pub fn add_tla(&self, name: IStr, value: Val) {610 self.settings_mut()611 .tla_vars612 .insert(name, TlaArg::Val(value));613 }614 pub fn add_tla_str(&self, name: IStr, value: IStr) {615 self.settings_mut()616 .tla_vars617 .insert(name, TlaArg::String(value));618 }619 pub fn add_tla_code(&self, name: IStr, code: &str) -> Result<()> {620 let source_name = format!("<top-level-arg:{name}>");621 let source = Source::new_virtual(source_name.into(), code.into());622 let parsed = jrsonnet_parser::parse(623 code,624 &ParserSettings {625 file_name: source.clone(),626 },627 )628 .map_err(|e| ImportSyntaxError {629 path: source,630 error: Box::new(e),631 })?;632 self.settings_mut()633 .tla_vars634 .insert(name, TlaArg::Code(parsed));635 Ok(())636 }637638 // Only panics in case of [`ImportResolver`] contract violation639 #[allow(clippy::missing_panics_doc)]640 pub fn resolve_from(&self, from: &SourcePath, path: &str) -> Result<SourcePath> {641 self.import_resolver().resolve_from(from, path.as_ref())642 }643644 // Only panics in case of [`ImportResolver`] contract violation645 #[allow(clippy::missing_panics_doc)]646 pub fn resolve(&self, path: impl AsRef<Path>) -> Result<SourcePath> {647 self.import_resolver().resolve(path.as_ref())648 }649 pub fn import_resolver(&self) -> Ref<'_, dyn ImportResolver> {650 Ref::map(self.settings(), |s| &*s.import_resolver)651 }652 pub fn set_import_resolver(&self, resolver: Box<dyn ImportResolver>) {653 self.settings_mut().import_resolver = resolver;654 }655 pub fn context_initializer(&self) -> Ref<'_, dyn ContextInitializer> {656 Ref::map(self.settings(), |s| &*s.context_initializer)657 }658659 pub fn manifest_format(&self) -> ManifestFormat {660 self.settings().manifest_format.clone()661 }662 pub fn set_manifest_format(&self, format: ManifestFormat) {663 self.settings_mut().manifest_format = format;664 }665666 pub fn trace_format(&self) -> Ref<'_, dyn TraceFormat> {667 Ref::map(self.settings(), |s| &*s.trace_format)668 }669 pub fn set_trace_format(&self, format: Box<dyn TraceFormat>) {670 self.settings_mut().trace_format = format;671 }672673 pub fn max_trace(&self) -> usize {674 self.settings().max_trace675 }676 pub fn set_max_trace(&self, trace: usize) {677 self.settings_mut().max_trace = trace;678 }679680 pub fn max_stack(&self) -> usize {681 self.settings().max_stack682 }683 pub fn set_max_stack(&self, trace: usize) {684 self.settings_mut().max_stack = trace;685 }686}crates/jrsonnet-evaluator/src/obj.rsdiffbeforeafterboth--- 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<ExprLocation>,
}
@@ -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<ExtendBuilder> {
+ pub fn extend_field(&mut self, name: IStr) -> ObjMemberBuilder<ExtendBuilder<'_>> {
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<ValueBuilder> {
+ pub fn member(&mut self, name: IStr) -> ObjMemberBuilder<ValueBuilder<'_>> {
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<ValueBuilder<'v>> {
+impl ObjMemberBuilder<ValueBuilder<'_>> {
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<dyn Unbound<Bound = Thunk<Val>>>,
) -> 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<ExtendBuilder<'v>> {
+impl ObjMemberBuilder<ExtendBuilder<'_>> {
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<dyn Unbound<Bound = Thunk<Val>>>) {
- 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);
crates/jrsonnet-evaluator/src/stdlib/format.rsdiffbeforeafterboth--- 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<CFlags> {
+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<Width> {
+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<Option<Width>> {
+pub fn try_parse_precision(str: &str) -> ParseResult<'_, Option<Width>> {
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<ConvType> {
+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<Code> {
+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<Vec<Element>> {
+pub fn parse_codes(mut str: &str) -> Result<Vec<Element<'_>>> {
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<usize>,
) -> Result<()> {
crates/jrsonnet-evaluator/src/stdlib/manifest.rsdiffbeforeafterboth--- 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(())
},
)?;
}
crates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth--- 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<Vec<Thunk<Val>>>),
+ /// Every field is already evaluated.
Eager(Cc<Vec<Val>>),
+ /// 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<Slice>),
+ /// Reversed array view.
+ /// Returned by `std.reverse(other)` call
Reversed(Box<Self>),
}
@@ -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<Option<Val>> {
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<Thunk<Val>> {
match self {
Self::Bytes(i) => i
@@ -337,6 +355,7 @@
}
}
+ /// Evaluate all array elements, returning new array.
pub fn evaluated(&self, s: State) -> Result<Cc<Vec<Val>>> {
Ok(match self {
Self::Bytes(i) => {
@@ -389,6 +408,7 @@
})
}
+ /// Iterate over elements, evaluating them.
pub fn iter(&self, s: State) -> impl DoubleEndedIterator<Item = Result<Val>> + '_ {
(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<Item = Thunk<Val>> + '_ {
(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<Val>) -> Result<Self> {
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<bool>) -> Result<Self> {
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<BoundedUsize<0, { i32::MAX as usize }>>,
@@ -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),
}
crates/jrsonnet-stdlib/src/lib.rsdiffbeforeafterboth--- 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");
tests/src/lib.rsdiffbeforeafterboth--- a/tests/src/lib.rs
+++ b/tests/src/lib.rs
@@ -1 +1 @@
-
+//! See tests/, suite/ and golden/ directories for tests