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_splitlimitr(str: IStr, c: IStr, maxsplits: Either![usize, M1]) -> ArrValue {51 use Either2::*;52 match maxsplits {53 A(n) =>54 55 56 {57 str.rsplitn(n + 1, &c as &str)58 .map(Val::string)59 .collect::<Vec<_>>()60 .into_iter()61 .rev()62 .collect()63 }64 B(_) => str.split(&c as &str).map(Val::string).collect(),65 }66}6768#[builtin]69pub fn builtin_ascii_upper(str: IStr) -> String {70 str.to_ascii_uppercase()71}7273#[builtin]74pub fn builtin_ascii_lower(str: IStr) -> String {75 str.to_ascii_lowercase()76}7778#[builtin]79pub fn builtin_find_substr(pat: IStr, str: IStr) -> ArrValue {80 if pat.is_empty() || str.is_empty() || pat.len() > str.len() {81 return ArrValue::empty();82 }8384 let str = str.as_str();85 let pat = pat.as_bytes();86 let strb = str.as_bytes();8788 let max_pos = str.len() - pat.len();8990 let mut out: Vec<Val> = Vec::new();91 for (ch_idx, (i, _)) in str92 .char_indices()93 .take_while(|(i, _)| i <= &max_pos)94 .enumerate()95 {96 if &strb[i..i + pat.len()] == pat {97 out.push(Val::Num(ch_idx as f64));98 }99 }100 out.into()101}102103#[builtin]104pub fn builtin_parse_int(str: IStr) -> Result<f64> {105 if let Some(raw) = str.strip_prefix('-') {106 if raw.is_empty() {107 bail!("integer only consists of a minus")108 }109110 parse_nat::<10>(raw).map(|value| -value)111 } else {112 if str.is_empty() {113 bail!("empty integer")114 }115116 parse_nat::<10>(str.as_str())117 }118}119120#[builtin]121pub fn builtin_parse_octal(str: IStr) -> Result<f64> {122 if str.is_empty() {123 bail!("empty octal integer");124 }125126 parse_nat::<8>(str.as_str())127}128129#[builtin]130pub fn builtin_parse_hex(str: IStr) -> Result<f64> {131 if str.is_empty() {132 bail!("empty hexadecimal integer");133 }134135 parse_nat::<16>(str.as_str())136}137138fn parse_nat<const BASE: u32>(raw: &str) -> Result<f64> {139 const ZERO_CODE: u32 = '0' as u32;140 const UPPER_A_CODE: u32 = 'A' as u32;141 const LOWER_A_CODE: u32 = 'a' as u32;142143 #[inline]144 fn checked_sub_if(condition: bool, lhs: u32, rhs: u32) -> Option<u32> {145 if condition {146 lhs.checked_sub(rhs)147 } else {148 None149 }150 }151152 debug_assert!(153 1 <= BASE && BASE <= 16,154 "integer base should be between 1 and 16"155 );156157 let base = f64::from(BASE);158159 raw.chars().try_fold(0f64, |aggregate, digit| {160 let digit = digit as u32;161 162 #[allow(clippy::option_if_let_else)]163 let digit = if let Some(digit) = checked_sub_if(BASE > 10, digit, LOWER_A_CODE) {164 digit + 10165 } else if let Some(digit) = checked_sub_if(BASE > 10, digit, UPPER_A_CODE) {166 digit + 10167 } else {168 digit.checked_sub(ZERO_CODE).unwrap_or(BASE)169 };170171 if digit < BASE {172 Ok(base.mul_add(aggregate, f64::from(digit)))173 } else {174 bail!("{raw:?} is not a base {BASE} integer");175 }176 })177}178179#[cfg(feature = "exp-bigint")]180#[builtin]181pub fn builtin_bigint(v: Either![f64, IStr]) -> Result<Val> {182 use jrsonnet_evaluator::runtime_error;183 use Either2::*;184 Ok(match v {185 A(a) => {186 Val::BigInt(Box::new(a.to_string().parse().map_err(|e| {187 runtime_error!("number is not convertible to bigint: {e}")188 })?))189 }190 B(b) => Val::BigInt(Box::new(191 b.as_str()192 .parse()193 .map_err(|e| runtime_error!("bad bigint: {e}"))?,194 )),195 })196}197198#[cfg(test)]199mod tests {200 use super::*;201202 #[test]203 fn parse_nat_base_8() {204 assert_eq!(parse_nat::<8>("0").unwrap(), 0.);205 assert_eq!(parse_nat::<8>("5").unwrap(), 5.);206 assert_eq!(parse_nat::<8>("32").unwrap(), 0o32 as f64);207 assert_eq!(parse_nat::<8>("761").unwrap(), 0o761 as f64);208 }209210 #[test]211 fn parse_nat_base_10() {212 assert_eq!(parse_nat::<10>("0").unwrap(), 0.);213 assert_eq!(parse_nat::<10>("3").unwrap(), 3.);214 assert_eq!(parse_nat::<10>("27").unwrap(), 27.);215 assert_eq!(parse_nat::<10>("123").unwrap(), 123.);216 }217218 #[test]219 fn parse_nat_base_16() {220 assert_eq!(parse_nat::<16>("0").unwrap(), 0.);221 assert_eq!(parse_nat::<16>("A").unwrap(), 10.);222 assert_eq!(parse_nat::<16>("a9").unwrap(), 0xA9 as f64);223 assert_eq!(parse_nat::<16>("BbC").unwrap(), 0xBBC as f64);224 }225}226227#[builtin]228pub fn builtin_string_chars(str: IStr) -> ArrValue {229 ArrValue::chars(str.chars())230}