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 69 70 {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 182 #[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}