difftreelog
feat add intrinsics for numeric parsing
in: master
2 files changed
crates/jrsonnet-stdlib/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/lib.rs
+++ b/crates/jrsonnet-stdlib/src/lib.rs
@@ -128,6 +128,9 @@
("asciiUpper", builtin_ascii_upper::INST),
("asciiLower", builtin_ascii_lower::INST),
("findSubstr", builtin_find_substr::INST),
+ ("parseInt", builtin_parse_int::INST),
+ ("parseOctal", builtin_parse_octal::INST),
+ ("parseHex", builtin_parse_hex::INST),
// Misc
("length", builtin_length::INST),
("startsWith", builtin_starts_with::INST),
@@ -312,7 +315,7 @@
out.build()
}
#[cfg(feature = "legacy-this-file")]
- fn initialize(&self, s: State, source: Source) -> jrsonnet_evaluator::Context {
+ fn initialize(&self, s: State, source: Source) -> Context {
let mut builder = ObjValueBuilder::new();
builder.with_super(self.stdlib_obj.clone());
builder
crates/jrsonnet-stdlib/src/strings.rsdiffbeforeafterboth1use jrsonnet_evaluator::{2 error::{ErrorKind::*, Result},3 function::builtin,4 typed::{Either2, VecVal, M1},5 val::ArrValue,6 Either, IStr, Val,7};8use jrsonnet_gcmodule::Cc;910#[builtin]11pub const fn builtin_codepoint(str: char) -> Result<u32> {12 Ok(str as u32)13}1415#[builtin]16pub fn builtin_substr(str: IStr, from: usize, len: usize) -> Result<String> {17 Ok(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) -> Result<String> {27 Ok(str.replace(&from as &str, &to as &str))28}2930#[builtin]31pub fn builtin_splitlimit(str: IStr, c: IStr, maxsplits: Either![usize, M1]) -> Result<VecVal> {32 use Either2::*;33 Ok(VecVal(Cc::new(match maxsplits {34 A(n) => str35 .splitn(n + 1, &c as &str)36 .map(|s| Val::Str(s.into()))37 .collect(),38 B(_) => str.split(&c as &str).map(|s| Val::Str(s.into())).collect(),39 })))40}4142#[builtin]43pub fn builtin_ascii_upper(str: IStr) -> Result<String> {44 Ok(str.to_ascii_uppercase())45}4647#[builtin]48pub fn builtin_ascii_lower(str: IStr) -> Result<String> {49 Ok(str.to_ascii_lowercase())50}5152#[builtin]53pub fn builtin_find_substr(pat: IStr, str: IStr) -> Result<ArrValue> {54 if pat.is_empty() || str.is_empty() || pat.len() > str.len() {55 return Ok(ArrValue::empty());56 }5758 let str = str.as_str();59 let pat = pat.as_bytes();60 let strb = str.as_bytes();6162 let max_pos = str.len() - pat.len();6364 let mut out: Vec<Val> = Vec::new();65 for (ch_idx, (i, _)) in str66 .char_indices()67 .take_while(|(i, _)| i <= &max_pos)68 .enumerate()69 {70 if &strb[i..i + pat.len()] == pat {71 out.push(Val::Num(ch_idx as f64))72 }73 }74 Ok(out.into())75}1use jrsonnet_evaluator::{2 error::{ErrorKind::*, Result},3 function::builtin,4 throw,5 typed::{Either2, VecVal, M1},6 val::ArrValue,7 Either, IStr, Val,8};9use jrsonnet_gcmodule::Cc;1011#[builtin]12pub const fn builtin_codepoint(str: char) -> Result<u32> {13 Ok(str as u32)14}1516#[builtin]17pub fn builtin_substr(str: IStr, from: usize, len: usize) -> Result<String> {18 Ok(str.chars().skip(from).take(len).collect())19}2021#[builtin]22pub fn builtin_char(n: u32) -> Result<char> {23 Ok(std::char::from_u32(n).ok_or_else(|| InvalidUnicodeCodepointGot(n))?)24}2526#[builtin]27pub fn builtin_str_replace(str: String, from: IStr, to: IStr) -> Result<String> {28 Ok(str.replace(&from as &str, &to as &str))29}3031#[builtin]32pub fn builtin_splitlimit(str: IStr, c: IStr, maxsplits: Either![usize, M1]) -> Result<VecVal> {33 use Either2::*;34 Ok(VecVal(Cc::new(match maxsplits {35 A(n) => str36 .splitn(n + 1, &c as &str)37 .map(|s| Val::Str(s.into()))38 .collect(),39 B(_) => str.split(&c as &str).map(|s| Val::Str(s.into())).collect(),40 })))41}4243#[builtin]44pub fn builtin_ascii_upper(str: IStr) -> Result<String> {45 Ok(str.to_ascii_uppercase())46}4748#[builtin]49pub fn builtin_ascii_lower(str: IStr) -> Result<String> {50 Ok(str.to_ascii_lowercase())51}5253#[builtin]54pub fn builtin_find_substr(pat: IStr, str: IStr) -> Result<ArrValue> {55 if pat.is_empty() || str.is_empty() || pat.len() > str.len() {56 return Ok(ArrValue::empty());57 }5859 let str = str.as_str();60 let pat = pat.as_bytes();61 let strb = str.as_bytes();6263 let max_pos = str.len() - pat.len();6465 let mut out: Vec<Val> = Vec::new();66 for (ch_idx, (i, _)) in str67 .char_indices()68 .take_while(|(i, _)| i <= &max_pos)69 .enumerate()70 {71 if &strb[i..i + pat.len()] == pat {72 out.push(Val::Num(ch_idx as f64))73 }74 }75 Ok(out.into())76}7778#[builtin]79pub fn builtin_parse_int(raw: IStr) -> Result<f64> {80 let mut chars = raw.chars();81 if let Some(first_char) = chars.next() {82 if first_char == '-' {83 let remaining = chars.as_str();84 if remaining.is_empty() {85 throw!("Not an integer: \"{}\"", raw);86 }87 parse_nat::<10>(remaining).map(|value| -value)88 } else {89 parse_nat::<10>(raw.as_str())90 }91 } else {92 throw!("Not an integer: \"{}\"", raw);93 }94}9596#[builtin]97pub fn builtin_parse_octal(raw: IStr) -> Result<f64> {98 if raw.is_empty() {99 throw!("Not an octal number: \"\"");100 }101102 parse_nat::<8>(raw.as_str())103}104105#[builtin]106pub fn builtin_parse_hex(raw: IStr) -> Result<f64> {107 if raw.is_empty() {108 throw!("Not hexadecimal: \"\"");109 }110111 parse_nat::<16>(raw.as_str())112}113114fn parse_nat<const BASE: u32>(raw: &str) -> Result<f64> {115 debug_assert!(116 1 <= BASE && BASE <= 16,117 "integer base should be between 1 and 16"118 );119120 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 let base = BASE as f64;134135 raw.chars().try_fold(0f64, |aggregate, digit| {136 let digit = digit as u32;137 let digit = if let Some(digit) = checked_sub_if(BASE > 10, digit, LOWER_A_CODE) {138 digit + 10139 } else if let Some(digit) = checked_sub_if(BASE > 10, digit, UPPER_A_CODE) {140 digit + 10141 } else {142 digit.checked_sub(ZERO_CODE).unwrap_or(BASE)143 };144145 if digit < BASE {146 Ok(base * aggregate + digit as f64)147 } else {148 throw!("{raw} is not a base {BASE} integer",);149 }150 })151}152153#[cfg(test)]154mod tests {155 use super::*;156157 #[test]158 fn parse_nat_base_10() {159 assert_eq!(parse_nat::<10>("0").unwrap(), 0.);160 assert_eq!(parse_nat::<10>("3").unwrap(), 3.);161 assert_eq!(parse_nat::<10>("27").unwrap(), 10. * 2. + 7.);162 assert_eq!(parse_nat::<10>("123").unwrap(), 10. * (10. * 1. + 2.) + 3.);163 }164}