difftreelog
feat add std.equalsIgnoreCase
in: master
Upstream issue: https://github.com/google/go-jsonnet/pull/692
2 files changed
crates/jrsonnet-stdlib/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/lib.rs
+++ b/crates/jrsonnet-stdlib/src/lib.rs
@@ -149,6 +149,7 @@
("char", builtin_char::INST),
("strReplace", builtin_str_replace::INST),
("isEmpty", builtin_is_empty::INST),
+ ("equalsIgnoreCase", builtin_equals_ignore_case::INST),
("splitLimit", builtin_splitlimit::INST),
("asciiUpper", builtin_ascii_upper::INST),
("asciiLower", builtin_ascii_lower::INST),
crates/jrsonnet-stdlib/src/strings.rsdiffbeforeafterboth1use jrsonnet_evaluator::{2 error::{ErrorKind::*, Result},3 function::builtin,4 throw,5 typed::{Either2, M1},6 val::{ArrValue, StrValue},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_splitlimit(str: IStr, c: IStr, maxsplits: Either![usize, M1]) -> ArrValue {37 use Either2::*;38 match maxsplits {39 A(n) => str40 .splitn(n + 1, &c as &str)41 .map(|s| Val::Str(StrValue::Flat(s.into())))42 .collect(),43 B(_) => str44 .split(&c as &str)45 .map(|s| Val::Str(StrValue::Flat(s.into())))46 .collect(),47 }48}4950#[builtin]51pub fn builtin_ascii_upper(str: IStr) -> String {52 str.to_ascii_uppercase()53}5455#[builtin]56pub fn builtin_ascii_lower(str: IStr) -> String {57 str.to_ascii_lowercase()58}5960#[builtin]61pub fn builtin_find_substr(pat: IStr, str: IStr) -> ArrValue {62 if pat.is_empty() || str.is_empty() || pat.len() > str.len() {63 return ArrValue::empty();64 }6566 let str = str.as_str();67 let pat = pat.as_bytes();68 let strb = str.as_bytes();6970 let max_pos = str.len() - pat.len();7172 let mut out: Vec<Val> = Vec::new();73 for (ch_idx, (i, _)) in str74 .char_indices()75 .take_while(|(i, _)| i <= &max_pos)76 .enumerate()77 {78 if &strb[i..i + pat.len()] == pat {79 out.push(Val::Num(ch_idx as f64))80 }81 }82 out.into()83}8485#[builtin]86pub fn builtin_parse_int(str: IStr) -> Result<f64> {87 if let Some(raw) = str.strip_prefix('-') {88 if raw.is_empty() {89 throw!("integer only consists of a minus")90 }9192 parse_nat::<10>(raw).map(|value| -value)93 } else {94 if str.is_empty() {95 throw!("empty integer")96 }9798 parse_nat::<10>(str.as_str())99 }100}101102#[builtin]103pub fn builtin_parse_octal(str: IStr) -> Result<f64> {104 if str.is_empty() {105 throw!("empty octal integer");106 }107108 parse_nat::<8>(str.as_str())109}110111#[builtin]112pub fn builtin_parse_hex(str: IStr) -> Result<f64> {113 if str.is_empty() {114 throw!("empty hexadecimal integer");115 }116117 parse_nat::<16>(str.as_str())118}119120fn parse_nat<const BASE: u32>(raw: &str) -> Result<f64> {121 debug_assert!(122 1 <= BASE && BASE <= 16,123 "integer base should be between 1 and 16"124 );125126 const ZERO_CODE: u32 = '0' as u32;127 const UPPER_A_CODE: u32 = 'A' as u32;128 const LOWER_A_CODE: u32 = 'a' as u32;129130 #[inline]131 fn checked_sub_if(condition: bool, lhs: u32, rhs: u32) -> Option<u32> {132 if condition {133 lhs.checked_sub(rhs)134 } else {135 None136 }137 }138139 let base = BASE as f64;140141 raw.chars().try_fold(0f64, |aggregate, digit| {142 let digit = digit as u32;143 let digit = if let Some(digit) = checked_sub_if(BASE > 10, digit, LOWER_A_CODE) {144 digit + 10145 } else if let Some(digit) = checked_sub_if(BASE > 10, digit, UPPER_A_CODE) {146 digit + 10147 } else {148 digit.checked_sub(ZERO_CODE).unwrap_or(BASE)149 };150151 if digit < BASE {152 Ok(base * aggregate + digit as f64)153 } else {154 throw!("{raw:?} is not a base {BASE} integer",);155 }156 })157}158159#[cfg(feature = "exp-bigint")]160#[builtin]161pub fn builtin_bigint(v: Either![f64, IStr]) -> Result<Val> {162 use Either2::*;163 Ok(match v {164 A(a) => Val::BigInt(Box::new((a as i64).into())),165 B(b) => Val::BigInt(Box::new(166 b.as_str()167 .parse()168 .map_err(|e| RuntimeError(format!("bad bigint: {e}").into()))?,169 )),170 })171}172173#[cfg(test)]174mod tests {175 use super::*;176177 #[test]178 fn parse_nat_base_8() {179 assert_eq!(parse_nat::<8>("0").unwrap(), 0.);180 assert_eq!(parse_nat::<8>("5").unwrap(), 5.);181 assert_eq!(parse_nat::<8>("32").unwrap(), 0o32 as f64);182 assert_eq!(parse_nat::<8>("761").unwrap(), 0o761 as f64);183 }184185 #[test]186 fn parse_nat_base_10() {187 assert_eq!(parse_nat::<10>("0").unwrap(), 0.);188 assert_eq!(parse_nat::<10>("3").unwrap(), 3.);189 assert_eq!(parse_nat::<10>("27").unwrap(), 27.);190 assert_eq!(parse_nat::<10>("123").unwrap(), 123.);191 }192193 #[test]194 fn parse_nat_base_16() {195 assert_eq!(parse_nat::<16>("0").unwrap(), 0.);196 assert_eq!(parse_nat::<16>("A").unwrap(), 10.);197 assert_eq!(parse_nat::<16>("a9").unwrap(), 0xA9 as f64);198 assert_eq!(parse_nat::<16>("BbC").unwrap(), 0xBBC as f64);199 }200}1use jrsonnet_evaluator::{2 error::{ErrorKind::*, Result},3 function::builtin,4 throw,5 typed::{Either2, M1},6 val::{ArrValue, StrValue},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(x: String, y: String) -> bool {37 x.to_ascii_lowercase() == y.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) => str45 .splitn(n + 1, &c as &str)46 .map(|s| Val::Str(StrValue::Flat(s.into())))47 .collect(),48 B(_) => str49 .split(&c as &str)50 .map(|s| Val::Str(StrValue::Flat(s.into())))51 .collect(),52 }53}5455#[builtin]56pub fn builtin_ascii_upper(str: IStr) -> String {57 str.to_ascii_uppercase()58}5960#[builtin]61pub fn builtin_ascii_lower(str: IStr) -> String {62 str.to_ascii_lowercase()63}6465#[builtin]66pub fn builtin_find_substr(pat: IStr, str: IStr) -> ArrValue {67 if pat.is_empty() || str.is_empty() || pat.len() > str.len() {68 return ArrValue::empty();69 }7071 let str = str.as_str();72 let pat = pat.as_bytes();73 let strb = str.as_bytes();7475 let max_pos = str.len() - pat.len();7677 let mut out: Vec<Val> = Vec::new();78 for (ch_idx, (i, _)) in str79 .char_indices()80 .take_while(|(i, _)| i <= &max_pos)81 .enumerate()82 {83 if &strb[i..i + pat.len()] == pat {84 out.push(Val::Num(ch_idx as f64))85 }86 }87 out.into()88}8990#[builtin]91pub fn builtin_parse_int(str: IStr) -> Result<f64> {92 if let Some(raw) = str.strip_prefix('-') {93 if raw.is_empty() {94 throw!("integer only consists of a minus")95 }9697 parse_nat::<10>(raw).map(|value| -value)98 } else {99 if str.is_empty() {100 throw!("empty integer")101 }102103 parse_nat::<10>(str.as_str())104 }105}106107#[builtin]108pub fn builtin_parse_octal(str: IStr) -> Result<f64> {109 if str.is_empty() {110 throw!("empty octal integer");111 }112113 parse_nat::<8>(str.as_str())114}115116#[builtin]117pub fn builtin_parse_hex(str: IStr) -> Result<f64> {118 if str.is_empty() {119 throw!("empty hexadecimal integer");120 }121122 parse_nat::<16>(str.as_str())123}124125fn parse_nat<const BASE: u32>(raw: &str) -> Result<f64> {126 debug_assert!(127 1 <= BASE && BASE <= 16,128 "integer base should be between 1 and 16"129 );130131 const ZERO_CODE: u32 = '0' as u32;132 const UPPER_A_CODE: u32 = 'A' as u32;133 const LOWER_A_CODE: u32 = 'a' as u32;134135 #[inline]136 fn checked_sub_if(condition: bool, lhs: u32, rhs: u32) -> Option<u32> {137 if condition {138 lhs.checked_sub(rhs)139 } else {140 None141 }142 }143144 let base = BASE as f64;145146 raw.chars().try_fold(0f64, |aggregate, digit| {147 let digit = digit as u32;148 let digit = if let Some(digit) = checked_sub_if(BASE > 10, digit, LOWER_A_CODE) {149 digit + 10150 } else if let Some(digit) = checked_sub_if(BASE > 10, digit, UPPER_A_CODE) {151 digit + 10152 } else {153 digit.checked_sub(ZERO_CODE).unwrap_or(BASE)154 };155156 if digit < BASE {157 Ok(base * aggregate + digit as f64)158 } else {159 throw!("{raw:?} is not a base {BASE} integer",);160 }161 })162}163164#[cfg(feature = "exp-bigint")]165#[builtin]166pub fn builtin_bigint(v: Either![f64, IStr]) -> Result<Val> {167 use Either2::*;168 Ok(match v {169 A(a) => Val::BigInt(Box::new((a as i64).into())),170 B(b) => Val::BigInt(Box::new(171 b.as_str()172 .parse()173 .map_err(|e| RuntimeError(format!("bad bigint: {e}").into()))?,174 )),175 })176}177178#[cfg(test)]179mod tests {180 use super::*;181182 #[test]183 fn parse_nat_base_8() {184 assert_eq!(parse_nat::<8>("0").unwrap(), 0.);185 assert_eq!(parse_nat::<8>("5").unwrap(), 5.);186 assert_eq!(parse_nat::<8>("32").unwrap(), 0o32 as f64);187 assert_eq!(parse_nat::<8>("761").unwrap(), 0o761 as f64);188 }189190 #[test]191 fn parse_nat_base_10() {192 assert_eq!(parse_nat::<10>("0").unwrap(), 0.);193 assert_eq!(parse_nat::<10>("3").unwrap(), 3.);194 assert_eq!(parse_nat::<10>("27").unwrap(), 27.);195 assert_eq!(parse_nat::<10>("123").unwrap(), 123.);196 }197198 #[test]199 fn parse_nat_base_16() {200 assert_eq!(parse_nat::<16>("0").unwrap(), 0.);201 assert_eq!(parse_nat::<16>("A").unwrap(), 10.);202 assert_eq!(parse_nat::<16>("a9").unwrap(), 0xA9 as f64);203 assert_eq!(parse_nat::<16>("BbC").unwrap(), 0xBBC as f64);204 }205}