git.delta.rocks / jrsonnet / refs/commits / bc97bc99f5fd

difftreelog

fix(evaluator) missing intrinsics for slice, mod

Yaroslav Bolyukin2021-01-06parent: #7c1d01a.patch.diff
in: master
Fixes #30

3 files changed

modifiedcrates/jrsonnet-evaluator/build.rsdiffbeforeafterboth
--- a/crates/jrsonnet-evaluator/build.rs
+++ b/crates/jrsonnet-evaluator/build.rs
@@ -39,7 +39,8 @@
 								if **name == *"join" || **name == *"manifestJsonEx" ||
 								**name == *"escapeStringJson" || **name == *"equals" ||
 								**name == *"base64" || **name == *"foldl" || **name == *"foldr" ||
-								**name == *"sortImpl" || **name == *"format" || **name == *"range" || **name == *"reverse"
+								**name == *"sortImpl" || **name == *"format" || **name == *"range" ||
+								**name == *"reverse" || **name == *"slice" || **name == *"mod"
 							)
 						})
 						.collect(),
modifiedcrates/jrsonnet-evaluator/src/builtin/mod.rsdiffbeforeafterboth
before · crates/jrsonnet-evaluator/src/builtin/mod.rs
1use crate::{2	equals,3	error::{Error::*, Result},4	evaluate, parse_args, primitive_equals, push, throw, with_state, Context, FuncVal, Val,5	ValType,6};7use format::{format_arr, format_obj};8use jrsonnet_parser::{ArgsDesc, ExprLocation};9use manifest::{escape_string_json, manifest_json_ex, ManifestJsonOptions, ManifestType};10use std::{path::PathBuf, rc::Rc};1112pub mod stdlib;13pub use stdlib::*;1415pub mod format;16pub mod manifest;17pub mod sort;1819#[allow(clippy::cognitive_complexity)]20pub fn call_builtin(21	context: Context,22	loc: &Option<ExprLocation>,23	name: &str,24	args: &ArgsDesc,25) -> Result<Val> {26	Ok(match name as &str {27		// arr/string/function28		"length" => parse_args!(context, "std.length", args, 1, [29			0, x: [Val::Str|Val::Arr|Val::Obj], vec![ValType::Str, ValType::Arr, ValType::Obj];30		], {31			Ok(match x {32				Val::Str(n) => Val::Num(n.chars().count() as f64),33				Val::Arr(i) => Val::Num(i.len() as f64),34				Val::Obj(o) => Val::Num(35					o.fields_visibility()36						.into_iter()37						.filter(|(_k, v)| *v)38						.count() as f64,39				),40				_ => unreachable!(),41			})42		})?,43		// any44		"type" => parse_args!(context, "std.type", args, 1, [45			0, x, vec![];46		], {47			Ok(Val::Str(x.value_type()?.name().into()))48		})?,49		// length, idx=>any50		"makeArray" => parse_args!(context, "std.makeArray", args, 2, [51			0, sz: [Val::Num]!!Val::Num, vec![ValType::Num];52			1, func: [Val::Func]!!Val::Func, vec![ValType::Func];53		], {54			if sz < 0.0 {55				throw!(RuntimeError(format!("makeArray requires size >= 0, got {}", sz).into()));56			}57			let mut out = Vec::with_capacity(sz as usize);58			for i in 0..sz as usize {59				out.push(func.evaluate_values(60					Context::new(),61					&[Val::Num(i as f64)]62				)?)63			}64			Ok(Val::Arr(Rc::new(out)))65		})?,66		// string67		"codepoint" => parse_args!(context, "std.codepoint", args, 1, [68			0, str: [Val::Str]!!Val::Str, vec![ValType::Str];69		], {70			assert!(71				str.chars().count() == 1,72				"std.codepoint should receive single char string"73			);74			Ok(Val::Num(str.chars().take(1).next().unwrap() as u32 as f64))75		})?,76		// object, includeHidden77		"objectFieldsEx" => parse_args!(context, "std.objectFieldsEx",args, 2, [78			0, obj: [Val::Obj]!!Val::Obj, vec![ValType::Obj];79			1, inc_hidden: [Val::Bool]!!Val::Bool, vec![ValType::Bool];80		], {81			let mut out = obj.fields_visibility()82				.into_iter()83				.filter(|(_k, v)| *v || inc_hidden)84				.map(|(k, _v)|k)85				.collect::<Vec<_>>();86			out.sort();87			Ok(Val::Arr(Rc::new(out.into_iter().map(Val::Str).collect())))88		})?,89		// object, field, includeHidden90		"objectHasEx" => parse_args!(context, "std.objectHasEx", args, 3, [91			0, obj: [Val::Obj]!!Val::Obj, vec![ValType::Obj];92			1, f: [Val::Str]!!Val::Str, vec![ValType::Str];93			2, inc_hidden: [Val::Bool]!!Val::Bool, vec![ValType::Bool];94		], {95			Ok(Val::Bool(96				obj.fields_visibility()97					.into_iter()98					.filter(|(_k, v)| *v || inc_hidden)99					.any(|(k, _v)| *k == *f),100			))101		})?,102		"primitiveEquals" => parse_args!(context, "std.primitiveEquals", args, 2, [103			0, a, vec![];104			1, b, vec![];105		], {106			Ok(Val::Bool(primitive_equals(&a, &b)?))107		})?,108		// faster109		"equals" => parse_args!(context, "std.equals", args, 2, [110			0, a, vec![];111			1, b, vec![];112		], {113			Ok(Val::Bool(equals(&a, &b)?))114		})?,115		"modulo" => parse_args!(context, "std.modulo", args, 2, [116			0, a: [Val::Num]!!Val::Num, vec![ValType::Num];117			1, b: [Val::Num]!!Val::Num, vec![ValType::Num];118		], {119			Ok(Val::Num(a % b))120		})?,121		"floor" => parse_args!(context, "std.floor", args, 1, [122			0, x: [Val::Num]!!Val::Num, vec![ValType::Num];123		], {124			Ok(Val::Num(x.floor()))125		})?,126		"log" => parse_args!(context, "std.log", args, 2, [127			0, n: [Val::Num]!!Val::Num, vec![ValType::Num];128		], {129			Ok(Val::Num(n.ln()))130		})?,131		"trace" => parse_args!(context, "std.trace", args, 2, [132			0, str: [Val::Str]!!Val::Str, vec![ValType::Str];133			1, rest, vec![];134		], {135			eprint!("TRACE:");136			if let Some(loc) = loc {137				with_state(|s|{138					let locs = s.map_source_locations(&loc.0, &[loc.1]);139					eprint!(" {}:{}", loc.0.file_name().unwrap().to_str().unwrap(), locs[0].line);140				});141			}142			eprintln!(" {}", str);143			Ok(rest)144		})?,145		"pow" => parse_args!(context, "std.modulo", args, 2, [146			0, x: [Val::Num]!!Val::Num, vec![ValType::Num];147			1, n: [Val::Num]!!Val::Num, vec![ValType::Num];148		], {149			Ok(Val::Num(x.powf(n)))150		})?,151		"extVar" => parse_args!(context, "std.extVar", args, 1, [152			0, x: [Val::Str]!!Val::Str, vec![ValType::Str];153		], {154			Ok(with_state(|s| s.settings().ext_vars.get(&x).cloned()).ok_or_else(155				|| UndefinedExternalVariable(x),156			)?)157		})?,158		"native" => parse_args!(context, "std.native", args, 1, [159			0, x: [Val::Str]!!Val::Str, vec![ValType::Str];160		], {161			Ok(with_state(|s| s.settings().ext_natives.get(&x).cloned()).map(|v| Val::Func(Rc::new(FuncVal::NativeExt(x.clone(), v)))).ok_or_else(162				|| UndefinedExternalFunction(x),163			)?)164		})?,165		"filter" => parse_args!(context, "std.filter", args, 2, [166			0, func: [Val::Func]!!Val::Func, vec![ValType::Func];167			1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr];168		], {169			Ok(Val::Arr(Rc::new(170				arr.iter()171					.cloned()172					.filter(|e| {173						func174							.evaluate_values(context.clone(), &[e.clone()])175							.unwrap()176							.try_cast_bool("filter predicate")177							.unwrap()178					})179					.collect(),180			)))181		})?,182		// faster183		"foldl" => parse_args!(context, "std.foldl", args, 3, [184			0, func: [Val::Func]!!Val::Func, vec![ValType::Func];185			1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr];186			2, init, vec![];187		], {188			let mut acc = init;189			for i in arr.iter().cloned() {190				acc = func.evaluate_values(context.clone(), &[acc, i])?;191			}192			Ok(acc)193		})?,194		// faster195		"foldr" => parse_args!(context, "std.foldr", args, 3, [196			0, func: [Val::Func]!!Val::Func, vec![ValType::Func];197			1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr];198			2, init, vec![];199		], {200			let mut acc = init;201			for i in arr.iter().rev().cloned() {202				acc = func.evaluate_values(context.clone(), &[acc, i])?;203			}204			Ok(acc)205		})?,206		// faster207		#[allow(non_snake_case)]208		"sortImpl" => parse_args!(context, "std.sort", args, 2, [209			0, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr];210			1, keyF: [Val::Func]!!Val::Func, vec![ValType::Func];211		], {212			if arr.len() <= 1 {213				return Ok(Val::Arr(arr))214			}215			Ok(Val::Arr(sort::sort(context, arr, &keyF)?))216		})?,217		// faster218		"format" => parse_args!(context, "std.format", args, 2, [219			0, str: [Val::Str]!!Val::Str, vec![ValType::Str];220			1, vals, vec![]221		], {222			push(&Some(ExprLocation(Rc::from(PathBuf::from("std.jsonnet")), 0, 0)), ||format!("std.format of {}", str), ||{223				Ok(match vals {224					Val::Arr(vals) => Val::Str(format_arr(&str, &vals)?.into()),225					Val::Obj(obj) => Val::Str(format_obj(&str, &obj)?.into()),226					o => Val::Str(format_arr(&str, &[o])?.into()),227				})228			})229		})?,230		// faster231		"range" => parse_args!(context, "std.range", args, 2, [232			0, from: [Val::Num]!!Val::Num, vec![ValType::Num];233			1, to: [Val::Num]!!Val::Num, vec![ValType::Num];234		], {235			let mut out = Vec::with_capacity((1+to as usize-from as usize).max(0));236			for i in from as usize..=to as usize {237				out.push(Val::Num(i as f64));238			}239			Ok(Val::Arr(Rc::new(out)))240		})?,241		"char" => parse_args!(context, "std.char", args, 1, [242			0, n: [Val::Num]!!Val::Num, vec![ValType::Num];243		], {244			let mut out = String::new();245			out.push(std::char::from_u32(n as u32).ok_or_else(||246				InvalidUnicodeCodepointGot(n as u32)247			)?);248			Ok(Val::Str(out.into()))249		})?,250		"encodeUTF8" => parse_args!(context, "std.encodeUtf8", args, 1, [251			0, str: [Val::Str]!!Val::Str, vec![ValType::Str];252		], {253			Ok(Val::Arr(Rc::new(str.bytes().map(|b| Val::Num(b as f64)).collect())))254		})?,255		"md5" => parse_args!(context, "std.md5", args, 1, [256			0, str: [Val::Str]!!Val::Str, vec![ValType::Str];257		], {258			Ok(Val::Str(format!("{:x}", md5::compute(&str.as_bytes())).into()))259		})?,260		// faster261		"base64" => parse_args!(context, "std.base64", args, 1, [262			0, input: [Val::Str | Val::Arr], vec![ValType::Arr, ValType::Str];263		], {264			Ok(Val::Str(match input {265				Val::Str(s) => {266					base64::encode(s.bytes().collect::<Vec<_>>()).into()267				},268				Val::Arr(a) => {269					base64::encode(a.iter().map(|v| {270						Ok(v.clone().try_cast_num("base64 array")? as u8)271					}).collect::<Result<Vec<_>>>()?).into()272				},273				_ => unreachable!()274			}))275		})?,276		// faster277		"join" => parse_args!(context, "std.join", args, 2, [278			0, sep: [Val::Str|Val::Arr], vec![ValType::Str, ValType::Arr];279			1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr];280		], {281			Ok(match sep {282				Val::Arr(joiner_items) => {283					let mut out = Vec::new();284285					let mut first = true;286					for item in arr.iter().cloned() {287						if let Val::Arr(items) = item.unwrap_if_lazy()? {288							if !first {289								out.reserve(joiner_items.len());290								out.extend(joiner_items.iter().cloned());291							}292							first = false;293							out.reserve(items.len());294							out.extend(items.iter().cloned());295						} else {296							throw!(RuntimeError("in std.join all items should be arrays".into()));297						}298					}299300					Val::Arr(Rc::new(out))301				},302				Val::Str(sep) => {303					let mut out = String::new();304305					let mut first = true;306					for item in arr.iter().cloned() {307						if let Val::Str(item) = item.unwrap_if_lazy()? {308							if !first {309								out += &sep;310							}311							first = false;312							out += &item;313						} else {314							throw!(RuntimeError("in std.join all items should be strings".into()));315						}316					}317318					Val::Str(out.into())319				},320				_ => unreachable!()321			})322		})?,323		// Faster324		"escapeStringJson" => parse_args!(context, "std.escapeStringJson", args, 1, [325			0, str_: [Val::Str]!!Val::Str, vec![ValType::Str];326		], {327			Ok(Val::Str(escape_string_json(&str_).into()))328		})?,329		// Faster330		"manifestJsonEx" => parse_args!(context, "std.manifestJsonEx", args, 2, [331			0, value, vec![];332			1, indent: [Val::Str]!!Val::Str, vec![ValType::Str];333		], {334			Ok(Val::Str(manifest_json_ex(&value, &ManifestJsonOptions {335				padding: &indent,336				mtype: ManifestType::Std,337			})?.into()))338		})?,339		// Faster340		"reverse" => parse_args!(context, "std.reverse", args, 1, [341			0, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr];342		], {343			let mut marr = arr;344			Rc::make_mut(&mut marr).reverse();345			Ok(Val::Arr(marr))346		})?,347		"id" => parse_args!(context, "std.id", args, 1, [348			0, v, vec![];349		], {350			Ok(v)351		})?,352		name => throw!(IntrinsicNotFound(name.into())),353	})354}
after · crates/jrsonnet-evaluator/src/builtin/mod.rs
1use crate::{2	equals,3	error::{Error::*, Result},4	evaluate, parse_args, primitive_equals, push, throw, with_state, Context, FuncVal, Val,5	ValType,6};7use format::{format_arr, format_obj};8use jrsonnet_parser::{ArgsDesc, ExprLocation};9use manifest::{escape_string_json, manifest_json_ex, ManifestJsonOptions, ManifestType};10use std::{path::PathBuf, rc::Rc};1112pub mod stdlib;13pub use stdlib::*;1415pub mod format;16pub mod manifest;17pub mod sort;1819fn std_format(str: Rc<str>, vals: Val) -> Result<Val> {20	push(21		&Some(ExprLocation(Rc::from(PathBuf::from("std.jsonnet")), 0, 0)),22		|| format!("std.format of {}", str),23		|| {24			Ok(match vals {25				Val::Arr(vals) => Val::Str(format_arr(&str, &vals)?.into()),26				Val::Obj(obj) => Val::Str(format_obj(&str, &obj)?.into()),27				o => Val::Str(format_arr(&str, &[o])?.into()),28			})29		},30	)31}3233#[allow(clippy::cognitive_complexity)]34pub fn call_builtin(35	context: Context,36	loc: &Option<ExprLocation>,37	name: &str,38	args: &ArgsDesc,39) -> Result<Val> {40	Ok(match name as &str {41		// arr/string/function42		"length" => parse_args!(context, "std.length", args, 1, [43			0, x: [Val::Str|Val::Arr|Val::Obj], vec![ValType::Str, ValType::Arr, ValType::Obj];44		], {45			Ok(match x {46				Val::Str(n) => Val::Num(n.chars().count() as f64),47				Val::Arr(i) => Val::Num(i.len() as f64),48				Val::Obj(o) => Val::Num(49					o.fields_visibility()50						.into_iter()51						.filter(|(_k, v)| *v)52						.count() as f64,53				),54				_ => unreachable!(),55			})56		})?,57		// any58		"type" => parse_args!(context, "std.type", args, 1, [59			0, x, vec![];60		], {61			Ok(Val::Str(x.value_type()?.name().into()))62		})?,63		// length, idx=>any64		"makeArray" => parse_args!(context, "std.makeArray", args, 2, [65			0, sz: [Val::Num]!!Val::Num, vec![ValType::Num];66			1, func: [Val::Func]!!Val::Func, vec![ValType::Func];67		], {68			if sz < 0.0 {69				throw!(RuntimeError(format!("makeArray requires size >= 0, got {}", sz).into()));70			}71			let mut out = Vec::with_capacity(sz as usize);72			for i in 0..sz as usize {73				out.push(func.evaluate_values(74					Context::new(),75					&[Val::Num(i as f64)]76				)?)77			}78			Ok(Val::Arr(Rc::new(out)))79		})?,80		// string81		"codepoint" => parse_args!(context, "std.codepoint", args, 1, [82			0, str: [Val::Str]!!Val::Str, vec![ValType::Str];83		], {84			assert!(85				str.chars().count() == 1,86				"std.codepoint should receive single char string"87			);88			Ok(Val::Num(str.chars().take(1).next().unwrap() as u32 as f64))89		})?,90		// object, includeHidden91		"objectFieldsEx" => parse_args!(context, "std.objectFieldsEx",args, 2, [92			0, obj: [Val::Obj]!!Val::Obj, vec![ValType::Obj];93			1, inc_hidden: [Val::Bool]!!Val::Bool, vec![ValType::Bool];94		], {95			let mut out = obj.fields_visibility()96				.into_iter()97				.filter(|(_k, v)| *v || inc_hidden)98				.map(|(k, _v)|k)99				.collect::<Vec<_>>();100			out.sort();101			Ok(Val::Arr(Rc::new(out.into_iter().map(Val::Str).collect())))102		})?,103		// object, field, includeHidden104		"objectHasEx" => parse_args!(context, "std.objectHasEx", args, 3, [105			0, obj: [Val::Obj]!!Val::Obj, vec![ValType::Obj];106			1, f: [Val::Str]!!Val::Str, vec![ValType::Str];107			2, inc_hidden: [Val::Bool]!!Val::Bool, vec![ValType::Bool];108		], {109			Ok(Val::Bool(110				obj.fields_visibility()111					.into_iter()112					.filter(|(_k, v)| *v || inc_hidden)113					.any(|(k, _v)| *k == *f),114			))115		})?,116117		// faster118		"slice" => parse_args!(context, "slice", args, 4, [119			0, indexable: [Val::Str | Val::Arr], vec![ValType::Str, ValType::Arr];120			1, index, vec![ValType::Num, ValType::Null];121			2, end, vec![ValType::Num, ValType::Null];122			3, step, vec![ValType::Num, ValType::Null];123		], {124			let index = match index {125				Val::Num(v) => v as usize,126				Val::Null => 0,127				_ => unreachable!(),128			};129			let end = match end {130				Val::Num(v) => v as usize,131				Val::Null => match &indexable {132					Val::Str(s) => s.chars().count(),133					Val::Arr(v) => v.len(),134					_ => unreachable!()135				},136				_ => unreachable!()137			};138			let step = match step {139				Val::Num(v) => v as usize,140				Val::Null => 1,141				_ => unreachable!()142			};143			match &indexable {144				Val::Str(s) => {145					Ok(Val::Str((s.chars().skip(index).take(end-index).step_by(step).collect::<String>()).into()))146				}147				Val::Arr(arr) => {148					Ok(Val::Arr((arr.iter().skip(index).take(end-index).step_by(step).cloned().collect::<Vec<Val>>()).into()))149				}150				_ => unreachable!()151			}152		})?,153		"primitiveEquals" => parse_args!(context, "std.primitiveEquals", args, 2, [154			0, a, vec![];155			1, b, vec![];156		], {157			Ok(Val::Bool(primitive_equals(&a, &b)?))158		})?,159		// faster160		"equals" => parse_args!(context, "std.equals", args, 2, [161			0, a, vec![];162			1, b, vec![];163		], {164			Ok(Val::Bool(equals(&a, &b)?))165		})?,166		"mod" => parse_args!(context, "std.mod", args, 2, [167			0, a: [Val::Num | Val::Str], vec![ValType::Num, ValType::Str];168			1, b, vec![];169		], {170			match (a, b) {171				(Val::Num(a), Val::Num(b)) => Ok(Val::Num(a % b)),172				(Val::Str(str), vals) => std_format(str, vals),173				(a, b) => throw!(BinaryOperatorDoesNotOperateOnValues(jrsonnet_parser::BinaryOpType::Mod, a.value_type()?, b.value_type()?))174			}175		})?,176		"modulo" => parse_args!(context, "std.modulo", args, 2, [177			0, a: [Val::Num]!!Val::Num, vec![ValType::Num];178			1, b: [Val::Num]!!Val::Num, vec![ValType::Num];179		], {180			Ok(Val::Num(a % b))181		})?,182		"floor" => parse_args!(context, "std.floor", args, 1, [183			0, x: [Val::Num]!!Val::Num, vec![ValType::Num];184		], {185			Ok(Val::Num(x.floor()))186		})?,187		"log" => parse_args!(context, "std.log", args, 2, [188			0, n: [Val::Num]!!Val::Num, vec![ValType::Num];189		], {190			Ok(Val::Num(n.ln()))191		})?,192		"trace" => parse_args!(context, "std.trace", args, 2, [193			0, str: [Val::Str]!!Val::Str, vec![ValType::Str];194			1, rest, vec![];195		], {196			eprint!("TRACE:");197			if let Some(loc) = loc {198				with_state(|s|{199					let locs = s.map_source_locations(&loc.0, &[loc.1]);200					eprint!(" {}:{}", loc.0.file_name().unwrap().to_str().unwrap(), locs[0].line);201				});202			}203			eprintln!(" {}", str);204			Ok(rest)205		})?,206		"pow" => parse_args!(context, "std.modulo", args, 2, [207			0, x: [Val::Num]!!Val::Num, vec![ValType::Num];208			1, n: [Val::Num]!!Val::Num, vec![ValType::Num];209		], {210			Ok(Val::Num(x.powf(n)))211		})?,212		"extVar" => parse_args!(context, "std.extVar", args, 1, [213			0, x: [Val::Str]!!Val::Str, vec![ValType::Str];214		], {215			Ok(with_state(|s| s.settings().ext_vars.get(&x).cloned()).ok_or_else(216				|| UndefinedExternalVariable(x),217			)?)218		})?,219		"native" => parse_args!(context, "std.native", args, 1, [220			0, x: [Val::Str]!!Val::Str, vec![ValType::Str];221		], {222			Ok(with_state(|s| s.settings().ext_natives.get(&x).cloned()).map(|v| Val::Func(Rc::new(FuncVal::NativeExt(x.clone(), v)))).ok_or_else(223				|| UndefinedExternalFunction(x),224			)?)225		})?,226		"filter" => parse_args!(context, "std.filter", args, 2, [227			0, func: [Val::Func]!!Val::Func, vec![ValType::Func];228			1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr];229		], {230			Ok(Val::Arr(Rc::new(231				arr.iter()232					.cloned()233					.filter(|e| {234						func235							.evaluate_values(context.clone(), &[e.clone()])236							.unwrap()237							.try_cast_bool("filter predicate")238							.unwrap()239					})240					.collect(),241			)))242		})?,243		// faster244		"foldl" => parse_args!(context, "std.foldl", args, 3, [245			0, func: [Val::Func]!!Val::Func, vec![ValType::Func];246			1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr];247			2, init, vec![];248		], {249			let mut acc = init;250			for i in arr.iter().cloned() {251				acc = func.evaluate_values(context.clone(), &[acc, i])?;252			}253			Ok(acc)254		})?,255		// faster256		"foldr" => parse_args!(context, "std.foldr", args, 3, [257			0, func: [Val::Func]!!Val::Func, vec![ValType::Func];258			1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr];259			2, init, vec![];260		], {261			let mut acc = init;262			for i in arr.iter().rev().cloned() {263				acc = func.evaluate_values(context.clone(), &[acc, i])?;264			}265			Ok(acc)266		})?,267		// faster268		#[allow(non_snake_case)]269		"sortImpl" => parse_args!(context, "std.sort", args, 2, [270			0, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr];271			1, keyF: [Val::Func]!!Val::Func, vec![ValType::Func];272		], {273			if arr.len() <= 1 {274				return Ok(Val::Arr(arr))275			}276			Ok(Val::Arr(sort::sort(context, arr, &keyF)?))277		})?,278		// faster279		"format" => parse_args!(context, "std.format", args, 2, [280			0, str: [Val::Str]!!Val::Str, vec![ValType::Str];281			1, vals, vec![]282		], {283			std_format(str, vals)284		})?,285		// faster286		"range" => parse_args!(context, "std.range", args, 2, [287			0, from: [Val::Num]!!Val::Num, vec![ValType::Num];288			1, to: [Val::Num]!!Val::Num, vec![ValType::Num];289		], {290			let mut out = Vec::with_capacity((1+to as usize-from as usize).max(0));291			for i in from as usize..=to as usize {292				out.push(Val::Num(i as f64));293			}294			Ok(Val::Arr(Rc::new(out)))295		})?,296		"char" => parse_args!(context, "std.char", args, 1, [297			0, n: [Val::Num]!!Val::Num, vec![ValType::Num];298		], {299			let mut out = String::new();300			out.push(std::char::from_u32(n as u32).ok_or_else(||301				InvalidUnicodeCodepointGot(n as u32)302			)?);303			Ok(Val::Str(out.into()))304		})?,305		"encodeUTF8" => parse_args!(context, "std.encodeUtf8", args, 1, [306			0, str: [Val::Str]!!Val::Str, vec![ValType::Str];307		], {308			Ok(Val::Arr(Rc::new(str.bytes().map(|b| Val::Num(b as f64)).collect())))309		})?,310		"md5" => parse_args!(context, "std.md5", args, 1, [311			0, str: [Val::Str]!!Val::Str, vec![ValType::Str];312		], {313			Ok(Val::Str(format!("{:x}", md5::compute(&str.as_bytes())).into()))314		})?,315		// faster316		"base64" => parse_args!(context, "std.base64", args, 1, [317			0, input: [Val::Str | Val::Arr], vec![ValType::Arr, ValType::Str];318		], {319			Ok(Val::Str(match input {320				Val::Str(s) => {321					base64::encode(s.bytes().collect::<Vec<_>>()).into()322				},323				Val::Arr(a) => {324					base64::encode(a.iter().map(|v| {325						Ok(v.clone().try_cast_num("base64 array")? as u8)326					}).collect::<Result<Vec<_>>>()?).into()327				},328				_ => unreachable!()329			}))330		})?,331		// faster332		"join" => parse_args!(context, "std.join", args, 2, [333			0, sep: [Val::Str|Val::Arr], vec![ValType::Str, ValType::Arr];334			1, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr];335		], {336			Ok(match sep {337				Val::Arr(joiner_items) => {338					let mut out = Vec::new();339340					let mut first = true;341					for item in arr.iter().cloned() {342						if let Val::Arr(items) = item.unwrap_if_lazy()? {343							if !first {344								out.reserve(joiner_items.len());345								out.extend(joiner_items.iter().cloned());346							}347							first = false;348							out.reserve(items.len());349							out.extend(items.iter().cloned());350						} else {351							throw!(RuntimeError("in std.join all items should be arrays".into()));352						}353					}354355					Val::Arr(Rc::new(out))356				},357				Val::Str(sep) => {358					let mut out = String::new();359360					let mut first = true;361					for item in arr.iter().cloned() {362						if let Val::Str(item) = item.unwrap_if_lazy()? {363							if !first {364								out += &sep;365							}366							first = false;367							out += &item;368						} else {369							throw!(RuntimeError("in std.join all items should be strings".into()));370						}371					}372373					Val::Str(out.into())374				},375				_ => unreachable!()376			})377		})?,378		// Faster379		"escapeStringJson" => parse_args!(context, "std.escapeStringJson", args, 1, [380			0, str_: [Val::Str]!!Val::Str, vec![ValType::Str];381		], {382			Ok(Val::Str(escape_string_json(&str_).into()))383		})?,384		// Faster385		"manifestJsonEx" => parse_args!(context, "std.manifestJsonEx", args, 2, [386			0, value, vec![];387			1, indent: [Val::Str]!!Val::Str, vec![ValType::Str];388		], {389			Ok(Val::Str(manifest_json_ex(&value, &ManifestJsonOptions {390				padding: &indent,391				mtype: ManifestType::Std,392			})?.into()))393		})?,394		// Faster395		"reverse" => parse_args!(context, "std.reverse", args, 1, [396			0, arr: [Val::Arr]!!Val::Arr, vec![ValType::Arr];397		], {398			let mut marr = arr;399			Rc::make_mut(&mut marr).reverse();400			Ok(Val::Arr(marr))401		})?,402		"id" => parse_args!(context, "std.id", args, 1, [403			0, v, vec![];404		], {405			Ok(v)406		})?,407		name => throw!(IntrinsicNotFound(name.into())),408	})409}
modifiedcrates/jrsonnet-parser/src/expr.rsdiffbeforeafterboth
--- a/crates/jrsonnet-parser/src/expr.rs
+++ b/crates/jrsonnet-parser/src/expr.rs
@@ -97,6 +97,8 @@
 	Mul,
 	Div,
 
+	Mod,
+
 	Add,
 	Sub,
 
@@ -124,6 +126,7 @@
 			match self {
 				Mul => "*",
 				Div => "/",
+				Mod => "%",
 				Add => "+",
 				Sub => "-",
 				Lhs => "<<",