difftreelog
build upgrade to 2021 edition
in: master
13 files changed
bindings/jsonnet/Cargo.tomldiffbeforeafterboth--- a/bindings/jsonnet/Cargo.toml
+++ b/bindings/jsonnet/Cargo.toml
@@ -4,7 +4,7 @@
version = "0.4.2"
authors = ["Yaroslav Bolyukin <iam@lach.pw>"]
license = "MIT"
-edition = "2018"
+edition = "2021"
publish = false
[dependencies]
cmds/jrsonnet/Cargo.tomldiffbeforeafterboth--- a/cmds/jrsonnet/Cargo.toml
+++ b/cmds/jrsonnet/Cargo.toml
@@ -4,8 +4,7 @@
version = "0.4.2"
authors = ["Yaroslav Bolyukin <iam@lach.pw>"]
license = "MIT"
-edition = "2018"
-publish = false
+edition = "2021"
[features]
# Use mimalloc as allocator
crates/jrsonnet-cli/Cargo.tomldiffbeforeafterboth--- a/crates/jrsonnet-cli/Cargo.toml
+++ b/crates/jrsonnet-cli/Cargo.toml
@@ -4,8 +4,7 @@
version = "0.4.2"
authors = ["Yaroslav Bolyukin <iam@lach.pw>"]
license = "MIT"
-edition = "2018"
-publish = false
+edition = "2021"
[dependencies]
jrsonnet-evaluator = { path = "../../crates/jrsonnet-evaluator", version = "0.4.2", features = [
crates/jrsonnet-evaluator/Cargo.tomldiffbeforeafterboth--- a/crates/jrsonnet-evaluator/Cargo.toml
+++ b/crates/jrsonnet-evaluator/Cargo.toml
@@ -4,7 +4,7 @@
version = "0.4.2"
authors = ["Yaroslav Bolyukin <iam@lach.pw>"]
license = "MIT"
-edition = "2018"
+edition = "2021"
[features]
default = ["serialized-stdlib", "explaining-traces"]
crates/jrsonnet-evaluator/src/builtin/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/builtin/mod.rs
+++ b/crates/jrsonnet-evaluator/src/builtin/mod.rs
@@ -7,7 +7,7 @@
operator::evaluate_mod_op,
primitive_equals, push_frame, throw,
typed::{Either2, Either4},
- with_state, ArrValue, Context, FuncVal, IndexableVal, Val,
+ with_state, ArrValue, FuncVal, IndexableVal, Val,
};
use crate::{Either, ObjValue};
use format::{format_arr, format_obj};
crates/jrsonnet-evaluator/src/lib.rsdiffbeforeafterboth1#![cfg_attr(feature = "unstable", feature(stmt_expr_attributes))]2#![warn(clippy::all, clippy::nursery)]3#![allow(4 macro_expanded_macro_exports_accessed_by_absolute_paths,5 clippy::ptr_arg6)]78// For jrsonnet-macros9extern crate self as jrsonnet_evaluator;1011mod builtin;12mod ctx;13mod dynamic;14pub mod error;15mod evaluate;16pub mod function;17mod import;18mod integrations;19mod map;20pub mod native;21mod obj;22pub mod trace;23pub mod typed;24mod val;2526pub use ctx::*;27pub use dynamic::*;28use error::{Error::*, LocError, Result, StackTraceElement};29pub use evaluate::*;30use function::{Builtin, TlaArg};31use gc::{GcHashMap, TraceBox};32use gcmodule::{Cc, Trace};33pub use import::*;34pub use jrsonnet_interner::IStr;35use jrsonnet_parser::*;36pub use obj::*;37use std::{38 cell::{Ref, RefCell, RefMut},39 collections::HashMap,40 fmt::Debug,41 path::{Path, PathBuf},42 rc::Rc,43};44use trace::{location_to_offset, offset_to_location, CodeLocation, CompactFormat, TraceFormat};45pub use val::*;46pub mod gc;4748pub trait Bindable: Trace + 'static {49 fn bind(&self, this: Option<ObjValue>, super_obj: Option<ObjValue>) -> Result<LazyVal>;50}5152#[derive(Clone, Trace)]53pub enum LazyBinding {54 Bindable(Cc<TraceBox<dyn Bindable>>),55 Bound(LazyVal),56}5758impl Debug for LazyBinding {59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {60 write!(f, "LazyBinding")61 }62}63impl LazyBinding {64 pub fn evaluate(&self, this: Option<ObjValue>, super_obj: Option<ObjValue>) -> Result<LazyVal> {65 match self {66 Self::Bindable(v) => v.bind(this, super_obj),67 Self::Bound(v) => Ok(v.clone()),68 }69 }70}7172pub struct EvaluationSettings {73 /// Limits recursion by limiting the number of stack frames74 pub max_stack: usize,75 /// Limits amount of stack trace items preserved76 pub max_trace: usize,77 /// Used for s`td.extVar`78 pub ext_vars: HashMap<IStr, Val>,79 /// Used for ext.native80 pub ext_natives: HashMap<IStr, Cc<TraceBox<dyn Builtin>>>,81 /// TLA vars82 pub tla_vars: HashMap<IStr, TlaArg>,83 /// Global variables are inserted in default context84 pub globals: HashMap<IStr, Val>,85 /// Used to resolve file locations/contents86 pub import_resolver: Box<dyn ImportResolver>,87 /// Used in manifestification functions88 pub manifest_format: ManifestFormat,89 /// Used for bindings90 pub trace_format: Box<dyn TraceFormat>,91}92impl Default for EvaluationSettings {93 fn default() -> Self {94 Self {95 max_stack: 200,96 max_trace: 20,97 globals: Default::default(),98 ext_vars: Default::default(),99 ext_natives: Default::default(),100 tla_vars: Default::default(),101 import_resolver: Box::new(DummyImportResolver),102 manifest_format: ManifestFormat::Json(4),103 trace_format: Box::new(CompactFormat {104 padding: 4,105 resolver: trace::PathResolver::Absolute,106 }),107 }108 }109}110111#[derive(Default)]112struct EvaluationData {113 /// Used for stack overflow detection, stacktrace is populated on unwind114 stack_depth: usize,115 /// Updated every time stack entry is popt116 stack_generation: usize,117118 breakpoints: Breakpoints,119 /// Contains file source codes and evaluation results for imports and pretty-printed stacktraces120 files: HashMap<Rc<Path>, FileData>,121 str_files: HashMap<Rc<Path>, IStr>,122}123124pub struct FileData {125 source_code: IStr,126 parsed: LocExpr,127 evaluated: Option<Val>,128}129130#[allow(clippy::type_complexity)]131pub struct Breakpoint {132 loc: ExprLocation,133 collected: RefCell<HashMap<usize, (usize, Vec<Result<Val>>)>>,134}135#[derive(Default)]136struct Breakpoints(Vec<Rc<Breakpoint>>);137impl Breakpoints {138 fn insert(139 &self,140 stack_depth: usize,141 stack_generation: usize,142 loc: &ExprLocation,143 result: Result<Val>,144 ) -> Result<Val> {145 if self.0.is_empty() {146 return result;147 }148 for item in self.0.iter() {149 if item.loc.belongs_to(loc) {150 let mut collected = item.collected.borrow_mut();151 let (depth, vals) = collected.entry(stack_generation).or_default();152 if stack_depth > *depth {153 vals.clear();154 }155 vals.push(result.clone());156 }157 }158 result159 }160}161162#[derive(Default)]163pub struct EvaluationStateInternals {164 /// Internal state165 data: RefCell<EvaluationData>,166 /// Settings, safe to change at runtime167 settings: RefCell<EvaluationSettings>,168}169170thread_local! {171 /// Contains the state for a currently executed file.172 /// Global state is fine here.173 pub(crate) static EVAL_STATE: RefCell<Option<EvaluationState>> = RefCell::new(None)174}175pub(crate) fn with_state<T>(f: impl FnOnce(&EvaluationState) -> T) -> T {176 EVAL_STATE.with(|s| f(s.borrow().as_ref().unwrap()))177}178pub(crate) fn push_frame<T>(179 e: Option<&ExprLocation>,180 frame_desc: impl FnOnce() -> String,181 f: impl FnOnce() -> Result<T>,182) -> Result<T> {183 with_state(|s| s.push(e, frame_desc, f))184}185186#[allow(dead_code)]187pub(crate) fn push_val_frame(188 e: &ExprLocation,189 frame_desc: impl FnOnce() -> String,190 f: impl FnOnce() -> Result<Val>,191) -> Result<Val> {192 with_state(|s| s.push_val(e, frame_desc, f))193}194#[allow(dead_code)]195pub(crate) fn push_description_frame<T>(196 frame_desc: impl FnOnce() -> String,197 f: impl FnOnce() -> Result<T>,198) -> Result<T> {199 with_state(|s| s.push_description(frame_desc, f))200}201202/// Maintains stack trace and import resolution203#[derive(Default, Clone)]204pub struct EvaluationState(Rc<EvaluationStateInternals>);205206impl EvaluationState {207 /// Parses and adds file as loaded208 pub fn add_file(&self, path: Rc<Path>, source_code: IStr) -> Result<LocExpr> {209 let parsed = parse(210 &source_code,211 &ParserSettings {212 file_name: path.clone(),213 },214 )215 .map_err(|error| ImportSyntaxError {216 error: Box::new(error),217 path: path.to_owned(),218 source_code: source_code.clone(),219 })?;220 self.add_parsed_file(path, source_code, parsed.clone())?;221222 Ok(parsed)223 }224225 pub fn reset_evaluation_state(&self, name: &Path) {226 self.data_mut()227 .files228 .get_mut(name)229 .unwrap()230 .evaluated231 .take();232 }233234 /// Adds file by source code and parsed expr235 pub fn add_parsed_file(236 &self,237 name: Rc<Path>,238 source_code: IStr,239 parsed: LocExpr,240 ) -> Result<()> {241 self.data_mut().files.insert(242 name,243 FileData {244 source_code,245 parsed,246 evaluated: None,247 },248 );249250 Ok(())251 }252 pub fn get_source(&self, name: &Path) -> Option<IStr> {253 let ro_map = &self.data().files;254 ro_map.get(name).map(|value| value.source_code.clone())255 }256 pub fn map_source_locations(&self, file: &Path, locs: &[usize]) -> Vec<CodeLocation> {257 offset_to_location(&self.get_source(file).unwrap_or_else(|| "".into()), locs)258 }259 pub fn map_from_source_location(260 &self,261 file: &Path,262 line: usize,263 column: usize,264 ) -> Option<usize> {265 location_to_offset(&self.get_source(file).unwrap(), line, column)266 }267 pub fn import_file(&self, from: &Path, path: &Path) -> Result<Val> {268 let file_path = self.resolve_file(from, path)?;269 {270 let data = self.data();271 let files = &data.files;272 if files.contains_key(&file_path as &Path) {273 drop(data);274 return self.evaluate_loaded_file_raw(&file_path);275 }276 }277 let contents = self.load_file_contents(&file_path)?;278 self.add_file(file_path.clone(), contents)?;279 self.evaluate_loaded_file_raw(&file_path)280 }281 pub(crate) fn import_file_str(&self, from: &Path, path: &Path) -> Result<IStr> {282 let path = self.resolve_file(from, path)?;283 if !self.data().str_files.contains_key(&path) {284 let file_str = self.load_file_contents(&path)?;285 self.data_mut().str_files.insert(path.clone(), file_str);286 }287 Ok(self.data().str_files.get(&path).cloned().unwrap())288 }289290 fn evaluate_loaded_file_raw(&self, name: &Path) -> Result<Val> {291 let expr: LocExpr = {292 let ro_map = &self.data().files;293 let value = ro_map294 .get(name)295 .unwrap_or_else(|| panic!("file not added: {:?}", name));296 if let Some(ref evaluated) = value.evaluated {297 return Ok(evaluated.clone());298 }299 value.parsed.clone()300 };301 let value = evaluate(self.create_default_context(), &expr)?;302 {303 self.data_mut()304 .files305 .get_mut(name)306 .unwrap()307 .evaluated308 .replace(value.clone());309 }310 Ok(value)311 }312313 /// Adds standard library global variable (std) to this evaluator314 pub fn with_stdlib(&self) -> &Self {315 use jrsonnet_stdlib::STDLIB_STR;316 let std_path: Rc<Path> = PathBuf::from("std.jsonnet").into();317 self.run_in_state(|| {318 self.add_parsed_file(319 std_path.clone(),320 STDLIB_STR.to_owned().into(),321 builtin::get_parsed_stdlib(),322 )323 .unwrap();324 let val = self.evaluate_loaded_file_raw(&std_path).unwrap();325 self.settings_mut().globals.insert("std".into(), val);326 });327 self328 }329330 /// Creates context with all passed global variables331 pub fn create_default_context(&self) -> Context {332 let globals = &self.settings().globals;333 let mut new_bindings = GcHashMap::with_capacity(globals.len());334 for (name, value) in globals.iter() {335 new_bindings.insert(name.clone(), LazyVal::new_resolved(value.clone()));336 }337 Context::new().extend_bound(new_bindings)338 }339340 /// Executes code creating a new stack frame341 pub fn push<T>(342 &self,343 e: Option<&ExprLocation>,344 frame_desc: impl FnOnce() -> String,345 f: impl FnOnce() -> Result<T>,346 ) -> Result<T> {347 {348 let mut data = self.data_mut();349 let stack_depth = &mut data.stack_depth;350 if *stack_depth > self.max_stack() {351 // Error creation uses data, so i drop guard here352 drop(data);353 throw!(StackOverflow);354 } else {355 *stack_depth += 1;356 }357 }358 let result = f();359 {360 let mut data = self.data_mut();361 data.stack_depth -= 1;362 data.stack_generation += 1;363 }364 if let Err(mut err) = result {365 err.trace_mut().0.push(StackTraceElement {366 location: e.cloned(),367 desc: frame_desc(),368 });369 return Err(err);370 }371 result372 }373374 /// Executes code creating a new stack frame375 pub fn push_val(376 &self,377 e: &ExprLocation,378 frame_desc: impl FnOnce() -> String,379 f: impl FnOnce() -> Result<Val>,380 ) -> Result<Val> {381 {382 let mut data = self.data_mut();383 let stack_depth = &mut data.stack_depth;384 if *stack_depth > self.max_stack() {385 // Error creation uses data, so i drop guard here386 drop(data);387 throw!(StackOverflow);388 } else {389 *stack_depth += 1;390 }391 }392 let mut result = f();393 {394 let mut data = self.data_mut();395 data.stack_depth -= 1;396 data.stack_generation += 1;397 result = data398 .breakpoints399 .insert(data.stack_depth, data.stack_generation, e, result);400 }401 if let Err(mut err) = result {402 err.trace_mut().0.push(StackTraceElement {403 location: Some(e.clone()),404 desc: frame_desc(),405 });406 return Err(err);407 }408 result409 }410 /// Executes code creating a new stack frame411 pub fn push_description<T>(412 &self,413 frame_desc: impl FnOnce() -> String,414 f: impl FnOnce() -> Result<T>,415 ) -> Result<T> {416 {417 let mut data = self.data_mut();418 let stack_depth = &mut data.stack_depth;419 if *stack_depth > self.max_stack() {420 // Error creation uses data, so i drop guard here421 drop(data);422 throw!(StackOverflow);423 } else {424 *stack_depth += 1;425 }426 }427 let result = f();428 {429 let mut data = self.data_mut();430 data.stack_depth -= 1;431 data.stack_generation += 1;432 }433 if let Err(mut err) = result {434 err.trace_mut().0.push(StackTraceElement {435 location: None,436 desc: frame_desc(),437 });438 return Err(err);439 }440 result441 }442443 /// Runs passed function in state (required if function needs to modify stack trace)444 pub fn run_in_state<T>(&self, f: impl FnOnce() -> T) -> T {445 EVAL_STATE.with(|v| {446 let has_state = v.borrow().is_some();447 if !has_state {448 v.borrow_mut().replace(self.clone());449 }450 let result = f();451 if !has_state {452 v.borrow_mut().take();453 }454 result455 })456 }457 pub fn run_in_state_with_breakpoint(458 &self,459 bp: Rc<Breakpoint>,460 f: impl FnOnce() -> Result<()>,461 ) -> Result<()> {462 {463 let mut data = self.data_mut();464 data.breakpoints.0.push(bp);465 }466467 let result = self.run_in_state(f);468469 {470 let mut data = self.data_mut();471 data.breakpoints.0.pop();472 }473474 result475 }476477 pub fn stringify_err(&self, e: &LocError) -> String {478 let mut out = String::new();479 self.settings()480 .trace_format481 .write_trace(&mut out, self, e)482 .unwrap();483 out484 }485486 pub fn manifest(&self, val: Val) -> Result<IStr> {487 self.run_in_state(|| {488 push_description_frame(489 || "manifestification".to_string(),490 || val.manifest(&self.manifest_format()),491 )492 })493 }494 pub fn manifest_multi(&self, val: Val) -> Result<Vec<(IStr, IStr)>> {495 self.run_in_state(|| val.manifest_multi(&self.manifest_format()))496 }497 pub fn manifest_stream(&self, val: Val) -> Result<Vec<IStr>> {498 self.run_in_state(|| val.manifest_stream(&self.manifest_format()))499 }500501 /// If passed value is function then call with set TLA502 pub fn with_tla(&self, val: Val) -> Result<Val> {503 self.run_in_state(|| {504 Ok(match val {505 Val::Func(func) => push_description_frame(506 || "during TLA call".to_owned(),507 || {508 func.evaluate(509 self.create_default_context(),510 None,511 &self.settings().tla_vars,512 true,513 )514 },515 )?,516 v => v,517 })518 })519 }520}521522/// Internals523impl EvaluationState {524 fn data(&self) -> Ref<EvaluationData> {525 self.0.data.borrow()526 }527 fn data_mut(&self) -> RefMut<EvaluationData> {528 self.0.data.borrow_mut()529 }530 pub fn settings(&self) -> Ref<EvaluationSettings> {531 self.0.settings.borrow()532 }533 pub fn settings_mut(&self) -> RefMut<EvaluationSettings> {534 self.0.settings.borrow_mut()535 }536}537538/// Raw methods evaluate passed values but don't perform TLA execution539impl EvaluationState {540 pub fn evaluate_file_raw(&self, name: &Path) -> Result<Val> {541 self.run_in_state(|| self.import_file(&std::env::current_dir().expect("cwd"), name))542 }543 pub fn evaluate_file_raw_nocwd(&self, name: &Path) -> Result<Val> {544 self.run_in_state(|| self.import_file(&PathBuf::from("."), name))545 }546 /// Parses and evaluates the given snippet547 pub fn evaluate_snippet_raw(&self, source: Rc<Path>, code: IStr) -> Result<Val> {548 let parsed = parse(549 &code,550 &ParserSettings {551 file_name: source.clone(),552 },553 )554 .map_err(|e| ImportSyntaxError {555 path: source.clone(),556 source_code: code.clone(),557 error: Box::new(e),558 })?;559 self.add_parsed_file(source, code, parsed.clone())?;560 self.evaluate_expr_raw(parsed)561 }562 /// Evaluates the parsed expression563 pub fn evaluate_expr_raw(&self, code: LocExpr) -> Result<Val> {564 self.run_in_state(|| evaluate(self.create_default_context(), &code))565 }566}567568/// Settings utilities569impl EvaluationState {570 pub fn add_ext_var(&self, name: IStr, value: Val) {571 self.settings_mut().ext_vars.insert(name, value);572 }573 pub fn add_ext_str(&self, name: IStr, value: IStr) {574 self.add_ext_var(name, Val::Str(value));575 }576 pub fn add_ext_code(&self, name: IStr, code: IStr) -> Result<()> {577 let value =578 self.evaluate_snippet_raw(PathBuf::from(format!("ext_code {}", name)).into(), code)?;579 self.add_ext_var(name, value);580 Ok(())581 }582583 pub fn add_tla(&self, name: IStr, value: Val) {584 self.settings_mut()585 .tla_vars586 .insert(name, TlaArg::Val(value));587 }588 pub fn add_tla_str(&self, name: IStr, value: IStr) {589 self.settings_mut()590 .tla_vars591 .insert(name, TlaArg::String(value));592 }593 pub fn add_tla_code(&self, name: IStr, code: IStr) -> Result<()> {594 let parsed = self.add_file(PathBuf::from(format!("tla_code {}", name)).into(), code)?;595 self.settings_mut()596 .tla_vars597 .insert(name, TlaArg::Code(parsed));598 Ok(())599 }600601 pub fn resolve_file(&self, from: &Path, path: &Path) -> Result<Rc<Path>> {602 self.settings().import_resolver.resolve_file(from, path)603 }604 pub fn load_file_contents(&self, path: &Path) -> Result<IStr> {605 self.settings().import_resolver.load_file_contents(path)606 }607608 pub fn import_resolver(&self) -> Ref<dyn ImportResolver> {609 Ref::map(self.settings(), |s| &*s.import_resolver)610 }611 pub fn set_import_resolver(&self, resolver: Box<dyn ImportResolver>) {612 self.settings_mut().import_resolver = resolver;613 }614615 pub fn add_native(&self, name: IStr, cb: Cc<TraceBox<dyn Builtin>>) {616 self.settings_mut().ext_natives.insert(name, cb);617 }618619 pub fn manifest_format(&self) -> ManifestFormat {620 self.settings().manifest_format.clone()621 }622 pub fn set_manifest_format(&self, format: ManifestFormat) {623 self.settings_mut().manifest_format = format;624 }625626 pub fn trace_format(&self) -> Ref<dyn TraceFormat> {627 Ref::map(self.settings(), |s| &*s.trace_format)628 }629 pub fn set_trace_format(&self, format: Box<dyn TraceFormat>) {630 self.settings_mut().trace_format = format;631 }632633 pub fn max_trace(&self) -> usize {634 self.settings().max_trace635 }636 pub fn set_max_trace(&self, trace: usize) {637 self.settings_mut().max_trace = trace;638 }639640 pub fn max_stack(&self) -> usize {641 self.settings().max_stack642 }643 pub fn set_max_stack(&self, trace: usize) {644 self.settings_mut().max_stack = trace;645 }646}647648pub fn cc_ptr_eq<T>(a: &Cc<T>, b: &Cc<T>) -> bool {649 let a = a as &T;650 let b = b as &T;651 std::ptr::eq(a, b)652}653654#[cfg(test)]655pub mod tests {656 use super::Val;657 use crate::{658 error::Error::*, function::BuiltinParam, gc::TraceBox, native::NativeCallbackHandler,659 primitive_equals, EvaluationState,660 };661 use gcmodule::{Cc, Trace};662 use jrsonnet_interner::IStr;663 use jrsonnet_parser::*;664 use std::{665 path::{Path, PathBuf},666 rc::Rc,667 };668669 #[test]670 #[should_panic]671 fn eval_state_stacktrace() {672 let state = EvaluationState::default();673 state.run_in_state(|| {674 state675 .push(676 Some(&ExprLocation(PathBuf::from("test1.jsonnet").into(), 10, 20)),677 || "outer".to_owned(),678 || {679 state.push(680 Some(&ExprLocation(PathBuf::from("test2.jsonnet").into(), 30, 40)),681 || "inner".to_owned(),682 || Err(RuntimeError("".into()).into()),683 )?;684 Ok(Val::Null)685 },686 )687 .unwrap();688 });689 }690691 #[test]692 fn eval_state_standard() {693 let state = EvaluationState::default();694 state.with_stdlib();695 assert!(primitive_equals(696 &state697 .evaluate_snippet_raw(698 PathBuf::from("raw.jsonnet").into(),699 r#"std.assertEqual(std.base64("test"), "dGVzdA==")"#.into()700 )701 .unwrap(),702 &Val::Bool(true),703 )704 .unwrap());705 }706707 macro_rules! eval {708 ($str: expr) => {709 EvaluationState::default()710 .with_stdlib()711 .evaluate_snippet_raw(PathBuf::from("raw.jsonnet").into(), $str.into())712 .unwrap()713 };714 }715 macro_rules! eval_json {716 ($str: expr) => {{717 let evaluator = EvaluationState::default();718 evaluator.with_stdlib();719 evaluator.run_in_state(|| {720 evaluator721 .evaluate_snippet_raw(PathBuf::from("raw.jsonnet").into(), $str.into())722 .unwrap()723 .to_json(0)724 .unwrap()725 .replace("\n", "")726 })727 }};728 }729730 /// Asserts given code returns `true`731 macro_rules! assert_eval {732 ($str: expr) => {733 assert!(primitive_equals(&eval!($str), &Val::Bool(true)).unwrap())734 };735 }736737 /// Asserts given code returns `false`738 macro_rules! assert_eval_neg {739 ($str: expr) => {740 assert!(primitive_equals(&eval!($str), &Val::Bool(false)).unwrap())741 };742 }743 macro_rules! assert_json {744 ($str: expr, $out: expr) => {745 assert_eq!(eval_json!($str), $out.replace("\t", ""))746 };747 }748749 /// Sanity checking, before trusting to another tests750 #[test]751 fn equality_operator() {752 assert_eval!("2 == 2");753 assert_eval_neg!("2 != 2");754 assert_eval!("2 != 3");755 assert_eval_neg!("2 == 3");756 assert_eval!("'Hello' == 'Hello'");757 assert_eval_neg!("'Hello' != 'Hello'");758 assert_eval!("'Hello' != 'World'");759 assert_eval_neg!("'Hello' == 'World'");760 }761762 #[test]763 fn math_evaluation() {764 assert_eval!("2 + 2 * 2 == 6");765 assert_eval!("3 + (2 + 2 * 2) == 9");766 }767768 #[test]769 fn string_concat() {770 assert_eval!("'Hello' + 'World' == 'HelloWorld'");771 assert_eval!("'Hello' * 3 == 'HelloHelloHello'");772 assert_eval!("'Hello' + 'World' * 3 == 'HelloWorldWorldWorld'");773 }774775 #[test]776 fn faster_join() {777 assert_eval!("std.join([0,0], [[1,2],[3,4],[5,6]]) == [1,2,0,0,3,4,0,0,5,6]");778 assert_eval!("std.join(',', ['1','2','3','4']) == '1,2,3,4'");779 }780781 #[test]782 fn function_contexts() {783 assert_eval!(784 r#"785 local k = {786 t(name = self.h): [self.h, name],787 h: 3,788 };789 local f = {790 t: k.t(),791 h: 4,792 };793 f.t[0] == f.t[1]794 "#795 );796 }797798 #[test]799 fn local() {800 assert_eval!("local a = 2; local b = 3; a + b == 5");801 assert_eval!("local a = 1, b = a + 1; a + b == 3");802 assert_eval!("local a = 1; local a = 2; a == 2");803 }804805 #[test]806 fn object_lazyness() {807 assert_json!("local a = {a:error 'test'}; {}", r#"{}"#);808 }809810 #[test]811 fn object_inheritance() {812 assert_json!("{a: self.b} + {b:3}", r#"{"a": 3,"b": 3}"#);813 }814815 #[test]816 fn object_assertion_success() {817 eval!("{assert \"a\" in self} + {a:2}");818 }819820 #[test]821 fn object_assertion_error() {822 eval!("{assert \"a\" in self}");823 }824825 #[test]826 fn lazy_args() {827 eval!("local test(a) = 2; test(error '3')");828 }829830 #[test]831 #[should_panic]832 fn tailstrict_args() {833 eval!("local test(a) = 2; test(error '3') tailstrict");834 }835836 #[test]837 #[should_panic]838 fn no_binding_error() {839 eval!("a");840 }841842 #[test]843 fn test_object() {844 assert_json!("{a:2}", r#"{"a": 2}"#);845 assert_json!("{a:2+2}", r#"{"a": 4}"#);846 assert_json!("{a:2}+{b:2}", r#"{"a": 2,"b": 2}"#);847 assert_json!("{b:3}+{b:2}", r#"{"b": 2}"#);848 assert_json!("{b:3}+{b+:2}", r#"{"b": 5}"#);849 assert_json!("local test='a'; {[test]:2}", r#"{"a": 2}"#);850 assert_json!(851 r#"852 {853 name: "Alice",854 welcome: "Hello " + self.name + "!",855 }856 "#,857 r#"{"name": "Alice","welcome": "Hello Alice!"}"#858 );859 assert_json!(860 r#"861 {862 name: "Alice",863 welcome: "Hello " + self.name + "!",864 } + {865 name: "Bob"866 }867 "#,868 r#"{"name": "Bob","welcome": "Hello Bob!"}"#869 );870 }871872 #[test]873 fn functions() {874 assert_json!(r#"local a = function(b, c = 2) b + c; a(2)"#, "4");875 assert_json!(876 r#"local a = function(b, c = "Dear") b + c + d, d = "World"; a("Hello")"#,877 r#""HelloDearWorld""#878 );879 }880881 #[test]882 fn local_methods() {883 assert_json!(r#"local a(b, c = 2) = b + c; a(2)"#, "4");884 assert_json!(885 r#"local a(b, c = "Dear") = b + c + d, d = "World"; a("Hello")"#,886 r#""HelloDearWorld""#887 );888 }889890 #[test]891 fn object_locals() {892 assert_json!(r#"{local a = 3, b: a}"#, r#"{"b": 3}"#);893 assert_json!(r#"{local a = 3, local c = a, b: c}"#, r#"{"b": 3}"#);894 assert_json!(895 r#"{local a = function (b) {[b]:4}, test: a("test")}"#,896 r#"{"test": {"test": 4}}"#897 );898 }899900 #[test]901 fn object_comp() {902 assert_json!(903 r#"{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}"#,904 "{\"h1_2\": \"0a\",\"h1_3\": \"0a\",\"h1_4\": \"0a\",\"h2_3\": \"a1\",\"h2_4\": \"a1\",\"h3_2\": \"0a\",\"h3_4\": \"a1\"}"905 )906 }907908 #[test]909 fn direct_self() {910 println!(911 "{:#?}",912 eval!(913 r#"914 {915 local me = self,916 a: 3,917 b(): me.a,918 }919 "#920 )921 );922 }923924 #[test]925 fn indirect_self() {926 // `self` assigned to `me` was lost when being927 // referenced from field928 eval!(929 r#"{930 local me = self,931 a: 3,932 b: me.a,933 }.b"#934 );935 }936937 // We can't trust other tests (And official jsonnet testsuite), if assert is not working correctly938 #[test]939 fn std_assert_ok() {940 eval!("std.assertEqual(4.5 << 2, 16)");941 }942943 #[test]944 #[should_panic]945 fn std_assert_failure() {946 eval!("std.assertEqual(4.5 << 2, 15)");947 }948949 #[test]950 fn string_is_string() {951 assert!(primitive_equals(952 &eval!("local arr = 'hello'; (!std.isArray(arr)) && (!std.isString(arr))"),953 &Val::Bool(false),954 )955 .unwrap());956 }957958 #[test]959 fn base64_works() {960 assert_json!(r#"std.base64("test")"#, r#""dGVzdA==""#);961 }962963 #[test]964 fn utf8_chars() {965 assert_json!(966 r#"local c="😎";{c:std.codepoint(c),l:std.length(c)}"#,967 r#"{"c": 128526,"l": 1}"#968 )969 }970971 #[test]972 fn json() {973 assert_json!(974 r#"std.manifestJsonEx({a:3, b:4, c:6},"")"#,975 r#""{\n\"a\": 3,\n\"b\": 4,\n\"c\": 6\n}""#976 );977 }978979 #[test]980 fn json_minified() {981 assert_json!(982 r#"std.manifestJsonMinified({a:3, b:4, c:6})"#,983 r#""{\"a\":3,\"b\":4,\"c\":6}""#984 );985 }986987 #[test]988 fn parse_json() {989 assert_json!(990 r#"std.parseJson('{"a": -1,"b": 1,"c": 3.141,"d": []}')"#,991 r#"{"a": -1,"b": 1,"c": 3.141,"d": []}"#992 );993 }994995 #[test]996 fn test() {997 assert_json!(998 r#"[[a, b] for a in [1,2,3] for b in [4,5,6]]"#,999 "[[1,4],[1,5],[1,6],[2,4],[2,5],[2,6],[3,4],[3,5],[3,6]]"1000 );1001 }10021003 #[test]1004 fn sjsonnet() {1005 eval!(1006 r#"1007 local x0 = {k: 1};1008 local x1 = {k: x0.k + x0.k};1009 local x2 = {k: x1.k + x1.k};1010 local x3 = {k: x2.k + x2.k};1011 local x4 = {k: x3.k + x3.k};1012 local x5 = {k: x4.k + x4.k};1013 local x6 = {k: x5.k + x5.k};1014 local x7 = {k: x6.k + x6.k};1015 local x8 = {k: x7.k + x7.k};1016 local x9 = {k: x8.k + x8.k};1017 local x10 = {k: x9.k + x9.k};1018 local x11 = {k: x10.k + x10.k};1019 local x12 = {k: x11.k + x11.k};1020 local x13 = {k: x12.k + x12.k};1021 local x14 = {k: x13.k + x13.k};1022 local x15 = {k: x14.k + x14.k};1023 local x16 = {k: x15.k + x15.k};1024 local x17 = {k: x16.k + x16.k};1025 local x18 = {k: x17.k + x17.k};1026 local x19 = {k: x18.k + x18.k};1027 local x20 = {k: x19.k + x19.k};1028 local x21 = {k: x20.k + x20.k};1029 x21.k1030 "#1031 );1032 }10331034 // This test is commented out by default, because of huge compilation slowdown1035 // #[bench]1036 // fn bench_codegen(b: &mut Bencher) {1037 // b.iter(|| {1038 // #[allow(clippy::all)]1039 // let stdlib = {1040 // use jrsonnet_parser::*;1041 // include!(concat!(env!("OUT_DIR"), "/stdlib.rs"))1042 // };1043 // stdlib1044 // })1045 // }10461047 /*1048 #[bench]1049 fn bench_serialize(b: &mut Bencher) {1050 b.iter(|| {1051 bincode::deserialize::<jrsonnet_parser::LocExpr>(include_bytes!(concat!(1052 env!("OUT_DIR"),1053 "/stdlib.bincode"1054 )))1055 .expect("deserialize stdlib")1056 })1057 }10581059 #[bench]1060 fn bench_parse(b: &mut Bencher) {1061 b.iter(|| {1062 jrsonnet_parser::parse(1063 jrsonnet_stdlib::STDLIB_STR,1064 &jrsonnet_parser::ParserSettings {1065 loc_data: true,1066 file_name: Rc::new(PathBuf::from("std.jsonnet")),1067 },1068 )1069 })1070 }1071 */10721073 #[test]1074 fn equality() {1075 println!(1076 "{:?}",1077 jrsonnet_parser::parse(1078 "{ x: 1, y: 2 } == { x: 1, y: 2 }",1079 &ParserSettings {1080 file_name: PathBuf::from("equality").into(),1081 }1082 )1083 );1084 assert_eval!("{ x: 1, y: 2 } == { x: 1, y: 2 }")1085 }10861087 #[test]1088 fn native_ext() -> crate::error::Result<()> {1089 use super::native::NativeCallback;1090 let evaluator = EvaluationState::default();10911092 evaluator.with_stdlib();10931094 #[derive(Trace)]1095 struct NativeAdd;1096 impl NativeCallbackHandler for NativeAdd {1097 fn call(&self, from: Option<Rc<Path>>, args: &[Val]) -> crate::error::Result<Val> {1098 assert_eq!(1099 &from.unwrap() as &Path,1100 &PathBuf::from("native_caller.jsonnet")1101 );1102 match (&args[0], &args[1]) {1103 (Val::Num(a), Val::Num(b)) => Ok(Val::Num(a + b)),1104 (_, _) => unreachable!(),1105 }1106 }1107 }1108 evaluator.settings_mut().ext_natives.insert(1109 "native_add".into(),1110 #[allow(deprecated)]1111 Cc::new(TraceBox(Box::new(NativeCallback::new(1112 vec![1113 BuiltinParam {1114 name: "a".into(),1115 has_default: false,1116 },1117 BuiltinParam {1118 name: "b".into(),1119 has_default: false,1120 },1121 ],1122 TraceBox(Box::new(NativeAdd)),1123 )))),1124 );1125 evaluator.evaluate_snippet_raw(1126 PathBuf::from("native_caller.jsonnet").into(),1127 "std.assertEqual(std.native(\"native_add\")(1, 2), 3)".into(),1128 )?;1129 Ok(())1130 }11311132 #[test]1133 fn constant_intrinsic() -> crate::error::Result<()> {1134 assert_eval!(1135 "local std2 = std; local std = std2 { primitiveEquals(a, b):: false }; 1 == 1"1136 );1137 Ok(())1138 }11391140 #[test]1141 fn standalone_super() -> crate::error::Result<()> {1142 assert_eval!(1143 r#"1144 local obj = {1145 a: 1,1146 b: 2,1147 c: 3,1148 };1149 local test = obj + {1150 fields: std.objectFields(super),1151 d: 5,1152 };1153 test.fields == ['a', 'b', 'c']1154 "#1155 );1156 Ok(())1157 }11581159 #[test]1160 fn comp_self() -> crate::error::Result<()> {1161 assert_eval!(1162 r#"1163 std.objectFields({1164 a:{1165 [name]: name for name in std.objectFields(self)1166 },1167 b: 2,1168 c: 3,1169 }.a) == ['a', 'b', 'c']1170 "#1171 );11721173 Ok(())1174 }11751176 struct TestImportResolver(IStr);1177 impl crate::import::ImportResolver for TestImportResolver {1178 fn resolve_file(&self, _: &Path, _: &Path) -> crate::error::Result<Rc<Path>> {1179 Ok(PathBuf::from("/test").into())1180 }11811182 fn load_file_contents(&self, _: &Path) -> crate::error::Result<IStr> {1183 Ok(self.0.clone())1184 }11851186 unsafe fn as_any(&self) -> &dyn std::any::Any {1187 panic!()1188 }1189 }11901191 #[test]1192 fn issue_23() {1193 let state = EvaluationState::default();1194 state.set_import_resolver(Box::new(TestImportResolver(r#"import "/test""#.into())));1195 let _ = state.evaluate_file_raw(&PathBuf::from("/test"));1196 }11971198 #[test]1199 fn issue_40() {1200 let state = EvaluationState::default();1201 state.with_stdlib();12021203 let error = state1204 .evaluate_snippet_raw(1205 PathBuf::from("issue40.jsonnet").into(),1206 r#"1207 local conf = {1208 n: ""1209 };12101211 local result = conf + {1212 assert std.isNumber(self.n): "is number"1213 };12141215 std.manifestJsonEx(result, "")1216 "#1217 .into(),1218 )1219 .unwrap_err();1220 assert_eq!(error.error().to_string(), "assert failed: is number");1221 }12221223 #[test]1224 fn test_ascii_upper_lower() {1225 assert_eval!(r#"std.assertEqual(std.asciiUpper("aBc😀"), "ABC😀")"#);1226 assert_eval!(r#"std.assertEqual(std.asciiLower("aBc😀"), "abc😀")"#);1227 }12281229 #[test]1230 fn test_member() {1231 assert_eval!(r#"!std.member("", "")"#);1232 assert_eval!(r#"std.member("abc", "a")"#);1233 assert_eval!(r#"!std.member("abc", "d")"#);1234 assert_eval!(r#"!std.member([], "")"#);1235 assert_eval!(r#"std.member(["a", "b", "c"], "a")"#);1236 assert_eval!(r#"!std.member(["a", "b", "c"], "d")"#);1237 }12381239 #[test]1240 fn test_count() {1241 assert_eval!(r#"std.assertEqual(std.count([], ""), 0)"#);1242 assert_eval!(r#"std.assertEqual(std.count(["a", "b", "a"], "d"), 0)"#);1243 assert_eval!(r#"std.assertEqual(std.count(["a", "b", "a"], "a"), 2)"#);1244 }1245}crates/jrsonnet-evaluator/src/typed/mod.rsdiffbeforeafterboth--- a/crates/jrsonnet-evaluator/src/typed/mod.rs
+++ b/crates/jrsonnet-evaluator/src/typed/mod.rs
@@ -13,9 +13,9 @@
#[macro_export]
macro_rules! unwrap_type {
- ($desc: expr, $value: expr, $typ: expr => $match: path) => {{
- use $crate::{push_stack_frame, typed::CheckType};
- push_stack_frame(None, $desc, || Ok($typ.check(&$value)?))?;
+ ($desc:expr, $value:expr, $typ:expr => $match:path) => {{
+ use $crate::{push_frame, typed::CheckType};
+ push_frame(None, $desc, || Ok($typ.check(&$value)?))?;
match $value {
$match(v) => v,
_ => unreachable!(),
crates/jrsonnet-interner/Cargo.tomldiffbeforeafterboth--- a/crates/jrsonnet-interner/Cargo.toml
+++ b/crates/jrsonnet-interner/Cargo.toml
@@ -4,7 +4,7 @@
version = "0.4.2"
authors = ["Yaroslav Bolyukin <iam@lach.pw>"]
license = "MIT"
-edition = "2018"
+edition = "2021"
[dependencies]
serde = { version = "1.0" }
crates/jrsonnet-macros/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-macros/src/lib.rs
+++ b/crates/jrsonnet-macros/src/lib.rs
@@ -136,7 +136,11 @@
#[derive(Clone, Copy, gcmodule::Trace)]
#vis struct #name {}
const _: () = {
- use ::jrsonnet_evaluator::function::{Builtin, StaticBuiltin, BuiltinParam, ArgsLike};
+ use ::jrsonnet_evaluator::{
+ function::{Builtin, StaticBuiltin, BuiltinParam, ArgsLike, parse_builtin_call},
+ error::Result, Context,
+ parser::ExprLocation,
+ };
const PARAMS: &'static [BuiltinParam] = &[
#(#params),*
];
@@ -156,7 +160,7 @@
PARAMS
}
fn call(&self, context: Context, loc: Option<&ExprLocation>, args: &dyn ArgsLike) -> Result<Val> {
- let parsed = ::jrsonnet_evaluator::function::parse_builtin_call(context, &PARAMS, args, false)?;
+ let parsed = parse_builtin_call(context, &PARAMS, args, false)?;
let result: #result = #name(#(#args),*);
let result = result?;
crates/jrsonnet-parser/Cargo.tomldiffbeforeafterboth--- a/crates/jrsonnet-parser/Cargo.toml
+++ b/crates/jrsonnet-parser/Cargo.toml
@@ -4,7 +4,7 @@
version = "0.4.2"
authors = ["Yaroslav Bolyukin <iam@lach.pw>"]
license = "MIT"
-edition = "2018"
+edition = "2021"
[features]
default = []
@@ -14,7 +14,7 @@
[dependencies]
jrsonnet-interner = { path = "../jrsonnet-interner", version = "0.4.2" }
-peg = "0.7.0"
+peg = "0.8.0"
serde = { version = "1.0", features = ["derive", "rc"], optional = true }
gcmodule = { git = "https://github.com/CertainLach/gcmodule", branch = "jrsonnet" }
crates/jrsonnet-parser/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-parser/src/lib.rs
+++ b/crates/jrsonnet-parser/src/lib.rs
@@ -114,8 +114,8 @@
/ "\\x" hex_char() hex_char()
/ ['\\'] (quiet! { ['b' | 'f' | 'n' | 'r' | 't'] / c() } / expected!("<escape character>"))
pub rule string() -> String
- = ['"'] str:$(string_char(<['"']>)*) ['"'] {? unescape::unescape(str).ok_or("<escaped string>")}
- / ['\''] str:$(string_char(<['\'']>)*) ['\''] {? unescape::unescape(str).ok_or("<escaped string>")}
+ = ['"'] str:$(string_char(<"\"">)*) ['"'] {? unescape::unescape(str).ok_or("<escaped string>")}
+ / ['\''] str:$(string_char(<"\'">)*) ['\''] {? unescape::unescape(str).ok_or("<escaped string>")}
/ quiet!{ "@'" str:$(("''" / (!['\''][_]))*) "'" {str.replace("''", "'")}
/ "@\"" str:$(("\"\"" / (!['"'][_]))*) "\"" {str.replace("\"\"", "\"")}
/ string_block() } / expected!("<string>")
crates/jrsonnet-stdlib/Cargo.tomldiffbeforeafterboth--- a/crates/jrsonnet-stdlib/Cargo.toml
+++ b/crates/jrsonnet-stdlib/Cargo.toml
@@ -4,7 +4,7 @@
version = "0.4.2"
authors = ["Yaroslav Bolyukin <iam@lach.pw>"]
license = "MIT"
-edition = "2018"
+edition = "2021"
[features]
crates/jrsonnet-types/Cargo.tomldiffbeforeafterboth--- a/crates/jrsonnet-types/Cargo.toml
+++ b/crates/jrsonnet-types/Cargo.toml
@@ -4,8 +4,8 @@
version = "0.4.2"
authors = ["Yaroslav Bolyukin <iam@lach.pw>"]
license = "MIT"
-edition = "2018"
+edition = "2021"
[dependencies]
-peg = "0.7.0"
+peg = "0.8.0"
gcmodule = { git = "https://github.com/CertainLach/gcmodule", branch = "jrsonnet" }