difftreelog
Merge pull request #84 from CertainLach/split-stdlib
in: master
Separate jrsonnet-evaluator and stdlib implementation #82
173 files changed
.envrcdiffbeforeafterboth--- a/.envrc
+++ /dev/null
@@ -1,3 +0,0 @@
-source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/1.2.3/direnvrc" "sha256-/aHqL/6nLpHcZJcB5/7/5+mO338l28uFbq88DMfWJn4="
-
-use flake
.gitlab-ci.ymldiffbeforeafterboth--- a/.gitlab-ci.yml
+++ /dev/null
@@ -1,58 +0,0 @@
-variables:
- CARGO_HOME: $CI_PROJECT_DIR/cache
-
-stages:
- - prepare
- - build
-
-build-container:
- image: docker:19.03.11
- stage: prepare
- services:
- - docker:19.03.11-dind
- before_script:
- - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
- script:
- - docker pull $CI_REGISTRY_IMAGE:build || true
- - docker build -t $CI_REGISTRY_IMAGE:build -f build/Dockerfile .
- - docker push $CI_REGISTRY_IMAGE:build
-
-test-library:
- image: $CI_REGISTRY_IMAGE:build
- stage: build
- script:
- - cargo clippy
- cache:
- key: test
- paths:
- - ./cache
-
-build-linux:
- image: $CI_REGISTRY_IMAGE:build
- stage: build
- script:
- - cargo build --release
- cache:
- key: linux
- paths:
- - ./cache
- - ./target
- artifacts:
- paths:
- - ./target/release/jrsonnet
- expire_in: 30 days
-
-build-wasm:
- image: $CI_REGISTRY_IMAGE:build
- stage: build
- script:
- - cargo build --target=wasm32-wasi --release
- cache:
- key: wasm
- paths:
- - ./cache
- - ./target
- artifacts:
- paths:
- - ./target/wasm32-wasi/release/jsonnet.wasm
- expire_in: 30 days
Cargo.lockdiffbeforeafterboth--- a/Cargo.lock
+++ b/Cargo.lock
@@ -87,16 +87,16 @@
[[package]]
name = "clap"
-version = "3.1.18"
+version = "3.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b"
+checksum = "190814073e85d238f31ff738fcb0bf6910cedeb73376c87cd69291028966fd83"
dependencies = [
"atty",
"bitflags",
"clap_derive",
"clap_lex",
"indexmap",
- "lazy_static",
+ "once_cell",
"strsim",
"termcolor",
"textwrap",
@@ -104,18 +104,18 @@
[[package]]
name = "clap_complete"
-version = "3.1.4"
+version = "3.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "da92e6facd8d73c22745a5d3cbb59bdf8e46e3235c923e516527d8e81eec14a4"
+checksum = "ead064480dfc4880a10764488415a97fdd36a4cf1bb022d372f02e8faf8386e1"
dependencies = [
"clap",
]
[[package]]
name = "clap_derive"
-version = "3.1.18"
+version = "3.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "25320346e922cffe59c0bbc5410c8d8784509efb321488971081313cb1e1a33c"
+checksum = "759bf187376e1afa7b85b959e6a664a3e7a95203415dba952ad19139e798f902"
dependencies = [
"heck",
"proc-macro-error",
@@ -126,9 +126,9 @@
[[package]]
name = "clap_lex"
-version = "0.2.0"
+version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213"
+checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5"
dependencies = [
"os_str_bytes",
]
@@ -221,6 +221,7 @@
"jrsonnet-evaluator",
"jrsonnet-gcmodule",
"jrsonnet-parser",
+ "jrsonnet-stdlib",
]
[[package]]
@@ -229,21 +230,17 @@
dependencies = [
"annotate-snippets",
"anyhow",
- "base64",
"bincode",
"hashbrown 0.12.1",
"jrsonnet-gcmodule",
"jrsonnet-interner",
"jrsonnet-macros",
"jrsonnet-parser",
- "jrsonnet-stdlib",
"jrsonnet-types",
- "md5",
"pathdiff",
"rustc-hash",
"serde",
"serde_json",
- "serde_yaml_with_quirks",
"static_assertions",
"strsim",
"thiserror",
@@ -278,6 +275,7 @@
"jrsonnet-gcmodule",
"rustc-hash",
"serde",
+ "structdump",
]
[[package]]
@@ -295,15 +293,28 @@
dependencies = [
"jrsonnet-gcmodule",
"jrsonnet-interner",
- "jrsonnet-stdlib",
"peg",
"serde",
"static_assertions",
+ "structdump",
]
[[package]]
name = "jrsonnet-stdlib"
version = "0.4.2"
+dependencies = [
+ "base64",
+ "bincode",
+ "jrsonnet-evaluator",
+ "jrsonnet-gcmodule",
+ "jrsonnet-macros",
+ "jrsonnet-parser",
+ "md5",
+ "serde",
+ "serde_json",
+ "serde_yaml_with_quirks",
+ "structdump",
+]
[[package]]
name = "jrsonnet-types"
@@ -314,31 +325,26 @@
]
[[package]]
-name = "jsonnet"
+name = "libc"
+version = "0.2.126"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
+
+[[package]]
+name = "libjsonnet"
version = "0.4.2"
dependencies = [
"jrsonnet-evaluator",
"jrsonnet-gcmodule",
"jrsonnet-parser",
+ "jrsonnet-stdlib",
]
-
-[[package]]
-name = "lazy_static"
-version = "1.4.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
-name = "libc"
-version = "0.2.126"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
-
-[[package]]
name = "linked-hash-map"
-version = "0.5.4"
+version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
+checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f"
[[package]]
name = "lock_api"
@@ -511,18 +517,18 @@
[[package]]
name = "serde"
-version = "1.0.137"
+version = "1.0.142"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"
+checksum = "e590c437916fb6b221e1d00df6e3294f3fccd70ca7e92541c475d6ed6ef5fee2"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.137"
+version = "1.0.142"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"
+checksum = "34b5b8d809babe02f538c2cfec6f2c1ed10804c0e5a6a041a049a4f5588ccc2e"
dependencies = [
"proc-macro2",
"quote",
@@ -531,9 +537,9 @@
[[package]]
name = "serde_json"
-version = "1.0.81"
+version = "1.0.82"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c"
+checksum = "82c2c1fdcd807d1098552c5b9a36e425e42e9fbd7c6a37a8425f390f781f7fa7"
dependencies = [
"indexmap",
"itoa",
@@ -572,6 +578,28 @@
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
+name = "structdump"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b0570327507bf281d8a6e6b0d4c082b12cb6bcee27efce755aa5efacd44076c1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "structdump-derive",
+]
+
+[[package]]
+name = "structdump-derive"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29cc0b59cfa11f1bceda09a9a7e37e6a6c3138575fd24ade8aa9af6d09aedf28"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
name = "syn"
version = "1.0.96"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -592,6 +620,16 @@
]
[[package]]
+name = "tests"
+version = "0.1.0"
+dependencies = [
+ "jrsonnet-evaluator",
+ "jrsonnet-gcmodule",
+ "jrsonnet-stdlib",
+ "serde",
+]
+
+[[package]]
name = "textwrap"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
Cargo.tomldiffbeforeafterboth--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,5 +1,5 @@
[workspace]
-members = ["crates/*", "bindings/jsonnet", "cmds/jrsonnet"]
+members = ["crates/*", "bindings/jsonnet", "cmds/jrsonnet", "tests"]
[profile.test]
opt-level = 1
bindings/jsonnet/Cargo.tomldiffbeforeafterboth--- a/bindings/jsonnet/Cargo.toml
+++ b/bindings/jsonnet/Cargo.toml
@@ -1,5 +1,5 @@
[package]
-name = "jsonnet"
+name = "libjsonnet"
description = "Rust implementation of libjsonnet.so"
version = "0.4.2"
authors = ["Yaroslav Bolyukin <iam@lach.pw>"]
@@ -10,12 +10,15 @@
[dependencies]
jrsonnet-evaluator = { path = "../../crates/jrsonnet-evaluator", version = "0.4.2" }
jrsonnet-parser = { path = "../../crates/jrsonnet-parser", version = "0.4.2" }
+jrsonnet-stdlib = { path = "../../crates/jrsonnet-stdlib", version = "0.4.2" }
jrsonnet-gcmodule = { version = "0.3.4" }
[lib]
+name = "jsonnet"
crate-type = ["cdylib"]
[features]
+# Export additional functions for native integration, i.e ability to set custom trace format
interop = []
experimental = ["exp-preserve-order", "exp-destruct"]
exp-preserve-order = ["jrsonnet-evaluator/exp-preserve-order"]
bindings/jsonnet/src/import.rsdiffbeforeafterboth--- a/bindings/jsonnet/src/import.rs
+++ b/bindings/jsonnet/src/import.rs
@@ -4,18 +4,18 @@
any::Any,
cell::RefCell,
collections::HashMap,
+ env::current_dir,
ffi::{c_void, CStr, CString},
- fs::File,
- io::Read,
os::raw::{c_char, c_int},
- path::{Path, PathBuf},
+ path::PathBuf,
ptr::null_mut,
};
use jrsonnet_evaluator::{
error::{Error::*, Result},
- throw, ImportResolver, State,
+ throw, FileImportResolver, ImportResolver, State,
};
+use jrsonnet_parser::{SourceDirectory, SourceFile, SourcePath};
pub type JsonnetImportCallback = unsafe extern "C" fn(
ctx: *mut c_void,
@@ -29,28 +29,34 @@
pub struct CallbackImportResolver {
cb: JsonnetImportCallback,
ctx: *mut c_void,
- out: RefCell<HashMap<PathBuf, Vec<u8>>>,
+ out: RefCell<HashMap<SourcePath, Vec<u8>>>,
}
impl ImportResolver for CallbackImportResolver {
- fn resolve_file(&self, from: &Path, path: &str) -> Result<PathBuf> {
- let base = CString::new(from.to_str().unwrap()).unwrap().into_raw();
- let rel = CString::new(path).unwrap().into_raw();
+ fn resolve_from(&self, from: &SourcePath, path: &str) -> Result<SourcePath> {
+ let base = if let Some(p) = from.downcast_ref::<SourceFile>() {
+ let mut o = p.path().to_owned();
+ o.pop();
+ o
+ } else if let Some(d) = from.downcast_ref::<SourceDirectory>() {
+ d.path().to_owned()
+ } else if from.is_default() {
+ current_dir().map_err(|e| ImportIo(e.to_string()))?
+ } else {
+ unreachable!("can't resolve this path");
+ };
+ let base = unsafe { crate::unparse_path(&base) };
+ let rel = CString::new(path).unwrap();
let found_here: *mut c_char = null_mut();
let mut success: i32 = 0;
let result_ptr = unsafe {
(self.cb)(
self.ctx,
- base,
- rel,
+ base.as_ptr(),
+ rel.as_ptr(),
&mut (found_here as *const _),
&mut success,
)
};
- // Release memory occipied by arguments passed
- unsafe {
- let _ = CString::from_raw(base);
- let _ = CString::from_raw(rel);
- }
let result_raw = unsafe { CStr::from_ptr(result_ptr) };
let result_str = result_raw.to_str().unwrap();
assert!(success == 0 || success == 1);
@@ -61,7 +67,9 @@
}
let found_here_raw = unsafe { CStr::from_ptr(found_here) };
- let found_here_buf = PathBuf::from(found_here_raw.to_str().unwrap());
+ let found_here_buf = SourcePath::new(SourceFile::new(PathBuf::from(
+ found_here_raw.to_str().unwrap(),
+ )));
unsafe {
let _ = CString::from_raw(found_here);
}
@@ -74,16 +82,18 @@
Ok(found_here_buf)
}
- fn load_file_contents(&self, resolved: &Path) -> Result<Vec<u8>> {
+ fn load_file_contents(&self, resolved: &SourcePath) -> Result<Vec<u8>> {
Ok(self.out.borrow().get(resolved).unwrap().clone())
}
- unsafe fn as_any(&self) -> &dyn Any {
+ fn as_any(&self) -> &dyn Any {
self
}
}
/// # Safety
+///
+/// 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,
@@ -97,56 +107,17 @@
}))
}
-/// Standard FS import resolver
-#[derive(Default)]
-pub struct NativeImportResolver {
- library_paths: RefCell<Vec<PathBuf>>,
-}
-impl NativeImportResolver {
- fn add_jpath(&self, path: PathBuf) {
- self.library_paths.borrow_mut().push(path);
- }
-}
-impl ImportResolver for NativeImportResolver {
- fn resolve_file(&self, from: &Path, path: &str) -> Result<PathBuf> {
- let mut new_path = from.to_owned();
- new_path.push(path);
- if new_path.exists() {
- Ok(new_path)
- } else {
- for library_path in self.library_paths.borrow().iter() {
- let mut cloned = library_path.clone();
- cloned.push(path);
- if cloned.exists() {
- return Ok(cloned);
- }
- }
- throw!(ImportFileNotFound(from.to_owned(), path.to_owned()))
- }
- }
- fn load_file_contents(&self, id: &Path) -> Result<Vec<u8>> {
- let mut file = File::open(id).map_err(|_e| ResolvedFileNotFound(id.to_owned()))?;
- let mut out = Vec::new();
- file.read_to_end(&mut out)
- .map_err(|e| ImportIo(e.to_string()))?;
- Ok(out)
- }
- unsafe fn as_any(&self) -> &dyn Any {
- self
- }
-}
-
/// # Safety
///
-/// This function is safe, if received v is a pointer to normal C string
+/// `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.settings().import_resolver;
+ let any_resolver = vm.import_resolver();
let resolver = any_resolver
.as_any()
- .downcast_ref::<NativeImportResolver>()
+ .downcast_ref::<FileImportResolver>()
.expect("jpaths are not compatible with callback imports!");
resolver.add_jpath(path);
}
bindings/jsonnet/src/lib.rsdiffbeforeafterboth--- a/bindings/jsonnet/src/lib.rs
+++ b/bindings/jsonnet/src/lib.rs
@@ -10,50 +10,98 @@
use std::{
alloc::Layout,
- ffi::{CStr, CString},
+ borrow::Cow,
+ ffi::{CStr, CString, OsStr},
os::raw::{c_char, c_double, c_int, c_uint},
- path::PathBuf,
+ path::Path,
};
-use import::NativeImportResolver;
-use jrsonnet_evaluator::{IStr, ManifestFormat, State, Val};
+use jrsonnet_evaluator::{
+ trace::PathResolver, FileImportResolver, IStr, ManifestFormat, State, Val,
+};
/// WASM stub
#[cfg(target_arch = "wasm32")]
#[no_mangle]
pub extern "C" fn _start() {}
+/// Return the version string of the Jsonnet interpreter.
+/// Conforms to [semantic versioning](http://semver.org/).
+/// If this does not match `LIB_JSONNET_VERSION`
+/// then there is a mismatch between header and compiled library.
#[no_mangle]
pub extern "C" fn jsonnet_version() -> &'static [u8; 8] {
b"v0.16.0\0"
}
+unsafe fn parse_path(input: &CStr) -> Cow<Path> {
+ #[cfg(target_family = "unix")]
+ {
+ use std::os::unix::ffi::OsStrExt;
+ let str = OsStr::from_bytes(input.to_bytes());
+ Cow::Borrowed(Path::new(str))
+ }
+ #[cfg(not(target_family = "unix"))]
+ {
+ let string = input.to_str().expect("bad utf-8");
+ Cow::Borrowed(string.as_ref())
+ }
+}
+
+unsafe fn unparse_path(input: &Path) -> Cow<CStr> {
+ #[cfg(target_family = "unix")]
+ {
+ use std::os::unix::ffi::OsStrExt;
+ let str = CString::new(input.as_os_str().as_bytes()).expect("input has zero byte in it");
+ Cow::Owned(str)
+ }
+ #[cfg(not(target_family = "unix"))]
+ {
+ 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)
+ }
+}
+
+/// Creates a new Jsonnet virtual machine.
#[no_mangle]
+#[allow(clippy::box_default)]
pub extern "C" fn jsonnet_make() -> *mut State {
let state = State::default();
- state.with_stdlib();
- state.settings_mut().import_resolver = Box::new(NativeImportResolver::default());
+ state.settings_mut().import_resolver = Box::new(FileImportResolver::default());
+ state.settings_mut().context_initializer = Box::new(jrsonnet_stdlib::ContextInitializer::new(
+ state.clone(),
+ PathResolver::new_cwd_fallback(),
+ ));
Box::into_raw(Box::new(state))
}
-/// # Safety
+/// Complement of [`jsonnet_vm_make`].
#[no_mangle]
#[allow(clippy::boxed_local)]
-pub unsafe extern "C" fn jsonnet_destroy(vm: *mut State) {
- Box::from_raw(vm);
+pub extern "C" fn jsonnet_destroy(vm: Box<State>) {
+ drop(vm);
}
+/// Set the maximum stack depth.
#[no_mangle]
pub extern "C" fn jsonnet_max_stack(vm: &State, v: c_uint) {
vm.settings_mut().max_stack = v as usize;
}
-// jrsonnet currently have no GC, so these functions is no-op
+/// Set the number of objects required before a garbage collection cycle is allowed.
+///
+/// No-op for now
#[no_mangle]
pub extern "C" fn jsonnet_gc_min_objects(_vm: &State, _v: c_uint) {}
+
+/// Run the garbage collector after this amount of growth in the number of objects
+///
+/// No-op for now
#[no_mangle]
pub extern "C" fn jsonnet_gc_growth_trigger(_vm: &State, _v: c_double) {}
+/// Expect a string as output and don't JSON encode it.
#[no_mangle]
pub extern "C" fn jsonnet_string_output(vm: &State, v: c_int) {
match v {
@@ -67,13 +115,20 @@
}
}
+/// Allocate, resize, or free a buffer. This will abort if the memory cannot be allocated. It will
+/// only return NULL if sz was zero.
+///
/// # Safety
///
+/// `buf` should be either previosly allocated by this library, or NULL
+///
/// This function is most definitely broken, but it works somehow, see TODO inside
#[no_mangle]
pub unsafe extern "C" fn jsonnet_realloc(_vm: &State, buf: *mut u8, sz: usize) -> *mut u8 {
if buf.is_null() {
- assert!(sz != 0);
+ if sz == 0 {
+ return std::ptr::null_mut();
+ }
return std::alloc::alloc(Layout::from_size_align(sz, std::mem::align_of::<u8>()).unwrap());
}
// TODO: Somehow store size of allocation, because its real size is probally not 16 :D
@@ -88,30 +143,37 @@
std::alloc::realloc(buf, old_layout, sz)
}
-/// # Safety
+/// Clean up a JSON subtree.
+///
+/// This is useful if you want to abort with an error mid-way through building a complex value.
#[no_mangle]
#[allow(clippy::boxed_local)]
-pub unsafe extern "C" fn jsonnet_json_destroy(_vm: &State, v: *mut Val) {
- Box::from_raw(v);
+pub extern "C" fn jsonnet_json_destroy(_vm: &State, v: Box<Val>) {
+ drop(v);
}
+/// Set the number of lines of stack trace to display (0 for all of them).
#[no_mangle]
pub extern "C" fn jsonnet_max_trace(vm: &State, v: c_uint) {
vm.set_max_trace(v as usize)
}
+/// Evaluate a file containing Jsonnet code, return a JSON string.
+///
+/// The returned string should be cleaned up with jsonnet_realloc.
+///
/// # Safety
///
-/// This function is safe, if received v is a pointer to normal C string
+/// `filename` should be a NUL-terminated string
#[no_mangle]
pub unsafe extern "C" fn jsonnet_evaluate_file(
vm: &State,
filename: *const c_char,
error: &mut c_int,
) -> *const c_char {
- let filename = CStr::from_ptr(filename);
+ let filename = parse_path(CStr::from_ptr(filename));
match vm
- .import(PathBuf::from(filename.to_str().unwrap()))
+ .import(&filename)
.and_then(|v| vm.with_tla(v))
.and_then(|v| vm.manifest(v))
{
@@ -127,9 +189,13 @@
}
}
+/// Evaluate a string containing Jsonnet code, return a JSON string.
+///
+/// The returned string should be cleaned up with jsonnet_realloc.
+///
/// # Safety
///
-/// This function is safe, if received v is a pointer to normal C string
+/// `filename`, `snippet` should be a NUL-terminated strings
#[no_mangle]
pub unsafe extern "C" fn jsonnet_evaluate_snippet(
vm: &State,
@@ -140,10 +206,7 @@
let filename = CStr::from_ptr(filename);
let snippet = CStr::from_ptr(snippet);
match vm
- .evaluate_snippet(
- filename.to_str().unwrap().into(),
- snippet.to_str().unwrap().into(),
- )
+ .evaluate_snippet(filename.to_str().unwrap(), snippet.to_str().unwrap())
.and_then(|v| vm.with_tla(v))
.and_then(|v| vm.manifest(v))
{
@@ -183,9 +246,9 @@
filename: *const c_char,
error: &mut c_int,
) -> *const c_char {
- let filename = CStr::from_ptr(filename);
+ let filename = parse_path(CStr::from_ptr(filename));
match vm
- .import(PathBuf::from(filename.to_str().unwrap()))
+ .import(&filename)
.and_then(|v| vm.with_tla(v))
.and_then(|v| vm.manifest_multi(v))
{
@@ -212,10 +275,7 @@
let filename = CStr::from_ptr(filename);
let snippet = CStr::from_ptr(snippet);
match vm
- .evaluate_snippet(
- filename.to_str().unwrap().into(),
- snippet.to_str().unwrap().into(),
- )
+ .evaluate_snippet(filename.to_str().unwrap(), snippet.to_str().unwrap())
.and_then(|v| vm.with_tla(v))
.and_then(|v| vm.manifest_multi(v))
{
@@ -253,9 +313,9 @@
filename: *const c_char,
error: &mut c_int,
) -> *const c_char {
- let filename = CStr::from_ptr(filename);
+ let filename = parse_path(CStr::from_ptr(filename));
match vm
- .import(PathBuf::from(filename.to_str().unwrap()))
+ .import(&filename)
.and_then(|v| vm.with_tla(v))
.and_then(|v| vm.manifest_stream(v))
{
@@ -266,7 +326,9 @@
Err(e) => {
*error = 1;
let out = vm.stringify_err(&e);
- CString::new(&out as &str).unwrap().into_raw()
+ CString::new(&out as &str)
+ .expect("there should be no \\0 in the error string")
+ .into_raw()
}
}
}
@@ -283,8 +345,8 @@
let snippet = CStr::from_ptr(snippet);
match vm
.evaluate_snippet(
- filename.to_str().unwrap().into(),
- snippet.to_str().unwrap().into(),
+ filename.to_str().expect("filename is not utf-8"),
+ snippet.to_str().expect("snippet is not utf-8"),
)
.and_then(|v| vm.with_tla(v))
.and_then(|v| vm.manifest_stream(v))
@@ -296,7 +358,9 @@
Err(e) => {
*error = 1;
let out = vm.stringify_err(&e);
- CString::new(&out as &str).unwrap().into_raw()
+ CString::new(&out as &str)
+ .expect("there should be no \\0 in the error string")
+ .into_raw()
}
}
}
bindings/jsonnet/src/native.rsdiffbeforeafterboth--- a/bindings/jsonnet/src/native.rs
+++ b/bindings/jsonnet/src/native.rs
@@ -1,17 +1,27 @@
use std::{
+ borrow::Cow,
ffi::{c_void, CStr},
os::raw::{c_char, c_int},
};
use jrsonnet_evaluator::{
error::{Error, LocError},
- function::builtin::{BuiltinParam, NativeCallback, NativeCallbackHandler},
+ function::builtin::{NativeCallback, NativeCallbackHandler},
tb,
typed::Typed,
IStr, State, Val,
};
use jrsonnet_gcmodule::Cc;
+/// The returned `JsonnetJsonValue*` should be allocated with `jsonnet_realloc`. It will be cleaned up
+/// along with the objects rooted at `argv` by `libjsonnet` when no-longer needed. Return a string upon
+/// failure, which will appear in Jsonnet as an error. The `argv` pointer is an array whose size
+/// matches the array of parameters supplied when the native callback was originally registered.
+///
+/// - `ctx` User pointer, given in jsonnet_native_callback.
+/// - `argv` Array of arguments from Jsonnet code.
+/// - `param` success Set this byref param to 1 to indicate success and 0 for failure.
+/// Returns the content of the imported file, or an error message.
type JsonnetNativeCallback = unsafe extern "C" fn(
ctx: *const c_void,
argv: *const *const Val,
@@ -44,13 +54,20 @@
if success == 1 {
Ok(v)
} else {
- let e = IStr::from_untyped(v, s).expect("error msg");
+ let e = IStr::from_untyped(v, s).expect("error msg should be a string");
Err(Error::RuntimeError(e).into())
}
}
}
+/// Callback to provide native extensions to Jsonnet.
+///
/// # Safety
+///
+/// `vm` should be a vm allocated by `jsonnet_make`
+/// `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,
@@ -59,26 +76,33 @@
ctx: *const c_void,
mut raw_params: *const *const c_char,
) {
- let name = CStr::from_ptr(name).to_str().expect("utf8 name").into();
+ let name = CStr::from_ptr(name)
+ .to_str()
+ .expect("name is not utf-8")
+ .into();
let mut params = Vec::new();
loop {
if (*raw_params).is_null() {
break;
}
- let param = CStr::from_ptr(*raw_params).to_str().expect("not utf8");
- params.push(BuiltinParam {
- name: param.into(),
- has_default: false,
- });
+ let param = CStr::from_ptr(*raw_params)
+ .to_str()
+ .expect("param name is not utf-8");
+ params.push(Cow::Owned(param.into()));
raw_params = raw_params.offset(1);
}
- vm.add_native(
- name,
- #[allow(deprecated)]
- Cc::new(tb!(NativeCallback::new(
- params,
- tb!(JsonnetNativeCallbackHandler { ctx, cb }),
- ))),
- )
+ let any_resolver = vm.context_initializer();
+ any_resolver
+ .as_any()
+ .downcast_ref::<jrsonnet_stdlib::ContextInitializer>()
+ .expect("only stdlib context initializer supported")
+ .add_native(
+ name,
+ #[allow(deprecated)]
+ Cc::new(tb!(NativeCallback::new(
+ params,
+ tb!(JsonnetNativeCallbackHandler { ctx, cb }),
+ ))),
+ )
}
bindings/jsonnet/src/val_extract.rsdiffbeforeafterboth--- a/bindings/jsonnet/src/val_extract.rs
+++ b/bindings/jsonnet/src/val_extract.rs
@@ -7,13 +7,16 @@
use jrsonnet_evaluator::{State, Val};
+/// If the value is a string, return it as UTF-8, otherwise return `NULL`.
#[no_mangle]
pub extern "C" fn jsonnet_json_extract_string(_vm: &State, v: &Val) -> *mut c_char {
match v {
- Val::Str(s) => CString::new(&*s as &str).unwrap().into_raw(),
+ Val::Str(s) => CString::new(s as &str).unwrap().into_raw(),
_ => std::ptr::null_mut(),
}
}
+
+/// If the value is a number, return `1` and store the number in out, otherwise return `0`.
#[no_mangle]
pub extern "C" fn jsonnet_json_extract_number(_vm: &State, v: &Val, out: &mut c_double) -> c_int {
match v {
@@ -24,6 +27,8 @@
_ => 0,
}
}
+
+/// Return `0` if the value is `false`, `1` if it is `true`, and `2` if it is not a `bool`.
#[no_mangle]
pub extern "C" fn jsonnet_json_extract_bool(_vm: &State, v: &Val) -> c_int {
match v {
@@ -32,6 +37,8 @@
_ => 2,
}
}
+
+/// Return `1` if the value is `null`, otherwise return `0`.
#[no_mangle]
pub extern "C" fn jsonnet_json_extract_null(_vm: &State, v: &Val) -> c_int {
match v {
bindings/jsonnet/src/val_make.rsdiffbeforeafterboth--- a/bindings/jsonnet/src/val_make.rs
+++ b/bindings/jsonnet/src/val_make.rs
@@ -8,37 +8,46 @@
use jrsonnet_evaluator::{val::ArrValue, ObjValue, State, Val};
use jrsonnet_gcmodule::Cc;
+/// Convert the given `UTF-8` string to a `JsonnetJsonValue`.
+///
/// # Safety
///
-/// This function is safe, if received v is a pointer to normal C string
+/// `v` should be a NUL-terminated string
#[no_mangle]
-pub unsafe extern "C" fn jsonnet_json_make_string(_vm: &State, v: *const c_char) -> *mut Val {
- let cstr = CStr::from_ptr(v);
- let str = cstr.to_str().unwrap();
- Box::into_raw(Box::new(Val::Str(str.into())))
+pub unsafe extern "C" fn jsonnet_json_make_string(_vm: &State, val: *const c_char) -> *mut Val {
+ let val = CStr::from_ptr(val);
+ let val = val.to_str().expect("string is not utf-8");
+ Box::into_raw(Box::new(Val::Str(val.into())))
}
+/// Convert the given double to a `JsonnetJsonValue`.
#[no_mangle]
pub extern "C" fn jsonnet_json_make_number(_vm: &State, v: c_double) -> *mut Val {
Box::into_raw(Box::new(Val::Num(v)))
}
+/// Convert the given `bool` (`1` or `0`) to a `JsonnetJsonValue`.
#[no_mangle]
pub extern "C" fn jsonnet_json_make_bool(_vm: &State, v: c_int) -> *mut Val {
- assert!(v == 0 || v == 1);
+ assert!(v == 0 || v == 1, "bad boolean value");
Box::into_raw(Box::new(Val::Bool(v == 1)))
}
+/// Make a `JsonnetJsonValue` representing `null`.
#[no_mangle]
pub extern "C" fn jsonnet_json_make_null(_vm: &State) -> *mut Val {
Box::into_raw(Box::new(Val::Null))
}
+/// Make a `JsonnetJsonValue` representing an array.
+///
+/// Assign elements with [`jsonnet_json_array_append`].
#[no_mangle]
pub extern "C" fn jsonnet_json_make_array(_vm: &State) -> *mut Val {
Box::into_raw(Box::new(Val::Arr(ArrValue::Eager(Cc::new(Vec::new())))))
}
+/// Make a `JsonnetJsonValue` representing an object.
#[no_mangle]
pub extern "C" fn jsonnet_json_make_object(_vm: &State) -> *mut Val {
Box::into_raw(Box::new(Val::Obj(ObjValue::new_empty())))
bindings/jsonnet/src/val_modify.rsdiffbeforeafterboth--- a/bindings/jsonnet/src/val_modify.rs
+++ b/bindings/jsonnet/src/val_modify.rs
@@ -7,9 +7,12 @@
use jrsonnet_evaluator::{val::ArrValue, State, Thunk, Val};
use jrsonnet_gcmodule::Cc;
+/// Adds value to the end of the array `arr`.
+///
/// # Safety
///
-/// Received arr value should be correct pointer to array allocated by make_array
+/// `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 {
@@ -26,9 +29,14 @@
}
}
+/// Adds the field to the object, bound to value.
+///
+/// This shadows any previous binding of the field.
+///
/// # Safety
///
-/// This function is safe if passed name is ok
+/// `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,40 +4,84 @@
use jrsonnet_evaluator::State;
+/// Binds a Jsonnet external variable to the given string.
+///
+/// Argument values are copied so memory should be managed by the caller.
+///
/// # Safety
+///
+/// `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);
let value = CStr::from_ptr(value);
- vm.add_ext_str(
- name.to_str().unwrap().into(),
- value.to_str().unwrap().into(),
- )
+
+ let any_initializer = vm.context_initializer();
+ any_initializer
+ .as_any()
+ .downcast_ref::<jrsonnet_stdlib::ContextInitializer>()
+ .expect("only stdlib context initializer supported")
+ .add_ext_str(
+ name.to_str().expect("name is not utf-8").into(),
+ value.to_str().expect("value is not utf-8").into(),
+ )
}
+/// Binds a Jsonnet external variable to the given code.
+///
+/// Argument values are copied so memory should be managed by the caller.
+///
/// # Safety
+///
+/// `name`, `code` should be a NUL-terminated strings
#[no_mangle]
-pub unsafe extern "C" fn jsonnet_ext_code(vm: &State, name: *const c_char, value: *const c_char) {
+pub unsafe extern "C" fn jsonnet_ext_code(vm: &State, name: *const c_char, code: *const c_char) {
let name = CStr::from_ptr(name);
- let value = CStr::from_ptr(value);
- vm.add_ext_code(name.to_str().unwrap(), value.to_str().unwrap().into())
- .unwrap()
+ let code = CStr::from_ptr(code);
+
+ let any_initializer = vm.context_initializer();
+ any_initializer
+ .as_any()
+ .downcast_ref::<jrsonnet_stdlib::ContextInitializer>()
+ .expect("only stdlib context initializer supported")
+ .add_ext_code(
+ name.to_str().expect("name is not utf-8"),
+ code.to_str().expect("code is not utf-8"),
+ )
+ .expect("can't parse ext code")
}
+
+/// Binds a top-level string argument for a top-level parameter.
+///
+/// Argument values are copied so memory should be managed by the caller.
+///
/// # Safety
+///
+/// `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);
let value = CStr::from_ptr(value);
vm.add_tla_str(
- name.to_str().unwrap().into(),
- value.to_str().unwrap().into(),
+ name.to_str().expect("name is not utf-8").into(),
+ value.to_str().expect("value is not utf-8").into(),
)
}
+
+/// Binds a top-level code argument for a top-level parameter.
+///
+/// Argument values are copied so memory should be managed by the caller.
+///
/// # Safety
+///
+/// `name`, `code` should be a NUL-terminated strings
#[no_mangle]
-pub unsafe extern "C" fn jsonnet_tla_code(vm: &State, name: *const c_char, value: *const c_char) {
+pub unsafe extern "C" fn jsonnet_tla_code(vm: &State, name: *const c_char, code: *const c_char) {
let name = CStr::from_ptr(name);
- let value = CStr::from_ptr(value);
- vm.add_tla_code(name.to_str().unwrap().into(), value.to_str().unwrap())
- .unwrap()
+ let code = CStr::from_ptr(code);
+ vm.add_tla_code(
+ name.to_str().expect("name is not utf-8").into(),
+ code.to_str().expect("code is not utf-8"),
+ )
+ .expect("can't parse tla code")
}
build/Dockerfilediffbeforeafterboth--- a/build/Dockerfile
+++ /dev/null
@@ -1,8 +0,0 @@
-FROM alpine:edge
-
-RUN apk add --no-cache \
- clang gcc g++ make cmake curl \
- openjdk8-jre-base \
- rustup && \
- rustup-init --default-toolchain nightly -y -t wasm32-wasi
-ENV PATH /root/.rustup/toolchains/nightly-x86_64-unknown-linux-musl/bin/:/root/.cargo/bin/:${PATH}
build/make-docker.shdiffbeforeafterboth--- a/build/make-docker.sh
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/usr/bin/env sh
-export DOCKER_BUILDKIT=1
-docker build -t jrsonnet -f build/Dockerfile build/
-docker run --rm -it -v $PWD:/build -e CARGO_HOME=/build/cache jrsonnet:latest ash -c "cd /build&&make $@"
build/run-docker.shdiffbeforeafterboth--- a/build/run-docker.sh
+++ /dev/null
@@ -1,4 +0,0 @@
-#!/usr/bin/env sh
-export DOCKER_BUILDKIT=1
-docker build -t jrsonnet -f build/Dockerfile build/
-docker run --rm -it -v $PWD:/build -e CARGO_HOME=/build/cache jrsonnet:latest ash -c "cd /build&&$@"
cmds/jrsonnet-fmt/Cargo.tomldiffbeforeafterboth--- a/cmds/jrsonnet-fmt/Cargo.toml
+++ /dev/null
@@ -1,8 +0,0 @@
-[package]
-name = "jrsonnet-fmt"
-version = "0.1.0"
-edition = "2021"
-
-[dependencies]
-dprint-core = "0.47.1"
-jrsonnet-parser = { path = "../../crates/jrsonnet-parser" }
cmds/jrsonnet-fmt/src/main.rsdiffbeforeafterboth--- a/cmds/jrsonnet-fmt/src/main.rs
+++ /dev/null
@@ -1,373 +0,0 @@
-use std::path::PathBuf;
-
-use dprint_core::formatting::{PrintItems, PrintOptions, Signal};
-use jrsonnet_parser::{
- ArgsDesc, BinaryOpType, BindSpec, Expr, FieldName, LocExpr, Member, ObjBody, Param, ParamsDesc,
- ParserSettings, Visibility,
-};
-
-pub trait Printable {
- fn print(&self) -> PrintItems;
-}
-
-macro_rules! pi {
- (@i; $($t:tt)*) => {{
- let mut o = PrintItems::new();
- pi!(@s; o: $($t)*);
- o
- }};
- (@s; $o:ident: str($e:expr) $($t:tt)*) => {{
- $o.push_str($e);
- pi!(@s; $o: $($t)*);
- }};
- (@s; $o:ident: nl $($t:tt)*) => {{
- $o.push_signal(Signal::NewLine);
- pi!(@s; $o: $($t)*);
- }};
- (@s; $o:ident: >i $($t:tt)*) => {{
- $o.push_signal(Signal::StartIndent);
- pi!(@s; $o: $($t)*);
- }};
- (@s; $o:ident: <i $($t:tt)*) => {{
- $o.push_signal(Signal::FinishIndent);
- pi!(@s; $o: $($t)*);
- }};
- (@s; $o:ident: {$expr:expr} $($t:tt)*) => {{
- $o.extend($expr.print());
- pi!(@s; $o: $($t)*);
- }};
- (@s; $o:ident: if ($e:expr)($($then:tt)*) $($t:tt)*) => {{
- if $e {
- pi!(@s; $o: $($then)*);
- }
- pi!(@s; $o: $($t)*);
- }};
- (@s; $o:ident: ifelse ($e:expr)($($then:tt)*)($($else:tt)*) $($t:tt)*) => {{
- if $e {
- pi!(@s; $o: $($then)*);
- } else {
- pi!(@s; $o: $($else)*);
- }
- pi!(@s; $o: $($t)*);
- }};
- (@s; $i:ident:) => {}
-}
-macro_rules! p {
- (new: $($t:tt)*) => {
- pi!(@i; $($t)*)
- };
- ($o:ident: $($t:tt)*) => {
- pi!(@s; $o: $($t)*)
- };
-}
-
-impl Printable for FieldName {
- fn print(&self) -> PrintItems {
- match self {
- FieldName::Fixed(f) => {
- p!(new: str(&f))
- }
- FieldName::Dyn(_) => todo!(),
- }
- }
-}
-
-impl Printable for Visibility {
- fn print(&self) -> PrintItems {
- match self {
- Visibility::Normal => p!(new: str(":")),
- Visibility::Hidden => p!(new: str("::")),
- Visibility::Unhide => p!(new: str(":::")),
- }
- }
-}
-
-impl Printable for BinaryOpType {
- fn print(&self) -> PrintItems {
- let o = self.to_string();
- p!(new: str(&o))
- }
-}
-
-impl<T: Printable> Printable for Option<T> {
- fn print(&self) -> PrintItems {
- if let Some(v) = self {
- v.print()
- } else {
- PrintItems::new()
- }
- }
-}
-
-impl Printable for Param {
- fn print(&self) -> PrintItems {
- p!(new:
- str(&self.0)
- if(self.1.is_some())(str(" = ") {self.1})
- )
- }
-}
-
-impl Printable for ParamsDesc {
- fn print(&self) -> PrintItems {
- let mut out = PrintItems::new();
- for (i, item) in self.0.iter().enumerate() {
- if i != 0 {
- p!(out: str(", "));
- }
- out.extend(item.print());
- }
- out
- }
-}
-
-impl Printable for ArgsDesc {
- fn print(&self) -> PrintItems {
- let mut out = PrintItems::new();
- let mut first = Some(());
- for u in self.unnamed.iter() {
- if first.take().is_none() {
- p!(out: str(", "));
- }
- p!(out: {u})
- }
- for (n, u) in self.named.iter() {
- if first.take().is_none() {
- p!(out: str(", "));
- }
- p!(out: str(&n) str(" = ") {u})
- }
-
- out
- }
-}
-
-impl Printable for BindSpec {
- fn print(&self) -> PrintItems {
- p!(new: str(&self.name) if(self.params.is_some())(str("(") {self.params} str(")")) str(" = ") {self.value})
- }
-}
-
-struct StrExpr<'s>(&'s str);
-
-impl<'s> Printable for StrExpr<'s> {
- fn print(&self) -> PrintItems {
- todo!()
- }
-}
-
-impl Printable for ObjBody {
- fn print(&self) -> PrintItems {
- let mut pi = PrintItems::new();
- p!(pi: str("{"));
- match self {
- ObjBody::MemberList(m) => {
- if !m.is_empty() {
- p!(pi: nl > i);
- for m in m {
- match m {
- Member::Field(f) => {
- p!(pi:
- {f.name} {f.params}
- if(f.plus)(str("+"))
- {f.visibility} str(" ")
- {f.value}
- str(",") nl
- );
- }
- Member::BindStmt(s) => {
- p!(pi: str("local ") {s} str(",") nl)
- }
- Member::AssertStmt(a) => p!(pi: str("assert ") {a.0} if(a.1.is_some())(
- str(" : ") {a.1}
- ) str(",") nl),
- }
- }
- p!(pi: <i);
- } else {
- p!(pi: str(" "))
- }
- }
- ObjBody::ObjComp(_) => todo!(),
- }
- p!(pi: str("}"));
- pi
- }
-}
-
-impl Printable for Expr {
- fn print(&self) -> PrintItems {
- let mut pi = PrintItems::new();
- match self {
- Expr::Literal(l) => match l {
- jrsonnet_parser::LiteralType::This => p!(pi: str("self")),
- jrsonnet_parser::LiteralType::Super => p!(pi: str("super")),
- jrsonnet_parser::LiteralType::Dollar => p!(pi: str("$")),
- jrsonnet_parser::LiteralType::Null => p!(pi: str("null")),
- jrsonnet_parser::LiteralType::True => p!(pi: str("true")),
- jrsonnet_parser::LiteralType::False => p!(pi: str("false")),
- },
- Expr::Str(s) => {
- p!(pi: str("\"") str(s) str("\""))
- }
- Expr::Num(n) => {
- let n = n.to_string();
- p!(pi: str(&n));
- }
- Expr::Var(v) => p!(pi: str(&v)),
- Expr::Arr(a) => {
- p!(pi: str("["));
- for (i, v) in a.iter().enumerate() {
- if i != 0 {
- p!(pi: str(", "));
- }
- p!(pi: {v})
- }
- p!(pi: str("]"));
- }
- Expr::ArrComp(_, _) => todo!(),
- Expr::Obj(o) => {
- p!(pi: {o});
- }
- Expr::ObjExtend(a, b) => p!(pi: {a} str(" ") {b}),
- Expr::Parened(v) => {
- if let Expr::Parened(_) = &v.0 as &Expr {
- p!(pi: {v})
- } else {
- p!(pi: str("(") {v} str(")"))
- }
- }
- Expr::UnaryOp(_, _) => todo!(),
- Expr::BinaryOp(a, o, b) => {
- p!(pi:
- {a} str(" ") if(!matches!(&b.0 as &Expr, Expr::Obj(_)))({o} str(" ")) {b}
- )
- }
- Expr::AssertExpr(_, _) => todo!(),
- Expr::LocalExpr(s, v) => {
- p!(pi:
- str("local") nl >i
- );
- for spec in s.iter() {
- p!(pi: {spec} str(";") nl)
- }
- p!(pi:
- <i
- {v}
- );
- }
- Expr::Import(i) => {
- let v = i.to_str().unwrap();
- p!(pi: str("import \"") str(&v) str("\""));
- }
- Expr::ImportStr(_) => todo!(),
- Expr::ErrorStmt(_) => todo!(),
- Expr::Apply(f, a, t) => p!(pi:
- {f} str("(") {a} str(")") if(*t)(str("tailstrict"))
- ),
- Expr::Index(a, b) => p!(pi: {a} str("[") {b} str("]")),
- Expr::Function(_, _) => todo!(),
- Expr::Intrinsic(_) => todo!(),
- Expr::IfElse {
- cond,
- cond_then,
- cond_else,
- } => p!(pi:
- str("if ") {cond.0} str(" then") ifelse(cond_else.is_some())(
- nl >i
- {cond_then} nl
- <i str("else") nl >i
- {cond_else}
- <i
- )(str(" ") {cond_then})
- ),
- Expr::Slice(v, d) => {
- p!(pi:
- {v}
- str("[") {d.start} str(":") {d.end}
- if(d.step.is_some())(
- str(":")
- {d.step}
- )
- str("]")
- )
- }
- }
- pi
- }
-}
-
-impl Printable for LocExpr {
- fn print(&self) -> PrintItems {
- self.0.print()
- }
-}
-
-fn main() {
- let parsed = jrsonnet_parser::parse(
- r#"
-
-
- # Edit me!
- local b = import "b.libsonnet"; # comment
- local a = import "a.libsonnet";
-
- local f(x,y)=x+y;
-
-
- local Template = {z: "foo"};
-
- Template + {
- local
-
- h = 3,
- assert self.a == 1
-
- : "error",
- "f": ((((((3)))))) ,
- "g g":
- f(4,2),
- arr: [[
- 1, 2,
- ],
- 3,
- {
- b: {
- c: {
- k: [16]
- }
- }
- }
- ],
- m: a[1::],
- m: b[::],
- k: if a == b then
-
-
- 2
-
- else Template {}
- }
-
-
-"#,
- &ParserSettings {
- file_name: PathBuf::from("example").into(),
- },
- )
- .unwrap();
-
- let o = dprint_core::formatting::format(
- || {
- let print_items = parsed.print();
- print_items
- },
- PrintOptions {
- indent_width: 2,
- max_width: 100,
- use_tabs: false,
- new_line_text: "\n",
- },
- );
- println!("{}", o);
-}
cmds/jrsonnet/Cargo.tomldiffbeforeafterboth--- a/cmds/jrsonnet/Cargo.toml
+++ b/cmds/jrsonnet/Cargo.toml
@@ -15,9 +15,12 @@
"jrsonnet-evaluator/exp-preserve-order",
"jrsonnet-evaluator/exp-serde-preserve-order",
"jrsonnet-cli/exp-preserve-order",
+ "jrsonnet-cli/exp-serde-preserve-order",
]
# Destructuring of locals
exp-destruct = ["jrsonnet-evaluator/exp-destruct"]
+# std.thisFile support
+legacy-this-file = ["jrsonnet-cli/legacy-this-file"]
[dependencies]
jrsonnet-evaluator = { path = "../../crates/jrsonnet-evaluator", version = "0.4.2" }
@@ -27,5 +30,5 @@
mimallocator = { version = "0.1.3", optional = true }
thiserror = "1.0"
-clap = { version = "3.1", features = ["derive"] }
-clap_complete = { version = "3.1" }
+clap = { version = "3.2", features = ["derive"] }
+clap_complete = { version = "3.2" }
cmds/jrsonnet/src/main.rsdiffbeforeafterboth--- a/cmds/jrsonnet/src/main.rs
+++ b/cmds/jrsonnet/src/main.rs
@@ -1,5 +1,4 @@
use std::{
- env::current_dir,
fs::{create_dir_all, File},
io::{Read, Write},
};
@@ -133,14 +132,14 @@
let input = opts.input.input.ok_or(Error::MissingInputArgument)?;
let val = if opts.input.exec {
- s.evaluate_snippet("<cmdline>".to_owned(), (&input as &str).into())?
+ s.evaluate_snippet("<cmdline>".to_owned(), &input as &str)?
} else if input == "-" {
let mut input = Vec::new();
std::io::stdin().read_to_end(&mut input)?;
- let input_str = std::str::from_utf8(&input)?.into();
+ let input_str = std::str::from_utf8(&input)?;
s.evaluate_snippet("<stdin>".to_owned(), input_str)?
} else {
- s.import(s.resolve_file(¤t_dir().expect("cwd"), &input)?)?
+ s.import(&input)?
};
let val = s.with_tla(val)?;
crates/jrsonnet-cli/Cargo.tomldiffbeforeafterboth--- a/crates/jrsonnet-cli/Cargo.toml
+++ b/crates/jrsonnet-cli/Cargo.toml
@@ -7,7 +7,15 @@
edition = "2021"
[features]
-exp-preserve-order = ["jrsonnet-evaluator/exp-preserve-order"]
+exp-preserve-order = [
+ "jrsonnet-evaluator/exp-preserve-order",
+ "jrsonnet-stdlib/exp-preserve-order",
+]
+exp-serde-preserve-order = [
+ "jrsonnet-evaluator/exp-serde-preserve-order",
+ "jrsonnet-stdlib/exp-serde-preserve-order",
+]
+legacy-this-file = ["jrsonnet-stdlib/legacy-this-file"]
[dependencies]
jrsonnet-evaluator = { path = "../../crates/jrsonnet-evaluator", version = "0.4.2", features = [
@@ -15,5 +23,6 @@
] }
jrsonnet-parser = { path = "../../crates/jrsonnet-parser", version = "0.4.2" }
jrsonnet-gcmodule = { version = "0.3.4" }
+jrsonnet-stdlib = { path = "../../crates/jrsonnet-stdlib", version = "0.4.2" }
-clap = { version = "3.1", features = ["derive"] }
+clap = { version = "3.2", features = ["derive"] }
crates/jrsonnet-cli/src/ext.rsdiffbeforeafterboth--- a/crates/jrsonnet-cli/src/ext.rs
+++ /dev/null
@@ -1,118 +0,0 @@
-use std::{fs::read_to_string, str::FromStr};
-
-use clap::Parser;
-use jrsonnet_evaluator::{error::Result, State};
-
-use crate::ConfigureState;
-
-#[derive(Clone)]
-pub struct ExtStr {
- pub name: String,
- pub value: String,
-}
-
-impl FromStr for ExtStr {
- type Err = &'static str;
- fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
- let out: Vec<_> = s.split('=').collect();
- match out.len() {
- 1 => Ok(ExtStr {
- name: out[0].to_owned(),
- value: std::env::var(out[0]).or(Err("missing env var"))?,
- }),
- 2 => Ok(ExtStr {
- name: out[0].to_owned(),
- value: out[1].to_owned(),
- }),
-
- _ => Err("bad ext-str syntax"),
- }
- }
-}
-
-#[derive(Clone)]
-pub struct ExtFile {
- pub name: String,
- pub value: String,
-}
-
-impl FromStr for ExtFile {
- type Err = String;
-
- fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
- let out: Vec<&str> = s.split('=').collect();
- if out.len() != 2 {
- return Err("bad ext-file syntax".to_owned());
- }
- let file = read_to_string(&out[1]);
- match file {
- Ok(content) => Ok(Self {
- name: out[0].into(),
- value: content,
- }),
- Err(e) => Err(format!("{}", e)),
- }
- }
-}
-
-#[derive(Parser)]
-#[clap(next_help_heading = "EXTERNAL VARIABLES")]
-pub struct ExtVarOpts {
- /// Add string external variable.
- /// External variables are globally available so it is preferred
- /// to use top level arguments whenever it's possible.
- /// If [=data] is not set then it will be read from `name` env variable.
- /// Can be accessed from code via `std.extVar("name")`.
- #[clap(
- long,
- short = 'V',
- name = "name[=var data]",
- number_of_values = 1,
- multiple_occurrences = true
- )]
- ext_str: Vec<ExtStr>,
- /// Read string external variable from file.
- /// See also `--ext-str`
- #[clap(
- long,
- name = "name=var path",
- number_of_values = 1,
- multiple_occurrences = true
- )]
- ext_str_file: Vec<ExtFile>,
- /// Add external variable from code.
- /// See also `--ext-str`
- #[clap(
- long,
- name = "name[=var source]",
- number_of_values = 1,
- multiple_occurrences = true
- )]
- ext_code: Vec<ExtStr>,
- /// Read string external variable from file.
- /// See also `--ext-str`
- #[clap(
- long,
- name = "name=var code path",
- number_of_values = 1,
- multiple_occurrences = true
- )]
- ext_code_file: Vec<ExtFile>,
-}
-impl ConfigureState for ExtVarOpts {
- fn configure(&self, s: &State) -> Result<()> {
- for ext in self.ext_str.iter() {
- s.add_ext_str((&ext.name as &str).into(), (&ext.value as &str).into());
- }
- for ext in self.ext_str_file.iter() {
- s.add_ext_str((&ext.name as &str).into(), (&ext.value as &str).into());
- }
- for ext in self.ext_code.iter() {
- s.add_ext_code(&ext.name as &str, (&ext.value as &str).into())?;
- }
- for ext in self.ext_code_file.iter() {
- s.add_ext_code(&ext.name as &str, (&ext.value as &str).into())?;
- }
- Ok(())
- }
-}
crates/jrsonnet-cli/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-cli/src/lib.rs
+++ b/crates/jrsonnet-cli/src/lib.rs
@@ -1,15 +1,15 @@
-mod ext;
mod manifest;
+mod stdlib;
mod tla;
mod trace;
use std::{env, path::PathBuf};
use clap::Parser;
-pub use ext::*;
use jrsonnet_evaluator::{error::Result, FileImportResolver, State};
use jrsonnet_gcmodule::with_thread_object_space;
pub use manifest::*;
+pub use stdlib::*;
pub use tla::*;
pub use trace::*;
@@ -31,13 +31,6 @@
#[derive(Parser)]
#[clap(next_help_heading = "OPTIONS")]
pub struct MiscOpts {
- /// Disable standard library.
- /// By default standard library will be available via global `std` variable.
- /// Note that standard library will still be loaded
- /// if chosen manifestification method is not `none`.
- #[clap(long)]
- no_stdlib: bool,
-
/// Maximal allowed number of stack frames,
/// stack overflow error will be raised if this number gets exceeded.
#[clap(long, short = 's', default_value = "200")]
@@ -52,17 +45,13 @@
}
impl ConfigureState for MiscOpts {
fn configure(&self, s: &State) -> Result<()> {
- if !self.no_stdlib {
- s.with_stdlib();
- }
-
let mut library_paths = self.jpath.clone();
library_paths.reverse();
if let Some(path) = env::var_os("JSONNET_PATH") {
library_paths.extend(env::split_paths(path.as_os_str()));
}
- s.set_import_resolver(Box::new(FileImportResolver { library_paths }));
+ s.set_import_resolver(Box::new(FileImportResolver::new(library_paths)));
s.set_max_stack(self.max_stack);
Ok(())
@@ -79,7 +68,7 @@
#[clap(flatten)]
tla: TLAOpts,
#[clap(flatten)]
- ext: ExtVarOpts,
+ std: StdOpts,
#[clap(flatten)]
trace: TraceOpts,
@@ -91,7 +80,7 @@
self.trace.configure(s)?;
self.misc.configure(s)?;
self.tla.configure(s)?;
- self.ext.configure(s)?;
+ self.std.configure(s)?;
Ok(())
}
}
@@ -113,6 +102,8 @@
}
impl GcOpts {
pub fn stats_printer(&self) -> (Option<GcStatsPrinter>, Option<LeakSpace>) {
+ // Constructed structs have side-effects in Drop impl
+ #[allow(clippy::unnecessary_lazy_evaluations)]
(
self.gc_print_stats.then(|| GcStatsPrinter {
collect_before_printing_stats: self.gc_collect_before_printing_stats,
crates/jrsonnet-cli/src/stdlib.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-cli/src/stdlib.rs
@@ -0,0 +1,130 @@
+use std::{fs::read_to_string, str::FromStr};
+
+use clap::Parser;
+use jrsonnet_evaluator::{error::Result, trace::PathResolver, State};
+
+use crate::ConfigureState;
+
+#[derive(Clone)]
+pub struct ExtStr {
+ pub name: String,
+ pub value: String,
+}
+
+impl FromStr for ExtStr {
+ type Err = &'static str;
+ fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
+ let out: Vec<_> = s.split('=').collect();
+ match out.len() {
+ 1 => Ok(ExtStr {
+ name: out[0].to_owned(),
+ value: std::env::var(out[0]).or(Err("missing env var"))?,
+ }),
+ 2 => Ok(ExtStr {
+ name: out[0].to_owned(),
+ value: out[1].to_owned(),
+ }),
+
+ _ => Err("bad ext-str syntax"),
+ }
+ }
+}
+
+#[derive(Clone)]
+pub struct ExtFile {
+ pub name: String,
+ pub value: String,
+}
+
+impl FromStr for ExtFile {
+ type Err = String;
+
+ fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
+ let out: Vec<&str> = s.split('=').collect();
+ if out.len() != 2 {
+ return Err("bad ext-file syntax".to_owned());
+ }
+ let file = read_to_string(out[1]);
+ match file {
+ Ok(content) => Ok(Self {
+ name: out[0].into(),
+ value: content,
+ }),
+ Err(e) => Err(format!("{}", e)),
+ }
+ }
+}
+
+#[derive(Parser)]
+#[clap(next_help_heading = "STANDARD LIBRARY")]
+pub struct StdOpts {
+ /// Disable standard library.
+ /// By default standard library will be available via global `std` variable.
+ /// Note that standard library will still be loaded
+ /// if chosen manifestification method is not `none`.
+ #[clap(long)]
+ no_stdlib: bool,
+ /// Add string external variable.
+ /// External variables are globally available so it is preferred
+ /// to use top level arguments whenever it's possible.
+ /// If [=data] is not set then it will be read from `name` env variable.
+ /// Can be accessed from code via `std.extVar("name")`.
+ #[clap(
+ long,
+ short = 'V',
+ name = "name[=var data]",
+ number_of_values = 1,
+ multiple_occurrences = true
+ )]
+ ext_str: Vec<ExtStr>,
+ /// Read string external variable from file.
+ /// See also `--ext-str`
+ #[clap(
+ long,
+ name = "name=var path",
+ number_of_values = 1,
+ multiple_occurrences = true
+ )]
+ ext_str_file: Vec<ExtFile>,
+ /// Add external variable from code.
+ /// See also `--ext-str`
+ #[clap(
+ long,
+ name = "name[=var source]",
+ number_of_values = 1,
+ multiple_occurrences = true
+ )]
+ ext_code: Vec<ExtStr>,
+ /// Read string external variable from file.
+ /// See also `--ext-str`
+ #[clap(
+ long,
+ name = "name=var code path",
+ number_of_values = 1,
+ multiple_occurrences = true
+ )]
+ ext_code_file: Vec<ExtFile>,
+}
+impl ConfigureState for StdOpts {
+ fn configure(&self, s: &State) -> Result<()> {
+ if self.no_stdlib {
+ return Ok(());
+ }
+ let ctx =
+ jrsonnet_stdlib::ContextInitializer::new(s.clone(), PathResolver::new_cwd_fallback());
+ for ext in self.ext_str.iter() {
+ ctx.add_ext_str((&ext.name as &str).into(), (&ext.value as &str).into());
+ }
+ for ext in self.ext_str_file.iter() {
+ ctx.add_ext_str((&ext.name as &str).into(), (&ext.value as &str).into());
+ }
+ for ext in self.ext_code.iter() {
+ ctx.add_ext_code(&ext.name as &str, &ext.value as &str)?;
+ }
+ for ext in self.ext_code_file.iter() {
+ ctx.add_ext_code(&ext.name as &str, &ext.value as &str)?;
+ }
+ s.settings_mut().context_initializer = Box::new(ctx);
+ Ok(())
+ }
+}
crates/jrsonnet-cli/src/trace.rsdiffbeforeafterboth--- a/crates/jrsonnet-cli/src/trace.rs
+++ b/crates/jrsonnet-cli/src/trace.rs
@@ -42,11 +42,7 @@
}
impl ConfigureState for TraceOpts {
fn configure(&self, s: &State) -> Result<()> {
- let resolver = if let Ok(dir) = std::env::current_dir() {
- PathResolver::Relative(dir)
- } else {
- PathResolver::Absolute
- };
+ let resolver = PathResolver::new_cwd_fallback();
match self
.trace_format
.as_ref()
crates/jrsonnet-evaluator/Cargo.tomldiffbeforeafterboth--- a/crates/jrsonnet-evaluator/Cargo.toml
+++ b/crates/jrsonnet-evaluator/Cargo.toml
@@ -7,9 +7,7 @@
edition = "2021"
[features]
-default = ["serialized-stdlib", "explaining-traces", "friendly-errors"]
-# Serializes standard library AST instead of parsing them every run
-serialized-stdlib = ["bincode", "jrsonnet-parser/serde"]
+default = ["explaining-traces", "friendly-errors"]
# Rustc-like trace visualization
explaining-traces = ["annotate-snippets"]
# Allows library authors to throw custom errors
@@ -23,11 +21,12 @@
exp-serde-preserve-order = ["serde_json/preserve_order"]
# Implements field destructuring
exp-destruct = ["jrsonnet-parser/exp-destruct"]
+# Provide Typed for conversions to/from serde_json::Value type
+serde_json = ["dep:serde_json"]
[dependencies]
jrsonnet-interner = { path = "../jrsonnet-interner", version = "0.4.2" }
jrsonnet-parser = { path = "../jrsonnet-parser", version = "0.4.2" }
-jrsonnet-stdlib = { path = "../jrsonnet-stdlib", version = "0.4.2" }
jrsonnet-types = { path = "../jrsonnet-types", version = "0.4.2" }
jrsonnet-macros = { path = "../jrsonnet-macros", version = "0.4.2" }
jrsonnet-gcmodule = { version = "0.3.4" }
@@ -36,15 +35,13 @@
hashbrown = "0.12.1"
static_assertions = "1.1"
-md5 = "0.7.0"
-base64 = "0.13.0"
rustc-hash = "1.1"
thiserror = "1.0"
serde = "1.0"
-serde_json = "1.0"
-serde_yaml_with_quirks = "0.8.24"
+# Optional integration
+serde_json = { version = "1.0.82", optional = true }
anyhow = { version = "1.0", optional = true }
# Friendly errors
@@ -53,9 +50,3 @@
bincode = { version = "1.3", optional = true }
# Explaining traces
annotate-snippets = { version = "0.9.1", features = ["color"], optional = true }
-
-[build-dependencies]
-jrsonnet-stdlib = { path = "../jrsonnet-stdlib", version = "0.4.2" }
-jrsonnet-parser = { path = "../jrsonnet-parser", version = "0.4.2" }
-serde = "1.0"
-bincode = "1.3"
crates/jrsonnet-evaluator/build.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/build.rs
+++ /dev/null
@@ -1,22 +0,0 @@
-use std::{borrow::Cow, env, fs::File, io::Write, path::Path};
-
-use bincode::serialize;
-use jrsonnet_parser::{parse, ParserSettings, Source};
-use jrsonnet_stdlib::STDLIB_STR;
-
-fn main() {
- let parsed = parse(
- STDLIB_STR,
- &ParserSettings {
- file_name: Source::new_virtual(Cow::Borrowed("<std>")),
- },
- )
- .expect("parse");
-
- {
- let out_dir = env::var("OUT_DIR").unwrap();
- let dest_path = Path::new(&out_dir).join("stdlib.bincode");
- let mut f = File::create(&dest_path).unwrap();
- f.write_all(&serialize(&parsed).unwrap()).unwrap();
- }
-}
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 {
@@ -138,3 +141,51 @@
Cc::ptr_eq(&self.0, &other.0)
}
}
+
+pub struct ContextBuilder {
+ bindings: GcHashMap<IStr, Thunk<Val>>,
+ extend: Option<Context>,
+}
+
+impl ContextBuilder {
+ pub fn new() -> Self {
+ Self::with_capacity(0)
+ }
+ pub fn with_capacity(capacity: usize) -> Self {
+ Self {
+ bindings: GcHashMap::with_capacity(capacity),
+ extend: None,
+ }
+ }
+ pub fn extend(parent: Context) -> Self {
+ Self {
+ bindings: GcHashMap::new(),
+ extend: Some(parent),
+ }
+ }
+ /// # Panics
+ /// If `name` is already bound
+ pub fn bind(&mut self, name: IStr, value: Thunk<Val>) -> &mut Self {
+ let old = self.bindings.insert(name, value);
+ assert!(old.is_none(), "variable bound twice in single context call");
+ self
+ }
+ pub fn build(self) -> Context {
+ if let Some(parent) = self.extend {
+ parent.extend(self.bindings, None, None, None)
+ } else {
+ Context(Cc::new(ContextInternals {
+ bindings: LayeredHashMap::new(self.bindings),
+ dollar: None,
+ sup: None,
+ this: None,
+ }))
+ }
+ }
+}
+
+impl Default for ContextBuilder {
+ fn default() -> Self {
+ Self::new()
+ }
+}
crates/jrsonnet-evaluator/src/dynamic.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/dynamic.rs
+++ b/crates/jrsonnet-evaluator/src/dynamic.rs
@@ -2,6 +2,7 @@
use jrsonnet_gcmodule::{Cc, Trace};
+// TODO: Replace with OnceCell once in std
#[derive(Clone, Trace)]
pub struct Pending<V: Trace + 'static>(pub Cc<RefCell<Option<V>>>);
impl<T: Trace + 'static> Pending<T> {
crates/jrsonnet-evaluator/src/error.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/error.rs
+++ b/crates/jrsonnet-evaluator/src/error.rs
@@ -2,14 +2,11 @@
use jrsonnet_gcmodule::Trace;
use jrsonnet_interner::IStr;
-use jrsonnet_parser::{BinaryOpType, ExprLocation, Source, UnaryOpType};
+use jrsonnet_parser::{BinaryOpType, ExprLocation, Source, SourcePath, UnaryOpType};
use jrsonnet_types::ValType;
use thiserror::Error;
-use crate::{
- stdlib::{format::FormatError, sort::SortError},
- typed::TypeLocError,
-};
+use crate::{stdlib::format::FormatError, typed::TypeLocError};
fn format_found(list: &[IStr], what: &str) -> String {
if list.is_empty() {
@@ -35,6 +32,31 @@
out
}
+fn format_signature(sig: &FunctionSignature) -> String {
+ let mut out = String::new();
+ out.push_str("\nFunction has the following signature: ");
+ out.push('(');
+ if sig.is_empty() {
+ out.push_str("/*no arguments*/");
+ } else {
+ for (i, (name, has_default)) in sig.iter().enumerate() {
+ if i != 0 {
+ out.push_str(", ");
+ }
+ if let Some(name) = name {
+ out.push_str(name);
+ } else {
+ out.push_str("<unnamed>");
+ }
+ if *has_default {
+ out.push_str(" = <default>");
+ }
+ }
+ }
+ out.push(')');
+ out
+}
+
const fn format_empty_str(str: &str) -> &str {
if str.is_empty() {
"\"\" (empty string)"
@@ -43,7 +65,12 @@
}
}
+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),
@@ -76,7 +103,7 @@
#[error("duplicate local var: {0}")]
DuplicateLocalVar(IStr),
- #[error("type mismatch: expected {}, got {2} {0}", .1.iter().map(|e| format!("{}", e)).collect::<Vec<_>>().join(", "))]
+ #[error("type mismatch: expected {}, got {2} {0}", .1.iter().map(|e| format!("{e}")).collect::<Vec<_>>().join(", "))]
TypeMismatch(&'static str, Vec<ValType>, ValType),
#[error("no such field: {}{}", format_empty_str(.0), format_found(.1, "field"))]
NoSuchField(IStr, Vec<IStr>),
@@ -87,10 +114,10 @@
UnknownFunctionParameter(String),
#[error("argument {0} is already bound")]
BindingParameterASecondTime(IStr),
- #[error("too many args, function has {0}")]
- TooManyArgsFunctionHas(usize),
- #[error("function argument is not passed: {0}")]
- FunctionParameterNotBoundInCall(IStr),
+ #[error("too many args, function has {0}{}", format_signature(.1))]
+ TooManyArgsFunctionHas(usize, FunctionSignature),
+ #[error("function argument is not passed: {}{}", .0.as_ref().map_or("<unnamed>", IStr::as_str), format_signature(.1))]
+ FunctionParameterNotBoundInCall(Option<IStr>, FunctionSignature),
#[error("external variable is not defined: {0}")]
UndefinedExternalVariable(IStr),
@@ -113,26 +140,31 @@
StandaloneSuper,
#[error("can't resolve {1} from {0}")]
- ImportFileNotFound(PathBuf, String),
- #[error("resolved file not found: {0}")]
- ResolvedFileNotFound(PathBuf),
+ ImportFileNotFound(SourcePath, String),
+ #[error("can't resolve absolute {0}")]
+ AbsoluteImportFileNotFound(PathBuf),
+ #[error("resolved file not found: {:?}", .0)]
+ ResolvedFileNotFound(SourcePath),
+ #[error("can't import {0}: is a directory")]
+ ImportIsADirectory(SourcePath),
#[error("imported file is not valid utf-8: {0:?}")]
- ImportBadFileUtf8(PathBuf),
+ ImportBadFileUtf8(SourcePath),
#[error("import io error: {0}")]
ImportIo(String),
- #[error("tried to import {1} from {0}, but imports is not supported")]
- ImportNotSupported(PathBuf, PathBuf),
+ #[error("tried to import {1} from {0}, but imports are not supported")]
+ ImportNotSupported(SourcePath, String),
+ #[error("tried to import {0}, but absolute imports are not supported")]
+ AbsoluteImportNotSupported(PathBuf),
#[error("can't import from virtual file")]
CantImportFromVirtualFile,
#[error(
"syntax error: expected {}, got {:?}",
.error.expected,
- .source_code.chars().nth(error.location.offset)
+ .path.code().chars().nth(error.location.offset)
.map_or_else(|| "EOF".into(), |c| c.to_string())
)]
ImportSyntaxError {
path: Source,
- source_code: IStr,
#[trace(skip)]
error: Box<jrsonnet_parser::ParseError>,
},
@@ -169,13 +201,6 @@
Format(#[from] FormatError),
#[error("type error: {0}")]
TypeError(TypeLocError),
- #[error("sort error: {0}")]
- Sort(#[from] SortError),
-
- /// Thrown as error, as this is legacy feature, and error here
- /// is acceptable for defeating object field cache
- #[error("should not reach outside: std.thisFile")]
- MagicThisFileUsed,
#[cfg(feature = "anyhow-error")]
#[error(transparent)]
@@ -195,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)]
@@ -227,7 +256,7 @@
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "{}", self.0 .0)?;
for el in &self.0 .1 .0 {
- writeln!(f, "\t{:?}", el)?;
+ writeln!(f, "\t{el:?}")?;
}
Ok(())
}
crates/jrsonnet-evaluator/src/evaluate/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate/mod.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/mod.rs
@@ -13,10 +13,9 @@
error::Error::*,
evaluate::operator::{evaluate_add_op, evaluate_binary_op_special, evaluate_unary_op},
function::{CallLocation, FuncDesc, FuncVal},
- stdlib::{std_slice, BUILTINS},
tb, throw,
typed::Typed,
- val::{ArrValue, CachedUnbound, Thunk, ThunkValue},
+ val::{ArrValue, CachedUnbound, IndexableVal, Thunk, ThunkValue},
Context, GcHashMap, ObjValue, ObjValueBuilder, ObjectAssertion, Pending, Result, State,
Unbound, Val,
};
@@ -270,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)
}
@@ -357,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)?;
@@ -437,7 +434,7 @@
UnaryOp(o, v) => evaluate_unary_op(*o, &evaluate(s, ctx, v)?)?,
Var(name) => s.push(
CallLocation::new(loc),
- || format!("variable <{}> access", name),
+ || format!("variable <{name}> access"),
|| ctx.binding(name.clone())?.evaluate(s.clone()),
)?,
Index(value, index) => {
@@ -447,7 +444,7 @@
) {
(Val::Obj(v), Val::Str(key)) => s.push(
CallLocation::new(loc),
- || format!("field <{}> access", key),
+ || format!("field <{key}> access"),
|| match v.get(s.clone(), key.clone()) {
Ok(Some(v)) => Ok(v),
#[cfg(not(feature = "friendly-errors"))]
@@ -472,9 +469,6 @@
key.clone(),
heap.into_iter().map(|(_, v)| v).collect()
))
- }
- Err(e) if matches!(e.error(), MagicThisFileUsed) => {
- Ok(Val::Str(loc.0.full_path().into()))
}
Err(e) => Err(e),
},
@@ -573,13 +567,6 @@
Function(params, body) => {
evaluate_method(ctx, "anonymous".into(), params.clone(), body.clone())
}
- Intrinsic(name) => Val::Func(FuncVal::StaticBuiltin(
- BUILTINS
- .with(|b| b.get(name).copied())
- .ok_or_else(|| IntrinsicNotFound(name.clone()))?,
- )),
- IntrinsicThisFile => return Err(MagicThisFileUsed.into()),
- IntrinsicId => Val::Func(FuncVal::identity()),
AssertExpr(assert, returned) => {
evaluate_assert(s.clone(), ctx.clone(), assert)?;
evaluate(s, ctx, returned)?
@@ -613,7 +600,7 @@
}
Slice(value, desc) => {
fn parse_idx<T: Typed>(
- loc: CallLocation,
+ loc: CallLocation<'_>,
s: State,
ctx: &Context,
expr: &Option<LocExpr>,
@@ -622,7 +609,7 @@
if let Some(value) = expr {
Ok(Some(s.push(
loc,
- || format!("slice {}", desc),
+ || format!("slice {desc}"),
|| T::from_untyped(evaluate(s.clone(), ctx.clone(), value)?, s.clone()),
)?))
} else {
@@ -635,29 +622,21 @@
let start = parse_idx(loc, s.clone(), &ctx, &desc.start, "start")?;
let end = parse_idx(loc, s.clone(), &ctx, &desc.end, "end")?;
- let step = parse_idx(loc, s, &ctx, &desc.step, "step")?;
+ let step = parse_idx(loc, s.clone(), &ctx, &desc.step, "step")?;
- std_slice(indexable.into_indexable()?, start, end, step)?
+ IndexableVal::into_untyped(indexable.into_indexable()?.slice(start, end, step)?, s)?
}
i @ (Import(path) | ImportStr(path) | ImportBin(path)) => {
let tmp = loc.clone().0;
- let import_location = tmp
- .path()
- .map(|p| {
- let mut p = p.to_owned();
- p.pop();
- p
- })
- .unwrap_or_default();
- let resolved_path = s.resolve_file(&import_location, path as &str)?;
+ let resolved_path = s.resolve_from(tmp.source_path(), path as &str)?;
match i {
Import(_) => s.push(
CallLocation::new(loc),
|| format!("import {:?}", path.clone()),
- || s.import(resolved_path.clone()),
+ || s.import_resolved(resolved_path),
)?,
- ImportStr(_) => Val::Str(s.import_str(resolved_path)?),
- ImportBin(_) => Val::Arr(ArrValue::Bytes(s.import_bin(resolved_path)?)),
+ ImportStr(_) => Val::Str(s.import_resolved_str(resolved_path)?),
+ ImportBin(_) => Val::Arr(ArrValue::Bytes(s.import_resolved_bin(resolved_path)?)),
_ => unreachable!(),
}
}
crates/jrsonnet-evaluator/src/evaluate/operator.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/evaluate/operator.rs
+++ b/crates/jrsonnet-evaluator/src/evaluate/operator.rs
@@ -21,14 +21,17 @@
pub fn evaluate_add_op(s: State, a: &Val, b: &Val) -> Result<Val> {
use Val::*;
Ok(match (a, b) {
+ (Str(a), Str(b)) if a.is_empty() => Val::Str(b.clone()),
+ (Str(a), Str(b)) if b.is_empty() => Val::Str(a.clone()),
(Str(v1), Str(v2)) => Str(((**v1).to_owned() + v2).into()),
// Can't use generic json serialization way, because it depends on number to string concatenation (std.jsonnet:890)
(Num(a), Str(b)) => Str(format!("{a}{b}").into()),
(Str(a), Num(b)) => Str(format!("{a}{b}").into()),
- (Str(a), o) => Str(format!("{}{}", a, o.clone().to_string(s)?).into()),
- (o, Str(a)) => Str(format!("{}{}", o.clone().to_string(s)?, a).into()),
+ (Str(a), o) | (o, Str(a)) if a.is_empty() => Val::Str(o.clone().to_string(s)?),
+ (Str(a), o) => Str(format!("{a}{}", o.clone().to_string(s)?).into()),
+ (o, Str(a)) => Str(format!("{}{a}", o.clone().to_string(s)?).into()),
(Obj(v1), Obj(v2)) => Obj(v2.extend_from(v1.clone())),
(Arr(a), Arr(b)) => {
crates/jrsonnet-evaluator/src/function/arglike.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/function/arglike.rs
+++ b/crates/jrsonnet-evaluator/src/function/arglike.rs
@@ -96,6 +96,34 @@
fn named_names(&self, handler: &mut dyn FnMut(&IStr));
}
+impl ArgsLike for Vec<Val> {
+ fn unnamed_len(&self) -> usize {
+ self.len()
+ }
+ fn unnamed_iter(
+ &self,
+ _s: State,
+ _ctx: Context,
+ _tailstrict: bool,
+ handler: &mut dyn FnMut(usize, Thunk<Val>) -> Result<()>,
+ ) -> Result<()> {
+ for (idx, el) in self.iter().enumerate() {
+ handler(idx, Thunk::evaluated(el.clone()))?;
+ }
+ Ok(())
+ }
+ fn named_iter(
+ &self,
+ _s: State,
+ _ctx: Context,
+ _tailstrict: bool,
+ _handler: &mut dyn FnMut(&IStr, Thunk<Val>) -> Result<()>,
+ ) -> Result<()> {
+ Ok(())
+ }
+ fn named_names(&self, _handler: &mut dyn FnMut(&IStr)) {}
+}
+
impl ArgsLike for ArgsDesc {
fn unnamed_len(&self) -> usize {
self.unnamed.len()
@@ -273,7 +301,8 @@
}
}
impl_args_like! {
- 0usize; A @ B C D E F G H I J K L
+ // First argument is already in position, so count starts from 1
+ 1usize; A @ B C D E F G H I J K L
}
impl ArgsLike for () {
crates/jrsonnet-evaluator/src/function/builtin.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/function/builtin.rs
+++ b/crates/jrsonnet-evaluator/src/function/builtin.rs
@@ -9,15 +9,28 @@
#[derive(Clone, Trace)]
pub struct BuiltinParam {
- pub name: BuiltinParamName,
+ /// Parameter name for named call parsing
+ pub name: Option<BuiltinParamName>,
+ /// Is implementation allowed to return empty value
pub has_default: bool,
}
-/// Do not implement it directly, instead use #[builtin] macro
+/// Description of function defined by native code
+///
+/// Prefer to use #[builtin] macro, instead of manual implementation of this trait
pub trait Builtin: Trace {
+ /// Function name to be used in stack traces
fn name(&self) -> &str;
+ /// Parameter names for named calls
fn params(&self) -> &[BuiltinParam];
- fn call(&self, s: State, ctx: Context, loc: CallLocation, args: &dyn ArgsLike) -> Result<Val>;
+ /// Call the builtin
+ fn call(
+ &self,
+ s: State,
+ ctx: Context,
+ loc: CallLocation<'_>,
+ args: &dyn ArgsLike,
+ ) -> Result<Val>;
}
pub trait StaticBuiltin: Builtin + Send + Sync
@@ -35,8 +48,20 @@
}
impl NativeCallback {
#[deprecated = "prefer using builtins directly, use this interface only for bindings"]
- pub fn new(params: Vec<BuiltinParam>, handler: TraceBox<dyn NativeCallbackHandler>) -> Self {
- Self { params, handler }
+ pub fn new(
+ params: Vec<Cow<'static, str>>,
+ handler: TraceBox<dyn NativeCallbackHandler>,
+ ) -> Self {
+ Self {
+ params: params
+ .into_iter()
+ .map(|n| BuiltinParam {
+ name: Some(n),
+ has_default: false,
+ })
+ .collect(),
+ handler,
+ }
}
}
@@ -51,13 +76,20 @@
&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 mut out_args = Vec::with_capacity(self.params.len());
- for p in &self.params {
- out_args.push(args[&p.name].evaluate(s.clone())?);
- }
- self.handler.call(s, &out_args)
+ let args = args
+ .into_iter()
+ .map(|a| a.expect("legacy natives have no default params"))
+ .map(|a| a.evaluate(s.clone()))
+ .collect::<Result<Vec<Val>>>()?;
+ self.handler.call(s, &args)
}
}
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/function/native.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/function/native.rs
+++ b/crates/jrsonnet-evaluator/src/function/native.rs
@@ -1,5 +1,5 @@
use super::{arglike::ArgLike, CallLocation, FuncVal};
-use crate::{error::Result, typed::Typed, State};
+use crate::{error::Result, typed::Typed, Context, State};
pub trait NativeDesc {
type Value;
@@ -19,7 +19,8 @@
Box::new(move |s: State, $($gen),*| {
let val = val.evaluate(
s.clone(),
- s.create_default_context(),
+ // This isn't intended to be used with ArgsDesc
+ Context::default(),
CallLocation::native(),
&($($gen,)*),
true
crates/jrsonnet-evaluator/src/function/parse.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/function/parse.rs
+++ b/crates/jrsonnet-evaluator/src/function/parse.rs
@@ -1,11 +1,10 @@
+use std::mem::replace;
+
use jrsonnet_gcmodule::Trace;
use jrsonnet_interner::IStr;
use jrsonnet_parser::{LocExpr, ParamsDesc};
-use super::{
- arglike::ArgsLike,
- builtin::{BuiltinParam, BuiltinParamName},
-};
+use super::{arglike::ArgsLike, builtin::BuiltinParam};
use crate::{
destructure::destruct,
error::{Error::*, Result},
@@ -48,7 +47,10 @@
) -> Result<Context> {
let mut passed_args = GcHashMap::with_capacity(params.len());
if args.unnamed_len() > params.len() {
- throw!(TooManyArgsFunctionHas(params.len()))
+ throw!(TooManyArgsFunctionHas(
+ params.len(),
+ params.iter().map(|p| (p.0.name(), p.1.is_some())).collect()
+ ))
}
let mut filled_named = 0;
@@ -122,11 +124,8 @@
});
if !found {
throw!(FunctionParameterNotBoundInCall(
- param
- .0
- .clone()
- .name()
- .unwrap_or_else(|| "<destruct>".into())
+ param.0.clone().name(),
+ params.iter().map(|p| (p.0.name(), p.1.is_some())).collect()
));
}
}
@@ -156,28 +155,33 @@
params: &[BuiltinParam],
args: &dyn ArgsLike,
tailstrict: bool,
-) -> Result<GcHashMap<BuiltinParamName, Thunk<Val>>> {
- let mut passed_args = GcHashMap::with_capacity(params.len());
+) -> Result<Vec<Option<Thunk<Val>>>> {
+ let mut passed_args: Vec<Option<Thunk<Val>>> = vec![None; params.len()];
if args.unnamed_len() > params.len() {
- throw!(TooManyArgsFunctionHas(params.len()))
+ throw!(TooManyArgsFunctionHas(
+ params.len(),
+ params
+ .iter()
+ .map(|p| (p.name.as_ref().map(|v| v.as_ref().into()), p.has_default))
+ .collect()
+ ))
}
let mut filled_args = 0;
args.unnamed_iter(s.clone(), ctx.clone(), tailstrict, &mut |id, arg| {
- let name = params[id].name.clone();
- passed_args.insert(name, arg);
+ passed_args[id] = Some(arg);
filled_args += 1;
Ok(())
})?;
args.named_iter(s, ctx, tailstrict, &mut |name, arg| {
// FIXME: O(n) for arg existence check
- let p = params
+ let id = params
.iter()
- .find(|p| p.name == name as &str)
+ .position(|p| p.name.as_ref().map_or(false, |v| v as &str == name as &str))
.ok_or_else(|| UnknownFunctionParameter((name as &str).to_owned()))?;
- if passed_args.insert(p.name.clone(), arg).is_some() {
+ if replace(&mut passed_args[id], Some(arg)).is_some() {
throw!(BindingParameterASecondTime(name.clone()));
}
filled_args += 1;
@@ -185,8 +189,8 @@
})?;
if filled_args < params.len() {
- for param in params.iter().filter(|p| p.has_default) {
- if passed_args.contains_key(¶m.name) {
+ for (id, _) in params.iter().enumerate().filter(|(_, p)| p.has_default) {
+ if passed_args[id].is_some() {
continue;
}
filled_args += 1;
@@ -197,12 +201,22 @@
for param in params.iter().skip(args.unnamed_len()) {
let mut found = false;
args.named_names(&mut |name| {
- if name as &str == ¶m.name as &str {
+ if param
+ .name
+ .as_ref()
+ .map_or(false, |v| v as &str == name as &str)
+ {
found = true;
}
});
if !found {
- throw!(FunctionParameterNotBoundInCall(param.name.clone().into()));
+ throw!(FunctionParameterNotBoundInCall(
+ param.name.as_ref().map(|v| v.as_ref().into()),
+ params
+ .iter()
+ .map(|p| (p.name.as_ref().map(|p| p.as_ref().into()), p.has_default))
+ .collect()
+ ));
}
}
unreachable!();
@@ -215,11 +229,15 @@
/// and with unbound values causing error to be returned
pub fn parse_default_function_call(body_ctx: Context, params: &ParamsDesc) -> Result<Context> {
#[derive(Trace)]
- struct DependsOnUnbound(IStr);
+ struct DependsOnUnbound(IStr, ParamsDesc);
impl ThunkValue for DependsOnUnbound {
type Output = Val;
fn get(self: Box<Self>, _: State) -> Result<Val> {
- Err(FunctionParameterNotBoundInCall(self.0.clone()).into())
+ Err(FunctionParameterNotBoundInCall(
+ Some(self.0.clone()),
+ self.1.iter().map(|p| (p.0.name(), p.1.is_some())).collect(),
+ )
+ .into())
}
}
@@ -243,7 +261,8 @@
destruct(
¶m.0,
Thunk::new(tb!(DependsOnUnbound(
- param.0.name().unwrap_or_else(|| "<destruct>".into())
+ param.0.name().unwrap_or_else(|| "<destruct>".into()),
+ params.clone()
))),
fctx.clone(),
&mut bindings,
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);
}
@@ -53,25 +53,25 @@
impl<T: ?Sized> Borrow<T> for TraceBox<T> {
fn borrow(&self) -> &T {
- &*self.0
+ &self.0
}
}
impl<T: ?Sized> BorrowMut<T> for TraceBox<T> {
fn borrow_mut(&mut self) -> &mut T {
- &mut *self.0
+ &mut self.0
}
}
impl<T: ?Sized> AsRef<T> for TraceBox<T> {
fn as_ref(&self) -> &T {
- &*self.0
+ &self.0
}
}
impl<T: ?Sized> AsMut<T> for TraceBox<T> {
fn as_mut(&mut self) -> &mut T {
- &mut *self.0
+ &mut self.0
}
}
@@ -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/import.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/import.rs
+++ b/crates/jrsonnet-evaluator/src/import.rs
@@ -1,47 +1,60 @@
use std::{
any::Any,
+ cell::RefCell,
+ env::current_dir,
fs,
- io::Read,
+ io::{ErrorKind, Read},
path::{Path, PathBuf},
};
use fs::File;
+use jrsonnet_parser::{SourceDirectory, SourceFile, SourcePath};
use crate::{
- error::{Error::*, Result},
+ error::{
+ Error::{self, *},
+ Result,
+ },
throw,
};
/// Implements file resolution logic for `import` and `importStr`
pub trait ImportResolver {
- /// Resolves real file path, e.g. `(/home/user/manifests, b.libjsonnet)` can correspond
+ /// Resolves file path, e.g. `(/home/user/manifests, b.libjsonnet)` can correspond
/// both to `/home/user/manifests/b.libjsonnet` and to `/home/user/${vendor}/b.libjsonnet`
/// where `${vendor}` is a library path.
- fn resolve_file(&self, from: &Path, path: &str) -> Result<PathBuf>;
+ ///
+ /// `from` should only be returned from [`ImportResolver::resolve`], or from other defined file, any other value
+ /// may result in panic
+ fn resolve_from(&self, from: &SourcePath, path: &str) -> Result<SourcePath> {
+ throw!(ImportNotSupported(from.clone(), path.into()))
+ }
+ fn resolve_from_default(&self, path: &str) -> Result<SourcePath> {
+ self.resolve_from(&SourcePath::default(), path)
+ }
+ /// Resolves absolute path, doesn't supports jpath and other fancy things
+ fn resolve(&self, path: &Path) -> Result<SourcePath> {
+ throw!(AbsoluteImportNotSupported(path.to_owned()))
+ }
- fn load_file_contents(&self, resolved: &Path) -> Result<Vec<u8>>;
+ /// Load resolved file
+ /// This should only be called with value returned from [`ImportResolver::resolve_file`]/[`ImportResolver::resolve`],
+ /// this cannot be resolved using associated type, as evaluator uses object instead of generic for [`ImportResolver`]
+ fn load_file_contents(&self, resolved: &SourcePath) -> Result<Vec<u8>>;
- /// # Safety
- ///
- /// For use only in bindings, should not be used elsewhere.
- /// Implementations which are not intended to be used in bindings
- /// should panic on call to this method.
- unsafe fn as_any(&self) -> &dyn Any;
+ /// For downcasts
+ fn as_any(&self) -> &dyn Any;
}
/// Dummy resolver, can't resolve/load any file
pub struct DummyImportResolver;
impl ImportResolver for DummyImportResolver {
- fn resolve_file(&self, from: &Path, path: &str) -> Result<PathBuf> {
- throw!(ImportNotSupported(from.into(), path.into()))
- }
-
- fn load_file_contents(&self, _resolved: &Path) -> Result<Vec<u8>> {
+ fn load_file_contents(&self, _resolved: &SourcePath) -> Result<Vec<u8>> {
panic!("dummy resolver can't load any file")
}
- unsafe fn as_any(&self) -> &dyn Any {
- panic!("`as_any($self)` is not supported by dummy resolver")
+ fn as_any(&self) -> &dyn Any {
+ self
}
}
#[allow(clippy::use_self)]
@@ -56,34 +69,91 @@
pub struct FileImportResolver {
/// Library directories to search for file.
/// Referred to as `jpath` in original jsonnet implementation.
- pub library_paths: Vec<PathBuf>,
+ library_paths: RefCell<Vec<PathBuf>>,
+}
+impl FileImportResolver {
+ pub fn new(jpath: Vec<PathBuf>) -> Self {
+ Self {
+ library_paths: RefCell::new(jpath),
+ }
+ }
+ /// Dynamically add new jpath, used by bindings
+ pub fn add_jpath(&self, path: PathBuf) {
+ self.library_paths.borrow_mut().push(path);
+ }
}
impl ImportResolver for FileImportResolver {
- fn resolve_file(&self, from: &Path, path: &str) -> Result<PathBuf> {
- let mut direct = from.to_path_buf();
+ fn resolve_from(&self, from: &SourcePath, path: &str) -> Result<SourcePath> {
+ let mut direct = if let Some(f) = from.downcast_ref::<SourceFile>() {
+ let mut o = f.path().to_owned();
+ o.pop();
+ o
+ } else if let Some(d) = from.downcast_ref::<SourceDirectory>() {
+ d.path().to_owned()
+ } else if from.is_default() {
+ current_dir().map_err(|e| Error::ImportIo(e.to_string()))?
+ } else {
+ unreachable!("resolver can't return this path")
+ };
direct.push(path);
- if direct.exists() {
- Ok(direct.canonicalize().map_err(|e| ImportIo(e.to_string()))?)
+ if direct.is_file() {
+ Ok(SourcePath::new(SourceFile::new(
+ direct.canonicalize().map_err(|e| ImportIo(e.to_string()))?,
+ )))
} else {
- for library_path in &self.library_paths {
+ for library_path in self.library_paths.borrow().iter() {
let mut cloned = library_path.clone();
cloned.push(path);
if cloned.exists() {
- return Ok(cloned.canonicalize().map_err(|e| ImportIo(e.to_string()))?);
+ return Ok(SourcePath::new(SourceFile::new(
+ cloned.canonicalize().map_err(|e| ImportIo(e.to_string()))?,
+ )));
}
}
- throw!(ImportFileNotFound(from.to_owned(), path.to_owned()))
+ throw!(ImportFileNotFound(from.clone(), path.to_owned()))
}
}
+ fn resolve(&self, path: &Path) -> Result<SourcePath> {
+ let meta = match fs::metadata(path) {
+ Ok(v) => v,
+ Err(e) if e.kind() == ErrorKind::NotFound => {
+ throw!(AbsoluteImportFileNotFound(path.to_owned()))
+ }
+ Err(e) => throw!(Error::ImportIo(e.to_string())),
+ };
+ if meta.is_file() {
+ Ok(SourcePath::new(SourceFile::new(
+ path.canonicalize().map_err(|e| ImportIo(e.to_string()))?,
+ )))
+ } else if meta.is_dir() {
+ Ok(SourcePath::new(SourceDirectory::new(
+ path.canonicalize().map_err(|e| ImportIo(e.to_string()))?,
+ )))
+ } else {
+ unreachable!("this can't be a symlink")
+ }
+ }
- fn load_file_contents(&self, id: &Path) -> Result<Vec<u8>> {
- let mut file = File::open(id).map_err(|_e| ResolvedFileNotFound(id.to_owned()))?;
+ fn load_file_contents(&self, id: &SourcePath) -> Result<Vec<u8>> {
+ let path = if let Some(f) = id.downcast_ref::<SourceFile>() {
+ f.path()
+ } else if id.downcast_ref::<SourceDirectory>().is_some() || id.is_default() {
+ throw!(Error::ImportIsADirectory(id.clone()))
+ } else {
+ unreachable!("other types are not supported in resolve");
+ };
+ let mut file = File::open(path).map_err(|_e| ResolvedFileNotFound(id.clone()))?;
let mut out = Vec::new();
file.read_to_end(&mut out)
.map_err(|e| ImportIo(e.to_string()))?;
Ok(out)
}
- unsafe fn as_any(&self) -> &dyn Any {
- panic!("this resolver can't be used as any")
+
+ fn as_any(&self) -> &dyn Any {
+ self
+ }
+
+ fn resolve_from_default(&self, path: &str) -> Result<SourcePath> {
+ self.resolve_from(&SourcePath::default(), path)
}
}
crates/jrsonnet-evaluator/src/integrations/serde.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/integrations/serde.rs
+++ b/crates/jrsonnet-evaluator/src/integrations/serde.rs
@@ -16,7 +16,7 @@
Self::Null => Val::Null,
Self::Bool(v) => Val::Bool(v),
Self::Number(n) => Val::Num(n.as_f64().ok_or_else(|| {
- RuntimeError(format!("json number can't be represented as jsonnet: {}", n).into())
+ RuntimeError(format!("json number can't be represented as jsonnet: {n}").into())
})?),
Self::String(s) => Val::Str((&s as &str).into()),
Self::Array(a) => {
crates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth--- 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,
@@ -37,17 +51,17 @@
mod integrations;
mod map;
mod obj;
-mod stdlib;
+pub mod stdlib;
pub mod trace;
pub mod typed;
pub mod val;
use std::{
- borrow::Cow,
+ any::Any,
cell::{Ref, RefCell, RefMut},
collections::HashMap,
fmt::{self, Debug},
- path::{Path, PathBuf},
+ path::Path,
rc::Rc,
};
@@ -55,36 +69,44 @@
pub use dynamic::*;
use error::{Error::*, LocError, Result, StackTraceElement};
pub use evaluate::*;
-use function::{builtin::Builtin, CallLocation, TlaArg};
+use function::{CallLocation, TlaArg};
use gc::{GcHashMap, TraceBox};
use hashbrown::hash_map::RawEntryMut;
pub use import::*;
use jrsonnet_gcmodule::{Cc, Trace};
-use jrsonnet_interner::IBytes;
-pub use jrsonnet_interner::IStr;
+pub use jrsonnet_interner::{IBytes, IStr};
pub use jrsonnet_parser as parser;
use jrsonnet_parser::*;
pub use obj::*;
-use trace::{location_to_offset, offset_to_location, CodeLocation, CompactFormat, TraceFormat};
+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<ObjValue>, this: Option<ObjValue>) -> Result<Self::Bound>;
}
+/// 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<TraceBox<dyn Unbound<Bound = Thunk<Val>>>>),
+pub enum MaybeUnbound {
+ /// Value needs to be bound to `this`/`super`
+ Unbound(Cc<TraceBox<dyn Unbound<Bound = Thunk<Val>>>>),
+ /// Value is object-independent
Bound(Thunk<Val>),
}
-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,
@@ -92,25 +114,44 @@
this: Option<ObjValue>,
) -> Result<Thunk<Val>> {
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.
+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;
+}
+
+/// Context initializer which adds nothing.
+pub struct DummyContextInitializer;
+impl ContextInitializer for DummyContextInitializer {
+ fn initialize(&self, _state: State, _for_file: Source) -> Context {
+ Context::default()
+ }
+ fn as_any(&self) -> &dyn Any {
+ self
+ }
+}
+
+/// Dynamically reconfigurable evaluation settings
pub struct EvaluationSettings {
/// Limits recursion by limiting the number of stack frames
pub max_stack: usize,
/// Limits amount of stack trace items preserved
pub max_trace: usize,
- /// Used for s`td.extVar`
- pub ext_vars: HashMap<IStr, TlaArg>,
- /// Used for ext.native
- pub ext_natives: HashMap<IStr, Cc<TraceBox<dyn Builtin>>>,
/// TLA vars
pub tla_vars: HashMap<IStr, TlaArg>,
- /// Global variables are inserted in default context
- pub globals: HashMap<IStr, Val>,
+ /// Context initializer, which will be used for imports and everything
+ /// [`NoopContextInitializer`] is used by default, most likely you want to have `jrsonnet-stdlib`
+ pub context_initializer: Box<dyn ContextInitializer>,
/// Used to resolve file locations/contents
pub import_resolver: Box<dyn ImportResolver>,
/// Used in manifestification functions
@@ -123,9 +164,7 @@
Self {
max_stack: 200,
max_trace: 20,
- globals: HashMap::default(),
- ext_vars: HashMap::default(),
- ext_natives: HashMap::default(),
+ context_initializer: Box::new(DummyContextInitializer),
tla_vars: HashMap::default(),
import_resolver: Box::new(DummyImportResolver),
manifest_format: ManifestFormat::Json {
@@ -151,9 +190,7 @@
breakpoints: Breakpoints,
/// Contains file source codes and evaluation results for imports and pretty-printed stacktraces
- files: GcHashMap<PathBuf, FileData>,
- /// Contains tla arguments and others, which aren't needed to be obtained by name
- volatile_files: GcHashMap<String, String>,
+ files: GcHashMap<SourcePath, FileData>,
}
struct FileData {
string: Option<IStr>,
@@ -229,7 +266,8 @@
pub struct State(Rc<EvaluationStateInternals>);
impl State {
- pub fn import_str(&self, path: PathBuf) -> Result<IStr> {
+ /// Should only be called with path retrieved from [`resolve_path`], may panic otherwise
+ pub fn import_resolved_str(&self, path: SourcePath) -> Result<IStr> {
let mut data = self.data_mut();
let mut file = data.files.raw_entry_mut().from_key(&path);
@@ -263,7 +301,8 @@
}
Ok(file.string.as_ref().expect("just set").clone())
}
- pub fn import_bin(&self, path: PathBuf) -> Result<IBytes> {
+ /// Should only be called with path retrieved from [`resolve_path`], may panic otherwise
+ pub fn import_resolved_bin(&self, path: SourcePath) -> Result<IBytes> {
let mut data = self.data_mut();
let mut file = data.files.raw_entry_mut().from_key(&path);
@@ -289,7 +328,8 @@
}
Ok(file.bytes.as_ref().expect("just set").clone())
}
- pub fn import(&self, path: PathBuf) -> Result<Val> {
+ /// Should only be called with path retrieved from [`resolve_path`], may panic otherwise
+ pub fn import_resolved(&self, path: SourcePath) -> Result<Val> {
let mut data = self.data_mut();
let mut file = data.files.raw_entry_mut().from_key(&path);
@@ -323,7 +363,7 @@
);
}
let code = file.string.as_ref().expect("just set");
- let file_name = Source::new(path.clone()).expect("resolver should return correct name");
+ let file_name = Source::new(path.clone(), code.clone());
if file.parsed.is_none() {
file.parsed = Some(
jrsonnet_parser::parse(
@@ -333,8 +373,7 @@
},
)
.map_err(|e| ImportSyntaxError {
- path: file_name,
- source_code: code.clone(),
+ path: file_name.clone(),
error: Box::new(e),
})?,
);
@@ -346,7 +385,11 @@
file.evaluating = true;
// Dropping file here, as it borrows data, which may be used in evaluation
drop(data);
- let res = evaluate(self.clone(), self.create_default_context(), &parsed);
+ let res = evaluate(
+ self.clone(),
+ self.create_default_context(file_name),
+ &parsed,
+ );
let mut data = self.data_mut();
let mut file = data.files.raw_entry_mut().from_key(&path);
@@ -365,58 +408,26 @@
}
}
- pub fn get_source(&self, name: Source) -> Option<String> {
- let data = self.data();
- match name.repr() {
- Ok(real) => data
- .files
- .get(real)
- .and_then(|f| f.string.as_ref())
- .map(ToString::to_string),
- Err(e) => data.volatile_files.get(e).map(ToOwned::to_owned),
- }
- }
- pub fn map_source_locations(&self, file: Source, locs: &[u32]) -> Vec<CodeLocation> {
- offset_to_location(&self.get_source(file).unwrap_or_else(|| "".into()), locs)
- }
- pub fn map_from_source_location(
- &self,
- file: Source,
- line: usize,
- column: usize,
- ) -> Option<usize> {
- location_to_offset(
- &self.get_source(file).expect("file not found"),
- line,
- column,
- )
+ /// Has same semantics as `import 'path'` called from `from` file
+ pub fn import_from(&self, from: &SourcePath, path: &str) -> Result<Val> {
+ let resolved = self.resolve_from(from, path)?;
+ self.import_resolved(resolved)
}
- /// Adds standard library global variable (std) to this evaluator
- pub fn with_stdlib(&self) -> &Self {
- let val = evaluate(
- self.clone(),
- self.create_default_context(),
- &stdlib::get_parsed_stdlib(),
- )
- .expect("std should not fail");
- self.settings_mut().globals.insert("std".into(), val);
- self
+ pub fn import(&self, path: impl AsRef<Path>) -> Result<Val> {
+ let resolved = self.resolve(path)?;
+ self.import_resolved(resolved)
}
/// Creates context with all passed global variables
- pub fn create_default_context(&self) -> Context {
- let globals = &self.settings().globals;
- let mut new_bindings = GcHashMap::with_capacity(globals.len());
- for (name, value) in globals.iter() {
- new_bindings.insert(name.clone(), Thunk::evaluated(value.clone()));
- }
- Context::new().extend(new_bindings, None, None, None)
+ pub fn create_default_context(&self, source: Source) -> Context {
+ let context_initializer = &self.settings().context_initializer;
+ context_initializer.initialize(self.clone(), source)
}
/// Executes code creating a new stack frame
pub fn push<T>(
&self,
- e: CallLocation,
+ e: CallLocation<'_>,
frame_desc: impl FnOnce() -> String,
f: impl FnOnce() -> Result<T>,
) -> Result<T> {
@@ -545,7 +556,10 @@
|| {
func.evaluate(
self.clone(),
- self.create_default_context(),
+ self.create_default_context(Source::new_virtual(
+ "<tla>".into(),
+ IStr::empty(),
+ )),
CallLocation::native(),
&self.settings().tla_vars,
true,
@@ -559,16 +573,13 @@
/// Internals
impl State {
- fn data(&self) -> Ref<EvaluationData> {
- self.0.data.borrow()
- }
- fn data_mut(&self) -> RefMut<EvaluationData> {
+ fn data_mut(&self) -> RefMut<'_, EvaluationData> {
self.0.data.borrow_mut()
}
- pub fn settings(&self) -> Ref<EvaluationSettings> {
+ pub fn settings(&self) -> Ref<'_, EvaluationSettings> {
self.0.settings.borrow()
}
- pub fn settings_mut(&self) -> RefMut<EvaluationSettings> {
+ pub fn settings_mut(&self) -> RefMut<'_, EvaluationSettings> {
self.0.settings.borrow_mut()
}
}
@@ -576,8 +587,9 @@
/// Raw methods evaluate passed values but don't perform TLA execution
impl State {
/// Parses and evaluates the given snippet
- pub fn evaluate_snippet(&self, name: String, code: String) -> Result<Val> {
- let source = Source::new_virtual(Cow::Owned(name.clone()));
+ pub fn evaluate_snippet(&self, name: impl Into<IStr>, code: impl Into<IStr>) -> Result<Val> {
+ let code = code.into();
+ let source = Source::new_virtual(name.into(), code.clone());
let parsed = jrsonnet_parser::parse(
&code,
&ParserSettings {
@@ -585,48 +597,15 @@
},
)
.map_err(|e| ImportSyntaxError {
- path: source,
- source_code: code.clone().into(),
+ path: source.clone(),
error: Box::new(e),
})?;
- self.data_mut().volatile_files.insert(name, code);
- evaluate(self.clone(), self.create_default_context(), &parsed)
+ evaluate(self.clone(), self.create_default_context(source), &parsed)
}
}
/// Settings utilities
impl State {
- pub fn add_ext_var(&self, name: IStr, value: Val) {
- self.settings_mut()
- .ext_vars
- .insert(name, TlaArg::Val(value));
- }
- pub fn add_ext_str(&self, name: IStr, value: IStr) {
- self.settings_mut()
- .ext_vars
- .insert(name, TlaArg::String(value));
- }
- pub fn add_ext_code(&self, name: &str, code: String) -> Result<()> {
- let source_name = format!("<extvar:{}>", name);
- let source = Source::new_virtual(Cow::Owned(source_name.clone()));
- let parsed = jrsonnet_parser::parse(
- &code,
- &ParserSettings {
- file_name: source.clone(),
- },
- )
- .map_err(|e| ImportSyntaxError {
- path: source,
- source_code: code.clone().into(),
- error: Box::new(e),
- })?;
- self.data_mut().volatile_files.insert(source_name, code);
- self.settings_mut()
- .ext_vars
- .insert(name.into(), TlaArg::Code(parsed));
- Ok(())
- }
-
pub fn add_tla(&self, name: IStr, value: Val) {
self.settings_mut()
.tla_vars
@@ -638,8 +617,8 @@
.insert(name, TlaArg::String(value));
}
pub fn add_tla_code(&self, name: IStr, code: &str) -> Result<()> {
- let source_name = format!("<top-level-arg:{}>", name);
- let source = Source::new_virtual(Cow::Owned(source_name.clone()));
+ let source_name = format!("<top-level-arg:{name}>");
+ let source = Source::new_virtual(source_name.into(), code.into());
let parsed = jrsonnet_parser::parse(
code,
&ParserSettings {
@@ -648,33 +627,33 @@
)
.map_err(|e| ImportSyntaxError {
path: source,
- source_code: code.into(),
error: Box::new(e),
})?;
- self.data_mut()
- .volatile_files
- .insert(source_name, code.to_owned());
self.settings_mut()
.tla_vars
.insert(name, TlaArg::Code(parsed));
Ok(())
}
- pub fn resolve_file(&self, from: &Path, path: &str) -> Result<PathBuf> {
- self.settings()
- .import_resolver
- .resolve_file(from, path.as_ref())
+ // Only panics in case of [`ImportResolver`] contract violation
+ #[allow(clippy::missing_panics_doc)]
+ pub fn resolve_from(&self, from: &SourcePath, path: &str) -> Result<SourcePath> {
+ self.import_resolver().resolve_from(from, path.as_ref())
}
- pub fn import_resolver(&self) -> Ref<dyn ImportResolver> {
+ // Only panics in case of [`ImportResolver`] contract violation
+ #[allow(clippy::missing_panics_doc)]
+ pub fn resolve(&self, path: impl AsRef<Path>) -> Result<SourcePath> {
+ self.import_resolver().resolve(path.as_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<dyn ImportResolver>) {
self.settings_mut().import_resolver = resolver;
}
-
- pub fn add_native(&self, name: IStr, cb: Cc<TraceBox<dyn Builtin>>) {
- self.settings_mut().ext_natives.insert(name, cb);
+ pub fn context_initializer(&self) -> Ref<'_, dyn ContextInitializer> {
+ Ref::map(self.settings(), |s| &*s.context_initializer)
}
pub fn manifest_format(&self) -> ManifestFormat {
@@ -684,7 +663,7 @@
self.settings_mut().manifest_format = format;
}
- pub fn trace_format(&self) -> Ref<dyn TraceFormat> {
+ pub fn trace_format(&self) -> Ref<'_, dyn TraceFormat> {
Ref::map(self.settings(), |s| &*s.trace_format)
}
pub fn set_trace_format(&self, format: Box<dyn TraceFormat>) {
crates/jrsonnet-evaluator/src/map.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/map.rs
+++ b/crates/jrsonnet-evaluator/src/map.rs
@@ -23,6 +23,13 @@
}
}
+ pub(crate) fn new(layer: GcHashMap<IStr, Thunk<Val>>) -> Self {
+ Self(Cc::new(LayeredHashMapInternals {
+ parent: None,
+ current: layer,
+ }))
+ }
+
pub fn extend(self, new_layer: GcHashMap<IStr, Thunk<Val>>) -> Self {
Self(Cc::new(LayeredHashMapInternals {
parent: Some(self),
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>,
}
@@ -109,7 +109,6 @@
}
// Field => This
-type CacheKey = (IStr, WeakObjValue);
#[derive(Trace)]
enum CacheValue {
@@ -129,7 +128,7 @@
assertions: Cc<Vec<TraceBox<dyn ObjectAssertion>>>,
assertions_ran: RefCell<GcHashSet<ObjValue>>,
this_entries: Cc<GcHashMap<IStr, ObjMember>>,
- value_cache: RefCell<GcHashMap<CacheKey, CacheValue>>,
+ value_cache: RefCell<GcHashMap<IStr, CacheValue>>,
}
#[derive(Clone, Trace)]
@@ -157,9 +156,9 @@
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(super_obj) = self.0.sup.as_ref() {
if f.alternate() {
- write!(f, "{:#?}", super_obj)?;
+ write!(f, "{super_obj:#?}")?;
} else {
- write!(f, "{:?}", super_obj)?;
+ write!(f, "{super_obj:?}")?;
}
write!(f, " + ")?;
}
@@ -209,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())
}
@@ -240,6 +239,8 @@
}
/// Run callback for every field found in object
+ ///
+ /// Returns true if ended prematurely
pub(crate) fn enum_fields(
&self,
depth: SuperDepth,
@@ -369,15 +370,7 @@
pub fn get(&self, s: State, key: IStr) -> Result<Option<Val>> {
self.run_assertions(s.clone())?;
- self.get_raw(s, key, self.0.this.clone().unwrap_or_else(|| self.clone()))
- }
-
- // pub fn extend_with(self, key: )
-
- fn get_raw(&self, s: State, key: IStr, real_this: Self) -> Result<Option<Val>> {
- let cache_key = (key.clone(), WeakObjValue(real_this.0.downgrade()));
-
- if let Some(v) = self.0.value_cache.borrow().get(&cache_key) {
+ if let Some(v) = self.0.value_cache.borrow().get(&key) {
return Ok(match v {
CacheValue::Cached(v) => Some(v.clone()),
CacheValue::NotFound => None,
@@ -388,26 +381,37 @@
self.0
.value_cache
.borrow_mut()
- .insert(cache_key.clone(), CacheValue::Pending);
- let fill_error = |e: LocError| {
- self.0
- .value_cache
- .borrow_mut()
- .insert(cache_key.clone(), CacheValue::Errored(e.clone()));
- e
- };
- let value = match (self.0.this_entries.get(&key), &self.0.sup) {
- (Some(k), None) => Ok(Some(
- self.evaluate_this(s, k, real_this).map_err(fill_error)?,
- )),
+ .insert(key.clone(), CacheValue::Pending);
+ let value = self
+ .get_raw(
+ s,
+ key.clone(),
+ self.0.this.clone().unwrap_or_else(|| self.clone()),
+ )
+ .map_err(|e| {
+ self.0
+ .value_cache
+ .borrow_mut()
+ .insert(key.clone(), CacheValue::Errored(e.clone()));
+ e
+ })?;
+ self.0.value_cache.borrow_mut().insert(
+ key,
+ value
+ .as_ref()
+ .map_or(CacheValue::NotFound, |v| CacheValue::Cached(v.clone())),
+ );
+ Ok(value)
+ }
+
+ fn get_raw(&self, s: State, key: IStr, real_this: Self) -> Result<Option<Val>> {
+ match (self.0.this_entries.get(&key), &self.0.sup) {
+ (Some(k), None) => Ok(Some(self.evaluate_this(s, k, real_this)?)),
(Some(k), Some(super_obj)) => {
- let our = self
- .evaluate_this(s.clone(), k, real_this.clone())
- .map_err(fill_error)?;
+ let our = self.evaluate_this(s.clone(), k, real_this.clone())?;
if k.add {
super_obj
- .get_raw(s.clone(), key, real_this)
- .map_err(fill_error)?
+ .get_raw(s.clone(), key, real_this)?
.map_or(Ok(Some(our.clone())), |v| {
Ok(Some(evaluate_add_op(s.clone(), &v, &our)?))
})
@@ -418,15 +422,6 @@
(None, Some(super_obj)) => super_obj.get_raw(s, key, real_this),
(None, None) => Ok(None),
}
- .map_err(fill_error)?;
- self.0.value_cache.borrow_mut().insert(
- cache_key,
- match &value {
- Some(v) => CacheValue::Cached(v.clone()),
- None => CacheValue::NotFound,
- },
- );
- Ok(value)
}
fn evaluate_this(&self, s: State, v: &ObjMember, real_this: Self) -> Result<Val> {
v.invoke
@@ -507,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)
@@ -565,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,
@@ -581,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);
@@ -608,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/expr.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/stdlib/expr.rs
+++ /dev/null
@@ -1,28 +0,0 @@
-use std::borrow::Cow;
-
-use jrsonnet_parser::{LocExpr, ParserSettings, Source};
-
-thread_local! {
- /// To avoid parsing again when issued from the same thread
- #[allow(unreachable_code)]
- static PARSED_STDLIB: LocExpr = {
- #[cfg(feature = "serialized-stdlib")]
- {
- // Should not panic, stdlib.bincode is generated in build.rs
- return bincode::deserialize(include_bytes!(concat!(env!("OUT_DIR"), "/stdlib.bincode")))
- .unwrap();
- }
-
- jrsonnet_parser::parse(
- jrsonnet_stdlib::STDLIB_STR,
- &ParserSettings {
- file_name: Source::new_virtual(Cow::Borrowed("<std>")),
- },
- )
- .unwrap()
- }
-}
-
-pub fn get_parsed_stdlib() -> LocExpr {
- PARSED_STDLIB.with(Clone::clone)
-}
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);
}
@@ -45,7 +45,7 @@
let mut i = 1;
while i < bytes.len() {
if bytes[i] == b')' {
- return Ok((&str[1..i as usize], &str[i as usize + 1..]));
+ return Ok((&str[1..i], &str[i + 1..]));
}
i += 1;
}
@@ -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;
@@ -285,7 +285,7 @@
#[inline]
pub fn render_integer(
out: &mut String,
- iv: i64,
+ iv: f64,
padding: usize,
precision: usize,
blank: bool,
@@ -294,20 +294,23 @@
prefix: &str,
caps: bool,
) {
+ let radix = radix as f64;
+ let iv = iv.floor();
// Digit char indexes in reverse order, i.e
// for radix = 16 and n = 12f: [15, 2, 1]
- let digits = if iv == 0 {
+ let digits = if iv == 0.0 {
vec![0u8]
} else {
let mut v = iv.abs();
let mut nums = Vec::with_capacity(1);
- while v > 0 {
+ while v != 0.0 {
nums.push((v % radix) as u8);
- v /= radix;
+ v = (v / radix).floor();
}
nums
};
- let neg = iv < 0;
+ let neg = iv < 0.0;
+ #[allow(clippy::bool_to_int_with_if)]
let zp = padding.saturating_sub(if neg || blank || sign { 1 } else { 0 });
let zp2 = zp
.max(precision)
@@ -335,7 +338,7 @@
pub fn render_decimal(
out: &mut String,
- iv: i64,
+ iv: f64,
padding: usize,
precision: usize,
blank: bool,
@@ -345,7 +348,7 @@
}
pub fn render_octal(
out: &mut String,
- iv: i64,
+ iv: f64,
padding: usize,
precision: usize,
alt: bool,
@@ -360,7 +363,7 @@
blank,
sign,
8,
- if alt && iv != 0 { "0" } else { "" },
+ if alt && iv != 0.0 { "0" } else { "" },
false,
);
}
@@ -368,7 +371,7 @@
#[allow(clippy::fn_params_excessive_bools)]
pub fn render_hexadecimal(
out: &mut String,
- iv: i64,
+ iv: f64,
padding: usize,
precision: usize,
alt: bool,
@@ -404,9 +407,10 @@
ensure_pt: bool,
trailing: bool,
) {
+ #[allow(clippy::bool_to_int_with_if)]
let dot_size = if precision == 0 && !ensure_pt { 0 } else { 1 };
padding = padding.saturating_sub(dot_size + precision);
- render_decimal(out, n.floor() as i64, padding, 0, blank, sign);
+ render_decimal(out, n.floor(), padding, 0, blank, sign);
if precision == 0 {
if ensure_pt {
out.push('.');
@@ -420,7 +424,7 @@
if trailing || frac > 0.0 {
out.push('.');
let mut frac_str = String::new();
- render_decimal(&mut frac_str, frac as i64, precision, 0, false, false);
+ render_decimal(&mut frac_str, frac, precision, 0, false, false);
let mut trim = frac_str.len();
if !trailing {
for b in frac_str.as_bytes().iter().rev() {
@@ -454,7 +458,7 @@
n / 10.0_f64.powf(exponent)
};
let mut exponent_str = String::new();
- render_decimal(&mut exponent_str, exponent as i64, 3, 0, false, true);
+ render_decimal(&mut exponent_str, exponent, 3, 0, false, true);
// +1 for e
padding = padding.saturating_sub(exponent_str.len() + 1);
@@ -471,15 +475,12 @@
s: State,
out: &mut String,
value: &Val,
- code: &Code,
+ code: &Code<'_>,
width: usize,
precision: Option<usize>,
) -> Result<()> {
let clfags = &code.cflags;
- let (fpprec, iprec) = match precision {
- Some(v) => (v, v),
- None => (6, 0),
- };
+ let (fpprec, iprec) = precision.map_or((6, 0), |v| (v, v));
let padding = if clfags.zero && !clfags.left {
width
} else {
@@ -495,7 +496,7 @@
let value = f64::from_untyped(value.clone(), s)?;
render_decimal(
&mut tmp_out,
- value as i64,
+ value,
padding,
iprec,
clfags.blank,
@@ -506,7 +507,7 @@
let value = f64::from_untyped(value.clone(), s)?;
render_octal(
&mut tmp_out,
- value as i64,
+ value,
padding,
iprec,
clfags.alt,
@@ -518,7 +519,7 @@
let value = f64::from_untyped(value.clone(), s)?;
render_hexadecimal(
&mut tmp_out,
- value as i64,
+ value,
padding,
iprec,
clfags.alt,
@@ -584,8 +585,10 @@
}
}
ConvTypeV::Char => match value.clone() {
- Val::Num(n) => tmp_out
- .push(std::char::from_u32(n as u32).ok_or(InvalidUnicodeCodepointGot(n as u32))?),
+ Val::Num(n) => tmp_out.push(
+ std::char::from_u32(n as u32)
+ .ok_or_else(|| InvalidUnicodeCodepointGot(n as u32))?,
+ ),
Val::Str(s) => {
if s.chars().count() != 1 {
throw!(RuntimeError(
@@ -774,10 +777,7 @@
format_arr(s.clone(), "%+-4o", &[Val::Num(8.0)]).unwrap(),
"+10 "
);
- assert_eq!(
- format_arr(s.clone(), "%+-04o", &[Val::Num(8.0)]).unwrap(),
- "+10 "
- );
+ assert_eq!(format_arr(s, "%+-04o", &[Val::Num(8.0)]).unwrap(), "+10 ");
}
#[test]
crates/jrsonnet-evaluator/src/stdlib/manifest.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/stdlib/manifest.rs
+++ b/crates/jrsonnet-evaluator/src/stdlib/manifest.rs
@@ -49,7 +49,7 @@
}
Val::Null => buf.push_str("null"),
Val::Str(s) => escape_string_json_buf(s, buf),
- Val::Num(n) => write!(buf, "{}", n).unwrap(),
+ Val::Num(n) => write!(buf, "{n}").unwrap(),
Val::Arr(items) => {
buf.push('[');
if !items.is_empty() {
@@ -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/stdlib/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/stdlib/mod.rs
+++ b/crates/jrsonnet-evaluator/src/stdlib/mod.rs
@@ -1,742 +1,24 @@
// All builtins should return results
#![allow(clippy::unnecessary_wraps)]
-use std::collections::HashMap;
-
use format::{format_arr, format_obj};
-use jrsonnet_gcmodule::Cc;
-use jrsonnet_interner::{IBytes, IStr};
-use serde::Deserialize;
-use serde_yaml_with_quirks::DeserializingQuirks;
-
-use crate::{
- error::{Error::*, Result},
- function::{builtin::StaticBuiltin, ArgLike, CallLocation, FuncVal},
- operator::evaluate_mod_op,
- stdlib::manifest::{manifest_yaml_ex, ManifestYamlOptions},
- throw,
- typed::{Any, BoundedUsize, Either2, Either4, PositiveF64, Typed, VecVal, M1},
- val::{equals, primitive_equals, ArrValue, IndexableVal, Slice},
- Either, ObjValue, State, Val,
-};
-
-pub mod expr;
-pub use expr::*;
+use jrsonnet_interner::IStr;
-use self::manifest::{escape_string_json, manifest_json_ex, ManifestJsonOptions, ManifestType};
+use crate::{error::Result, function::CallLocation, State, Val};
pub mod format;
pub mod manifest;
-pub mod sort;
pub fn std_format(s: State, str: IStr, vals: Val) -> Result<String> {
s.push(
CallLocation::native(),
- || format!("std.format of {}", str),
+ || format!("std.format of {str}"),
|| {
Ok(match vals {
Val::Arr(vals) => format_arr(s.clone(), &str, &vals.evaluated(s.clone())?)?,
Val::Obj(obj) => format_obj(s.clone(), &str, &obj)?,
o => format_arr(s.clone(), &str, &[o])?,
})
- },
- )
-}
-
-pub fn std_slice(
- indexable: IndexableVal,
- index: Option<BoundedUsize<0, { i32::MAX as usize }>>,
- end: Option<BoundedUsize<0, { i32::MAX as usize }>>,
- step: Option<BoundedUsize<1, { i32::MAX as usize }>>,
-) -> Result<Val> {
- match &indexable {
- IndexableVal::Str(s) => {
- let index = index.as_deref().copied().unwrap_or(0);
- let end = end.as_deref().copied().unwrap_or(usize::MAX);
- let step = step.as_deref().copied().unwrap_or(1);
-
- if index >= end {
- return Ok(Val::Str("".into()));
- }
-
- Ok(Val::Str(
- (s.chars()
- .skip(index)
- .take(end - index)
- .step_by(step)
- .collect::<String>())
- .into(),
- ))
- }
- IndexableVal::Arr(arr) => {
- let index = index.as_deref().copied().unwrap_or(0);
- let end = end.as_deref().copied().unwrap_or(usize::MAX).min(arr.len());
- let step = step.as_deref().copied().unwrap_or(1);
-
- if index >= end {
- return Ok(Val::Arr(ArrValue::new_eager()));
- }
-
- Ok(Val::Arr(ArrValue::Slice(Box::new(Slice {
- inner: arr.clone(),
- from: index as u32,
- to: end as u32,
- step: step as u32,
- }))))
- }
- }
-}
-
-type BuiltinsType = HashMap<IStr, &'static dyn StaticBuiltin>;
-
-thread_local! {
- pub static BUILTINS: BuiltinsType = {
- [
- ("length".into(), builtin_length::INST),
- ("type".into(), builtin_type::INST),
- ("makeArray".into(), builtin_make_array::INST),
- ("codepoint".into(), builtin_codepoint::INST),
- ("objectFieldsEx".into(), builtin_object_fields_ex::INST),
- ("objectHasEx".into(), builtin_object_has_ex::INST),
- ("slice".into(), builtin_slice::INST),
- ("substr".into(), builtin_substr::INST),
- ("primitiveEquals".into(), builtin_primitive_equals::INST),
- ("equals".into(), builtin_equals::INST),
- ("modulo".into(), builtin_modulo::INST),
- ("mod".into(), builtin_mod::INST),
- ("floor".into(), builtin_floor::INST),
- ("ceil".into(), builtin_ceil::INST),
- ("log".into(), builtin_log::INST),
- ("pow".into(), builtin_pow::INST),
- ("sqrt".into(), builtin_sqrt::INST),
- ("sin".into(), builtin_sin::INST),
- ("cos".into(), builtin_cos::INST),
- ("tan".into(), builtin_tan::INST),
- ("asin".into(), builtin_asin::INST),
- ("acos".into(), builtin_acos::INST),
- ("atan".into(), builtin_atan::INST),
- ("exp".into(), builtin_exp::INST),
- ("mantissa".into(), builtin_mantissa::INST),
- ("exponent".into(), builtin_exponent::INST),
- ("extVar".into(), builtin_ext_var::INST),
- ("native".into(), builtin_native::INST),
- ("filter".into(), builtin_filter::INST),
- ("map".into(), builtin_map::INST),
- ("flatMap".into(), builtin_flatmap::INST),
- ("foldl".into(), builtin_foldl::INST),
- ("foldr".into(), builtin_foldr::INST),
- ("sort".into(), builtin_sort::INST),
- ("format".into(), builtin_format::INST),
- ("range".into(), builtin_range::INST),
- ("char".into(), builtin_char::INST),
- ("encodeUTF8".into(), builtin_encode_utf8::INST),
- ("decodeUTF8".into(), builtin_decode_utf8::INST),
- ("md5".into(), builtin_md5::INST),
- ("base64".into(), builtin_base64::INST),
- ("base64DecodeBytes".into(), builtin_base64_decode_bytes::INST),
- ("base64Decode".into(), builtin_base64_decode::INST),
- ("trace".into(), builtin_trace::INST),
- ("join".into(), builtin_join::INST),
- ("escapeStringJson".into(), builtin_escape_string_json::INST),
- ("manifestJsonEx".into(), builtin_manifest_json_ex::INST),
- ("manifestYamlDoc".into(), builtin_manifest_yaml_doc::INST),
- ("reverse".into(), builtin_reverse::INST),
- ("strReplace".into(), builtin_str_replace::INST),
- ("splitLimit".into(), builtin_splitlimit::INST),
- ("parseJson".into(), builtin_parse_json::INST),
- ("parseYaml".into(), builtin_parse_yaml::INST),
- ("asciiUpper".into(), builtin_ascii_upper::INST),
- ("asciiLower".into(), builtin_ascii_lower::INST),
- ("member".into(), builtin_member::INST),
- ("count".into(), builtin_count::INST),
- ("any".into(), builtin_any::INST),
- ("all".into(), builtin_all::INST),
- ].iter().cloned().collect()
- };
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_length(x: Either![IStr, ArrValue, ObjValue, FuncVal]) -> Result<usize> {
- use Either4::*;
- Ok(match x {
- A(x) => x.chars().count(),
- B(x) => x.len(),
- C(x) => x.len(),
- D(f) => f.params_len(),
- })
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_type(x: Any) -> Result<IStr> {
- Ok(x.0.value_type().name().into())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_make_array(s: State, sz: usize, func: FuncVal) -> Result<VecVal> {
- let mut out = Vec::with_capacity(sz);
- for i in 0..sz {
- out.push(func.evaluate_simple(s.clone(), &(i as f64,))?);
- }
- Ok(VecVal(Cc::new(out)))
-}
-
-#[jrsonnet_macros::builtin]
-const fn builtin_codepoint(str: char) -> Result<u32> {
- Ok(str as u32)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_object_fields_ex(
- obj: ObjValue,
- inc_hidden: bool,
- #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
-) -> Result<VecVal> {
- #[cfg(feature = "exp-preserve-order")]
- let preserve_order = preserve_order.unwrap_or(false);
- let out = obj.fields_ex(
- inc_hidden,
- #[cfg(feature = "exp-preserve-order")]
- preserve_order,
- );
- Ok(VecVal(Cc::new(
- out.into_iter().map(Val::Str).collect::<Vec<_>>(),
- )))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_object_has_ex(obj: ObjValue, f: IStr, inc_hidden: bool) -> Result<bool> {
- Ok(obj.has_field_ex(f, inc_hidden))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_parse_json(st: State, s: IStr) -> Result<Any> {
- use serde_json::Value;
- let value: Value = serde_json::from_str(&s)
- .map_err(|e| RuntimeError(format!("failed to parse json: {}", e).into()))?;
- Ok(Any(Value::into_untyped(value, st)?))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_parse_yaml(st: State, s: IStr) -> Result<Any> {
- use serde_json::Value;
- let value = serde_yaml_with_quirks::Deserializer::from_str_with_quirks(
- &s,
- DeserializingQuirks { old_octals: true },
- );
- let mut out = vec![];
- for item in value {
- let value = Value::deserialize(item)
- .map_err(|e| RuntimeError(format!("failed to parse yaml: {}", e).into()))?;
- let val = Value::into_untyped(value, st.clone())?;
- out.push(val);
- }
- Ok(Any(if out.is_empty() {
- Val::Null
- } else if out.len() == 1 {
- out.into_iter().next().unwrap()
- } else {
- Val::Arr(out.into())
- }))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_slice(
- indexable: IndexableVal,
- index: Option<BoundedUsize<0, { i32::MAX as usize }>>,
- end: Option<BoundedUsize<0, { i32::MAX as usize }>>,
- step: Option<BoundedUsize<1, { i32::MAX as usize }>>,
-) -> Result<Any> {
- std_slice(indexable, index, end, step).map(Any)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_substr(str: IStr, from: usize, len: usize) -> Result<String> {
- Ok(str.chars().skip(from as usize).take(len as usize).collect())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_primitive_equals(a: Any, b: Any) -> Result<bool> {
- primitive_equals(&a.0, &b.0)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_equals(s: State, a: Any, b: Any) -> Result<bool> {
- equals(s, &a.0, &b.0)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_modulo(a: f64, b: f64) -> Result<f64> {
- Ok(a % b)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_mod(s: State, a: Either![f64, IStr], b: Any) -> Result<Any> {
- use Either2::*;
- Ok(Any(evaluate_mod_op(
- s,
- &match a {
- A(v) => Val::Num(v),
- B(s) => Val::Str(s),
- },
- &b.0,
- )?))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_floor(x: f64) -> Result<f64> {
- Ok(x.floor())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_ceil(x: f64) -> Result<f64> {
- Ok(x.ceil())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_log(n: f64) -> Result<f64> {
- Ok(n.ln())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_pow(x: f64, n: f64) -> Result<f64> {
- Ok(x.powf(n))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_sqrt(x: PositiveF64) -> Result<f64> {
- Ok(x.0.sqrt())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_sin(x: f64) -> Result<f64> {
- Ok(x.sin())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_cos(x: f64) -> Result<f64> {
- Ok(x.cos())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_tan(x: f64) -> Result<f64> {
- Ok(x.tan())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_asin(x: f64) -> Result<f64> {
- Ok(x.asin())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_acos(x: f64) -> Result<f64> {
- Ok(x.acos())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_atan(x: f64) -> Result<f64> {
- Ok(x.atan())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_exp(x: f64) -> Result<f64> {
- Ok(x.exp())
-}
-
-fn frexp(s: f64) -> (f64, i16) {
- if 0.0 == s {
- (s, 0)
- } else {
- let lg = s.abs().log2();
- let x = (lg - lg.floor() - 1.0).exp2();
- let exp = lg.floor() + 1.0;
- (s.signum() * x, exp as i16)
- }
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_mantissa(x: f64) -> Result<f64> {
- Ok(frexp(x).0)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_exponent(x: f64) -> Result<i16> {
- Ok(frexp(x).1)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_ext_var(s: State, x: IStr) -> Result<Any> {
- let ctx = s.create_default_context();
- Ok(Any(s
- .clone()
- .settings()
- .ext_vars
- .get(&x)
- .cloned()
- .ok_or(UndefinedExternalVariable(x))?
- .evaluate_arg(s.clone(), ctx, true)?
- .evaluate(s)?))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_native(s: State, name: IStr) -> Result<Any> {
- Ok(Any(s
- .settings()
- .ext_natives
- .get(&name)
- .cloned()
- .map_or(Val::Null, |v| {
- Val::Func(FuncVal::Builtin(v.clone()))
- })))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_filter(s: State, func: FuncVal, arr: ArrValue) -> Result<ArrValue> {
- arr.filter(s.clone(), |val| {
- bool::from_untyped(
- func.evaluate_simple(s.clone(), &(Any(val.clone()),))?,
- s.clone(),
- )
- })
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_map(s: State, func: FuncVal, arr: ArrValue) -> Result<ArrValue> {
- arr.map(s.clone(), |val| {
- func.evaluate_simple(s.clone(), &(Any(val),))
- })
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_flatmap(s: State, func: FuncVal, arr: IndexableVal) -> Result<IndexableVal> {
- match arr {
- IndexableVal::Str(str) => {
- let mut out = String::new();
- for c in str.chars() {
- match func.evaluate_simple(s.clone(), &(c.to_string(),))? {
- Val::Str(o) => out.push_str(&o),
- Val::Null => continue,
- _ => throw!(RuntimeError(
- "in std.join all items should be strings".into()
- )),
- };
- }
- Ok(IndexableVal::Str(out.into()))
- }
- IndexableVal::Arr(a) => {
- let mut out = Vec::new();
- for el in a.iter(s.clone()) {
- let el = el?;
- match func.evaluate_simple(s.clone(), &(Any(el),))? {
- Val::Arr(o) => {
- for oe in o.iter(s.clone()) {
- out.push(oe?);
- }
- }
- Val::Null => continue,
- _ => throw!(RuntimeError(
- "in std.join all items should be arrays".into()
- )),
- };
- }
- Ok(IndexableVal::Arr(out.into()))
- }
- }
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_foldl(s: State, func: FuncVal, arr: ArrValue, init: Any) -> Result<Any> {
- let mut acc = init.0;
- for i in arr.iter(s.clone()) {
- acc = func.evaluate_simple(s.clone(), &(Any(acc), Any(i?)))?;
- }
- Ok(Any(acc))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_foldr(s: State, func: FuncVal, arr: ArrValue, init: Any) -> Result<Any> {
- let mut acc = init.0;
- for i in arr.iter(s.clone()).rev() {
- acc = func.evaluate_simple(s.clone(), &(Any(i?), Any(acc)))?;
- }
- Ok(Any(acc))
-}
-
-#[jrsonnet_macros::builtin]
-#[allow(non_snake_case)]
-fn builtin_sort(s: State, arr: ArrValue, keyF: Option<FuncVal>) -> Result<ArrValue> {
- if arr.len() <= 1 {
- return Ok(arr);
- }
- Ok(ArrValue::Eager(sort::sort(
- s.clone(),
- arr.evaluated(s)?,
- keyF.unwrap_or_else(FuncVal::identity),
- )?))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_format(s: State, str: IStr, vals: Any) -> Result<String> {
- std_format(s, str, vals.0)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_range(from: i32, to: i32) -> Result<ArrValue> {
- if to < from {
- return Ok(ArrValue::new_eager());
- }
- Ok(ArrValue::new_range(from, to))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_char(n: u32) -> Result<char> {
- Ok(std::char::from_u32(n as u32).ok_or(InvalidUnicodeCodepointGot(n as u32))?)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_encode_utf8(str: IStr) -> Result<IBytes> {
- Ok(str.cast_bytes())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_decode_utf8(arr: IBytes) -> Result<IStr> {
- Ok(arr
- .cast_str()
- .ok_or_else(|| RuntimeError("bad utf8".into()))?)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_md5(str: IStr) -> Result<String> {
- Ok(format!("{:x}", md5::compute(&str.as_bytes())))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_trace(s: State, loc: CallLocation, str: IStr, rest: Any) -> Result<Any> {
- eprint!("TRACE:");
- if let Some(loc) = loc.0 {
- let locs = s.map_source_locations(loc.0.clone(), &[loc.1]);
- eprint!(" {}:{}", loc.0.short_display(), locs[0].line);
- }
- eprintln!(" {}", str);
- Ok(rest) as Result<Any>
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_base64(input: Either![IBytes, IStr]) -> Result<String> {
- use Either2::*;
- Ok(match input {
- A(a) => base64::encode(a.as_slice()),
- B(l) => base64::encode(l.bytes().collect::<Vec<_>>()),
- })
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_base64_decode_bytes(input: IStr) -> Result<IBytes> {
- Ok(base64::decode(&input.as_bytes())
- .map_err(|_| RuntimeError("bad base64".into()))?
- .as_slice()
- .into())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_base64_decode(input: IStr) -> Result<String> {
- let bytes = base64::decode(&input.as_bytes()).map_err(|_| RuntimeError("bad base64".into()))?;
- Ok(String::from_utf8(bytes).map_err(|_| RuntimeError("bad utf8".into()))?)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_join(s: State, sep: IndexableVal, arr: ArrValue) -> Result<IndexableVal> {
- Ok(match sep {
- IndexableVal::Arr(joiner_items) => {
- let mut out = Vec::new();
-
- let mut first = true;
- for item in arr.iter(s.clone()) {
- let item = item?.clone();
- if let Val::Arr(items) = item {
- if !first {
- out.reserve(joiner_items.len());
- // TODO: extend
- for item in joiner_items.iter(s.clone()) {
- out.push(item?);
- }
- }
- first = false;
- out.reserve(items.len());
- for item in items.iter(s.clone()) {
- out.push(item?);
- }
- } else if matches!(item, Val::Null) {
- continue;
- } else {
- throw!(RuntimeError(
- "in std.join all items should be arrays".into()
- ));
- }
- }
-
- IndexableVal::Arr(out.into())
- }
- IndexableVal::Str(sep) => {
- let mut out = String::new();
-
- let mut first = true;
- for item in arr.iter(s) {
- let item = item?.clone();
- if let Val::Str(item) = item {
- if !first {
- out += &sep;
- }
- first = false;
- out += &item;
- } else if matches!(item, Val::Null) {
- continue;
- } else {
- throw!(RuntimeError(
- "in std.join all items should be strings".into()
- ));
- }
- }
-
- IndexableVal::Str(out.into())
- }
- })
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_escape_string_json(str_: IStr) -> Result<String> {
- Ok(escape_string_json(&str_))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_manifest_json_ex(
- s: State,
- value: Any,
- indent: IStr,
- newline: Option<IStr>,
- key_val_sep: Option<IStr>,
- #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
-) -> Result<String> {
- let newline = newline.as_deref().unwrap_or("\n");
- let key_val_sep = key_val_sep.as_deref().unwrap_or(": ");
- manifest_json_ex(
- s,
- &value.0,
- &ManifestJsonOptions {
- padding: &indent,
- mtype: ManifestType::Std,
- newline,
- key_val_sep,
- #[cfg(feature = "exp-preserve-order")]
- preserve_order: preserve_order.unwrap_or(false),
},
)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_manifest_yaml_doc(
- s: State,
- value: Any,
- indent_array_in_object: Option<bool>,
- quote_keys: Option<bool>,
- #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
-) -> Result<String> {
- manifest_yaml_ex(
- s,
- &value.0,
- &ManifestYamlOptions {
- padding: " ",
- arr_element_padding: if indent_array_in_object.unwrap_or(false) {
- " "
- } else {
- ""
- },
- quote_keys: quote_keys.unwrap_or(true),
- #[cfg(feature = "exp-preserve-order")]
- preserve_order: preserve_order.unwrap_or(false),
- },
- )
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_reverse(value: ArrValue) -> Result<ArrValue> {
- Ok(value.reversed())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_str_replace(str: String, from: IStr, to: IStr) -> Result<String> {
- Ok(str.replace(&from as &str, &to as &str))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_splitlimit(str: IStr, c: IStr, maxsplits: Either![usize, M1]) -> Result<VecVal> {
- use Either2::*;
- Ok(VecVal(Cc::new(match maxsplits {
- A(n) => str
- .splitn(n + 1, &c as &str)
- .map(|s| Val::Str(s.into()))
- .collect(),
- B(_) => str.split(&c as &str).map(|s| Val::Str(s.into())).collect(),
- })))
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_ascii_upper(str: IStr) -> Result<String> {
- Ok(str.to_ascii_uppercase())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_ascii_lower(str: IStr) -> Result<String> {
- Ok(str.to_ascii_lowercase())
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_member(s: State, arr: IndexableVal, x: Any) -> Result<bool> {
- match arr {
- IndexableVal::Str(str) => {
- let x: IStr = IStr::from_untyped(x.0, s)?;
- Ok(!x.is_empty() && str.contains(&*x))
- }
- IndexableVal::Arr(a) => {
- for item in a.iter(s.clone()) {
- let item = item?;
- if equals(s.clone(), &item, &x.0)? {
- return Ok(true);
- }
- }
- Ok(false)
- }
- }
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_count(s: State, arr: Vec<Any>, v: Any) -> Result<usize> {
- let mut count = 0;
- for item in &arr {
- if equals(s.clone(), &item.0, &v.0)? {
- count += 1;
- }
- }
- Ok(count)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_any(s: State, arr: ArrValue) -> Result<bool> {
- for v in arr.iter(s.clone()) {
- let v = bool::from_untyped(v?, s.clone())?;
- if v {
- return Ok(true);
- }
- }
- Ok(false)
-}
-
-#[jrsonnet_macros::builtin]
-fn builtin_all(s: State, arr: ArrValue) -> Result<bool> {
- for v in arr.iter(s.clone()) {
- let v = bool::from_untyped(v?, s.clone())?;
- if !v {
- return Ok(false);
- }
- }
- Ok(true)
}
crates/jrsonnet-evaluator/src/stdlib/sort.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/stdlib/sort.rs
+++ /dev/null
@@ -1,110 +0,0 @@
-use jrsonnet_gcmodule::{Cc, Trace};
-
-use crate::{
- error::{Error, LocError, Result},
- function::FuncVal,
- throw,
- typed::Any,
- State, Val,
-};
-
-#[derive(Debug, Clone, thiserror::Error, Trace)]
-pub enum SortError {
- #[error("sort key should be string or number")]
- SortKeyShouldBeStringOrNumber,
- #[error("sort elements should have equal types")]
- SortElementsShouldHaveEqualType,
-}
-
-impl From<SortError> for LocError {
- fn from(s: SortError) -> Self {
- Self::new(Error::Sort(s))
- }
-}
-
-#[derive(Copy, Clone)]
-enum SortKeyType {
- Number,
- String,
- Unknown,
-}
-
-#[derive(PartialEq)]
-struct NonNaNf64(f64);
-impl PartialOrd for NonNaNf64 {
- fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
- self.0.partial_cmp(&other.0)
- }
-}
-impl Eq for NonNaNf64 {}
-impl Ord for NonNaNf64 {
- fn cmp(&self, other: &Self) -> std::cmp::Ordering {
- self.partial_cmp(other).expect("non nan")
- }
-}
-
-fn get_sort_type<T>(
- values: &mut Vec<T>,
- key_getter: impl Fn(&mut T) -> &mut Val,
-) -> Result<SortKeyType> {
- let mut sort_type = SortKeyType::Unknown;
- for i in values.iter_mut() {
- let i = key_getter(i);
- match (i, sort_type) {
- (Val::Str(_), SortKeyType::Unknown) => sort_type = SortKeyType::String,
- (Val::Num(_), SortKeyType::Unknown) => sort_type = SortKeyType::Number,
- (Val::Str(_), SortKeyType::String) | (Val::Num(_), SortKeyType::Number) => {}
- (Val::Str(_) | Val::Num(_), _) => {
- throw!(SortError::SortElementsShouldHaveEqualType)
- }
- _ => throw!(SortError::SortKeyShouldBeStringOrNumber),
- }
- }
- Ok(sort_type)
-}
-
-/// * `key_getter` - None, if identity sort required
-pub fn sort(s: State, values: Cc<Vec<Val>>, key_getter: FuncVal) -> Result<Cc<Vec<Val>>> {
- if values.len() <= 1 {
- return Ok(values);
- }
- if key_getter.is_identity() {
- // Fast path, identity key getter
- let mut values = (*values).clone();
- let sort_type = get_sort_type(&mut values, |k| k)?;
- match sort_type {
- SortKeyType::Number => values.sort_unstable_by_key(|v| match v {
- Val::Num(n) => NonNaNf64(*n),
- _ => unreachable!(),
- }),
- SortKeyType::String => values.sort_unstable_by_key(|v| match v {
- Val::Str(s) => s.clone(),
- _ => unreachable!(),
- }),
- SortKeyType::Unknown => unreachable!(),
- };
- Ok(Cc::new(values))
- } else {
- // Slow path, user provided key getter
- let mut vk = Vec::with_capacity(values.len());
- for value in values.iter() {
- vk.push((
- value.clone(),
- key_getter.evaluate_simple(s.clone(), &(Any(value.clone()),))?,
- ));
- }
- let sort_type = get_sort_type(&mut vk, |v| &mut v.1)?;
- match sort_type {
- SortKeyType::Number => vk.sort_by_key(|v| match v.1 {
- Val::Num(n) => NonNaNf64(n),
- _ => unreachable!(),
- }),
- SortKeyType::String => vk.sort_by_key(|v| match &v.1 {
- Val::Str(s) => s.clone(),
- _ => unreachable!(),
- }),
- SortKeyType::Unknown => unreachable!(),
- };
- Ok(Cc::new(vk.into_iter().map(|v| v.0).collect()))
- }
-}
crates/jrsonnet-evaluator/src/trace/location.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/trace/location.rs
+++ /dev/null
@@ -1,124 +0,0 @@
-#[allow(clippy::module_name_repetitions)]
-#[derive(Clone, PartialEq, Eq, Debug)]
-pub struct CodeLocation {
- pub offset: usize,
-
- pub line: usize,
- pub column: usize,
-
- pub line_start_offset: usize,
- pub line_end_offset: usize,
-}
-
-#[allow(clippy::module_name_repetitions)]
-pub fn location_to_offset(mut file: &str, mut line: usize, column: usize) -> Option<usize> {
- let mut offset = 0;
- while line > 1 {
- let pos = file.find('\n')?;
- offset += pos + 1;
- file = &file[pos + 1..];
- line -= 1;
- }
- offset += column - 1;
- Some(offset)
-}
-
-#[allow(clippy::module_name_repetitions)]
-pub fn offset_to_location(file: &str, offsets: &[u32]) -> Vec<CodeLocation> {
- if offsets.is_empty() {
- return vec![];
- }
- let mut line = 1;
- let mut column = 1;
- let max_offset = *offsets.iter().max().expect("offsets is not empty");
-
- let mut offset_map = offsets
- .iter()
- .enumerate()
- .map(|(pos, offset)| (*offset, pos))
- .collect::<Vec<_>>();
- offset_map.sort_by_key(|v| v.0);
- offset_map.reverse();
-
- let mut out = vec![
- CodeLocation {
- offset: 0,
- column: 0,
- line: 0,
- line_start_offset: 0,
- line_end_offset: 0
- };
- offsets.len()
- ];
- let mut with_no_known_line_ending = vec![];
- let mut this_line_offset = 0;
- for (pos, ch) in file
- .chars()
- .enumerate()
- .chain(std::iter::once((file.len(), ' ')))
- {
- column += 1;
- match offset_map.last() {
- Some(x) if x.0 == pos as u32 => {
- let out_idx = x.1;
- with_no_known_line_ending.push(out_idx);
- out[out_idx].offset = pos;
- out[out_idx].line = line;
- out[out_idx].column = column;
- out[out_idx].line_start_offset = this_line_offset;
- offset_map.pop();
- }
- _ => {}
- }
- if ch == '\n' {
- line += 1;
- column = 1;
-
- for idx in with_no_known_line_ending.drain(..) {
- out[idx].line_end_offset = pos;
- }
- this_line_offset = pos + 1;
-
- if pos == max_offset as usize + 1 {
- break;
- }
- }
- }
- let file_end = file.chars().count();
- for idx in with_no_known_line_ending {
- out[idx].line_end_offset = file_end;
- }
-
- out
-}
-
-#[cfg(test)]
-pub mod tests {
- use super::{offset_to_location, CodeLocation};
-
- #[test]
- fn test() {
- assert_eq!(
- offset_to_location(
- "hello world\n_______________________________________________________",
- &[0, 14]
- ),
- vec![
- CodeLocation {
- offset: 0,
- line: 1,
- column: 2,
- line_start_offset: 0,
- line_end_offset: 11,
- },
- CodeLocation {
- offset: 14,
- line: 2,
- column: 4,
- line_start_offset: 12,
- line_end_offset: 67
- }
- ]
- )
- }
-}
crates/jrsonnet-evaluator/src/trace/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/trace/mod.rs
+++ b/crates/jrsonnet-evaluator/src/trace/mod.rs
@@ -1,13 +1,11 @@
-mod location;
-
use std::path::{Path, PathBuf};
-use jrsonnet_parser::Source;
-pub use location::*;
+use jrsonnet_parser::{CodeLocation, Source};
use crate::{error::Error, LocError, State};
/// The way paths should be displayed
+#[derive(Clone)]
pub enum PathResolver {
/// Only filename
FileName,
@@ -18,6 +16,10 @@
}
impl PathResolver {
+ /// Will return `Self::Relative(cwd)`, or `Self::Absolute` on cwd failure
+ pub fn new_cwd_fallback() -> Self {
+ std::env::current_dir().map_or(Self::Absolute, Self::Relative)
+ }
pub fn resolve(&self, from: &Path) -> String {
match self {
Self::FileName => from
@@ -84,31 +86,27 @@
fn write_trace(
&self,
out: &mut dyn std::fmt::Write,
- s: &State,
+ _s: &State,
error: &LocError,
) -> Result<(), std::fmt::Error> {
write!(out, "{}", error.error())?;
- if let Error::ImportSyntaxError {
- path,
- source_code,
- error,
- } = error.error()
- {
+ if let Error::ImportSyntaxError { path, error } = error.error() {
use std::fmt::Write;
writeln!(out)?;
- let mut n = match path.repr() {
- Ok(r) => self.resolver.resolve(r),
- Err(v) => v.to_string(),
- };
+ let mut n = path.source_path().path().map_or_else(
+ || path.source_path().to_string(),
+ |r| self.resolver.resolve(r),
+ );
let mut offset = error.location.offset;
- let is_eof = if offset >= source_code.len() {
- offset = source_code.len().saturating_sub(1);
+ let is_eof = if offset >= path.code().len() {
+ offset = path.code().len().saturating_sub(1);
true
} else {
false
};
- let mut location = offset_to_location(source_code, &[offset as u32])
+ let mut location = path
+ .map_source_locations(&[offset as u32])
.into_iter()
.next()
.unwrap();
@@ -118,7 +116,7 @@
write!(n, ":").unwrap();
print_code_location(&mut n, &location, &location).unwrap();
- write!(out, "{:<p$}{}", "", n, p = self.padding,)?;
+ write!(out, "{:<p$}{n}", "", p = self.padding)?;
}
let file_names = error
.trace()
@@ -129,13 +127,12 @@
use std::fmt::Write;
#[allow(clippy::option_if_let_else)]
if let Some(location) = location {
- let mut resolved_path = match location.0.repr() {
- Ok(r) => self.resolver.resolve(r),
- Err(v) => v.to_string(),
+ let mut resolved_path = match location.0.source_path().path() {
+ Some(r) => self.resolver.resolve(r),
+ None => location.0.source_path().to_string(),
};
// TODO: Process all trace elements first
- let location =
- s.map_source_locations(location.0.clone(), &[location.1, location.2]);
+ let location = location.0.map_source_locations(&[location.1, location.2]);
write!(resolved_path, ":").unwrap();
print_code_location(&mut resolved_path, &location[0], &location[1]).unwrap();
write!(resolved_path, ":").unwrap();
@@ -176,7 +173,7 @@
fn write_trace(
&self,
out: &mut dyn std::fmt::Write,
- s: &State,
+ _s: &State,
error: &LocError,
) -> Result<(), std::fmt::Error> {
write!(out, "{}", error.error())?;
@@ -184,11 +181,11 @@
writeln!(out)?;
let desc = &item.desc;
if let Some(source) = &item.location {
- let start_end = s.map_source_locations(source.0.clone(), &[source.1, source.2]);
- let resolved_path = match source.0.repr() {
- Ok(r) => r.display().to_string(),
- Err(v) => v.to_string(),
- };
+ let start_end = source.0.map_source_locations(&[source.1, source.2]);
+ let resolved_path = source.0.source_path().path().map_or_else(
+ || source.0.source_path().to_string(),
+ |r| r.display().to_string(),
+ );
write!(
out,
@@ -196,7 +193,7 @@
desc, resolved_path, start_end[0].line, start_end[0].column,
)?;
} else {
- write!(out, " during {}", desc)?;
+ write!(out, " during {desc}")?;
}
}
Ok(())
@@ -213,19 +210,15 @@
fn write_trace(
&self,
out: &mut dyn std::fmt::Write,
- s: &State,
+ _s: &State,
error: &LocError,
) -> Result<(), std::fmt::Error> {
write!(out, "{}", error.error())?;
- if let Error::ImportSyntaxError {
- path,
- source_code,
- error,
- } = error.error()
- {
+ if let Error::ImportSyntaxError { path, error } = error.error() {
writeln!(out)?;
let offset = error.location.offset;
- let location = offset_to_location(source_code, &[offset as u32])
+ let location = path
+ .map_source_locations(&[offset as u32])
.into_iter()
.next()
.unwrap();
@@ -234,7 +227,7 @@
self.print_snippet(
out,
- source_code,
+ path.code(),
path,
&location,
&end_location,
@@ -246,17 +239,17 @@
writeln!(out)?;
let desc = &item.desc;
if let Some(source) = &item.location {
- let start_end = s.map_source_locations(source.0.clone(), &[source.1, source.2]);
+ let start_end = source.0.map_source_locations(&[source.1, source.2]);
self.print_snippet(
out,
- &s.get_source(source.0.clone()).unwrap(),
+ source.0.code(),
&source.0,
&start_end[0],
&start_end[1],
desc,
)?;
} else {
- write!(out, "{}", desc)?;
+ write!(out, "{desc}")?;
}
}
Ok(())
@@ -284,10 +277,10 @@
.take(end.line_end_offset - end.line_start_offset)
.collect();
- let origin = match origin.repr() {
- Ok(r) => self.resolver.resolve(r),
- Err(v) => v.to_string(),
- };
+ let origin = origin.source_path().path().map_or_else(
+ || origin.source_path().to_string(),
+ |r| self.resolver.resolve(r),
+ );
let snippet = Snippet {
opt: FormatOptions {
color: true,
@@ -312,7 +305,7 @@
};
let dl = DisplayList::from(snippet);
- write!(out, "{}", dl)?;
+ write!(out, "{dl}")?;
Ok(())
}
crates/jrsonnet-evaluator/src/typed/conversions.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/typed/conversions.rs
+++ b/crates/jrsonnet-evaluator/src/typed/conversions.rs
@@ -393,6 +393,7 @@
($a:ty, $b:ty, $c:ty, $d:ty, $e:ty, $f:ty) => {Either6<$a, $b, $c, $d, $e, $f>};
($a:ty, $b:ty, $c:ty, $d:ty, $e:ty, $f:ty, $g:ty) => {Either7<$a, $b, $c, $d, $e, $f, $g>};
}
+pub use Either;
pub type MyType = Either![u32, f64, String];
crates/jrsonnet-evaluator/src/typed/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/typed/mod.rs
+++ b/crates/jrsonnet-evaluator/src/typed/mod.rs
@@ -21,8 +21,8 @@
UnionFailed(ComplexValType, TypeLocErrorList),
#[error(
"number out of bounds: {0} not in {}..{}",
- .1.map(|v|v.to_string()).unwrap_or_else(|| "".to_owned()),
- .2.map(|v|v.to_string()).unwrap_or_else(|| "".to_owned()),
+ .1.map(|v|v.to_string()).unwrap_or_default(),
+ .2.map(|v|v.to_string()).unwrap_or_default(),
)]
BoundsFailed(f64, Option<f64>, Option<f64>),
}
@@ -65,7 +65,7 @@
writeln!(f)?;
}
out.clear();
- write!(out, "{}", err)?;
+ write!(out, "{err}")?;
for (i, line) in out.lines().enumerate() {
if line.trim().is_empty() {
@@ -77,7 +77,7 @@
writeln!(f)?;
write!(f, " ")?;
}
- write!(f, "{}", line)?;
+ write!(f, "{line}")?;
}
}
Ok(())
@@ -125,8 +125,8 @@
impl Display for ValuePathItem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
- Self::Field(name) => write!(f, ".{:?}", name)?,
- Self::Index(idx) => write!(f, "[{}]", idx)?,
+ Self::Field(name) => write!(f, ".{name:?}")?,
+ Self::Index(idx) => write!(f, "[{idx}]")?,
}
Ok(())
}
@@ -138,7 +138,7 @@
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "self")?;
for elem in self.0.iter().rev() {
- write!(f, "{}", elem)?;
+ write!(f, "{elem}")?;
}
Ok(())
}
@@ -171,7 +171,7 @@
for (i, item) in a.iter(s.clone()).enumerate() {
push_type_description(
s.clone(),
- || format!("array index {}", i),
+ || format!("array index {i}"),
|| ValuePathItem::Index(i as u64),
|| elem_type.check(s.clone(), &item.clone()?),
)?;
@@ -185,7 +185,7 @@
for (i, item) in a.iter(s.clone()).enumerate() {
push_type_description(
s.clone(),
- || format!("array index {}", i),
+ || format!("array index {i}"),
|| ValuePathItem::Index(i as u64),
|| elem_type.check(s.clone(), &item.clone()?),
)?;
@@ -200,7 +200,7 @@
if let Some(got_v) = obj.get(s.clone(), (*k).into())? {
push_type_description(
s.clone(),
- || format!("property {}", k),
+ || format!("property {k}"),
|| ValuePathItem::Field((*k).into()),
|| v.check(s.clone(), &got_v),
)?;
crates/jrsonnet-evaluator/src/val.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/val.rs
+++ b/crates/jrsonnet-evaluator/src/val.rs
@@ -11,7 +11,9 @@
stdlib::manifest::{
manifest_json_ex, manifest_yaml_ex, ManifestJsonOptions, ManifestType, ManifestYamlOptions,
},
- throw, ObjValue, Result, State, Unbound, WeakObjValue,
+ throw,
+ typed::BoundedUsize,
+ ObjValue, Result, State, Unbound, WeakObjValue,
};
pub trait ThunkValue: Trace {
@@ -184,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>),
}
@@ -204,9 +216,13 @@
pub fn new_eager() -> Self {
Self::Eager(Cc::new(Vec::new()))
}
+ pub fn empty() -> Self {
+ Self::new_range(0, 0)
+ }
/// # Panics
/// If a > b
+ #[inline]
pub fn new_range(a: i32, b: i32) -> Self {
assert!(a <= b);
Self::Range(a, b)
@@ -231,6 +247,7 @@
}))
}
+ /// Array length.
pub fn len(&self) -> usize {
match self {
Self::Bytes(i) => i.len(),
@@ -243,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
@@ -286,11 +307,14 @@
if index >= v.to() {
return Ok(None);
}
- v.inner.get(s, index as usize)
+ v.inner.get(s, index)
}
}
}
+ /// 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
@@ -326,11 +350,12 @@
if index >= s.to() {
return None;
}
- s.inner.get_lazy(index as usize)
+ s.inner.get_lazy(index)
}
}
}
+ /// Evaluate all array elements, returning new array.
pub fn evaluated(&self, s: State) -> Result<Cc<Vec<Val>>> {
Ok(match self {
Self::Bytes(i) => {
@@ -383,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]))),
@@ -394,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]))),
@@ -405,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());
@@ -420,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());
@@ -454,25 +484,100 @@
}
}
+/// 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 }>>,
+ end: Option<BoundedUsize<0, { i32::MAX as usize }>>,
+ step: Option<BoundedUsize<1, { i32::MAX as usize }>>,
+ ) -> Result<Self> {
+ match &self {
+ IndexableVal::Str(s) => {
+ let index = index.as_deref().copied().unwrap_or(0);
+ let end = end.as_deref().copied().unwrap_or(usize::MAX);
+ let step = step.as_deref().copied().unwrap_or(1);
+ if index >= end {
+ return Ok(Self::Str("".into()));
+ }
+
+ Ok(Self::Str(
+ (s.chars()
+ .skip(index)
+ .take(end - index)
+ .step_by(step)
+ .collect::<String>())
+ .into(),
+ ))
+ }
+ IndexableVal::Arr(arr) => {
+ let index = index.as_deref().copied().unwrap_or(0);
+ let end = end.as_deref().copied().unwrap_or(usize::MAX).min(arr.len());
+ let step = step.as_deref().copied().unwrap_or(1);
+
+ if index >= end {
+ return Ok(Self::Arr(ArrValue::new_eager()));
+ }
+
+ Ok(Self::Arr(ArrValue::Slice(Box::new(Slice {
+ inner: arr.clone(),
+ from: index as u32,
+ to: end as u32,
+ step: step as u32,
+ }))))
+ }
+ }
+ }
+}
+
+/// 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),
}
-#[cfg(target_pointer_width = "64")]
-static_assertions::assert_eq_size!(Val, [u8; 32]);
+impl From<IndexableVal> for Val {
+ fn from(v: IndexableVal) -> Self {
+ match v {
+ IndexableVal::Str(s) => Self::Str(s),
+ IndexableVal::Arr(a) => Self::Arr(a),
+ }
+ }
+}
+
+// Broken between stable and nightly, as there is new layout size optimization
+// #[cfg(target_pointer_width = "64")]
+// static_assertions::assert_eq_size!(Val, [u8; 24]);
impl Val {
pub const fn as_bool(&self) -> Option<bool> {
crates/jrsonnet-evaluator/tests/as_native.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/as_native.rs
+++ /dev/null
@@ -1,19 +0,0 @@
-use jrsonnet_evaluator::{error::Result, State};
-
-mod common;
-
-#[test]
-fn as_native() -> Result<()> {
- let s = State::default();
- s.with_stdlib();
-
- let val = s.evaluate_snippet("snip".to_owned(), r#"function(a, b) a + b"#.into())?;
- let func = val.as_func().expect("this is function");
-
- let native = func.into_native::<((u32, u32), u32)>();
-
- ensure_eq!(native(s.clone(), 1, 2)?, 3);
- ensure_eq!(native(s, 3, 4)?, 7);
-
- Ok(())
-}
crates/jrsonnet-evaluator/tests/builtin.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/builtin.rs
+++ /dev/null
@@ -1,100 +0,0 @@
-mod common;
-
-use jrsonnet_evaluator::{
- error::Result,
- function::{builtin, builtin::Builtin, CallLocation, FuncVal},
- tb,
- typed::Typed,
- State, Val,
-};
-use jrsonnet_gcmodule::Cc;
-
-#[builtin]
-fn a() -> Result<u32> {
- Ok(1)
-}
-
-#[test]
-fn basic_function() -> Result<()> {
- let s = State::default();
- let a: a = a {};
- let v = u32::from_untyped(
- a.call(
- s.clone(),
- s.create_default_context(),
- CallLocation::native(),
- &(),
- )?,
- s,
- )?;
-
- ensure_eq!(v, 1);
- Ok(())
-}
-
-#[builtin]
-fn native_add(a: u32, b: u32) -> Result<u32> {
- Ok(a + b)
-}
-
-#[test]
-fn call_from_code() -> Result<()> {
- let s = State::default();
- s.with_stdlib();
- s.settings_mut().globals.insert(
- "nativeAdd".into(),
- Val::Func(FuncVal::StaticBuiltin(native_add::INST)),
- );
-
- let v = s.evaluate_snippet(
- "snip".to_owned(),
- "
- assert nativeAdd(1, 2) == 3;
- assert nativeAdd(100, 200) == 300;
- null
- "
- .into(),
- )?;
- ensure_val_eq!(s, v, Val::Null);
- Ok(())
-}
-
-#[builtin(fields(
- a: u32
-))]
-fn curried_add(this: &curried_add, b: u32) -> Result<u32> {
- Ok(this.a + b)
-}
-
-#[builtin]
-fn curry_add(a: u32) -> Result<FuncVal> {
- Ok(FuncVal::Builtin(Cc::new(tb!(curried_add { a }))))
-}
-
-#[test]
-fn nonstatic_builtin() -> Result<()> {
- let s = State::default();
- s.with_stdlib();
- s.settings_mut().globals.insert(
- "curryAdd".into(),
- Val::Func(FuncVal::StaticBuiltin(curry_add::INST)),
- );
-
- let v = s.evaluate_snippet(
- "snip".to_owned(),
- "
- local a = curryAdd(1);
- local b = curryAdd(4);
-
- assert a(2) == 3;
- assert a(200) == 201;
-
- assert b(2) == 6;
- assert b(200) == 204;
- null
- "
- .into(),
- )?;
- ensure_val_eq!(s, v, Val::Null);
- Ok(())
-}
crates/jrsonnet-evaluator/tests/common.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/common.rs
+++ /dev/null
@@ -1,78 +0,0 @@
-use jrsonnet_evaluator::{
- error::Result,
- function::{builtin, FuncVal},
- throw_runtime, ObjValueBuilder, State, Thunk, Val,
-};
-
-#[macro_export]
-macro_rules! ensure_eq {
- ($a:expr, $b:expr $(,)?) => {{
- let a = &$a;
- let b = &$b;
- if a != b {
- ::jrsonnet_evaluator::throw_runtime!("assertion failed: a != b\na={:#?}\nb={:#?}", a, b)
- }
- }};
-}
-
-#[macro_export]
-macro_rules! ensure {
- ($v:expr $(,)?) => {
- if !$v {
- ::jrsonnet_evaluator::throw_runtime!("assertion failed: {}", stringify!($v))
- }
- };
-}
-
-#[macro_export]
-macro_rules! ensure_val_eq {
- ($s:expr, $a:expr, $b:expr) => {{
- if !::jrsonnet_evaluator::val::equals($s.clone(), &$a.clone(), &$b.clone())? {
- ::jrsonnet_evaluator::throw_runtime!(
- "assertion failed: a != b\na={:#?}\nb={:#?}",
- $a.to_json(
- $s.clone(),
- 2,
- #[cfg(feature = "exp-preserve-order")]
- false
- )?,
- $b.to_json(
- $s.clone(),
- 2,
- #[cfg(feature = "exp-preserve-order")]
- false
- )?,
- )
- }
- }};
-}
-
-#[builtin]
-fn assert_throw(s: State, lazy: Thunk<Val>, message: String) -> Result<bool> {
- match lazy.evaluate(s) {
- Ok(_) => {
- throw_runtime!("expected argument to throw on evaluation, but it returned instead")
- }
- Err(e) => {
- let error = format!("{}", e.error());
- ensure_eq!(message, error);
- }
- }
- Ok(true)
-}
-
-#[allow(dead_code)]
-pub fn with_test(s: &State) {
- let mut bobj = ObjValueBuilder::new();
- bobj.member("assertThrow".into())
- .hide()
- .value(
- s.clone(),
- Val::Func(FuncVal::StaticBuiltin(assert_throw::INST)),
- )
- .expect("no error");
-
- s.settings_mut()
- .globals
- .insert("test".into(), Val::Obj(bobj.build()));
-}
crates/jrsonnet-evaluator/tests/golden.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/golden.rs
+++ /dev/null
@@ -1,69 +0,0 @@
-use std::{
- fs, io,
- path::{Path, PathBuf},
-};
-
-use jrsonnet_evaluator::{
- trace::{CompactFormat, PathResolver},
- FileImportResolver, State,
-};
-
-mod common;
-
-fn run(root: &Path, file: &Path) -> String {
- let s = State::default();
- s.set_trace_format(Box::new(CompactFormat {
- resolver: PathResolver::Relative(root.to_owned()),
- padding: 3,
- }));
- s.with_stdlib();
- common::with_test(&s);
- s.set_import_resolver(Box::new(FileImportResolver::default()));
-
- let v = match s.import(file.to_owned()) {
- Ok(v) => v,
- Err(e) => return s.stringify_err(&e),
- };
- match v.to_json(
- s.clone(),
- 3,
- #[cfg(feature = "exp-preserve-order")]
- false,
- ) {
- Ok(v) => v.to_string(),
- Err(e) => s.stringify_err(&e),
- }
-}
-
-#[test]
-fn test() -> io::Result<()> {
- let mut root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
- root.push("tests/golden");
-
- for entry in fs::read_dir(&root)? {
- let entry = entry?;
- if !entry.path().extension().map_or(false, |e| e == "jsonnet") {
- continue;
- }
-
- let result = run(&root, &entry.path());
-
- let mut golden_path = entry.path();
- golden_path.set_extension("jsonnet.golden");
-
- if !golden_path.exists() {
- fs::write(golden_path, &result)?;
- } else {
- let golden = fs::read_to_string(golden_path)?;
-
- assert_eq!(
- result,
- golden,
- "golden didn't match for {}",
- entry.path().display()
- )
- }
- }
-
- Ok(())
-}
crates/jrsonnet-evaluator/tests/golden/array_comp.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/golden/array_comp.jsonnet
+++ /dev/null
@@ -1 +0,0 @@
-[[a, b] for a in [1, 2, 3] for b in [4, 5, 6]]
crates/jrsonnet-evaluator/tests/golden/array_comp.jsonnet.goldendiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/golden/array_comp.jsonnet.golden
+++ /dev/null
@@ -1,38 +0,0 @@
-[
- [
- 1,
- 4
- ],
- [
- 1,
- 5
- ],
- [
- 1,
- 6
- ],
- [
- 2,
- 4
- ],
- [
- 2,
- 5
- ],
- [
- 2,
- 6
- ],
- [
- 3,
- 4
- ],
- [
- 3,
- 5
- ],
- [
- 3,
- 6
- ]
-]
\ No newline at end of file
crates/jrsonnet-evaluator/tests/golden/builtin_json.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/golden/builtin_json.jsonnet
+++ /dev/null
@@ -1 +0,0 @@
-std.manifestJsonEx({ a: 3, b: 4, c: 6 }, '')
crates/jrsonnet-evaluator/tests/golden/builtin_json.jsonnet.goldendiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/golden/builtin_json.jsonnet.golden
+++ /dev/null
@@ -1 +0,0 @@
-"{\n\"a\": 3,\n\"b\": 4,\n\"c\": 6\n}"
\ No newline at end of file
crates/jrsonnet-evaluator/tests/golden/builtin_json_minified.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/golden/builtin_json_minified.jsonnet
+++ /dev/null
@@ -1 +0,0 @@
-std.manifestJsonMinified({ a: 3, b: 4, c: 6 })
crates/jrsonnet-evaluator/tests/golden/builtin_json_minified.jsonnet.goldendiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/golden/builtin_json_minified.jsonnet.golden
+++ /dev/null
@@ -1 +0,0 @@
-"{\"a\":3,\"b\":4,\"c\":6}"
\ No newline at end of file
crates/jrsonnet-evaluator/tests/golden/builtin_parseJson.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/golden/builtin_parseJson.jsonnet
+++ /dev/null
@@ -1 +0,0 @@
-std.parseJson('{"a": -1,"b": 1,"c": 3.141,"d": []}')
crates/jrsonnet-evaluator/tests/golden/builtin_parseJson.jsonnet.goldendiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/golden/builtin_parseJson.jsonnet.golden
+++ /dev/null
@@ -1,6 +0,0 @@
-{
- "a": -1,
- "b": 1,
- "c": 3.141,
- "d": [ ]
-}
\ No newline at end of file
crates/jrsonnet-evaluator/tests/golden/issue23.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/golden/issue23.jsonnet
+++ /dev/null
@@ -1 +0,0 @@
-import 'issue23.jsonnet'
crates/jrsonnet-evaluator/tests/golden/issue23.jsonnet.goldendiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/golden/issue23.jsonnet.golden
+++ /dev/null
@@ -1,2 +0,0 @@
-infinite recursion detected
- issue23.jsonnet:1:1-26: import "issue23.jsonnet"
\ No newline at end of file
crates/jrsonnet-evaluator/tests/golden/issue40.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/golden/issue40.jsonnet
+++ /dev/null
@@ -1,9 +0,0 @@
-local conf = {
- n: '',
-};
-
-local result = conf {
- assert std.isNumber(self.n) : 'is number',
-};
-
-std.manifestJsonEx(result, '')
crates/jrsonnet-evaluator/tests/golden/issue40.jsonnet.goldendiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/golden/issue40.jsonnet.golden
+++ /dev/null
@@ -1,3 +0,0 @@
-assert failed: is number
- issue40.jsonnet:6:10-31: assertion failure
- issue40.jsonnet:9:1-32: function <builtin_manifest_json_ex> call
\ No newline at end of file
crates/jrsonnet-evaluator/tests/golden/missing_binding.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/golden/missing_binding.jsonnet
+++ /dev/null
@@ -1 +0,0 @@
-sta
crates/jrsonnet-evaluator/tests/golden/missing_binding.jsonnet.goldendiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/golden/missing_binding.jsonnet.golden
+++ /dev/null
@@ -1,3 +0,0 @@
-variable is not defined: sta
-There is variable with similar name present: std
- missing_binding.jsonnet:1:1-5: variable <sta> access
\ No newline at end of file
crates/jrsonnet-evaluator/tests/golden/object_comp.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/golden/object_comp.jsonnet
+++ /dev/null
@@ -1 +0,0 @@
-{ local t = 'a', ['h' + i + '_' + z]: if 'h' + (i - 1) + '_' + z in self then t + 1 else 0 + t for i in [1, 2, 3] for z in [2, 3, 4] if z != i }
crates/jrsonnet-evaluator/tests/golden/object_comp.jsonnet.goldendiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/golden/object_comp.jsonnet.golden
+++ /dev/null
@@ -1,9 +0,0 @@
-{
- "h1_2": "0a",
- "h1_3": "0a",
- "h1_4": "0a",
- "h2_3": "a1",
- "h2_4": "a1",
- "h3_2": "0a",
- "h3_4": "a1"
-}
\ No newline at end of file
crates/jrsonnet-evaluator/tests/golden/test_assertThrow.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/golden/test_assertThrow.jsonnet
+++ /dev/null
@@ -1,2 +0,0 @@
-// Test that test.assertThrow will return error, if body is not errored
-test.assertThrow(1, '1')
crates/jrsonnet-evaluator/tests/golden/test_assertThrow.jsonnet.goldendiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/golden/test_assertThrow.jsonnet.golden
+++ /dev/null
@@ -1,2 +0,0 @@
-runtime error: expected argument to throw on evaluation, but it returned instead
- test_assertThrow.jsonnet:2:1-26: function <assert_throw> call
\ No newline at end of file
crates/jrsonnet-evaluator/tests/sanity.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/sanity.rs
+++ /dev/null
@@ -1,41 +0,0 @@
-use jrsonnet_evaluator::{error::Result, throw_runtime, State, Val};
-
-mod common;
-
-#[test]
-fn assert_positive() -> Result<()> {
- let s = State::default();
- s.with_stdlib();
-
- let v = s.evaluate_snippet("snip".to_owned(), "assert 1 == 1: 'fail'; null".into())?;
- ensure_val_eq!(s, v, Val::Null);
- let v = s.evaluate_snippet("snip".to_owned(), "std.assertEqual(1, 1)".into())?;
- ensure_val_eq!(s, v, Val::Bool(true));
-
- Ok(())
-}
-
-#[test]
-fn assert_negative() -> Result<()> {
- let s = State::default();
- s.with_stdlib();
-
- {
- let e = match s.evaluate_snippet("snip".to_owned(), "assert 1 == 2: 'fail'; null".into()) {
- Ok(_) => throw_runtime!("assertion should fail"),
- Err(e) => e,
- };
- let e = s.stringify_err(&e);
- ensure!(e.starts_with("assert failed: fail\n"));
- }
- {
- let e = match s.evaluate_snippet("snip".to_owned(), "std.assertEqual(1, 2)".into()) {
- Ok(_) => throw_runtime!("assertion should fail"),
- Err(e) => e,
- };
- let e = s.stringify_err(&e);
- ensure!(e.starts_with("runtime error: Assertion failed. 1 != 2"))
- }
-
- Ok(())
-}
crates/jrsonnet-evaluator/tests/suite.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/suite.rs
+++ /dev/null
@@ -1,46 +0,0 @@
-use std::{
- fs, io,
- path::{Path, PathBuf},
-};
-
-use jrsonnet_evaluator::{
- trace::{CompactFormat, PathResolver},
- FileImportResolver, State, Val,
-};
-
-mod common;
-
-fn run(root: &Path, file: &Path) {
- let s = State::default();
- s.set_trace_format(Box::new(CompactFormat {
- resolver: PathResolver::Relative(root.to_owned()),
- padding: 3,
- }));
- s.with_stdlib();
- common::with_test(&s);
- s.set_import_resolver(Box::new(FileImportResolver::default()));
-
- match s.import(file.to_owned()) {
- Ok(Val::Bool(true)) => {}
- Ok(Val::Bool(false)) => panic!("test {} returned false", file.display()),
- Ok(_) => panic!("test {} returned wrong type as result", file.display()),
- Err(e) => panic!("test {} failed:\n{}", file.display(), s.stringify_err(&e)),
- };
-}
-
-#[test]
-fn test() -> io::Result<()> {
- let mut root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
- root.push("tests/suite");
-
- for entry in fs::read_dir(&root)? {
- let entry = entry?;
- if !entry.path().extension().map_or(false, |e| e == "jsonnet") {
- continue;
- }
-
- run(&root, &entry.path());
- }
-
- Ok(())
-}
crates/jrsonnet-evaluator/tests/suite/builtin_ascii.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/suite/builtin_ascii.jsonnet
+++ /dev/null
@@ -1,3 +0,0 @@
-std.assertEqual(std.asciiUpper('aBc😀'), 'ABC😀') &&
-std.assertEqual(std.asciiLower('aBc😀'), 'abc😀') &&
-true
crates/jrsonnet-evaluator/tests/suite/builtin_base64.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/suite/builtin_base64.jsonnet
+++ /dev/null
@@ -1,2 +0,0 @@
-std.assertEqual(std.base64('test'), 'dGVzdA==') &&
-true
crates/jrsonnet-evaluator/tests/suite/builtin_chars.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/suite/builtin_chars.jsonnet
+++ /dev/null
@@ -1,3 +0,0 @@
-local c = '😎';
-std.assertEqual({ c: std.codepoint(c), l: std.length(c) }, { c: 128526, l: 1 }) &&
-true
crates/jrsonnet-evaluator/tests/suite/builtin_constant.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/suite/builtin_constant.jsonnet
+++ /dev/null
@@ -1,3 +0,0 @@
-local std2 = std; local std = std2 { primitiveEquals(a, b):: false };
-// In jsonnet, this expression was failing because of being desugared to std.primitiveEquals(1, 1)
-std.assertEqual(1 == 1, true)
crates/jrsonnet-evaluator/tests/suite/builtin_count.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/suite/builtin_count.jsonnet
+++ /dev/null
@@ -1,4 +0,0 @@
-std.assertEqual(std.count([], ''), 0) &&
-std.assertEqual(std.count(['a', 'b', 'a'], 'd'), 0) &&
-std.assertEqual(std.count(['a', 'b', 'a'], 'a'), 2) &&
-true
crates/jrsonnet-evaluator/tests/suite/builtin_join.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/suite/builtin_join.jsonnet
+++ /dev/null
@@ -1,4 +0,0 @@
-std.assertEqual(std.join([0, 0], [[1, 2], [3, 4], [5, 6]]), [1, 2, 0, 0, 3, 4, 0, 0, 5, 6]) &&
-std.assertEqual(std.join(',', ['1', '2', '3', '4']), '1,2,3,4') &&
-std.assertEqual(std.join(',', ['1', null, '2', null, '3']), '1,2,3') &&
-true
crates/jrsonnet-evaluator/tests/suite/builtin_member.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/suite/builtin_member.jsonnet
+++ /dev/null
@@ -1,7 +0,0 @@
-!std.member('', '') &&
-std.member('abc', 'a') &&
-!std.member('abc', 'd') &&
-!std.member([], '') &&
-std.member(['a', 'b', 'c'], 'a') &&
-!std.member(['a', 'b', 'c'], 'd') &&
-true
crates/jrsonnet-evaluator/tests/suite/function_args.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/suite/function_args.jsonnet
+++ /dev/null
@@ -1,3 +0,0 @@
-std.assertEqual(local a = function(b, c=2) b + c; a(2), 4) &&
-std.assertEqual(local a = function(b, c='Dear') b + c + d, d = 'World'; a('Hello'), 'HelloDearWorld') &&
-true
crates/jrsonnet-evaluator/tests/suite/function_context.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/suite/function_context.jsonnet
+++ /dev/null
@@ -1,10 +0,0 @@
-local k = {
- t(name=self.h): [self.h, name],
- h: 3,
-};
-local f = {
- t: k.t(),
- h: 4,
-};
-std.assertEqual(f.t[0], f.t[1]) &&
-true
crates/jrsonnet-evaluator/tests/suite/function_lazy_args.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/suite/function_lazy_args.jsonnet
+++ /dev/null
@@ -1,5 +0,0 @@
-local fun(a) = 2;
-std.assertEqual(fun(error '3'), 2) &&
-// But in tailstrict mode arguments are evaluated eagerly
-test.assertThrow(fun(error '3') tailstrict, 'runtime error: 3') &&
-true
crates/jrsonnet-evaluator/tests/suite/local.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/suite/local.jsonnet
+++ /dev/null
@@ -1,4 +0,0 @@
-std.assertEqual(local a = 2; local b = 3; a + b, 5) &&
-std.assertEqual(local a = 1, b = a + 1; a + b, 3) &&
-std.assertEqual(local a = 1; local a = 2; a, 2) &&
-true
crates/jrsonnet-evaluator/tests/suite/math.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/suite/math.jsonnet
+++ /dev/null
@@ -1,3 +0,0 @@
-std.assertEqual(2 + 2 * 2, 6) &&
-std.assertEqual(3 + (2 + 2 * 2), 9) &&
-true
crates/jrsonnet-evaluator/tests/suite/object_assertion.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/suite/object_assertion.jsonnet
+++ /dev/null
@@ -1,3 +0,0 @@
-std.assertEqual({ assert 'a' in self : 'missing a' } + { a: 2 }, { a: 2 }) &&
-test.assertThrow({ assert 'a' in self : 'missing a', b: 1 }.b, 'assert failed: missing a') &&
-true
crates/jrsonnet-evaluator/tests/suite/object_comp_self.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/suite/object_comp_self.jsonnet
+++ /dev/null
@@ -1,8 +0,0 @@
-std.assertEqual(std.objectFields({
- a: {
- [name]: name
- for name in std.objectFields(self)
- },
- b: 2,
- c: 3,
-}.a), ['a', 'b', 'c'])
crates/jrsonnet-evaluator/tests/suite/object_context.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/suite/object_context.jsonnet
+++ /dev/null
@@ -1,13 +0,0 @@
-// `self` assigned to `me` was lost when being
-// referenced from field
-std.assertEqual({
- local me = self,
- a: 3,
- b: me.a,
-}.b, 3) &&
-std.assertEqual({
- local me = self,
- a: 3,
- b(): me.a,
-}.b(), 3) &&
-true
crates/jrsonnet-evaluator/tests/suite/object_fields.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/suite/object_fields.jsonnet
+++ /dev/null
@@ -1,4 +0,0 @@
-local a = 'a', b = null;
-std.assertEqual({ [a]: 2 }, { a: 2 }) &&
-std.assertEqual({ [b]: 2 }, {}) &&
-true
crates/jrsonnet-evaluator/tests/suite/object_inheritance.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/suite/object_inheritance.jsonnet
+++ /dev/null
@@ -1,17 +0,0 @@
-std.assertEqual({ a: self.b } + { b: 3 }, { a: 3, b: 3 }) &&
-std.assertEqual(
- {
- name: 'Alice',
- welcome: 'Hello ' + self.name + '!',
- },
- { name: 'Alice', welcome: 'Hello Alice!' },
-) &&
-std.assertEqual(
- {
- name: 'Alice',
- welcome: 'Hello ' + self.name + '!',
- } + {
- name: 'Bob',
- }, { name: 'Bob', welcome: 'Hello Bob!' }
-) &&
-true
crates/jrsonnet-evaluator/tests/suite/object_locals.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/suite/object_locals.jsonnet
+++ /dev/null
@@ -1,4 +0,0 @@
-std.assertEqual({ local a = 3, b: a }, { b: 3 }) &&
-std.assertEqual({ local a = 3, local c = a, b: c }, { b: 3 }) &&
-std.assertEqual({ local a = function(b) { [b]: 4 }, test: a('test') }, { test: { test: 4 } }) &&
-true
crates/jrsonnet-evaluator/tests/suite/object_super_standalone.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/suite/object_super_standalone.jsonnet
+++ /dev/null
@@ -1,11 +0,0 @@
-local obj = {
- a: 1,
- b: 2,
- c: 3,
-};
-local test = obj + {
- fields: std.objectFields(super),
- d: 5,
-};
-std.assertEqual(test.fields, ['a', 'b', 'c']) &&
-true
crates/jrsonnet-evaluator/tests/suite/sjsonnet_issue_127.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/suite/sjsonnet_issue_127.jsonnet
+++ /dev/null
@@ -1,6 +0,0 @@
-local myFunc = function(a)
- if (a) then "a" else "b";
-
-local b = "aaa";
-
-std.assertEqual(myFunc(b == [] || b == ['e']), "b")
crates/jrsonnet-evaluator/tests/suite/string_concat.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/suite/string_concat.jsonnet
+++ /dev/null
@@ -1,4 +0,0 @@
-std.assertEqual('Hello' + 'World', 'HelloWorld') &&
-std.assertEqual('Hello' * 3, 'HelloHelloHello') &&
-std.assertEqual('Hello' + 'World' * 3, 'HelloWorldWorldWorld') &&
-true
crates/jrsonnet-evaluator/tests/typed_obj.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/tests/typed_obj.rs
+++ /dev/null
@@ -1,194 +0,0 @@
-mod common;
-
-use std::fmt::Debug;
-
-use jrsonnet_evaluator::{error::Result, typed::Typed, State};
-
-#[derive(Clone, Typed, PartialEq, Debug)]
-struct A {
- a: u32,
- b: u16,
-}
-
-fn test_roundtrip<T: Typed + PartialEq + Debug + Clone>(value: T, s: State) -> Result<()> {
- let untyped = T::into_untyped(value.clone(), s.clone())?;
- let value2 = T::from_untyped(untyped.clone(), s.clone())?;
- ensure_eq!(value, value2);
- let untyped2 = T::into_untyped(value2, s.clone())?;
- ensure_val_eq!(s, untyped, untyped2);
-
- Ok(())
-}
-
-#[test]
-fn simple_object() -> Result<()> {
- let s = State::default();
- s.with_stdlib();
- let a = A::from_untyped(
- s.evaluate_snippet("snip".to_owned(), "{a: 1, b: 2}".into())?,
- s.clone(),
- )?;
- ensure_eq!(a, A { a: 1, b: 2 });
- test_roundtrip(a, s)?;
- Ok(())
-}
-
-#[derive(Clone, Typed, PartialEq, Debug)]
-struct B {
- a: u32,
- #[typed(rename = "c")]
- b: u16,
-}
-
-#[test]
-fn renamed_field() -> Result<()> {
- let s = State::default();
- s.with_stdlib();
- let b = B::from_untyped(
- s.evaluate_snippet("snip".to_owned(), "{a: 1, c: 2}".into())?,
- s.clone(),
- )?;
- ensure_eq!(b, B { a: 1, b: 2 });
- ensure_eq!(
- &B::into_untyped(b.clone(), s.clone())?.to_string(s.clone())? as &str,
- r#"{"a": 1, "c": 2}"#,
- );
- test_roundtrip(b, s)?;
- Ok(())
-}
-
-#[derive(Clone, Typed, PartialEq, Debug)]
-struct ObjectKind {
- #[typed(rename = "apiVersion")]
- api_version: String,
- #[typed(rename = "kind")]
- kind: String,
-}
-
-#[derive(Clone, Typed, PartialEq, Debug)]
-struct Object {
- #[typed(flatten)]
- kind: ObjectKind,
- b: u16,
-}
-
-#[test]
-fn flattened_object() -> Result<()> {
- let s = State::default();
- s.with_stdlib();
- let obj = Object::from_untyped(
- s.evaluate_snippet(
- "snip".to_owned(),
- "{apiVersion: 'ver', kind: 'kind', b: 2}".into(),
- )?,
- s.clone(),
- )?;
- ensure_eq!(
- obj,
- Object {
- kind: ObjectKind {
- api_version: "ver".into(),
- kind: "kind".into(),
- },
- b: 2
- }
- );
- ensure_eq!(
- &Object::into_untyped(obj.clone(), s.clone())?.to_string(s.clone())? as &str,
- r#"{"apiVersion": "ver", "b": 2, "kind": "kind"}"#,
- );
- test_roundtrip(obj, s)?;
- Ok(())
-}
-
-#[derive(Clone, Typed, PartialEq, Debug)]
-struct C {
- a: Option<u32>,
- b: u16,
-}
-
-#[test]
-fn optional_field_some() -> Result<()> {
- let s = State::default();
- s.with_stdlib();
- let c = C::from_untyped(
- s.evaluate_snippet("snip".to_owned(), "{a: 1, b: 2}".into())?,
- s.clone(),
- )?;
- ensure_eq!(c, C { a: Some(1), b: 2 });
- ensure_eq!(
- &C::into_untyped(c.clone(), s.clone())?.to_string(s.clone())? as &str,
- r#"{"a": 1, "b": 2}"#,
- );
- test_roundtrip(c, s)?;
- Ok(())
-}
-
-#[test]
-fn optional_field_none() -> Result<()> {
- let s = State::default();
- s.with_stdlib();
- let c = C::from_untyped(
- s.evaluate_snippet("snip".to_owned(), "{b: 2}".into())?,
- s.clone(),
- )?;
- ensure_eq!(c, C { a: None, b: 2 });
- ensure_eq!(
- &C::into_untyped(c.clone(), s.clone())?.to_string(s.clone())? as &str,
- r#"{"b": 2}"#,
- );
- test_roundtrip(c, s)?;
- Ok(())
-}
-
-#[derive(Clone, Typed, PartialEq, Debug)]
-struct D {
- #[typed(flatten(ok))]
- e: Option<E>,
- b: u16,
-}
-
-#[derive(Clone, Typed, PartialEq, Debug)]
-struct E {
- v: u32,
-}
-
-#[test]
-fn flatten_optional_some() -> Result<()> {
- let s = State::default();
- s.with_stdlib();
- let d = D::from_untyped(
- s.evaluate_snippet("snip".to_owned(), "{b: 2, v:1}".into())?,
- s.clone(),
- )?;
- ensure_eq!(
- d,
- D {
- e: Some(E { v: 1 }),
- b: 2
- }
- );
- ensure_eq!(
- &D::into_untyped(d.clone(), s.clone())?.to_string(s.clone())? as &str,
- r#"{"b": 2, "v": 1}"#,
- );
- test_roundtrip(d, s)?;
- Ok(())
-}
-
-#[test]
-fn flatten_optional_none() -> Result<()> {
- let s = State::default();
- s.with_stdlib();
- let d = D::from_untyped(
- s.evaluate_snippet("snip".to_owned(), "{b: 2, v: '1'}".into())?,
- s.clone(),
- )?;
- ensure_eq!(d, D { e: None, b: 2 });
- ensure_eq!(
- &D::into_untyped(d.clone(), s.clone())?.to_string(s.clone())? as &str,
- r#"{"b": 2}"#,
- );
- test_roundtrip(d, s)?;
- Ok(())
-}
crates/jrsonnet-interner/Cargo.tomldiffbeforeafterboth--- a/crates/jrsonnet-interner/Cargo.toml
+++ b/crates/jrsonnet-interner/Cargo.toml
@@ -7,12 +7,19 @@
edition = "2021"
[features]
-default = ["serde"]
+default = []
+# Implement value serialization using structdump
+structdump = ["dep:structdump"]
+# Implement value serialization using serde
+#
+# Warning: serialized values won't be deduplicated
serde = ["dep:serde"]
[dependencies]
jrsonnet-gcmodule = { version = "0.3.4" }
serde = { version = "1.0", optional = true }
+structdump = { version = "0.2.0", optional = true }
+
rustc-hash = "1.1"
hashbrown = { version = "0.12.1", features = ["inline-more"] }
crates/jrsonnet-interner/src/inner.rsdiffbeforeafterboth--- a/crates/jrsonnet-interner/src/inner.rs
+++ b/crates/jrsonnet-interner/src/inner.rs
@@ -84,6 +84,8 @@
unsafe { Self::new_raw(str.as_bytes(), true) }
}
+ // `slice::from_raw_parts` is not yet stabilized
+ #[allow(clippy::missing_const_for_fn)]
pub fn as_slice(&self) -> &[u8] {
let header = Self::header(self);
// SAFETY: data is not null, and it is correctly initialized
crates/jrsonnet-interner/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-interner/src/lib.rs
+++ b/crates/jrsonnet-interner/src/lib.rs
@@ -33,6 +33,10 @@
impl IStr {
#[must_use]
+ pub fn empty() -> Self {
+ "".into()
+ }
+ #[must_use]
pub fn as_str(&self) -> &str {
self as &str
}
@@ -201,6 +205,7 @@
}
}
+#[cfg(feature = "serde")]
impl serde::Serialize for IStr {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
@@ -210,6 +215,7 @@
}
}
+#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for IStr {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
@@ -220,6 +226,24 @@
}
}
+#[cfg(feature = "structdump")]
+impl structdump::Codegen for IStr {
+ fn gen_code(
+ &self,
+ res: &mut structdump::CodegenResult,
+ _unique: bool,
+ ) -> structdump::TokenStream {
+ let s: &str = self;
+ res.add_code(
+ structdump::quote! {
+ structdump_import::IStr::from(#s)
+ },
+ Some(structdump::quote![structdump_import::IStr]),
+ false,
+ )
+ }
+}
+
thread_local! {
static POOL: RefCell<HashMap<Inner, (), BuildHasherDefault<FxHasher>>> = RefCell::new(HashMap::with_capacity_and_hasher(200, BuildHasherDefault::default()));
}
crates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-macros/src/lib.rs
+++ b/crates/jrsonnet-macros/src/lib.rs
@@ -122,13 +122,13 @@
Normal {
ty: Box<Type>,
is_option: bool,
- name: String,
+ name: Option<String>,
cfg_attrs: Vec<Attribute>,
// ident: Ident,
},
Lazy {
is_option: bool,
- name: String,
+ name: Option<String>,
},
State,
Location,
@@ -142,8 +142,8 @@
FnArg::Typed(a) => a,
};
let ident = match &arg.pat as &Pat {
- Pat::Ident(i) => i.ident.clone(),
- _ => return Err(Error::new(arg.pat.span(), "arg should be plain identifier")),
+ Pat::Ident(i) => Some(i.ident.clone()),
+ _ => None,
};
let ty = &arg.ty;
if type_is_path(ty, "State").is_some() {
@@ -153,7 +153,7 @@
} else if type_is_path(ty, "Thunk").is_some() {
return Ok(Self::Lazy {
is_option: false,
- name: ident.to_string(),
+ name: ident.map(|v| v.to_string()),
});
}
@@ -166,7 +166,7 @@
if type_is_path(ty, "Thunk").is_some() {
return Ok(Self::Lazy {
is_option: true,
- name: ident.to_string(),
+ name: ident.map(|v| v.to_string()),
});
}
@@ -185,7 +185,7 @@
Ok(Self::Normal {
ty,
is_option,
- name: ident.to_string(),
+ name: ident.map(|v| v.to_string()),
cfg_attrs,
})
}
@@ -248,69 +248,95 @@
name,
cfg_attrs,
..
- } => Some(quote! {
- #(#cfg_attrs)*
- BuiltinParam {
- name: std::borrow::Cow::Borrowed(#name),
- has_default: #is_option,
- },
- }),
- ArgInfo::Lazy { is_option, name } => Some(quote! {
- BuiltinParam {
- name: std::borrow::Cow::Borrowed(#name),
- has_default: #is_option,
- },
- }),
+ } => {
+ let name = name
+ .as_ref()
+ .map(|n| quote! {Some(std::borrow::Cow::Borrowed(#n))})
+ .unwrap_or_else(|| quote! {None});
+ Some(quote! {
+ #(#cfg_attrs)*
+ BuiltinParam {
+ name: #name,
+ has_default: #is_option,
+ },
+ })
+ }
+ ArgInfo::Lazy { is_option, name } => {
+ let name = name
+ .as_ref()
+ .map(|n| quote! {Some(std::borrow::Cow::Borrowed(#n))})
+ .unwrap_or_else(|| quote! {None});
+ Some(quote! {
+ BuiltinParam {
+ name: #name,
+ has_default: #is_option,
+ },
+ })
+ }
ArgInfo::State => None,
ArgInfo::Location => None,
ArgInfo::This => None,
});
- let pass = args.iter().map(|a| match a {
- ArgInfo::Normal {
- ty,
- is_option,
- name,
- cfg_attrs,
- } => {
- let eval = quote! {s.push_description(
- || format!("argument <{}> evaluation", #name),
- || <#ty>::from_untyped(value.evaluate(s.clone())?, s.clone()),
- )?};
- let value = if *is_option {
- quote! {if let Some(value) = parsed.get(#name) {
- Some(#eval)
+ let mut id = 0usize;
+ let pass = args
+ .iter()
+ .map(|a| match a {
+ ArgInfo::Normal { .. } | ArgInfo::Lazy { .. } => {
+ let cid = id;
+ id += 1;
+ (quote! {#cid}, a)
+ }
+ ArgInfo::State | ArgInfo::Location | ArgInfo::This => {
+ (quote! {compile_error!("should not use id")}, a)
+ }
+ })
+ .map(|(id, a)| match a {
+ ArgInfo::Normal {
+ ty,
+ is_option,
+ name,
+ cfg_attrs,
+ } => {
+ let name = name.as_ref().map(|v| v.as_str()).unwrap_or("<unnamed>");
+ let eval = quote! {s.push_description(
+ || format!("argument <{}> evaluation", #name),
+ || <#ty>::from_untyped(value.evaluate(s.clone())?, s.clone()),
+ )?};
+ let value = if *is_option {
+ quote! {if let Some(value) = &parsed[#id] {
+ Some(#eval)
+ } else {
+ None
+ },}
} else {
- None
- },}
- } else {
- quote! {{
- let value = parsed.get(#name).expect("args shape is checked");
- #eval
- },}
- };
- quote! {
- #(#cfg_attrs)*
- #value
+ quote! {{
+ let value = parsed[#id].as_ref().expect("args shape is checked");
+ #eval
+ },}
+ };
+ quote! {
+ #(#cfg_attrs)*
+ #value
+ }
}
- }
- ArgInfo::Lazy { is_option, name } => {
- if *is_option {
- quote! {if let Some(value) = parsed.get(#name) {
- Some(value.clone())
+ ArgInfo::Lazy { is_option, .. } => {
+ if *is_option {
+ quote! {if let Some(value) = &parsed[#id] {
+ Some(value.clone())
+ } else {
+ None
+ }}
} else {
- None
- }}
- } else {
- quote! {
- parsed.get(#name).expect("args shape is correct").clone(),
+ quote! {
+ parsed[#id].as_ref().expect("args shape is correct").clone(),
+ }
}
}
- }
- ArgInfo::State => quote! {s.clone(),},
- ArgInfo::Location => quote! {location,},
- ArgInfo::This => quote! {self,},
- });
+ ArgInfo::State => quote! {s.clone(),},
+ ArgInfo::Location => quote! {location,},
+ ArgInfo::This => quote! {self,},
+ });
let fields = attr.fields.iter().map(|field| {
let name = &field.name;
crates/jrsonnet-parser/Cargo.tomldiffbeforeafterboth--- a/crates/jrsonnet-parser/Cargo.toml
+++ b/crates/jrsonnet-parser/Cargo.toml
@@ -7,7 +7,23 @@
edition = "2021"
[features]
+default = []
exp-destruct = []
+# Implement serialization of AST using structdump
+#
+# Structdump generates code, which exactly replicated passed AST
+# Contrary to serde, has no code bloat problem, and is recommended
+#
+# The only limitation is serialized form is only useable if built from build script
+structdump = ["dep:structdump", "jrsonnet-interner/structdump"]
+# Implement serialization of AST using serde
+#
+# Warning: as serde doesn't deduplicate strings, `Source` struct will bloat
+# output binary with repeating source code. To resolve this issue, you should either
+# override serialization of this struct using custom `Serializer`/`Deserializer`,
+# not rely on Source, and fill its `source_code` with empty value, or use `structdump`
+# instead
+serde = ["dep:serde"]
[dependencies]
jrsonnet-interner = { path = "../jrsonnet-interner", version = "0.4.2" }
@@ -18,6 +34,4 @@
peg = "0.8.0"
serde = { version = "1.0", features = ["derive", "rc"], optional = true }
-
-[dev-dependencies]
-jrsonnet-stdlib = { path = "../jrsonnet-stdlib", version = "0.4.2" }
+structdump = { version = "0.2.0", features = ["derive"], optional = true }
crates/jrsonnet-parser/src/expr.rsdiffbeforeafterboth--- a/crates/jrsonnet-parser/src/expr.rs
+++ b/crates/jrsonnet-parser/src/expr.rs
@@ -8,10 +8,13 @@
use jrsonnet_interner::IStr;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
+#[cfg(feature = "structdump")]
+use structdump::Codegen;
use crate::source::Source;
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "structdump", derive(Codegen))]
#[derive(Debug, PartialEq, Trace)]
pub enum FieldName {
/// {fixed: 2}
@@ -20,6 +23,7 @@
Dyn(LocExpr),
}
+#[cfg_attr(feature = "structdump", derive(Codegen))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Trace)]
pub enum Visibility {
@@ -37,10 +41,12 @@
}
}
+#[cfg_attr(feature = "structdump", derive(Codegen))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Trace)]
pub struct AssertStmt(pub LocExpr, pub Option<LocExpr>);
+#[cfg_attr(feature = "structdump", derive(Codegen))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Trace)]
pub struct FieldMember {
@@ -51,6 +57,7 @@
pub value: LocExpr,
}
+#[cfg_attr(feature = "structdump", derive(Codegen))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Trace)]
pub enum Member {
@@ -59,6 +66,7 @@
AssertStmt(AssertStmt),
}
+#[cfg_attr(feature = "structdump", derive(Codegen))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Trace)]
pub enum UnaryOpType {
@@ -84,6 +92,7 @@
}
}
+#[cfg_attr(feature = "structdump", derive(Codegen))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Trace)]
pub enum BinaryOpType {
@@ -150,11 +159,13 @@
}
/// name, default value
+#[cfg_attr(feature = "structdump", derive(Codegen))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Trace)]
pub struct Param(pub Destruct, pub Option<LocExpr>);
/// Defined function parameters
+#[cfg_attr(feature = "structdump", derive(Codegen))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Trace)]
pub struct ParamsDesc(pub Rc<Vec<Param>>);
@@ -166,6 +177,7 @@
}
}
+#[cfg_attr(feature = "structdump", derive(Codegen))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Trace)]
pub struct ArgsDesc {
@@ -187,6 +199,7 @@
Drop,
}
+#[cfg_attr(feature = "structdump", derive(Codegen))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Trace)]
pub enum Destruct {
@@ -216,6 +229,7 @@
}
}
+#[cfg_attr(feature = "structdump", derive(Codegen))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, Clone, PartialEq, Trace)]
pub enum BindSpec {
@@ -230,14 +244,17 @@
},
}
+#[cfg_attr(feature = "structdump", derive(Codegen))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Trace)]
pub struct IfSpecData(pub LocExpr);
+#[cfg_attr(feature = "structdump", derive(Codegen))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Trace)]
pub struct ForSpecData(pub IStr, pub LocExpr);
+#[cfg_attr(feature = "structdump", derive(Codegen))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Trace)]
pub enum CompSpec {
@@ -245,6 +262,7 @@
ForSpec(ForSpecData),
}
+#[cfg_attr(feature = "structdump", derive(Codegen))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Trace)]
pub struct ObjComp {
@@ -256,6 +274,7 @@
pub compspecs: Vec<CompSpec>,
}
+#[cfg_attr(feature = "structdump", derive(Codegen))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Trace)]
pub enum ObjBody {
@@ -263,6 +282,7 @@
ObjComp(ObjComp),
}
+#[cfg_attr(feature = "structdump", derive(Codegen))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Eq, Clone, Copy, Trace)]
pub enum LiteralType {
@@ -274,6 +294,7 @@
False,
}
+#[cfg_attr(feature = "structdump", derive(Codegen))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Trace)]
pub struct SliceDesc {
@@ -283,6 +304,7 @@
}
/// Syntax base
+#[cfg_attr(feature = "structdump", derive(Codegen))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialEq, Trace)]
pub enum Expr {
@@ -341,12 +363,6 @@
Index(LocExpr, LocExpr),
/// function(x) x
Function(ParamsDesc, LocExpr),
- /// std.thisFile
- IntrinsicThisFile,
- /// std.id,
- IntrinsicId,
- /// std.primitiveEquals
- Intrinsic(IStr),
/// if true == false then 1 else 2
IfElse {
cond: IfSpecData,
@@ -357,6 +373,7 @@
}
/// file, begin offset, end offset
+#[cfg_attr(feature = "structdump", derive(Codegen))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, PartialEq, Eq, Trace)]
#[trace(skip)]
@@ -379,6 +396,7 @@
/// Holds AST expression and its location in source file
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "structdump", derive(Codegen))]
#[derive(Clone, PartialEq, Trace)]
pub struct LocExpr(pub Rc<Expr>, pub ExprLocation);
crates/jrsonnet-parser/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-parser/src/lib.rs
+++ b/crates/jrsonnet-parser/src/lib.rs
@@ -7,9 +7,11 @@
pub use expr::*;
pub use jrsonnet_interner::IStr;
pub use peg;
+mod location;
mod source;
mod unescape;
-pub use source::Source;
+pub use location::CodeLocation;
+pub use source::{Source, SourceDirectory, SourceFile, SourcePath, SourcePathT, SourceVirtual};
pub struct ParserSettings {
pub file_name: Source,
@@ -193,7 +195,7 @@
/ assertion:assertion(s) {expr::Member::AssertStmt(assertion)}
/ field:field(s) {expr::Member::Field(field)}
pub rule objinside(s: &ParserSettings) -> expr::ObjBody
- = pre_locals:(b: obj_local(s) comma() {b})* "[" _ key:expr(s) _ "]" _ plus:"+"? _ ":" _ value:expr(s) post_locals:(comma() b:obj_local(s) {b})* _ forspec:forspec(s) others:(_ rest:compspec(s) {rest})? {
+ = pre_locals:(b: obj_local(s) comma() {b})* "[" _ key:expr(s) _ "]" _ plus:"+"? _ ":" _ value:expr(s) post_locals:(comma() b:obj_local(s) {b})* _ ("," _)? forspec:forspec(s) others:(_ rest:compspec(s) {rest})? {
let mut compspecs = vec![CompSpec::ForSpec(forspec)];
compspecs.extend(others.unwrap_or_default());
expr::ObjBody::ObjComp(expr::ObjComp{
@@ -251,10 +253,6 @@
pub rule expr_basic(s: &ParserSettings) -> Expr
= literal(s)
-
- / quiet!{"$intrinsicThisFile" {Expr::IntrinsicThisFile}}
- / quiet!{"$intrinsicId" {Expr::IntrinsicId}}
- / quiet!{"$intrinsic(" name:id() ")" {Expr::Intrinsic(name)}}
/ string_expr(s) / number_expr(s)
/ array_expr(s)
@@ -362,8 +360,7 @@
#[cfg(test)]
pub mod tests {
- use std::borrow::Cow;
-
+ use jrsonnet_interner::IStr;
use BinaryOpType::*;
use super::{expr::*, parse};
@@ -374,7 +371,7 @@
parse(
$s,
&ParserSettings {
- file_name: Source::new_virtual(Cow::Borrowed("<test>")),
+ file_name: Source::new_virtual("<test>".into(), IStr::empty()),
},
)
.unwrap()
@@ -385,7 +382,11 @@
($expr:expr, $from:expr, $to:expr$(,)?) => {
LocExpr(
std::rc::Rc::new($expr),
- ExprLocation(Source::new_virtual(Cow::Borrowed("<test>")), $from, $to),
+ ExprLocation(
+ Source::new_virtual("<test>".into(), IStr::empty()),
+ $from,
+ $to,
+ ),
)
};
}
@@ -715,15 +716,10 @@
}
#[test]
- fn can_parse_stdlib() {
- parse!(jrsonnet_stdlib::STDLIB_STR);
- }
-
- #[test]
fn add_location_info_to_all_sub_expressions() {
use Expr::*;
- let file_name = Source::new_virtual(Cow::Borrowed("<test>"));
+ let file_name = Source::new_virtual("<test>".into(), IStr::empty());
let expr = parse(
"{} { local x = 1, x: x } + {}",
&ParserSettings { file_name },
crates/jrsonnet-parser/src/location.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-parser/src/location.rs
@@ -0,0 +1,124 @@
+#[allow(clippy::module_name_repetitions)]
+#[derive(Clone, PartialEq, Eq, Debug)]
+pub struct CodeLocation {
+ pub offset: usize,
+
+ pub line: usize,
+ pub column: usize,
+
+ pub line_start_offset: usize,
+ pub line_end_offset: usize,
+}
+
+#[allow(clippy::module_name_repetitions)]
+pub fn location_to_offset(mut file: &str, mut line: usize, column: usize) -> Option<usize> {
+ let mut offset = 0;
+ while line > 1 {
+ let pos = file.find('\n')?;
+ offset += pos + 1;
+ file = &file[pos + 1..];
+ line -= 1;
+ }
+ offset += column - 1;
+ Some(offset)
+}
+
+#[allow(clippy::module_name_repetitions)]
+pub fn offset_to_location(file: &str, offsets: &[u32]) -> Vec<CodeLocation> {
+ if offsets.is_empty() {
+ return vec![];
+ }
+ let mut line = 1;
+ let mut column = 1;
+ let max_offset = *offsets.iter().max().expect("offsets is not empty");
+
+ let mut offset_map = offsets
+ .iter()
+ .enumerate()
+ .map(|(pos, offset)| (*offset, pos))
+ .collect::<Vec<_>>();
+ offset_map.sort_by_key(|v| v.0);
+ offset_map.reverse();
+
+ let mut out = vec![
+ CodeLocation {
+ offset: 0,
+ column: 0,
+ line: 0,
+ line_start_offset: 0,
+ line_end_offset: 0
+ };
+ offsets.len()
+ ];
+ let mut with_no_known_line_ending = vec![];
+ let mut this_line_offset = 0;
+ for (pos, ch) in file
+ .chars()
+ .enumerate()
+ .chain(std::iter::once((file.len(), ' ')))
+ {
+ column += 1;
+ match offset_map.last() {
+ Some(x) if x.0 == pos as u32 => {
+ let out_idx = x.1;
+ with_no_known_line_ending.push(out_idx);
+ out[out_idx].offset = pos;
+ out[out_idx].line = line;
+ out[out_idx].column = column;
+ out[out_idx].line_start_offset = this_line_offset;
+ offset_map.pop();
+ }
+ _ => {}
+ }
+ if ch == '\n' {
+ line += 1;
+ column = 1;
+
+ for idx in with_no_known_line_ending.drain(..) {
+ out[idx].line_end_offset = pos;
+ }
+ this_line_offset = pos + 1;
+
+ if pos == max_offset as usize + 1 {
+ break;
+ }
+ }
+ }
+ let file_end = file.chars().count();
+ for idx in with_no_known_line_ending {
+ out[idx].line_end_offset = file_end;
+ }
+
+ out
+}
+
+#[cfg(test)]
+pub mod tests {
+ use super::{offset_to_location, CodeLocation};
+
+ #[test]
+ fn test() {
+ assert_eq!(
+ offset_to_location(
+ "hello world\n_______________________________________________________",
+ &[0, 14]
+ ),
+ vec![
+ CodeLocation {
+ offset: 0,
+ line: 1,
+ column: 2,
+ line_start_offset: 0,
+ line_end_offset: 11,
+ },
+ CodeLocation {
+ offset: 14,
+ line: 2,
+ column: 4,
+ line_start_offset: 12,
+ line_end_offset: 67
+ }
+ ]
+ )
+ }
+}
crates/jrsonnet-parser/src/source.rsdiffbeforeafterboth--- a/crates/jrsonnet-parser/src/source.rs
+++ b/crates/jrsonnet-parser/src/source.rs
@@ -1,26 +1,267 @@
use std::{
- borrow::Cow,
- fmt,
- path::{Component, Path, PathBuf},
+ any::Any,
+ fmt::{self, Debug, Display},
+ hash::{Hash, Hasher},
+ path::{Path, PathBuf},
rc::Rc,
};
use jrsonnet_gcmodule::{Trace, Tracer};
+use jrsonnet_interner::IStr;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
+#[cfg(feature = "structdump")]
+use structdump::Codegen;
+
+use crate::location::{location_to_offset, offset_to_location, CodeLocation};
+
+macro_rules! any_ext_methods {
+ ($T:ident) => {
+ fn as_any(&self) -> &dyn Any;
+ fn dyn_hash(&self, hasher: &mut dyn Hasher);
+ fn dyn_eq(&self, other: &dyn $T) -> bool;
+ fn dyn_debug(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
+ };
+}
+macro_rules! any_ext_impl {
+ ($T:ident) => {
+ fn as_any(&self) -> &dyn Any {
+ self
+ }
+ fn dyn_hash(&self, mut hasher: &mut dyn Hasher) {
+ self.hash(&mut hasher)
+ }
+ fn dyn_eq(&self, other: &dyn $T) -> bool {
+ let other = if let Some(v) = other.as_any().downcast_ref::<Self>() {
+ v
+ } else {
+ return false;
+ };
+ let this = <Self as $T>::as_any(self)
+ .downcast_ref::<Self>()
+ .expect("restricted by impl");
+ this == other
+ }
+ fn dyn_debug(&self, fmt: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ <Self as std::fmt::Debug>::fmt(self, fmt)
+ }
+ };
+}
+macro_rules! any_ext {
+ ($T:ident) => {
+ impl Hash for dyn $T {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.dyn_hash(state)
+ }
+ }
+ impl PartialEq for dyn $T {
+ fn eq(&self, other: &Self) -> bool {
+ self.dyn_eq(other)
+ }
+ }
+ impl Eq for dyn $T {}
+ };
+}
+pub trait SourcePathT: Trace + Debug + Display {
+ /// This method should be checked by resolver before panicking with bad SourcePath input
+ /// if `true` - then resolver may threat this path as default, and default is usally a CWD
+ fn is_default(&self) -> bool;
+ fn path(&self) -> Option<&Path>;
+ any_ext_methods!(SourcePathT);
+}
+any_ext!(SourcePathT);
+
+/// Represents location of a file
+///
+/// Standard CLI only operates using
+/// - [`SourceFile`] - for any file
+/// - [`SourceDirectory`] - for resolution from CWD
+/// - [`SourceVirtual`] - for stdlib/ext-str
+///
+/// From all of those, only [`SourceVirtual`] may be constructed manually, any other path kind should be only obtained
+/// from assigned `ImportResolver`
+/// However, you should always check `is_default` method return, as it will return true for any paths, where default
+/// search location is applicable
+///
+/// Resolver may also return custom implementations of this trait, for example it may return http url in case of remotely loaded files
+#[derive(Eq, Debug, Clone)]
+pub struct SourcePath(Rc<dyn SourcePathT>);
+impl SourcePath {
+ pub fn new(inner: impl SourcePathT) -> Self {
+ Self(Rc::new(inner))
+ }
+ pub fn downcast_ref<T: SourcePathT>(&self) -> Option<&T> {
+ self.0.as_any().downcast_ref()
+ }
+ pub fn is_default(&self) -> bool {
+ self.0.is_default()
+ }
+ pub fn path(&self) -> Option<&Path> {
+ self.0.path()
+ }
+}
+impl Hash for SourcePath {
+ fn hash<H: Hasher>(&self, state: &mut H) {
+ self.0.hash(state);
+ }
+}
+impl PartialEq for SourcePath {
+ #[allow(clippy::op_ref)]
+ fn eq(&self, other: &Self) -> bool {
+ &*self.0 == &*other.0
+ }
+}
+impl Trace for SourcePath {
+ fn trace(&self, tracer: &mut Tracer) {
+ (*self.0).trace(tracer)
+ }
+
+ fn is_type_tracked() -> bool
+ where
+ Self: Sized,
+ {
+ true
+ }
+}
+impl Display for SourcePath {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
+impl Default for SourcePath {
+ fn default() -> Self {
+ Self(Rc::new(SourceDefault))
+ }
+}
-#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
-#[derive(PartialEq, Eq, Debug, Hash)]
-enum Inner {
- Real(PathBuf),
- Virtual(Cow<'static, str>),
+#[cfg(feature = "structdump")]
+impl Codegen for SourcePath {
+ fn gen_code(
+ &self,
+ res: &mut structdump::CodegenResult,
+ unique: bool,
+ ) -> structdump::TokenStream {
+ let source_virtual = self
+ .0
+ .as_any()
+ .downcast_ref::<SourceVirtual>()
+ .expect("can only codegen for virtual source paths!")
+ .0
+ .clone();
+ let val = res.add_value(source_virtual, false);
+ res.add_code(
+ structdump::quote! {
+ structdump_import::SourcePath::new(structdump_import::SourceVirtual(#val))
+ },
+ Some(structdump::quote!(SourcePath)),
+ unique,
+ )
+ }
}
+#[derive(Trace, Hash, PartialEq, Eq, Debug)]
+struct SourceDefault;
+impl Display for SourceDefault {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "<default>")
+ }
+}
+impl SourcePathT for SourceDefault {
+ fn is_default(&self) -> bool {
+ true
+ }
+ fn path(&self) -> Option<&Path> {
+ None
+ }
+ any_ext_impl!(SourcePathT);
+}
+
+/// Represents path to the file on the disk
+/// Directories shouldn't be put here, as resolution for files differs from resolution for directories:
+///
+/// When `file` is being resolved from `SourceFile(a/b/c)`, it should be resolved to `SourceFile(a/b/file)`,
+/// however if it is being resolved from `SourceDirectory(a/b/c)`, then it should be resolved to `SourceDirectory(a/b/c/file)`
+#[derive(Trace, Hash, PartialEq, Eq, Debug)]
+pub struct SourceFile(PathBuf);
+impl SourceFile {
+ pub fn new(path: PathBuf) -> Self {
+ Self(path)
+ }
+ pub fn path(&self) -> &Path {
+ &self.0
+ }
+}
+impl Display for SourceFile {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", self.0.display())
+ }
+}
+impl SourcePathT for SourceFile {
+ fn is_default(&self) -> bool {
+ false
+ }
+ fn path(&self) -> Option<&Path> {
+ Some(&self.0)
+ }
+ any_ext_impl!(SourcePathT);
+}
+
+/// Represents path to the directory on the disk
+///
+/// See also [`SourceFile`]
+#[derive(Trace, Hash, PartialEq, Eq, Debug)]
+pub struct SourceDirectory(PathBuf);
+impl SourceDirectory {
+ pub fn new(path: PathBuf) -> Self {
+ Self(path)
+ }
+ pub fn path(&self) -> &Path {
+ &self.0
+ }
+}
+impl Display for SourceDirectory {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", self.0.display())
+ }
+}
+impl SourcePathT for SourceDirectory {
+ fn is_default(&self) -> bool {
+ false
+ }
+ fn path(&self) -> Option<&Path> {
+ Some(&self.0)
+ }
+ any_ext_impl!(SourcePathT);
+}
+
+/// Represents virtual file, whose are located in memory, and shouldn't be cached
+///
+/// It is used for --ext-code=.../--tla-code=.../standard library source code by default,
+/// and user can construct arbitrary values by hand, without asking import resolver
+#[cfg_attr(feature = "structdump", derive(Codegen))]
+#[derive(Trace, Hash, PartialEq, Eq, Debug, Clone)]
+pub struct SourceVirtual(pub IStr);
+impl Display for SourceVirtual {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
+impl SourcePathT for SourceVirtual {
+ fn is_default(&self) -> bool {
+ true
+ }
+ fn path(&self) -> Option<&Path> {
+ None
+ }
+ any_ext_impl!(SourcePathT);
+}
+
/// Either real file, or virtual
/// Hash of FileName always have same value as raw Path, to make it possible to use with raw_entry_mut
+#[cfg_attr(feature = "structdump", derive(Codegen))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, PartialEq, Eq, Debug)]
-pub struct Source(Rc<Inner>);
+pub struct Source(pub Rc<(SourcePath, IStr)>);
static_assertions::assert_eq_size!(Source, *const ());
impl Trace for Source {
@@ -32,62 +273,26 @@
}
impl Source {
- /// Fails when path contains inner /../ or /./ references, or not absolute
- pub fn new(path: PathBuf) -> Option<Self> {
- if !path.is_absolute()
- || path
- .components()
- .any(|c| matches!(c, Component::CurDir | Component::ParentDir))
- {
- return None;
- }
- Some(Self(Rc::new(Inner::Real(path))))
+ pub fn new(path: SourcePath, code: IStr) -> Self {
+ Self(Rc::new((path, code)))
}
- pub fn new_virtual(n: Cow<'static, str>) -> Self {
- Self(Rc::new(Inner::Virtual(n)))
+ pub fn new_virtual(name: IStr, code: IStr) -> Self {
+ Self::new(SourcePath::new(SourceVirtual(name)), code)
}
- pub fn short_display(&self) -> ShortDisplay {
- ShortDisplay(self.clone())
- }
- pub fn full_path(&self) -> String {
- match self.inner() {
- Inner::Real(r) => r.display().to_string(),
- Inner::Virtual(v) => v.to_string(),
- }
+ pub fn code(&self) -> &str {
+ &self.0 .1
}
- /// Returns None if file is virtual
- pub fn path(&self) -> Option<&Path> {
- match self.inner() {
- Inner::Real(r) => Some(r),
- Inner::Virtual(_) => None,
- }
- }
- pub fn repr(&self) -> Result<&Path, &str> {
- match self.inner() {
- Inner::Real(r) => Ok(r),
- Inner::Virtual(v) => Err(v.as_ref()),
- }
+ pub fn source_path(&self) -> &SourcePath {
+ &self.0 .0
}
- fn inner(&self) -> &Inner {
- &self.0 as &Inner
+ pub fn map_source_locations(&self, locs: &[u32]) -> Vec<CodeLocation> {
+ offset_to_location(&self.0 .1, locs)
}
-}
-pub struct ShortDisplay(Source);
-impl fmt::Display for ShortDisplay {
- fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
- match &self.0 .0 as &Inner {
- Inner::Real(r) => {
- write!(
- f,
- "{}",
- r.file_name().expect("path is valid").to_string_lossy()
- )
- }
- Inner::Virtual(n) => write!(f, "{}", n),
- }
+ pub fn map_from_source_location(&self, line: usize, column: usize) -> Option<usize> {
+ location_to_offset(&self.0 .1, line, column)
}
}
crates/jrsonnet-stdlib/Cargo.tomldiffbeforeafterboth--- a/crates/jrsonnet-stdlib/Cargo.toml
+++ b/crates/jrsonnet-stdlib/Cargo.toml
@@ -7,5 +7,44 @@
edition = "2021"
[features]
+default = ["codegenerated-stdlib"]
+# Speed-up initialization by generating code for parsed stdlib, instead
+# of invoking parser for it
+codegenerated-stdlib = ["jrsonnet-parser/structdump"]
+# Enables legacy `std.thisFile` support, at the cost of worse caching
+legacy-this-file = []
+# Add order preservation flag to some functions
+exp-preserve-order = ["jrsonnet-evaluator/exp-preserve-order"]
+# Preserve order for files parsed via `std.parseJson`
+# Shame it isn't possible to enable per parse call, instead of globally
+exp-serde-preserve-order = [
+ "serde_json/preserve_order",
+ "jrsonnet-evaluator/exp-serde-preserve-order",
+]
[dependencies]
+jrsonnet-evaluator = { path = "../jrsonnet-evaluator", features = [
+ # std.parseJson parses file via serde, then converts Value to evaluator Val
+ "serde_json",
+], version = "0.4.2" }
+jrsonnet-macros = { path = "../jrsonnet-macros", version = "0.4.2" }
+jrsonnet-parser = { path = "../jrsonnet-parser", version = "0.4.2" }
+jrsonnet-gcmodule = "0.3.4"
+
+# Used for stdlib AST serialization
+bincode = { version = "1.3", optional = true }
+# Used both for stdlib AST serialization and std.parseJson/std.parseYaml
+serde = "1.0"
+
+# std.md5
+md5 = "0.7.0"
+# std.base64
+base64 = "0.13.0"
+# std.parseJson
+serde_json = "1.0"
+# std.parseYaml, custom library fork is used for C++/golang compatibility
+serde_yaml_with_quirks = "0.8.24"
+
+[build-dependencies]
+jrsonnet-parser = { path = "../jrsonnet-parser", version = "0.4.2" }
+structdump = { version = "0.2.0", features = ["derive"] }
crates/jrsonnet-stdlib/README.mddiffbeforeafterboth--- a/crates/jrsonnet-stdlib/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# jrsonnet-stdlib
-
-Jsonnet standard library packaged as crate
crates/jrsonnet-stdlib/build.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-stdlib/build.rs
@@ -0,0 +1,33 @@
+use std::{env, fs::File, io::Write, path::Path};
+
+use jrsonnet_parser::{parse, ParserSettings, Source};
+use structdump::CodegenResult;
+
+fn main() {
+ let parsed = parse(
+ include_str!("./src/std.jsonnet"),
+ &ParserSettings {
+ file_name: Source::new_virtual(
+ "<std>".into(),
+ include_str!("./src/std.jsonnet").into(),
+ ),
+ },
+ )
+ .expect("parse");
+
+ let mut out = CodegenResult::default();
+
+ let v = out.codegen(&parsed, true);
+
+ {
+ let out_dir = env::var("OUT_DIR").unwrap();
+ let dest_path = Path::new(&out_dir).join("stdlib.rs");
+ let mut f = File::create(&dest_path).unwrap();
+ f.write_all(
+ ("#[allow(clippy::redundant_clone)]".to_owned() + &v.to_string())
+ .replace(';', ";\n")
+ .as_bytes(),
+ )
+ .unwrap();
+ }
+}
crates/jrsonnet-stdlib/src/arrays.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-stdlib/src/arrays.rs
@@ -0,0 +1,216 @@
+use jrsonnet_evaluator::{
+ error::Result,
+ function::{builtin, FuncVal},
+ throw_runtime,
+ typed::{Any, BoundedUsize, Typed, VecVal},
+ val::{equals, ArrValue, IndexableVal},
+ IStr, State, Val,
+};
+use jrsonnet_gcmodule::Cc;
+
+#[builtin]
+pub fn builtin_make_array(s: State, sz: usize, func: FuncVal) -> Result<VecVal> {
+ let mut out = Vec::with_capacity(sz);
+ for i in 0..sz {
+ out.push(func.evaluate_simple(s.clone(), &(i as f64,))?);
+ }
+ Ok(VecVal(Cc::new(out)))
+}
+
+#[builtin]
+pub fn builtin_slice(
+ indexable: IndexableVal,
+ index: Option<BoundedUsize<0, { i32::MAX as usize }>>,
+ end: Option<BoundedUsize<0, { i32::MAX as usize }>>,
+ step: Option<BoundedUsize<1, { i32::MAX as usize }>>,
+) -> Result<Any> {
+ indexable.slice(index, end, step).map(Val::from).map(Any)
+}
+
+#[builtin]
+pub fn builtin_map(s: State, func: FuncVal, arr: ArrValue) -> Result<ArrValue> {
+ arr.map(s.clone(), |val| {
+ func.evaluate_simple(s.clone(), &(Any(val),))
+ })
+}
+
+#[builtin]
+pub fn builtin_flatmap(s: State, func: FuncVal, arr: IndexableVal) -> Result<IndexableVal> {
+ match arr {
+ IndexableVal::Str(str) => {
+ let mut out = String::new();
+ for c in str.chars() {
+ match func.evaluate_simple(s.clone(), &(c.to_string(),))? {
+ Val::Str(o) => out.push_str(&o),
+ Val::Null => continue,
+ _ => throw_runtime!("in std.join all items should be strings"),
+ };
+ }
+ Ok(IndexableVal::Str(out.into()))
+ }
+ IndexableVal::Arr(a) => {
+ let mut out = Vec::new();
+ for el in a.iter(s.clone()) {
+ let el = el?;
+ match func.evaluate_simple(s.clone(), &(Any(el),))? {
+ Val::Arr(o) => {
+ for oe in o.iter(s.clone()) {
+ out.push(oe?);
+ }
+ }
+ Val::Null => continue,
+ _ => throw_runtime!("in std.join all items should be arrays"),
+ };
+ }
+ Ok(IndexableVal::Arr(out.into()))
+ }
+ }
+}
+
+#[builtin]
+pub fn builtin_filter(s: State, func: FuncVal, arr: ArrValue) -> Result<ArrValue> {
+ arr.filter(s.clone(), |val| {
+ bool::from_untyped(
+ func.evaluate_simple(s.clone(), &(Any(val.clone()),))?,
+ s.clone(),
+ )
+ })
+}
+
+#[builtin]
+pub fn builtin_foldl(s: State, func: FuncVal, arr: ArrValue, init: Any) -> Result<Any> {
+ let mut acc = init.0;
+ for i in arr.iter(s.clone()) {
+ acc = func.evaluate_simple(s.clone(), &(Any(acc), Any(i?)))?;
+ }
+ Ok(Any(acc))
+}
+
+#[builtin]
+pub fn builtin_foldr(s: State, func: FuncVal, arr: ArrValue, init: Any) -> Result<Any> {
+ let mut acc = init.0;
+ for i in arr.iter(s.clone()).rev() {
+ acc = func.evaluate_simple(s.clone(), &(Any(i?), Any(acc)))?;
+ }
+ Ok(Any(acc))
+}
+
+#[builtin]
+pub fn builtin_range(from: i32, to: i32) -> Result<ArrValue> {
+ if to < from {
+ return Ok(ArrValue::new_eager());
+ }
+ Ok(ArrValue::new_range(from, to))
+}
+
+#[builtin]
+pub fn builtin_join(s: State, sep: IndexableVal, arr: ArrValue) -> Result<IndexableVal> {
+ Ok(match sep {
+ IndexableVal::Arr(joiner_items) => {
+ let mut out = Vec::new();
+
+ let mut first = true;
+ for item in arr.iter(s.clone()) {
+ let item = item?.clone();
+ if let Val::Arr(items) = item {
+ if !first {
+ out.reserve(joiner_items.len());
+ // TODO: extend
+ for item in joiner_items.iter(s.clone()) {
+ out.push(item?);
+ }
+ }
+ first = false;
+ out.reserve(items.len());
+ for item in items.iter(s.clone()) {
+ out.push(item?);
+ }
+ } else if matches!(item, Val::Null) {
+ continue;
+ } else {
+ throw_runtime!("in std.join all items should be arrays");
+ }
+ }
+
+ IndexableVal::Arr(out.into())
+ }
+ IndexableVal::Str(sep) => {
+ let mut out = String::new();
+
+ let mut first = true;
+ for item in arr.iter(s) {
+ let item = item?.clone();
+ if let Val::Str(item) = item {
+ if !first {
+ out += &sep;
+ }
+ first = false;
+ out += &item;
+ } else if matches!(item, Val::Null) {
+ continue;
+ } else {
+ throw_runtime!("in std.join all items should be strings");
+ }
+ }
+
+ IndexableVal::Str(out.into())
+ }
+ })
+}
+
+#[builtin]
+pub fn builtin_reverse(value: ArrValue) -> Result<ArrValue> {
+ Ok(value.reversed())
+}
+
+#[builtin]
+pub fn builtin_any(s: State, arr: ArrValue) -> Result<bool> {
+ for v in arr.iter(s.clone()) {
+ let v = bool::from_untyped(v?, s.clone())?;
+ if v {
+ return Ok(true);
+ }
+ }
+ Ok(false)
+}
+
+#[builtin]
+pub fn builtin_all(s: State, arr: ArrValue) -> Result<bool> {
+ for v in arr.iter(s.clone()) {
+ let v = bool::from_untyped(v?, s.clone())?;
+ if !v {
+ return Ok(false);
+ }
+ }
+ Ok(true)
+}
+
+#[builtin]
+pub fn builtin_member(s: State, arr: IndexableVal, x: Any) -> Result<bool> {
+ match arr {
+ IndexableVal::Str(str) => {
+ let x: IStr = IStr::from_untyped(x.0, s)?;
+ Ok(!x.is_empty() && str.contains(&*x))
+ }
+ IndexableVal::Arr(a) => {
+ for item in a.iter(s.clone()) {
+ let item = item?;
+ if equals(s.clone(), &item, &x.0)? {
+ return Ok(true);
+ }
+ }
+ Ok(false)
+ }
+ }
+}
+
+#[builtin]
+pub fn builtin_count(s: State, arr: Vec<Any>, v: Any) -> Result<usize> {
+ let mut count = 0;
+ for item in &arr {
+ if equals(s.clone(), &item.0, &v.0)? {
+ count += 1;
+ }
+ }
+ Ok(count)
+}
crates/jrsonnet-stdlib/src/encoding.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-stdlib/src/encoding.rs
@@ -0,0 +1,41 @@
+use jrsonnet_evaluator::{
+ error::{Error::RuntimeError, Result},
+ function::builtin,
+ typed::{Either, Either2},
+ IBytes, IStr,
+};
+
+#[builtin]
+pub fn builtin_encode_utf8(str: IStr) -> Result<IBytes> {
+ Ok(str.cast_bytes())
+}
+
+#[builtin]
+pub fn builtin_decode_utf8(arr: IBytes) -> Result<IStr> {
+ Ok(arr
+ .cast_str()
+ .ok_or_else(|| RuntimeError("bad utf8".into()))?)
+}
+
+#[builtin]
+pub fn builtin_base64(input: Either![IBytes, IStr]) -> Result<String> {
+ use Either2::*;
+ Ok(match input {
+ A(a) => base64::encode(a.as_slice()),
+ B(l) => base64::encode(l.bytes().collect::<Vec<_>>()),
+ })
+}
+
+#[builtin]
+pub fn builtin_base64_decode_bytes(input: IStr) -> Result<IBytes> {
+ Ok(base64::decode(input.as_bytes())
+ .map_err(|_| RuntimeError("bad base64".into()))?
+ .as_slice()
+ .into())
+}
+
+#[builtin]
+pub fn builtin_base64_decode(input: IStr) -> Result<String> {
+ let bytes = base64::decode(input.as_bytes()).map_err(|_| RuntimeError("bad base64".into()))?;
+ Ok(String::from_utf8(bytes).map_err(|_| RuntimeError("bad utf8".into()))?)
+}
crates/jrsonnet-stdlib/src/expr.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-stdlib/src/expr.rs
@@ -0,0 +1,101 @@
+use jrsonnet_parser::LocExpr;
+
+mod structdump_import {
+ pub(super) use std::{option::Option, rc::Rc, vec};
+
+ pub(super) use jrsonnet_parser::*;
+}
+
+pub fn stdlib_expr() -> LocExpr {
+ #[cfg(feature = "serialized-stdlib")]
+ {
+ use bincode::{BincodeRead, DefaultOptions, Options};
+ use serde::{Deserialize, Deserializer};
+
+ struct LocDeserializer<R, O: Options> {
+ source: Source,
+ wrapped: bincode::Deserializer<R, O>,
+ }
+ macro_rules! delegate {
+ ($(fn $name:ident($($arg:ident: $ty:ty),*))+) => {$(
+ fn $name<V>(mut self $(, $arg: $ty)*, visitor: V) -> Result<V::Value, Self::Error>
+ where V: serde::de::Visitor<'de>,
+ {
+ self.wrapped.$name($($arg,)* visitor)
+ }
+ )+};
+ }
+ impl<'de, R, O> Deserializer<'de> for LocDeserializer<R, O>
+ where
+ R: BincodeRead<'de>,
+ O: Options,
+ {
+ type Error = <&'de mut bincode::Deserializer<R, O> as Deserializer<'de>>::Error;
+
+ delegate! {
+ fn deserialize_any()
+ fn deserialize_bool()
+ fn deserialize_u16()
+ fn deserialize_u32()
+ fn deserialize_u64()
+ fn deserialize_i16()
+ fn deserialize_i32()
+ fn deserialize_i64()
+ fn deserialize_f32()
+ fn deserialize_f64()
+ fn deserialize_u128()
+ fn deserialize_i128()
+ fn deserialize_u8()
+ fn deserialize_i8()
+ fn deserialize_unit()
+ fn deserialize_char()
+ fn deserialize_str()
+ fn deserialize_string()
+ fn deserialize_bytes()
+ fn deserialize_byte_buf()
+ fn deserialize_enum(name: &'static str, variants: &'static [&'static str])
+ fn deserialize_tuple(len: usize)
+ fn deserialize_option()
+ fn deserialize_seq()
+ fn deserialize_map()
+ fn deserialize_struct(name: &'static str, fields: &'static [&'static str])
+ fn deserialize_identifier()
+ fn deserialize_newtype_struct(name: &'static str)
+ fn deserialize_unit_struct(name: &'static str)
+ fn deserialize_tuple_struct(name: &'static str, len: usize)
+ fn deserialize_ignored_any()
+ }
+
+ fn is_human_readable(&self) -> bool {
+ false
+ }
+ }
+
+ // In build.rs, Source object is populated with empty values, deserializer wrapper loads correct values on deserialize
+ let mut deserializer = bincode::Deserializer::from_slice(
+ include_bytes!(concat!(env!("OUT_DIR"), "/stdlib.bincode")),
+ DefaultOptions::new()
+ .with_fixint_encoding()
+ .allow_trailing_bytes(),
+ );
+
+ // Should not panic, stdlib.bincode is generated in build.rs
+ LocExpr::deserialize(&mut deserializer).unwrap()
+ }
+
+ #[cfg(feature = "codegenerated-stdlib")]
+ {
+ include!(concat!(env!("OUT_DIR"), "/stdlib.rs"))
+ }
+
+ #[cfg(not(feature = "codegenerated-stdlib"))]
+ {
+ jrsonnet_parser::parse(
+ STDLIB_STR,
+ &ParserSettings {
+ file_name: Source::new_virtual(Cow::Borrowed("<std>"), STDLIB_STR.into()),
+ },
+ )
+ .unwrap()
+ }
+}
crates/jrsonnet-stdlib/src/hash.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-stdlib/src/hash.rs
@@ -0,0 +1,6 @@
+use jrsonnet_evaluator::{error::Result, function::builtin, IStr};
+
+#[builtin]
+pub fn builtin_md5(str: IStr) -> Result<String> {
+ Ok(format!("{:x}", md5::compute(str.as_bytes())))
+}
crates/jrsonnet-stdlib/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/lib.rs
+++ b/crates/jrsonnet-stdlib/src/lib.rs
@@ -1 +1,562 @@
-pub const STDLIB_STR: &str = include_str!("./std.jsonnet");
+use std::{
+ cell::{Ref, RefCell, RefMut},
+ collections::HashMap,
+ rc::Rc,
+};
+
+use jrsonnet_evaluator::{
+ error::{Error::*, Result},
+ function::{builtin::Builtin, ArgLike, CallLocation, FuncVal, TlaArg},
+ gc::{GcHashMap, TraceBox},
+ tb, throw_runtime,
+ trace::PathResolver,
+ typed::{Any, Either, Either2, Either4, VecVal, M1},
+ val::{equals, ArrValue},
+ Context, ContextBuilder, IStr, ObjValue, ObjValueBuilder, State, Thunk, Val,
+};
+use jrsonnet_gcmodule::Cc;
+use jrsonnet_macros::builtin;
+use jrsonnet_parser::Source;
+
+mod expr;
+mod types;
+pub use types::*;
+mod arrays;
+pub use arrays::*;
+mod math;
+pub use math::*;
+mod operator;
+pub use operator::*;
+mod sort;
+pub use sort::*;
+mod hash;
+pub use hash::*;
+mod encoding;
+pub use encoding::*;
+mod objects;
+pub use objects::*;
+mod manifest;
+pub use manifest::*;
+mod parse;
+pub use parse::*;
+
+pub fn stdlib_uncached(s: State, settings: Rc<RefCell<Settings>>) -> ObjValue {
+ let mut builder = ObjValueBuilder::new();
+
+ let expr = expr::stdlib_expr();
+ let eval = jrsonnet_evaluator::evaluate(s.clone(), Context::default(), &expr)
+ .expect("stdlib.jsonnet should have no errors")
+ .as_obj()
+ .expect("stdlib.jsonnet should evaluate to object");
+
+ builder.with_super(eval);
+
+ for (name, builtin) in [
+ ("length", builtin_length::INST),
+ // Types
+ ("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", 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", 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", builtin_mod::INST),
+ ("primitiveEquals", builtin_primitive_equals::INST),
+ ("equals", builtin_equals::INST),
+ ("format", builtin_format::INST),
+ // Sort
+ ("sort", builtin_sort::INST),
+ // Hash
+ ("md5", builtin_md5::INST),
+ // Encoding
+ ("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", builtin_object_fields_ex::INST),
+ ("objectHasEx", builtin_object_has_ex::INST),
+ // Manifest
+ ("escapeStringJson", builtin_escape_string_json::INST),
+ ("manifestJsonEx", builtin_manifest_json_ex::INST),
+ ("manifestYamlDoc", builtin_manifest_yaml_doc::INST),
+ // Parsing
+ ("parseJson", builtin_parse_json::INST),
+ ("parseYaml", builtin_parse_yaml::INST),
+ // Misc
+ ("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.into())
+ .hide()
+ .value(s.clone(), Val::Func(FuncVal::StaticBuiltin(builtin)))
+ .expect("no conflict");
+ }
+
+ builder
+ .member("extVar".into())
+ .hide()
+ .value(
+ s.clone(),
+ Val::Func(FuncVal::Builtin(Cc::new(tb!(builtin_ext_var {
+ settings: settings.clone()
+ })))),
+ )
+ .expect("no conflict");
+ builder
+ .member("native".into())
+ .hide()
+ .value(
+ s.clone(),
+ Val::Func(FuncVal::Builtin(Cc::new(tb!(builtin_native {
+ settings: settings.clone()
+ })))),
+ )
+ .expect("no conflict");
+ builder
+ .member("trace".into())
+ .hide()
+ .value(
+ s.clone(),
+ Val::Func(FuncVal::Builtin(Cc::new(tb!(builtin_trace { settings })))),
+ )
+ .expect("no conflict");
+
+ builder
+ .member("id".into())
+ .hide()
+ .value(s, Val::Func(FuncVal::Id))
+ .expect("no conflict");
+
+ builder.build()
+}
+
+pub trait TracePrinter {
+ fn print_trace(&self, s: State, loc: CallLocation, value: IStr);
+}
+
+pub struct StdTracePrinter {
+ resolver: PathResolver,
+}
+impl StdTracePrinter {
+ pub fn new(resolver: PathResolver) -> Self {
+ Self { resolver }
+ }
+}
+impl TracePrinter for StdTracePrinter {
+ fn print_trace(&self, _s: State, loc: CallLocation, value: IStr) {
+ eprint!("TRACE:");
+ if let Some(loc) = loc.0 {
+ let locs = loc.0.map_source_locations(&[loc.1]);
+ eprint!(
+ " {}:{}",
+ match loc.0.source_path().path() {
+ Some(p) => self.resolver.resolve(p),
+ None => loc.0.source_path().to_string(),
+ },
+ locs[0].line
+ );
+ }
+ eprintln!(" {}", value);
+ }
+}
+
+pub struct Settings {
+ /// Used for `std.extVar`
+ pub ext_vars: HashMap<IStr, TlaArg>,
+ /// Used for `std.native`
+ pub ext_natives: HashMap<IStr, Cc<TraceBox<dyn Builtin>>>,
+ /// Helper to add globals without implementing custom ContextInitializer
+ pub globals: GcHashMap<IStr, Thunk<Val>>,
+ /// Used for `std.trace`
+ pub trace_printer: Box<dyn TracePrinter>,
+ /// Used for `std.thisFile`
+ pub path_resolver: PathResolver,
+}
+
+pub fn extvar_source(name: &str, code: impl Into<IStr>) -> Source {
+ let source_name = format!("<extvar:{}>", name);
+ Source::new_virtual(source_name.into(), code.into())
+}
+
+pub struct ContextInitializer {
+ // When we don't need to support legacy-this-file, we can reuse same context for all files
+ #[cfg(not(feature = "legacy-this-file"))]
+ context: Context,
+ // Otherwise, we can only keep first stdlib layer, and then stack thisFile on top of it
+ #[cfg(feature = "legacy-this-file")]
+ stdlib_obj: ObjValue,
+ settings: Rc<RefCell<Settings>>,
+}
+impl ContextInitializer {
+ pub fn new(s: State, resolver: PathResolver) -> Self {
+ let settings = Settings {
+ ext_vars: Default::default(),
+ ext_natives: Default::default(),
+ globals: Default::default(),
+ trace_printer: Box::new(StdTracePrinter::new(resolver.clone())),
+ path_resolver: resolver,
+ };
+ let settings = Rc::new(RefCell::new(settings));
+ Self {
+ #[cfg(not(feature = "legacy-this-file"))]
+ context: {
+ let mut context = ContextBuilder::with_capacity(1);
+ context.bind(
+ "std".into(),
+ Thunk::evaluated(Val::Obj(stdlib_uncached(s, settings.clone()))),
+ );
+ context.build()
+ },
+ #[cfg(feature = "legacy-this-file")]
+ stdlib_obj: stdlib_uncached(s, settings.clone()),
+ settings,
+ }
+ }
+ pub fn settings(&self) -> Ref<Settings> {
+ self.settings.borrow()
+ }
+ pub fn settings_mut(&self) -> RefMut<Settings> {
+ self.settings.borrow_mut()
+ }
+ pub fn add_ext_var(&self, name: IStr, value: Val) {
+ self.settings_mut()
+ .ext_vars
+ .insert(name, TlaArg::Val(value));
+ }
+ pub fn add_ext_str(&self, name: IStr, value: IStr) {
+ self.settings_mut()
+ .ext_vars
+ .insert(name, TlaArg::String(value));
+ }
+ pub fn add_ext_code(&self, name: &str, code: impl Into<IStr>) -> Result<()> {
+ let code = code.into();
+ let source = extvar_source(name, code.clone());
+ let parsed = jrsonnet_parser::parse(
+ &code,
+ &jrsonnet_parser::ParserSettings {
+ file_name: source.clone(),
+ },
+ )
+ .map_err(|e| ImportSyntaxError {
+ path: source,
+ error: Box::new(e),
+ })?;
+ // self.data_mut().volatile_files.insert(source_name, code);
+ self.settings_mut()
+ .ext_vars
+ .insert(name.into(), TlaArg::Code(parsed));
+ Ok(())
+ }
+ pub fn add_native(&self, name: IStr, cb: Cc<TraceBox<dyn Builtin>>) {
+ self.settings_mut().ext_natives.insert(name, cb);
+ }
+}
+impl jrsonnet_evaluator::ContextInitializer for ContextInitializer {
+ #[cfg(not(feature = "legacy-this-file"))]
+ fn initialize(&self, _s: State, _source: Source) -> jrsonnet_evaluator::Context {
+ let out = self.context.clone();
+ let globals = &self.settings().globals;
+ if globals.is_empty() {
+ return out;
+ }
+
+ let mut out = ContextBuilder::extend(out);
+ for (k, v) in globals.iter() {
+ out.bind(k.clone(), v.clone());
+ }
+ out.build()
+ }
+ #[cfg(feature = "legacy-this-file")]
+ fn initialize(&self, s: State, source: Source) -> jrsonnet_evaluator::Context {
+ let mut builder = ObjValueBuilder::new();
+ builder.with_super(self.stdlib_obj.clone());
+ builder
+ .member("thisFile".into())
+ .hide()
+ .value(
+ s,
+ Val::Str(match source.source_path().path() {
+ Some(p) => self.settings().path_resolver.resolve(p).into(),
+ None => source.source_path().to_string().into(),
+ }),
+ )
+ .expect("this object builder is empty");
+ let stdlib_with_this_file = builder.build();
+
+ let mut context = ContextBuilder::with_capacity(1);
+ context.bind(
+ "std".into(),
+ Thunk::evaluated(Val::Obj(stdlib_with_this_file)),
+ );
+ for (k, v) in self.settings().globals.iter() {
+ context.bind(k.clone(), v.clone());
+ }
+ context.build()
+ }
+ fn as_any(&self) -> &dyn std::any::Any {
+ self
+ }
+}
+
+#[builtin]
+fn builtin_length(x: Either![IStr, ArrValue, ObjValue, FuncVal]) -> Result<usize> {
+ use Either4::*;
+ Ok(match x {
+ A(x) => x.chars().count(),
+ B(x) => x.len(),
+ C(x) => x.len(),
+ D(f) => f.params_len(),
+ })
+}
+
+#[builtin]
+const fn builtin_codepoint(str: char) -> Result<u32> {
+ Ok(str as u32)
+}
+
+#[builtin]
+fn builtin_substr(str: IStr, from: usize, len: usize) -> Result<String> {
+ Ok(str.chars().skip(from).take(len).collect())
+}
+
+#[builtin(fields(
+ settings: Rc<RefCell<Settings>>,
+))]
+fn builtin_ext_var(this: &builtin_ext_var, s: State, x: IStr) -> Result<Any> {
+ let ctx = s.create_default_context(extvar_source(&x, ""));
+ Ok(Any(this
+ .settings
+ .borrow()
+ .ext_vars
+ .get(&x)
+ .cloned()
+ .ok_or_else(|| UndefinedExternalVariable(x))?
+ .evaluate_arg(s.clone(), ctx, true)?
+ .evaluate(s)?))
+}
+
+#[builtin(fields(
+ settings: Rc<RefCell<Settings>>,
+))]
+fn builtin_native(this: &builtin_native, name: IStr) -> Result<Any> {
+ Ok(Any(this
+ .settings
+ .borrow()
+ .ext_natives
+ .get(&name)
+ .cloned()
+ .map_or(Val::Null, |v| {
+ Val::Func(FuncVal::Builtin(v.clone()))
+ })))
+}
+
+#[builtin]
+fn builtin_char(n: u32) -> Result<char> {
+ Ok(std::char::from_u32(n).ok_or_else(|| InvalidUnicodeCodepointGot(n))?)
+}
+
+#[builtin(fields(
+ settings: Rc<RefCell<Settings>>,
+))]
+fn builtin_trace(
+ this: &builtin_trace,
+ s: State,
+ loc: CallLocation,
+ str: IStr,
+ rest: Thunk<Val>,
+) -> Result<Any> {
+ this.settings
+ .borrow()
+ .trace_printer
+ .print_trace(s.clone(), loc, str);
+ Ok(Any(rest.evaluate(s)?))
+}
+
+#[builtin]
+fn builtin_str_replace(str: String, from: IStr, to: IStr) -> Result<String> {
+ Ok(str.replace(&from as &str, &to as &str))
+}
+
+#[builtin]
+fn builtin_splitlimit(str: IStr, c: IStr, maxsplits: Either![usize, M1]) -> Result<VecVal> {
+ use Either2::*;
+ Ok(VecVal(Cc::new(match maxsplits {
+ A(n) => str
+ .splitn(n + 1, &c as &str)
+ .map(|s| Val::Str(s.into()))
+ .collect(),
+ B(_) => str.split(&c as &str).map(|s| Val::Str(s.into())).collect(),
+ })))
+}
+
+#[builtin]
+fn builtin_ascii_upper(str: IStr) -> Result<String> {
+ Ok(str.to_ascii_uppercase())
+}
+
+#[builtin]
+fn builtin_ascii_lower(str: IStr) -> Result<String> {
+ Ok(str.to_ascii_lowercase())
+}
+
+#[builtin]
+fn builtin_find_substr(pat: IStr, str: IStr) -> Result<ArrValue> {
+ if pat.is_empty() || str.is_empty() || pat.len() > str.len() {
+ return Ok(ArrValue::empty());
+ }
+
+ let str = str.as_str();
+ let pat = pat.as_bytes();
+ let strb = str.as_bytes();
+
+ let max_pos = str.len() - pat.len();
+
+ let mut out: Vec<Val> = Vec::new();
+ for (ch_idx, (i, _)) in str
+ .char_indices()
+ .take_while(|(i, _)| i <= &max_pos)
+ .enumerate()
+ {
+ if &strb[i..i + pat.len()] == pat {
+ out.push(Val::Num(ch_idx as f64))
+ }
+ }
+ Ok(out.into())
+}
+
+#[allow(clippy::comparison_chain)]
+#[builtin]
+fn builtin_starts_with(
+ s: State,
+ a: Either![IStr, ArrValue],
+ b: Either![IStr, ArrValue],
+) -> Result<bool> {
+ Ok(match (a, b) {
+ (Either2::A(a), Either2::A(b)) => a.starts_with(b.as_str()),
+ (Either2::B(a), Either2::B(b)) => {
+ if b.len() > a.len() {
+ return Ok(false);
+ } else if b.len() == a.len() {
+ return equals(s, &Val::Arr(a), &Val::Arr(b));
+ } else {
+ for (a, b) in a
+ .slice(None, Some(b.len()), None)
+ .iter(s.clone())
+ .zip(b.iter(s.clone()))
+ {
+ let a = a?;
+ let b = b?;
+ if !equals(s.clone(), &a, &b)? {
+ return Ok(false);
+ }
+ }
+ true
+ }
+ }
+ _ => throw_runtime!("both arguments should be of the same type"),
+ })
+}
+
+#[allow(clippy::comparison_chain)]
+#[builtin]
+fn builtin_ends_with(
+ s: State,
+ a: Either![IStr, ArrValue],
+ b: Either![IStr, ArrValue],
+) -> Result<bool> {
+ Ok(match (a, b) {
+ (Either2::A(a), Either2::A(b)) => a.ends_with(b.as_str()),
+ (Either2::B(a), Either2::B(b)) => {
+ if b.len() > a.len() {
+ return Ok(false);
+ } else if b.len() == a.len() {
+ return equals(s, &Val::Arr(a), &Val::Arr(b));
+ } else {
+ let a_len = a.len();
+ for (a, b) in a
+ .slice(Some(a_len - b.len()), None, None)
+ .iter(s.clone())
+ .zip(b.iter(s.clone()))
+ {
+ let a = a?;
+ let b = b?;
+ if !equals(s.clone(), &a, &b)? {
+ return Ok(false);
+ }
+ }
+ true
+ }
+ }
+ _ => throw_runtime!("both arguments should be of the same type"),
+ })
+}
+
+pub trait StateExt {
+ /// This method was previously implemented in jrsonnet-evaluator itself
+ fn with_stdlib(&self);
+ fn add_global(&self, name: IStr, value: Thunk<Val>);
+}
+
+impl StateExt for State {
+ fn with_stdlib(&self) {
+ let initializer = ContextInitializer::new(self.clone(), PathResolver::new_cwd_fallback());
+ self.settings_mut().context_initializer = Box::new(initializer)
+ }
+ fn add_global(&self, name: IStr, value: Thunk<Val>) {
+ self.settings()
+ .context_initializer
+ .as_any()
+ .downcast_ref::<ContextInitializer>()
+ .expect("not standard context initializer")
+ .settings_mut()
+ .globals
+ .insert(name, value);
+ }
+}
crates/jrsonnet-stdlib/src/manifest.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-stdlib/src/manifest.rs
@@ -0,0 +1,65 @@
+use jrsonnet_evaluator::{
+ error::Result,
+ function::builtin,
+ stdlib::manifest::{
+ escape_string_json, manifest_json_ex, manifest_yaml_ex, ManifestJsonOptions, ManifestType,
+ ManifestYamlOptions,
+ },
+ typed::Any,
+ IStr, State,
+};
+
+#[builtin]
+pub fn builtin_escape_string_json(str_: IStr) -> Result<String> {
+ Ok(escape_string_json(&str_))
+}
+
+#[builtin]
+pub fn builtin_manifest_json_ex(
+ s: State,
+ value: Any,
+ indent: IStr,
+ newline: Option<IStr>,
+ key_val_sep: Option<IStr>,
+ #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+) -> Result<String> {
+ let newline = newline.as_deref().unwrap_or("\n");
+ let key_val_sep = key_val_sep.as_deref().unwrap_or(": ");
+ manifest_json_ex(
+ s,
+ &value.0,
+ &ManifestJsonOptions {
+ padding: &indent,
+ mtype: ManifestType::Std,
+ newline,
+ key_val_sep,
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: preserve_order.unwrap_or(false),
+ },
+ )
+}
+
+#[builtin]
+pub fn builtin_manifest_yaml_doc(
+ s: State,
+ value: Any,
+ indent_array_in_object: Option<bool>,
+ quote_keys: Option<bool>,
+ #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+) -> Result<String> {
+ manifest_yaml_ex(
+ s,
+ &value.0,
+ &ManifestYamlOptions {
+ padding: " ",
+ arr_element_padding: if indent_array_in_object.unwrap_or(false) {
+ " "
+ } else {
+ ""
+ },
+ quote_keys: quote_keys.unwrap_or(true),
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order: preserve_order.unwrap_or(false),
+ },
+ )
+}
crates/jrsonnet-stdlib/src/math.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-stdlib/src/math.rs
@@ -0,0 +1,87 @@
+use jrsonnet_evaluator::{error::Result, function::builtin, typed::PositiveF64};
+
+#[builtin]
+pub fn builtin_modulo(a: f64, b: f64) -> Result<f64> {
+ Ok(a % b)
+}
+
+#[builtin]
+pub fn builtin_floor(x: f64) -> Result<f64> {
+ Ok(x.floor())
+}
+
+#[builtin]
+pub fn builtin_ceil(x: f64) -> Result<f64> {
+ Ok(x.ceil())
+}
+
+#[builtin]
+pub fn builtin_log(n: f64) -> Result<f64> {
+ Ok(n.ln())
+}
+
+#[builtin]
+pub fn builtin_pow(x: f64, n: f64) -> Result<f64> {
+ Ok(x.powf(n))
+}
+
+#[builtin]
+pub fn builtin_sqrt(x: PositiveF64) -> Result<f64> {
+ Ok(x.0.sqrt())
+}
+
+#[builtin]
+pub fn builtin_sin(x: f64) -> Result<f64> {
+ Ok(x.sin())
+}
+
+#[builtin]
+pub fn builtin_cos(x: f64) -> Result<f64> {
+ Ok(x.cos())
+}
+
+#[builtin]
+pub fn builtin_tan(x: f64) -> Result<f64> {
+ Ok(x.tan())
+}
+
+#[builtin]
+pub fn builtin_asin(x: f64) -> Result<f64> {
+ Ok(x.asin())
+}
+
+#[builtin]
+pub fn builtin_acos(x: f64) -> Result<f64> {
+ Ok(x.acos())
+}
+
+#[builtin]
+pub fn builtin_atan(x: f64) -> Result<f64> {
+ Ok(x.atan())
+}
+
+#[builtin]
+pub fn builtin_exp(x: f64) -> Result<f64> {
+ Ok(x.exp())
+}
+
+fn frexp(s: f64) -> (f64, i16) {
+ if s == 0.0 {
+ (s, 0)
+ } else {
+ let lg = s.abs().log2();
+ let x = (lg - lg.floor() - 1.0).exp2();
+ let exp = lg.floor() + 1.0;
+ (s.signum() * x, exp as i16)
+ }
+}
+
+#[builtin]
+pub fn builtin_mantissa(x: f64) -> Result<f64> {
+ Ok(frexp(x).0)
+}
+
+#[builtin]
+pub fn builtin_exponent(x: f64) -> Result<i16> {
+ Ok(frexp(x).1)
+}
crates/jrsonnet-stdlib/src/objects.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-stdlib/src/objects.rs
@@ -0,0 +1,27 @@
+use jrsonnet_evaluator::{
+ error::Result, function::builtin, typed::VecVal, val::Val, IStr, ObjValue,
+};
+use jrsonnet_gcmodule::Cc;
+
+#[builtin]
+pub fn builtin_object_fields_ex(
+ obj: ObjValue,
+ inc_hidden: bool,
+ #[cfg(feature = "exp-preserve-order")] preserve_order: Option<bool>,
+) -> Result<VecVal> {
+ #[cfg(feature = "exp-preserve-order")]
+ let preserve_order = preserve_order.unwrap_or(false);
+ let out = obj.fields_ex(
+ inc_hidden,
+ #[cfg(feature = "exp-preserve-order")]
+ preserve_order,
+ );
+ Ok(VecVal(Cc::new(
+ out.into_iter().map(Val::Str).collect::<Vec<_>>(),
+ )))
+}
+
+#[builtin]
+pub fn builtin_object_has_ex(obj: ObjValue, f: IStr, inc_hidden: bool) -> Result<bool> {
+ Ok(obj.has_field_ex(f, inc_hidden))
+}
crates/jrsonnet-stdlib/src/operator.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-stdlib/src/operator.rs
@@ -0,0 +1,40 @@
+//! Some jsonnet operations are desugared to stdlib functions...
+//! However, in our case we instead implement them in native, and implement native functions on top of core for backwards compatibility
+
+use jrsonnet_evaluator::{
+ error::Result,
+ function::builtin,
+ operator::evaluate_mod_op,
+ stdlib::std_format,
+ typed::{Any, Either, Either2},
+ val::{equals, primitive_equals},
+ IStr, State, Val,
+};
+
+#[builtin]
+pub fn builtin_mod(s: State, a: Either![f64, IStr], b: Any) -> Result<Any> {
+ use Either2::*;
+ Ok(Any(evaluate_mod_op(
+ s,
+ &match a {
+ A(v) => Val::Num(v),
+ B(s) => Val::Str(s),
+ },
+ &b.0,
+ )?))
+}
+
+#[builtin]
+pub fn builtin_primitive_equals(a: Any, b: Any) -> Result<bool> {
+ primitive_equals(&a.0, &b.0)
+}
+
+#[builtin]
+pub fn builtin_equals(s: State, a: Any, b: Any) -> Result<bool> {
+ equals(s, &a.0, &b.0)
+}
+
+#[builtin]
+pub fn builtin_format(s: State, str: IStr, vals: Any) -> Result<String> {
+ std_format(s, str, vals.0)
+}
crates/jrsonnet-stdlib/src/parse.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-stdlib/src/parse.rs
@@ -0,0 +1,39 @@
+use jrsonnet_evaluator::{
+ error::{Error::RuntimeError, Result},
+ function::builtin,
+ typed::{Any, Typed},
+ IStr, State, Val,
+};
+use serde::Deserialize;
+
+#[builtin]
+pub fn builtin_parse_json(st: State, s: IStr) -> Result<Any> {
+ use serde_json::Value;
+ let value: Value = serde_json::from_str(&s)
+ .map_err(|e| RuntimeError(format!("failed to parse json: {}", e).into()))?;
+ Ok(Any(Value::into_untyped(value, st)?))
+}
+
+#[builtin]
+pub fn builtin_parse_yaml(st: State, s: IStr) -> Result<Any> {
+ use serde_json::Value;
+ use serde_yaml_with_quirks::DeserializingQuirks;
+ let value = serde_yaml_with_quirks::Deserializer::from_str_with_quirks(
+ &s,
+ DeserializingQuirks { old_octals: true },
+ );
+ let mut out = vec![];
+ for item in value {
+ let value = Value::deserialize(item)
+ .map_err(|e| RuntimeError(format!("failed to parse yaml: {}", e).into()))?;
+ let val = Value::into_untyped(value, st.clone())?;
+ out.push(val);
+ }
+ Ok(Any(if out.is_empty() {
+ Val::Null
+ } else if out.len() == 1 {
+ out.into_iter().next().unwrap()
+ } else {
+ Val::Arr(out.into())
+ }))
+}
crates/jrsonnet-stdlib/src/sort.rsdiffbeforeafterboth--- /dev/null
+++ b/crates/jrsonnet-stdlib/src/sort.rs
@@ -0,0 +1,109 @@
+use jrsonnet_evaluator::{
+ error::Result,
+ function::{builtin, FuncVal},
+ throw_runtime,
+ typed::Any,
+ val::ArrValue,
+ State, Val,
+};
+use jrsonnet_gcmodule::Cc;
+
+#[derive(Copy, Clone)]
+enum SortKeyType {
+ Number,
+ String,
+ Unknown,
+}
+
+#[derive(PartialEq)]
+struct NonNaNf64(f64);
+impl PartialOrd for NonNaNf64 {
+ fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+ self.0.partial_cmp(&other.0)
+ }
+}
+impl Eq for NonNaNf64 {}
+impl Ord for NonNaNf64 {
+ fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+ self.partial_cmp(other).expect("non nan")
+ }
+}
+
+fn get_sort_type<T>(
+ values: &mut [T],
+ key_getter: impl Fn(&mut T) -> &mut Val,
+) -> Result<SortKeyType> {
+ let mut sort_type = SortKeyType::Unknown;
+ for i in values.iter_mut() {
+ let i = key_getter(i);
+ match (i, sort_type) {
+ (Val::Str(_), SortKeyType::Unknown) => sort_type = SortKeyType::String,
+ (Val::Num(_), SortKeyType::Unknown) => sort_type = SortKeyType::Number,
+ (Val::Str(_), SortKeyType::String) | (Val::Num(_), SortKeyType::Number) => {}
+ (Val::Str(_) | Val::Num(_), _) => {
+ throw_runtime!("sort elements should have the same types")
+ }
+ _ => throw_runtime!("sort key should either be a string or a number"),
+ }
+ }
+ Ok(sort_type)
+}
+
+/// * `key_getter` - None, if identity sort required
+pub fn sort(s: State, values: Cc<Vec<Val>>, key_getter: FuncVal) -> Result<Cc<Vec<Val>>> {
+ if values.len() <= 1 {
+ return Ok(values);
+ }
+ if key_getter.is_identity() {
+ // Fast path, identity key getter
+ let mut values = (*values).clone();
+ let sort_type = get_sort_type(&mut values, |k| k)?;
+ match sort_type {
+ SortKeyType::Number => values.sort_unstable_by_key(|v| match v {
+ Val::Num(n) => NonNaNf64(*n),
+ _ => unreachable!(),
+ }),
+ SortKeyType::String => values.sort_unstable_by_key(|v| match v {
+ Val::Str(s) => s.clone(),
+ _ => unreachable!(),
+ }),
+ SortKeyType::Unknown => unreachable!(),
+ };
+ Ok(Cc::new(values))
+ } else {
+ // Slow path, user provided key getter
+ let mut vk = Vec::with_capacity(values.len());
+ for value in values.iter() {
+ vk.push((
+ value.clone(),
+ key_getter.evaluate_simple(s.clone(), &(Any(value.clone()),))?,
+ ));
+ }
+ let sort_type = get_sort_type(&mut vk, |v| &mut v.1)?;
+ match sort_type {
+ SortKeyType::Number => vk.sort_by_key(|v| match v.1 {
+ Val::Num(n) => NonNaNf64(n),
+ _ => unreachable!(),
+ }),
+ SortKeyType::String => vk.sort_by_key(|v| match &v.1 {
+ Val::Str(s) => s.clone(),
+ _ => unreachable!(),
+ }),
+ SortKeyType::Unknown => unreachable!(),
+ };
+ Ok(Cc::new(vk.into_iter().map(|v| v.0).collect()))
+ }
+}
+
+#[builtin]
+#[allow(non_snake_case)]
+pub fn builtin_sort(s: State, arr: ArrValue, keyF: Option<FuncVal>) -> Result<ArrValue> {
+ if arr.len() <= 1 {
+ return Ok(arr);
+ }
+ Ok(ArrValue::Eager(super::sort::sort(
+ s.clone(),
+ arr.evaluated(s)?,
+ keyF.unwrap_or_else(FuncVal::identity),
+ )?))
+}
crates/jrsonnet-stdlib/src/std.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/std.jsonnet
+++ b/crates/jrsonnet-stdlib/src/std.jsonnet
@@ -2,73 +2,9 @@
local std = self,
local id = std.id,
- # Magic legacy field
- thisFile:: $intrinsicThisFile,
- id:: $intrinsicId,
+ thisFile:: error 'std.thisFile is deprecated, to enable its support in jrsonnet - recompile it with "legacy-this-file" support.\nThis will slow down stdlib caching a bit, though',
- # Those functions aren't normally located in stdlib
- length:: $intrinsic(length),
- type:: $intrinsic(type),
- makeArray:: $intrinsic(makeArray),
- codepoint:: $intrinsic(codepoint),
- objectFieldsEx:: $intrinsic(objectFieldsEx),
- objectHasEx:: $intrinsic(objectHasEx),
- primitiveEquals:: $intrinsic(primitiveEquals),
- modulo:: $intrinsic(modulo),
- floor:: $intrinsic(floor),
- ceil:: $intrinsic(ceil),
- extVar:: $intrinsic(extVar),
- native:: $intrinsic(native),
- filter:: $intrinsic(filter),
- char:: $intrinsic(char),
- encodeUTF8:: $intrinsic(encodeUTF8),
- decodeUTF8:: $intrinsic(decodeUTF8),
- md5:: $intrinsic(md5),
- trace:: $intrinsic(trace),
- parseJson:: $intrinsic(parseJson),
- parseYaml:: $intrinsic(parseYaml),
-
- log:: $intrinsic(log),
- pow:: $intrinsic(pow),
- sqrt:: $intrinsic(sqrt),
-
- sin:: $intrinsic(sin),
- cos:: $intrinsic(cos),
- tan:: $intrinsic(tan),
- asin:: $intrinsic(asin),
- acos:: $intrinsic(acos),
- atan:: $intrinsic(atan),
-
- exp:: $intrinsic(exp),
- mantissa:: $intrinsic(mantissa),
- exponent:: $intrinsic(exponent),
-
- any:: $intrinsic(any),
- all:: $intrinsic(all),
-
- isString(v):: std.type(v) == 'string',
- isNumber(v):: std.type(v) == 'number',
- isBoolean(v):: std.type(v) == 'boolean',
- isObject(v):: std.type(v) == 'object',
- isArray(v):: std.type(v) == 'array',
- isFunction(v):: std.type(v) == 'function',
-
- toString(a)::
- if std.type(a) == 'string' then a else '' + a,
-
- substr:: $intrinsic(substr),
-
- startsWith(a, b)::
- if std.length(a) < std.length(b) then
- false
- else
- std.substr(a, 0, std.length(b)) == b,
-
- endsWith(a, b)::
- if std.length(a) < std.length(b) then
- false
- else
- std.substr(a, std.length(a) - std.length(b), std.length(b)) == b,
+ toString(a):: '' + a,
lstripChars(str, chars)::
if std.length(str) > 0 && std.member(chars, str[0]) then
@@ -127,32 +63,12 @@
split(str, c):: std.splitLimit(str, c, -1),
- splitLimit:: $intrinsic(splitLimit),
-
- strReplace:: $intrinsic(strReplace),
-
- asciiUpper:: $intrinsic(asciiUpper),
-
- asciiLower:: $intrinsic(asciiLower),
-
- range:: $intrinsic(range),
-
repeat(what, count)::
local joiner =
if std.isString(what) then ''
else if std.isArray(what) then []
else error 'std.repeat first argument must be an array or a string';
std.join(joiner, std.makeArray(count, function(i) what)),
-
- slice:: $intrinsic(slice),
-
- member:: $intrinsic(member),
-
- count:: $intrinsic(count),
-
- mod:: $intrinsic(mod),
-
- map:: $intrinsic(map),
mapWithIndex(func, arr)::
if !std.isFunction(func) then
@@ -169,11 +85,7 @@
error ('std.mapWithKey second param must be object, got ' + std.type(obj))
else
{ [k]: func(k, obj[k]) for k in std.objectFields(obj) },
-
- flatMap:: $intrinsic(flatMap),
- join:: $intrinsic(join),
-
lines(arr)::
std.join('\n', arr + ['']),
@@ -185,13 +97,6 @@
else
error 'Expected string or array, got %s' % std.type(arr),
-
- format:: $intrinsic(format),
-
- foldr:: $intrinsic(foldr),
-
- foldl:: $intrinsic(foldl),
-
filterMap(filter_func, map_func, arr)::
if !std.isFunction(filter_func) then
error ('std.filterMap first param must be function, got ' + std.type(filter_func))
@@ -349,8 +254,6 @@
renderTableInternal(value, [], [], '')
else
error 'TOML body must be an object. Got ' + std.type(value),
-
- escapeStringJson:: $intrinsic(escapeStringJson),
escapeStringPython(str)::
std.escapeStringJson(str),
@@ -377,10 +280,6 @@
manifestJsonMinified(value):: std.manifestJsonEx(value, '', '', ':'),
- manifestJsonEx:: $intrinsic(manifestJsonEx),
-
- manifestYamlDoc:: $intrinsic(manifestYamlDoc),
-
manifestYamlStream(value, indent_array_in_object=false, c_document_end=true)::
if !std.isArray(value) then
error 'manifestYamlStream only takes arrays, got ' + std.type(value)
@@ -434,19 +333,6 @@
aux(value),
- local base64_table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
- local base64_inv = { [base64_table[i]]: i for i in std.range(0, 63) },
-
- base64:: $intrinsic(base64),
-
- base64DecodeBytes:: $intrinsic(base64DecodeBytes),
-
- base64Decode:: $intrinsic(base64Decode),
-
- reverse:: $intrinsic(reverse),
-
- sort:: $intrinsic(sort),
-
uniq(arr, keyF=id)::
local f(a, b) =
if std.length(a) == 0 then
@@ -534,7 +420,7 @@
else
patch,
- get(o, f, default = null, inc_hidden = true)::
+ get(o, f, default=null, inc_hidden=true)::
if std.objectHasEx(o, f, inc_hidden) then o[f] else default,
objectFields(o)::
@@ -554,8 +440,6 @@
objectValuesAll(o)::
[o[k] for k in std.objectFieldsAll(o)],
-
- equals:: $intrinsic(equals),
resolvePath(f, r)::
local arr = std.split(f, '/');
@@ -579,19 +463,6 @@
if isContent(std.prune(a[x]))
} else
a,
-
- findSubstr(pat, str)::
- if !std.isString(pat) then
- error 'findSubstr first parameter should be a string, got ' + std.type(pat)
- else if !std.isString(str) then
- error 'findSubstr second parameter should be a string, got ' + std.type(str)
- else
- local pat_len = std.length(pat);
- local str_len = std.length(str);
- if pat_len == 0 || str_len == 0 || pat_len > str_len then
- []
- else
- std.filter(function(i) str[i:i + pat_len] == pat, std.range(0, str_len - pat_len)),
find(value, arr)::
if !std.isArray(arr) then
crates/jrsonnet-stdlib/src/types.rsdiffbeforeafterbothno changes
flake.lockdiffbeforeafterboth--- a/flake.lock
+++ /dev/null
@@ -1,43 +0,0 @@
-{
- "nodes": {
- "flake-utils": {
- "locked": {
- "lastModified": 1623875721,
- "narHash": "sha256-A8BU7bjS5GirpAUv4QA+QnJ4CceLHkcXdRp4xITDB0s=",
- "owner": "numtide",
- "repo": "flake-utils",
- "rev": "f7e004a55b120c02ecb6219596820fcd32ca8772",
- "type": "github"
- },
- "original": {
- "owner": "numtide",
- "repo": "flake-utils",
- "type": "github"
- }
- },
- "nixpkgs": {
- "locked": {
- "lastModified": 1625281901,
- "narHash": "sha256-DkZDtTIPzhXATqIps2ifNFpnI+PTcfMYdcrx/oFm00Q=",
- "owner": "NixOS",
- "repo": "nixpkgs",
- "rev": "09c38c29f2c719cd76ca17a596c2fdac9e186ceb",
- "type": "github"
- },
- "original": {
- "owner": "NixOS",
- "ref": "nixos-unstable",
- "repo": "nixpkgs",
- "type": "github"
- }
- },
- "root": {
- "inputs": {
- "flake-utils": "flake-utils",
- "nixpkgs": "nixpkgs"
- }
- }
- },
- "root": "root",
- "version": 7
-}
flake.nixdiffbeforeafterboth--- a/flake.nix
+++ /dev/null
@@ -1,59 +0,0 @@
-{
- description = "Dotfiles manager";
- inputs = {
- nixpkgs.url = "github:nixos/nixpkgs";
- flake-utils.url = "github:numtide/flake-utils";
- naersk.url = "github:nix-community/naersk";
- rust-overlay.url = "github:oxalica/rust-overlay";
- pre-commit-hooks.url = "github:cachix/pre-commit-hooks.nix";
- };
- outputs = { self, nixpkgs, flake-utils, rust-overlay, pre-commit-hooks, naersk }:
- flake-utils.lib.eachDefaultSystem (system:
- let
- pkgs = import nixpkgs
- {
- inherit system;
- overlays = [ rust-overlay.overlay ];
- };
- rust = ((pkgs.rustChannelOf { date = "2021-11-11"; channel = "nightly"; }).default.override {
- extensions = [ "rust-src" ];
- });
- naersk-lib = naersk.lib."${system}".override {
- rustc = rust;
- cargo = rust;
- };
- in
- rec {
- checks = {
- pre-commit-check = pre-commit-hooks.lib.${system}.run {
- src = ./.;
- hooks = {
- nixpkgs-fmt.enable = true;
- };
- };
- };
- defaultPackage = naersk-lib.buildPackage {
- pname = "dotman";
- root = ./.;
- buildInputs = with pkgs; [
- pkgs.sqlite
- ];
- };
- devShell = pkgs.mkShell {
- inherit (checks.pre-commit-check) shellHook;
- nativeBuildInputs = with pkgs;[
- pkgs.binutils
- pkgs.pkgconfig
- pkgs.clang
- pkgs.x11
- pkgs.alsaLib
- pkgs.libudev
- pkgs.sqlite
- rust
- cargo-edit
- go-jsonnet
- ];
- };
- }
- );
-}
tests/Cargo.tomldiffbeforeafterboth--- /dev/null
+++ b/tests/Cargo.toml
@@ -0,0 +1,11 @@
+[package]
+name = "tests"
+version = "0.1.0"
+edition = "2021"
+publish = false
+
+[dependencies]
+jrsonnet-evaluator = { path = "../crates/jrsonnet-evaluator" }
+jrsonnet-gcmodule = "0.3.4"
+jrsonnet-stdlib = { path = "../crates/jrsonnet-stdlib" }
+serde = "1.0.142"
tests/golden/array_comp.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/golden/array_comp.jsonnet
@@ -0,0 +1 @@
+[[a, b] for a in [1, 2, 3] for b in [4, 5, 6]]
tests/golden/array_comp.jsonnet.goldendiffbeforeafterboth--- /dev/null
+++ b/tests/golden/array_comp.jsonnet.golden
@@ -0,0 +1,38 @@
+[
+ [
+ 1,
+ 4
+ ],
+ [
+ 1,
+ 5
+ ],
+ [
+ 1,
+ 6
+ ],
+ [
+ 2,
+ 4
+ ],
+ [
+ 2,
+ 5
+ ],
+ [
+ 2,
+ 6
+ ],
+ [
+ 3,
+ 4
+ ],
+ [
+ 3,
+ 5
+ ],
+ [
+ 3,
+ 6
+ ]
+]
\ No newline at end of file
tests/golden/builtin_json.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/golden/builtin_json.jsonnet
@@ -0,0 +1 @@
+std.manifestJsonEx({ a: 3, b: 4, c: 6 }, '')
tests/golden/builtin_json.jsonnet.goldendiffbeforeafterboth--- /dev/null
+++ b/tests/golden/builtin_json.jsonnet.golden
@@ -0,0 +1 @@
+"{\n\"a\": 3,\n\"b\": 4,\n\"c\": 6\n}"
\ No newline at end of file
tests/golden/builtin_json_minified.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/golden/builtin_json_minified.jsonnet
@@ -0,0 +1 @@
+std.manifestJsonMinified({ a: 3, b: 4, c: 6 })
tests/golden/builtin_json_minified.jsonnet.goldendiffbeforeafterboth--- /dev/null
+++ b/tests/golden/builtin_json_minified.jsonnet.golden
@@ -0,0 +1 @@
+"{\"a\":3,\"b\":4,\"c\":6}"
\ No newline at end of file
tests/golden/builtin_parseJson.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/golden/builtin_parseJson.jsonnet
@@ -0,0 +1 @@
+std.parseJson('{"a": -1,"b": 1,"c": 3.141,"d": []}')
tests/golden/builtin_parseJson.jsonnet.goldendiffbeforeafterboth--- /dev/null
+++ b/tests/golden/builtin_parseJson.jsonnet.golden
@@ -0,0 +1,6 @@
+{
+ "a": -1,
+ "b": 1,
+ "c": 3.141,
+ "d": [ ]
+}
\ No newline at end of file
tests/golden/issue23.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/golden/issue23.jsonnet
@@ -0,0 +1 @@
+import 'issue23.jsonnet'
tests/golden/issue23.jsonnet.goldendiffbeforeafterboth--- /dev/null
+++ b/tests/golden/issue23.jsonnet.golden
@@ -0,0 +1,2 @@
+infinite recursion detected
+ issue23.jsonnet:1:1-26: import "issue23.jsonnet"
\ No newline at end of file
tests/golden/issue40.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/golden/issue40.jsonnet
@@ -0,0 +1,9 @@
+local conf = {
+ n: '',
+};
+
+local result = conf {
+ assert std.isNumber(self.n) : 'is number',
+};
+
+std.manifestJsonEx(result, '')
tests/golden/issue40.jsonnet.goldendiffbeforeafterboth--- /dev/null
+++ b/tests/golden/issue40.jsonnet.golden
@@ -0,0 +1,3 @@
+assert failed: is number
+ issue40.jsonnet:6:10-31: assertion failure
+ issue40.jsonnet:9:1-32: function <builtin_manifest_json_ex> call
\ No newline at end of file
tests/golden/missing_binding.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/golden/missing_binding.jsonnet
@@ -0,0 +1 @@
+sta
tests/golden/missing_binding.jsonnet.goldendiffbeforeafterboth--- /dev/null
+++ b/tests/golden/missing_binding.jsonnet.golden
@@ -0,0 +1,3 @@
+variable is not defined: sta
+There is variable with similar name present: std
+ missing_binding.jsonnet:1:1-5: variable <sta> access
\ No newline at end of file
tests/golden/object_comp.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/golden/object_comp.jsonnet
@@ -0,0 +1 @@
+{ local t = 'a', ['h' + i + '_' + z]: if 'h' + (i - 1) + '_' + z in self then t + 1 else 0 + t for i in [1, 2, 3] for z in [2, 3, 4] if z != i }
tests/golden/object_comp.jsonnet.goldendiffbeforeafterboth--- /dev/null
+++ b/tests/golden/object_comp.jsonnet.golden
@@ -0,0 +1,9 @@
+{
+ "h1_2": "0a",
+ "h1_3": "0a",
+ "h1_4": "0a",
+ "h2_3": "a1",
+ "h2_4": "a1",
+ "h3_2": "0a",
+ "h3_4": "a1"
+}
\ No newline at end of file
tests/golden/test_assertThrow.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/golden/test_assertThrow.jsonnet
@@ -0,0 +1,2 @@
+// Test that test.assertThrow will return error, if body is not errored
+test.assertThrow(1, '1')
tests/golden/test_assertThrow.jsonnet.goldendiffbeforeafterboth--- /dev/null
+++ b/tests/golden/test_assertThrow.jsonnet.golden
@@ -0,0 +1,2 @@
+runtime error: expected argument to throw on evaluation, but it returned instead
+ test_assertThrow.jsonnet:2:1-26: function <assert_throw> call
\ No newline at end of file
tests/src/lib.rsdiffbeforeafterboth--- /dev/null
+++ b/tests/src/lib.rs
@@ -0,0 +1 @@
+//! See tests/, suite/ and golden/ directories for tests
tests/suite/builtin_ascii.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/suite/builtin_ascii.jsonnet
@@ -0,0 +1,3 @@
+std.assertEqual(std.asciiUpper('aBc😀'), 'ABC😀') &&
+std.assertEqual(std.asciiLower('aBc😀'), 'abc😀') &&
+true
tests/suite/builtin_base64.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/suite/builtin_base64.jsonnet
@@ -0,0 +1,2 @@
+std.assertEqual(std.base64('test'), 'dGVzdA==') &&
+true
tests/suite/builtin_chars.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/suite/builtin_chars.jsonnet
@@ -0,0 +1,3 @@
+local c = '😎';
+std.assertEqual({ c: std.codepoint(c), l: std.length(c) }, { c: 128526, l: 1 }) &&
+true
tests/suite/builtin_constant.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/suite/builtin_constant.jsonnet
@@ -0,0 +1,3 @@
+local std2 = std; local std = std2 { primitiveEquals(a, b):: false };
+// In jsonnet, this expression was failing because of being desugared to std.primitiveEquals(1, 1)
+std.assertEqual(1 == 1, true)
tests/suite/builtin_count.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/suite/builtin_count.jsonnet
@@ -0,0 +1,4 @@
+std.assertEqual(std.count([], ''), 0) &&
+std.assertEqual(std.count(['a', 'b', 'a'], 'd'), 0) &&
+std.assertEqual(std.count(['a', 'b', 'a'], 'a'), 2) &&
+true
tests/suite/builtin_join.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/suite/builtin_join.jsonnet
@@ -0,0 +1,4 @@
+std.assertEqual(std.join([0, 0], [[1, 2], [3, 4], [5, 6]]), [1, 2, 0, 0, 3, 4, 0, 0, 5, 6]) &&
+std.assertEqual(std.join(',', ['1', '2', '3', '4']), '1,2,3,4') &&
+std.assertEqual(std.join(',', ['1', null, '2', null, '3']), '1,2,3') &&
+true
tests/suite/builtin_member.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/suite/builtin_member.jsonnet
@@ -0,0 +1,7 @@
+!std.member('', '') &&
+std.member('abc', 'a') &&
+!std.member('abc', 'd') &&
+!std.member([], '') &&
+std.member(['a', 'b', 'c'], 'a') &&
+!std.member(['a', 'b', 'c'], 'd') &&
+true
tests/suite/function_args.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/suite/function_args.jsonnet
@@ -0,0 +1,3 @@
+std.assertEqual(local a = function(b, c=2) b + c; a(2), 4) &&
+std.assertEqual(local a = function(b, c='Dear') b + c + d, d = 'World'; a('Hello'), 'HelloDearWorld') &&
+true
tests/suite/function_context.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/suite/function_context.jsonnet
@@ -0,0 +1,10 @@
+local k = {
+ t(name=self.h): [self.h, name],
+ h: 3,
+};
+local f = {
+ t: k.t(),
+ h: 4,
+};
+std.assertEqual(f.t[0], f.t[1]) &&
+true
tests/suite/function_lazy_args.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/suite/function_lazy_args.jsonnet
@@ -0,0 +1,5 @@
+local fun(a) = 2;
+std.assertEqual(fun(error '3'), 2) &&
+// But in tailstrict mode arguments are evaluated eagerly
+test.assertThrow(fun(error '3') tailstrict, 'runtime error: 3') &&
+true
tests/suite/local.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/suite/local.jsonnet
@@ -0,0 +1,4 @@
+std.assertEqual(local a = 2; local b = 3; a + b, 5) &&
+std.assertEqual(local a = 1, b = a + 1; a + b, 3) &&
+std.assertEqual(local a = 1; local a = 2; a, 2) &&
+true
tests/suite/math.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/suite/math.jsonnet
@@ -0,0 +1,3 @@
+std.assertEqual(2 + 2 * 2, 6) &&
+std.assertEqual(3 + (2 + 2 * 2), 9) &&
+true
tests/suite/object_assertion.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/suite/object_assertion.jsonnet
@@ -0,0 +1,3 @@
+std.assertEqual({ assert 'a' in self : 'missing a' } + { a: 2 }, { a: 2 }) &&
+test.assertThrow({ assert 'a' in self : 'missing a', b: 1 }.b, 'assert failed: missing a') &&
+true
tests/suite/object_comp_self.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/suite/object_comp_self.jsonnet
@@ -0,0 +1,8 @@
+std.assertEqual(std.objectFields({
+ a: {
+ [name]: name
+ for name in std.objectFields(self)
+ },
+ b: 2,
+ c: 3,
+}.a), ['a', 'b', 'c'])
tests/suite/object_context.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/suite/object_context.jsonnet
@@ -0,0 +1,13 @@
+// `self` assigned to `me` was lost when being
+// referenced from field
+std.assertEqual({
+ local me = self,
+ a: 3,
+ b: me.a,
+}.b, 3) &&
+std.assertEqual({
+ local me = self,
+ a: 3,
+ b(): me.a,
+}.b(), 3) &&
+true
tests/suite/object_fields.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/suite/object_fields.jsonnet
@@ -0,0 +1,4 @@
+local a = 'a', b = null;
+std.assertEqual({ [a]: 2 }, { a: 2 }) &&
+std.assertEqual({ [b]: 2 }, {}) &&
+true
tests/suite/object_inheritance.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/suite/object_inheritance.jsonnet
@@ -0,0 +1,17 @@
+std.assertEqual({ a: self.b } + { b: 3 }, { a: 3, b: 3 }) &&
+std.assertEqual(
+ {
+ name: 'Alice',
+ welcome: 'Hello ' + self.name + '!',
+ },
+ { name: 'Alice', welcome: 'Hello Alice!' },
+) &&
+std.assertEqual(
+ {
+ name: 'Alice',
+ welcome: 'Hello ' + self.name + '!',
+ } + {
+ name: 'Bob',
+ }, { name: 'Bob', welcome: 'Hello Bob!' }
+) &&
+true
tests/suite/object_locals.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/suite/object_locals.jsonnet
@@ -0,0 +1,4 @@
+std.assertEqual({ local a = 3, b: a }, { b: 3 }) &&
+std.assertEqual({ local a = 3, local c = a, b: c }, { b: 3 }) &&
+std.assertEqual({ local a = function(b) { [b]: 4 }, test: a('test') }, { test: { test: 4 } }) &&
+true
tests/suite/object_super_standalone.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/suite/object_super_standalone.jsonnet
@@ -0,0 +1,11 @@
+local obj = {
+ a: 1,
+ b: 2,
+ c: 3,
+};
+local test = obj + {
+ fields: std.objectFields(super),
+ d: 5,
+};
+std.assertEqual(test.fields, ['a', 'b', 'c']) &&
+true
tests/suite/sjsonnet_issue_127.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/suite/sjsonnet_issue_127.jsonnet
@@ -0,0 +1,6 @@
+local myFunc = function(a)
+ if (a) then "a" else "b";
+
+local b = "aaa";
+
+std.assertEqual(myFunc(b == [] || b == ['e']), "b")
tests/suite/string_concat.jsonnetdiffbeforeafterboth--- /dev/null
+++ b/tests/suite/string_concat.jsonnet
@@ -0,0 +1,4 @@
+std.assertEqual('Hello' + 'World', 'HelloWorld') &&
+std.assertEqual('Hello' * 3, 'HelloHelloHello') &&
+std.assertEqual('Hello' + 'World' * 3, 'HelloWorldWorldWorld') &&
+true
tests/tests/as_native.rsdiffbeforeafterboth--- /dev/null
+++ b/tests/tests/as_native.rs
@@ -0,0 +1,20 @@
+use jrsonnet_evaluator::{error::Result, State};
+use jrsonnet_stdlib::StateExt;
+
+mod common;
+
+#[test]
+fn as_native() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+
+ let val = s.evaluate_snippet("snip".to_owned(), r#"function(a, b) a + b"#)?;
+ let func = val.as_func().expect("this is function");
+
+ let native = func.into_native::<((u32, u32), u32)>();
+
+ ensure_eq!(native(s.clone(), 1, 2)?, 3);
+ ensure_eq!(native(s, 3, 4)?, 7);
+
+ Ok(())
+}
tests/tests/builtin.rsdiffbeforeafterboth--- /dev/null
+++ b/tests/tests/builtin.rs
@@ -0,0 +1,94 @@
+mod common;
+
+use jrsonnet_evaluator::{
+ error::Result,
+ function::{builtin, builtin::Builtin, CallLocation, FuncVal},
+ tb,
+ typed::Typed,
+ Context, State, Thunk, Val,
+};
+use jrsonnet_gcmodule::Cc;
+use jrsonnet_stdlib::StateExt;
+
+#[builtin]
+fn a() -> Result<u32> {
+ Ok(1)
+}
+
+#[test]
+fn basic_function() -> Result<()> {
+ let s = State::default();
+ let a: a = a {};
+ let v = u32::from_untyped(
+ a.call(s.clone(), Context::new(), CallLocation::native(), &())?,
+ s,
+ )?;
+
+ ensure_eq!(v, 1);
+ Ok(())
+}
+
+#[builtin]
+fn native_add(a: u32, b: u32) -> Result<u32> {
+ Ok(a + b)
+}
+
+#[test]
+fn call_from_code() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ s.add_global(
+ "nativeAdd".into(),
+ Thunk::evaluated(Val::Func(FuncVal::StaticBuiltin(native_add::INST))),
+ );
+
+ let v = s.evaluate_snippet(
+ "snip".to_owned(),
+ "
+ assert nativeAdd(1, 2) == 3;
+ assert nativeAdd(100, 200) == 300;
+ null
+ ",
+ )?;
+ ensure_val_eq!(s, v, Val::Null);
+ Ok(())
+}
+
+#[builtin(fields(
+ a: u32
+))]
+fn curried_add(this: &curried_add, b: u32) -> Result<u32> {
+ Ok(this.a + b)
+}
+
+#[builtin]
+fn curry_add(a: u32) -> Result<FuncVal> {
+ Ok(FuncVal::Builtin(Cc::new(tb!(curried_add { a }))))
+}
+
+#[test]
+fn nonstatic_builtin() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ s.add_global(
+ "curryAdd".into(),
+ Thunk::evaluated(Val::Func(FuncVal::StaticBuiltin(curry_add::INST))),
+ );
+
+ let v = s.evaluate_snippet(
+ "snip".to_owned(),
+ "
+ local a = curryAdd(1);
+ local b = curryAdd(4);
+
+ assert a(2) == 3;
+ assert a(200) == 201;
+
+ assert b(2) == 6;
+ assert b(200) == 204;
+ null
+ ",
+ )?;
+ ensure_val_eq!(s, v, Val::Null);
+ Ok(())
+}
tests/tests/common.rsdiffbeforeafterboth--- /dev/null
+++ b/tests/tests/common.rs
@@ -0,0 +1,77 @@
+use jrsonnet_evaluator::{
+ error::Result,
+ function::{builtin, FuncVal},
+ throw_runtime, ObjValueBuilder, State, Thunk, Val,
+};
+use jrsonnet_stdlib::StateExt;
+
+#[macro_export]
+macro_rules! ensure_eq {
+ ($a:expr, $b:expr $(,)?) => {{
+ let a = &$a;
+ let b = &$b;
+ if a != b {
+ ::jrsonnet_evaluator::throw_runtime!("assertion failed: a != b\na={:#?}\nb={:#?}", a, b)
+ }
+ }};
+}
+
+#[macro_export]
+macro_rules! ensure {
+ ($v:expr $(,)?) => {
+ if !$v {
+ ::jrsonnet_evaluator::throw_runtime!("assertion failed: {}", stringify!($v))
+ }
+ };
+}
+
+#[macro_export]
+macro_rules! ensure_val_eq {
+ ($s:expr, $a:expr, $b:expr) => {{
+ if !::jrsonnet_evaluator::val::equals($s.clone(), &$a.clone(), &$b.clone())? {
+ ::jrsonnet_evaluator::throw_runtime!(
+ "assertion failed: a != b\na={:#?}\nb={:#?}",
+ $a.to_json(
+ $s.clone(),
+ 2,
+ #[cfg(feature = "exp-preserve-order")]
+ false
+ )?,
+ $b.to_json(
+ $s.clone(),
+ 2,
+ #[cfg(feature = "exp-preserve-order")]
+ false
+ )?,
+ )
+ }
+ }};
+}
+
+#[builtin]
+fn assert_throw(s: State, lazy: Thunk<Val>, message: String) -> Result<bool> {
+ match lazy.evaluate(s) {
+ Ok(_) => {
+ throw_runtime!("expected argument to throw on evaluation, but it returned instead")
+ }
+ Err(e) => {
+ let error = format!("{}", e.error());
+ ensure_eq!(message, error);
+ }
+ }
+ Ok(true)
+}
+
+#[allow(dead_code)]
+pub fn with_test(s: &State) {
+ let mut bobj = ObjValueBuilder::new();
+ bobj.member("assertThrow".into())
+ .hide()
+ .value(
+ s.clone(),
+ Val::Func(FuncVal::StaticBuiltin(assert_throw::INST)),
+ )
+ .expect("no error");
+
+ s.add_global("test".into(), Thunk::evaluated(Val::Obj(bobj.build())))
+}
tests/tests/golden.rsdiffbeforeafterboth--- /dev/null
+++ b/tests/tests/golden.rs
@@ -0,0 +1,70 @@
+use std::{
+ fs, io,
+ path::{Path, PathBuf},
+};
+
+use jrsonnet_evaluator::{
+ trace::{CompactFormat, PathResolver},
+ FileImportResolver, State,
+};
+use jrsonnet_stdlib::StateExt;
+
+mod common;
+
+fn run(root: &Path, file: &Path) -> String {
+ let s = State::default();
+ s.set_trace_format(Box::new(CompactFormat {
+ resolver: PathResolver::Relative(root.to_owned()),
+ padding: 3,
+ }));
+ s.with_stdlib();
+ common::with_test(&s);
+ s.set_import_resolver(Box::new(FileImportResolver::default()));
+
+ let v = match s.import(file) {
+ Ok(v) => v,
+ Err(e) => return s.stringify_err(&e),
+ };
+ match v.to_json(
+ s.clone(),
+ 3,
+ #[cfg(feature = "exp-preserve-order")]
+ false,
+ ) {
+ Ok(v) => v.to_string(),
+ Err(e) => s.stringify_err(&e),
+ }
+}
+
+#[test]
+fn test() -> io::Result<()> {
+ let mut root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+ root.push("golden");
+
+ for entry in fs::read_dir(&root)? {
+ let entry = entry?;
+ if !entry.path().extension().map_or(false, |e| e == "jsonnet") {
+ continue;
+ }
+
+ let result = run(&root, &entry.path());
+
+ let mut golden_path = entry.path();
+ golden_path.set_extension("jsonnet.golden");
+
+ if !golden_path.exists() {
+ fs::write(golden_path, &result)?;
+ } else {
+ let golden = fs::read_to_string(golden_path)?;
+
+ assert_eq!(
+ result,
+ golden,
+ "golden didn't match for {}",
+ entry.path().display()
+ )
+ }
+ }
+
+ Ok(())
+}
tests/tests/sanity.rsdiffbeforeafterboth--- /dev/null
+++ b/tests/tests/sanity.rs
@@ -0,0 +1,42 @@
+use jrsonnet_evaluator::{error::Result, throw_runtime, State, Val};
+use jrsonnet_stdlib::StateExt;
+
+mod common;
+
+#[test]
+fn assert_positive() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+
+ let v = s.evaluate_snippet("snip".to_owned(), "assert 1 == 1: 'fail'; null")?;
+ ensure_val_eq!(s, v, Val::Null);
+ let v = s.evaluate_snippet("snip".to_owned(), "std.assertEqual(1, 1)")?;
+ ensure_val_eq!(s, v, Val::Bool(true));
+
+ Ok(())
+}
+
+#[test]
+fn assert_negative() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+
+ {
+ let e = match s.evaluate_snippet("snip".to_owned(), "assert 1 == 2: 'fail'; null") {
+ Ok(_) => throw_runtime!("assertion should fail"),
+ Err(e) => e,
+ };
+ let e = s.stringify_err(&e);
+ ensure!(e.starts_with("assert failed: fail\n"));
+ }
+ {
+ let e = match s.evaluate_snippet("snip".to_owned(), "std.assertEqual(1, 2)") {
+ Ok(_) => throw_runtime!("assertion should fail"),
+ Err(e) => e,
+ };
+ let e = s.stringify_err(&e);
+ ensure!(e.starts_with("runtime error: Assertion failed. 1 != 2"))
+ }
+
+ Ok(())
+}
tests/tests/suite.rsdiffbeforeafterboth--- /dev/null
+++ b/tests/tests/suite.rs
@@ -0,0 +1,47 @@
+use std::{
+ fs, io,
+ path::{Path, PathBuf},
+};
+
+use jrsonnet_evaluator::{
+ trace::{CompactFormat, PathResolver},
+ FileImportResolver, State, Val,
+};
+use jrsonnet_stdlib::StateExt;
+
+mod common;
+
+fn run(root: &Path, file: &Path) {
+ let s = State::default();
+ s.set_trace_format(Box::new(CompactFormat {
+ resolver: PathResolver::Relative(root.to_owned()),
+ padding: 3,
+ }));
+ s.with_stdlib();
+ common::with_test(&s);
+ s.set_import_resolver(Box::new(FileImportResolver::default()));
+
+ match s.import(file) {
+ Ok(Val::Bool(true)) => {}
+ Ok(Val::Bool(false)) => panic!("test {} returned false", file.display()),
+ Ok(_) => panic!("test {} returned wrong type as result", file.display()),
+ Err(e) => panic!("test {} failed:\n{}", file.display(), s.stringify_err(&e)),
+ };
+}
+
+#[test]
+fn test() -> io::Result<()> {
+ let mut root = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+ root.push("suite");
+
+ for entry in fs::read_dir(&root)? {
+ let entry = entry?;
+ if !entry.path().extension().map_or(false, |e| e == "jsonnet") {
+ continue;
+ }
+
+ run(&root, &entry.path());
+ }
+
+ Ok(())
+}
tests/tests/typed_obj.rsdiffbeforeafterboth--- /dev/null
+++ b/tests/tests/typed_obj.rs
@@ -0,0 +1,189 @@
+mod common;
+
+use std::fmt::Debug;
+
+use jrsonnet_evaluator::{error::Result, typed::Typed, State};
+use jrsonnet_stdlib::StateExt;
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct A {
+ a: u32,
+ b: u16,
+}
+
+fn test_roundtrip<T: Typed + PartialEq + Debug + Clone>(value: T, s: State) -> Result<()> {
+ let untyped = T::into_untyped(value.clone(), s.clone())?;
+ let value2 = T::from_untyped(untyped.clone(), s.clone())?;
+ ensure_eq!(value, value2);
+ let untyped2 = T::into_untyped(value2, s.clone())?;
+ ensure_val_eq!(s, untyped, untyped2);
+
+ Ok(())
+}
+
+#[test]
+fn simple_object() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ let a = A::from_untyped(
+ s.evaluate_snippet("snip".to_owned(), "{a: 1, b: 2}")?,
+ s.clone(),
+ )?;
+ ensure_eq!(a, A { a: 1, b: 2 });
+ test_roundtrip(a, s)?;
+ Ok(())
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct B {
+ a: u32,
+ #[typed(rename = "c")]
+ b: u16,
+}
+
+#[test]
+fn renamed_field() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ let b = B::from_untyped(
+ s.evaluate_snippet("snip".to_owned(), "{a: 1, c: 2}")?,
+ s.clone(),
+ )?;
+ ensure_eq!(b, B { a: 1, b: 2 });
+ ensure_eq!(
+ &B::into_untyped(b.clone(), s.clone())?.to_string(s.clone())? as &str,
+ r#"{"a": 1, "c": 2}"#,
+ );
+ test_roundtrip(b, s)?;
+ Ok(())
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct ObjectKind {
+ #[typed(rename = "apiVersion")]
+ api_version: String,
+ #[typed(rename = "kind")]
+ kind: String,
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct Object {
+ #[typed(flatten)]
+ kind: ObjectKind,
+ b: u16,
+}
+
+#[test]
+fn flattened_object() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ let obj = Object::from_untyped(
+ s.evaluate_snippet("snip".to_owned(), "{apiVersion: 'ver', kind: 'kind', b: 2}")?,
+ s.clone(),
+ )?;
+ ensure_eq!(
+ obj,
+ Object {
+ kind: ObjectKind {
+ api_version: "ver".into(),
+ kind: "kind".into(),
+ },
+ b: 2
+ }
+ );
+ ensure_eq!(
+ &Object::into_untyped(obj.clone(), s.clone())?.to_string(s.clone())? as &str,
+ r#"{"apiVersion": "ver", "b": 2, "kind": "kind"}"#,
+ );
+ test_roundtrip(obj, s)?;
+ Ok(())
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct C {
+ a: Option<u32>,
+ b: u16,
+}
+
+#[test]
+fn optional_field_some() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ let c = C::from_untyped(
+ s.evaluate_snippet("snip".to_owned(), "{a: 1, b: 2}")?,
+ s.clone(),
+ )?;
+ ensure_eq!(c, C { a: Some(1), b: 2 });
+ ensure_eq!(
+ &C::into_untyped(c.clone(), s.clone())?.to_string(s.clone())? as &str,
+ r#"{"a": 1, "b": 2}"#,
+ );
+ test_roundtrip(c, s)?;
+ Ok(())
+}
+
+#[test]
+fn optional_field_none() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ let c = C::from_untyped(s.evaluate_snippet("snip".to_owned(), "{b: 2}")?, s.clone())?;
+ ensure_eq!(c, C { a: None, b: 2 });
+ ensure_eq!(
+ &C::into_untyped(c.clone(), s.clone())?.to_string(s.clone())? as &str,
+ r#"{"b": 2}"#,
+ );
+ test_roundtrip(c, s)?;
+ Ok(())
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct D {
+ #[typed(flatten(ok))]
+ e: Option<E>,
+ b: u16,
+}
+
+#[derive(Clone, Typed, PartialEq, Debug)]
+struct E {
+ v: u32,
+}
+
+#[test]
+fn flatten_optional_some() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ let d = D::from_untyped(
+ s.evaluate_snippet("snip".to_owned(), "{b: 2, v:1}")?,
+ s.clone(),
+ )?;
+ ensure_eq!(
+ d,
+ D {
+ e: Some(E { v: 1 }),
+ b: 2
+ }
+ );
+ ensure_eq!(
+ &D::into_untyped(d.clone(), s.clone())?.to_string(s.clone())? as &str,
+ r#"{"b": 2, "v": 1}"#,
+ );
+ test_roundtrip(d, s)?;
+ Ok(())
+}
+
+#[test]
+fn flatten_optional_none() -> Result<()> {
+ let s = State::default();
+ s.with_stdlib();
+ let d = D::from_untyped(
+ s.evaluate_snippet("snip".to_owned(), "{b: 2, v: '1'}")?,
+ s.clone(),
+ )?;
+ ensure_eq!(d, D { e: None, b: 2 });
+ ensure_eq!(
+ &D::into_untyped(d.clone(), s.clone())?.to_string(s.clone())? as &str,
+ r#"{"b": 2}"#,
+ );
+ test_roundtrip(d, s)?;
+ Ok(())
+}