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

difftreelog

perf native implementation of std.splitLimitR (#163)

Tristan Stenner2024-05-17parent: #c5b983e.patch.diff
in: master
* native implementation of std.splitLimitR

* style: fix formatting

---------

5 files changed

modifiedcrates/jrsonnet-stdlib/src/lib.rsdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/lib.rs
+++ b/crates/jrsonnet-stdlib/src/lib.rs
@@ -178,6 +178,7 @@
 		("isEmpty", builtin_is_empty::INST),
 		("equalsIgnoreCase", builtin_equals_ignore_case::INST),
 		("splitLimit", builtin_splitlimit::INST),
+		("splitLimitR", builtin_splitlimitr::INST),
 		("asciiUpper", builtin_ascii_upper::INST),
 		("asciiLower", builtin_ascii_lower::INST),
 		("findSubstr", builtin_find_substr::INST),
modifiedcrates/jrsonnet-stdlib/src/std.jsonnetdiffbeforeafterboth
--- a/crates/jrsonnet-stdlib/src/std.jsonnet
+++ b/crates/jrsonnet-stdlib/src/std.jsonnet
@@ -20,13 +20,6 @@
   stripChars(str, chars)::
     std.lstripChars(std.rstripChars(str, chars), chars),
 
-  splitLimitR(str, c, maxsplits)::
-    if maxsplits == -1 then
-      std.splitLimit(str, c, -1)
-    else
-      local revStr(str) = std.join('', std.reverse(std.stringChars(str)));
-      std.map(function(e) revStr(e), std.reverse(std.splitLimit(revStr(str), revStr(c), maxsplits))),
-
   split(str, c):: std.splitLimit(str, c, -1),
 
   mapWithIndex(func, arr)::
modifiedcrates/jrsonnet-stdlib/src/strings.rsdiffbeforeafterboth
before · crates/jrsonnet-stdlib/src/strings.rs
1use jrsonnet_evaluator::{2	bail,3	error::{ErrorKind::*, Result},4	function::builtin,5	typed::{Either2, M1},6	val::ArrValue,7	Either, IStr, Val,8};910#[builtin]11pub const fn builtin_codepoint(str: char) -> u32 {12	str as u3213}1415#[builtin]16pub fn builtin_substr(str: IStr, from: usize, len: usize) -> String {17	str.chars().skip(from).take(len).collect()18}1920#[builtin]21pub fn builtin_char(n: u32) -> Result<char> {22	Ok(std::char::from_u32(n).ok_or_else(|| InvalidUnicodeCodepointGot(n))?)23}2425#[builtin]26pub fn builtin_str_replace(str: String, from: IStr, to: IStr) -> String {27	str.replace(&from as &str, &to as &str)28}2930#[builtin]31pub fn builtin_is_empty(str: String) -> bool {32	str.is_empty()33}3435#[builtin]36pub fn builtin_equals_ignore_case(str1: String, str2: String) -> bool {37	str1.to_ascii_lowercase() == str2.to_ascii_lowercase()38}3940#[builtin]41pub fn builtin_splitlimit(str: IStr, c: IStr, maxsplits: Either![usize, M1]) -> ArrValue {42	use Either2::*;43	match maxsplits {44		A(n) => str.splitn(n + 1, &c as &str).map(Val::string).collect(),45		B(_) => str.split(&c as &str).map(Val::string).collect(),46	}47}4849#[builtin]50pub fn builtin_ascii_upper(str: IStr) -> String {51	str.to_ascii_uppercase()52}5354#[builtin]55pub fn builtin_ascii_lower(str: IStr) -> String {56	str.to_ascii_lowercase()57}5859#[builtin]60pub fn builtin_find_substr(pat: IStr, str: IStr) -> ArrValue {61	if pat.is_empty() || str.is_empty() || pat.len() > str.len() {62		return ArrValue::empty();63	}6465	let str = str.as_str();66	let pat = pat.as_bytes();67	let strb = str.as_bytes();6869	let max_pos = str.len() - pat.len();7071	let mut out: Vec<Val> = Vec::new();72	for (ch_idx, (i, _)) in str73		.char_indices()74		.take_while(|(i, _)| i <= &max_pos)75		.enumerate()76	{77		if &strb[i..i + pat.len()] == pat {78			out.push(Val::Num(ch_idx as f64));79		}80	}81	out.into()82}8384#[builtin]85pub fn builtin_parse_int(str: IStr) -> Result<f64> {86	if let Some(raw) = str.strip_prefix('-') {87		if raw.is_empty() {88			bail!("integer only consists of a minus")89		}9091		parse_nat::<10>(raw).map(|value| -value)92	} else {93		if str.is_empty() {94			bail!("empty integer")95		}9697		parse_nat::<10>(str.as_str())98	}99}100101#[builtin]102pub fn builtin_parse_octal(str: IStr) -> Result<f64> {103	if str.is_empty() {104		bail!("empty octal integer");105	}106107	parse_nat::<8>(str.as_str())108}109110#[builtin]111pub fn builtin_parse_hex(str: IStr) -> Result<f64> {112	if str.is_empty() {113		bail!("empty hexadecimal integer");114	}115116	parse_nat::<16>(str.as_str())117}118119fn parse_nat<const BASE: u32>(raw: &str) -> Result<f64> {120	const ZERO_CODE: u32 = '0' as u32;121	const UPPER_A_CODE: u32 = 'A' as u32;122	const LOWER_A_CODE: u32 = 'a' as u32;123124	#[inline]125	fn checked_sub_if(condition: bool, lhs: u32, rhs: u32) -> Option<u32> {126		if condition {127			lhs.checked_sub(rhs)128		} else {129			None130		}131	}132133	debug_assert!(134		1 <= BASE && BASE <= 16,135		"integer base should be between 1 and 16"136	);137138	let base = f64::from(BASE);139140	raw.chars().try_fold(0f64, |aggregate, digit| {141		let digit = digit as u32;142		// if-let-else looks better here than Option combinators143		#[allow(clippy::option_if_let_else)]144		let digit = if let Some(digit) = checked_sub_if(BASE > 10, digit, LOWER_A_CODE) {145			digit + 10146		} else if let Some(digit) = checked_sub_if(BASE > 10, digit, UPPER_A_CODE) {147			digit + 10148		} else {149			digit.checked_sub(ZERO_CODE).unwrap_or(BASE)150		};151152		if digit < BASE {153			Ok(base.mul_add(aggregate, f64::from(digit)))154		} else {155			bail!("{raw:?} is not a base {BASE} integer");156		}157	})158}159160#[cfg(feature = "exp-bigint")]161#[builtin]162pub fn builtin_bigint(v: Either![f64, IStr]) -> Result<Val> {163	use jrsonnet_evaluator::runtime_error;164	use Either2::*;165	Ok(match v {166		A(a) => {167			Val::BigInt(Box::new(a.to_string().parse().map_err(|e| {168				runtime_error!("number is not convertible to bigint: {e}")169			})?))170		}171		B(b) => Val::BigInt(Box::new(172			b.as_str()173				.parse()174				.map_err(|e| runtime_error!("bad bigint: {e}"))?,175		)),176	})177}178179#[cfg(test)]180mod tests {181	use super::*;182183	#[test]184	fn parse_nat_base_8() {185		assert_eq!(parse_nat::<8>("0").unwrap(), 0.);186		assert_eq!(parse_nat::<8>("5").unwrap(), 5.);187		assert_eq!(parse_nat::<8>("32").unwrap(), 0o32 as f64);188		assert_eq!(parse_nat::<8>("761").unwrap(), 0o761 as f64);189	}190191	#[test]192	fn parse_nat_base_10() {193		assert_eq!(parse_nat::<10>("0").unwrap(), 0.);194		assert_eq!(parse_nat::<10>("3").unwrap(), 3.);195		assert_eq!(parse_nat::<10>("27").unwrap(), 27.);196		assert_eq!(parse_nat::<10>("123").unwrap(), 123.);197	}198199	#[test]200	fn parse_nat_base_16() {201		assert_eq!(parse_nat::<16>("0").unwrap(), 0.);202		assert_eq!(parse_nat::<16>("A").unwrap(), 10.);203		assert_eq!(parse_nat::<16>("a9").unwrap(), 0xA9 as f64);204		assert_eq!(parse_nat::<16>("BbC").unwrap(), 0xBBC as f64);205	}206}207208#[builtin]209pub fn builtin_string_chars(str: IStr) -> ArrValue {210	ArrValue::chars(str.chars())211}
addedtests/golden/builtin_strings.jsonnetdiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/builtin_strings.jsonnet
@@ -0,0 +1,7 @@
+local str = 'ab::cd::ef';
+{
+	split: std.split(str, '::'),
+	splitlimit: std.splitLimit(str, '::', 1),
+	splitlimitRNoLimit: std.splitLimit(str, '::', -1),
+	splitlimitR: std.splitLimitR(str, '::', 1),
+}
addedtests/golden/builtin_strings.jsonnet.goldendiffbeforeafterboth
--- /dev/null
+++ b/tests/golden/builtin_strings.jsonnet.golden
@@ -0,0 +1,20 @@
+{
+    "split": [
+        "ab",
+        "cd",
+        "ef"
+    ],
+    "splitlimit": [
+        "ab",
+        "cd::ef"
+    ],
+    "splitlimitR": [
+        "ab::cd",
+        "ef"
+    ],
+    "splitlimitRNoLimit": [
+        "ab",
+        "cd",
+        "ef"
+    ]
+}
\ No newline at end of file