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

difftreelog

source

crates/jrsonnet-stdlib/src/strings.rs5.5 KiBsourcehistory
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_escape_string_bash(str: String) -> String {32	const QUOTE: char = '\'';33	let mut out = str.replace(QUOTE, "'\"'\"'");34	out.insert(0, QUOTE);35	out.push(QUOTE);36	out37}3839#[builtin]40pub fn builtin_escape_string_dollars(str: String) -> String {41	str.replace('$', "$$")42}4344#[builtin]45pub fn builtin_is_empty(str: String) -> bool {46	str.is_empty()47}4849#[builtin]50pub fn builtin_equals_ignore_case(str1: String, str2: String) -> bool {51	str1.to_ascii_lowercase() == str2.to_ascii_lowercase()52}5354#[builtin]55pub fn builtin_splitlimit(str: IStr, c: IStr, maxsplits: Either![usize, M1]) -> ArrValue {56	use Either2::*;57	match maxsplits {58		A(n) => str.splitn(n + 1, &c as &str).map(Val::string).collect(),59		B(_) => str.split(&c as &str).map(Val::string).collect(),60	}61}6263#[builtin]64pub fn builtin_splitlimitr(str: IStr, c: IStr, maxsplits: Either![usize, M1]) -> ArrValue {65	use Either2::*;66	match maxsplits {67		A(n) =>68		// rsplitn does not implement DoubleEndedIterator so collect into69		// a temporary vec70		{71			str.rsplitn(n + 1, &c as &str)72				.map(Val::string)73				.collect::<Vec<_>>()74				.into_iter()75				.rev()76				.collect()77		}78		B(_) => str.split(&c as &str).map(Val::string).collect(),79	}80}8182#[builtin]83pub fn builtin_split(str: IStr, c: IStr) -> ArrValue {84	use Either2::*;85	builtin_splitlimit(str, c, B(M1))86}8788#[builtin]89pub fn builtin_ascii_upper(str: IStr) -> String {90	str.to_ascii_uppercase()91}9293#[builtin]94pub fn builtin_ascii_lower(str: IStr) -> String {95	str.to_ascii_lowercase()96}9798#[builtin]99pub fn builtin_find_substr(pat: IStr, str: IStr) -> ArrValue {100	if pat.is_empty() || str.is_empty() || pat.len() > str.len() {101		return ArrValue::empty();102	}103104	let str = str.as_str();105	let pat = pat.as_bytes();106	let strb = str.as_bytes();107108	let max_pos = str.len() - pat.len();109110	let mut out: Vec<Val> = Vec::new();111	for (ch_idx, (i, _)) in str112		.char_indices()113		.take_while(|(i, _)| i <= &max_pos)114		.enumerate()115	{116		if &strb[i..i + pat.len()] == pat {117			out.push(Val::Num(ch_idx as f64));118		}119	}120	out.into()121}122123#[builtin]124pub fn builtin_parse_int(str: IStr) -> Result<f64> {125	if let Some(raw) = str.strip_prefix('-') {126		if raw.is_empty() {127			bail!("integer only consists of a minus")128		}129130		parse_nat::<10>(raw).map(|value| -value)131	} else {132		if str.is_empty() {133			bail!("empty integer")134		}135136		parse_nat::<10>(str.as_str())137	}138}139140#[builtin]141pub fn builtin_parse_octal(str: IStr) -> Result<f64> {142	if str.is_empty() {143		bail!("empty octal integer");144	}145146	parse_nat::<8>(str.as_str())147}148149#[builtin]150pub fn builtin_parse_hex(str: IStr) -> Result<f64> {151	if str.is_empty() {152		bail!("empty hexadecimal integer");153	}154155	parse_nat::<16>(str.as_str())156}157158fn parse_nat<const BASE: u32>(raw: &str) -> Result<f64> {159	const ZERO_CODE: u32 = '0' as u32;160	const UPPER_A_CODE: u32 = 'A' as u32;161	const LOWER_A_CODE: u32 = 'a' as u32;162163	#[inline]164	fn checked_sub_if(condition: bool, lhs: u32, rhs: u32) -> Option<u32> {165		if condition {166			lhs.checked_sub(rhs)167		} else {168			None169		}170	}171172	debug_assert!(173		1 <= BASE && BASE <= 16,174		"integer base should be between 1 and 16"175	);176177	let base = f64::from(BASE);178179	raw.chars().try_fold(0f64, |aggregate, digit| {180		let digit = digit as u32;181		// if-let-else looks better here than Option combinators182		#[allow(clippy::option_if_let_else)]183		let digit = if let Some(digit) = checked_sub_if(BASE > 10, digit, LOWER_A_CODE) {184			digit + 10185		} else if let Some(digit) = checked_sub_if(BASE > 10, digit, UPPER_A_CODE) {186			digit + 10187		} else {188			digit.checked_sub(ZERO_CODE).unwrap_or(BASE)189		};190191		if digit < BASE {192			Ok(base.mul_add(aggregate, f64::from(digit)))193		} else {194			bail!("{raw:?} is not a base {BASE} integer");195		}196	})197}198199#[cfg(feature = "exp-bigint")]200#[builtin]201pub fn builtin_bigint(v: Either![f64, IStr]) -> Result<Val> {202	use jrsonnet_evaluator::runtime_error;203	use Either2::*;204	Ok(match v {205		A(a) => {206			Val::BigInt(Box::new(a.to_string().parse().map_err(|e| {207				runtime_error!("number is not convertible to bigint: {e}")208			})?))209		}210		B(b) => Val::BigInt(Box::new(211			b.as_str()212				.parse()213				.map_err(|e| runtime_error!("bad bigint: {e}"))?,214		)),215	})216}217218#[cfg(test)]219mod tests {220	use super::*;221222	#[test]223	fn parse_nat_base_8() {224		assert_eq!(parse_nat::<8>("0").unwrap(), 0.);225		assert_eq!(parse_nat::<8>("5").unwrap(), 5.);226		assert_eq!(parse_nat::<8>("32").unwrap(), 0o32 as f64);227		assert_eq!(parse_nat::<8>("761").unwrap(), 0o761 as f64);228	}229230	#[test]231	fn parse_nat_base_10() {232		assert_eq!(parse_nat::<10>("0").unwrap(), 0.);233		assert_eq!(parse_nat::<10>("3").unwrap(), 3.);234		assert_eq!(parse_nat::<10>("27").unwrap(), 27.);235		assert_eq!(parse_nat::<10>("123").unwrap(), 123.);236	}237238	#[test]239	fn parse_nat_base_16() {240		assert_eq!(parse_nat::<16>("0").unwrap(), 0.);241		assert_eq!(parse_nat::<16>("A").unwrap(), 10.);242		assert_eq!(parse_nat::<16>("a9").unwrap(), 0xA9 as f64);243		assert_eq!(parse_nat::<16>("BbC").unwrap(), 0xBBC as f64);244	}245}246247#[builtin]248pub fn builtin_string_chars(str: IStr) -> ArrValue {249	ArrValue::chars(str.chars())250}