difftreelog
chore simplify `builtin_parse_int`
in: master
1 file changed
crates/jrsonnet-stdlib/src/strings.rsdiffbeforeafterboth1use 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}5253pub fn repeat(what: Either![IStr, ArrValue], count: i32) {54 joi55}5657#[builtin]58pub fn builtin_find_substr(pat: IStr, str: IStr) -> Result<ArrValue> {59 if pat.is_empty() || str.is_empty() || pat.len() > str.len() {60 return Ok(ArrValue::empty());61 }6263 let str = str.as_str();64 let pat = pat.as_bytes();65 let strb = str.as_bytes();6667 let max_pos = str.len() - pat.len();6869 let mut out: Vec<Val> = Vec::new();70 for (ch_idx, (i, _)) in str71 .char_indices()72 .take_while(|(i, _)| i <= &max_pos)73 .enumerate()74 {75 if &strb[i..i + pat.len()] == pat {76 out.push(Val::Num(ch_idx as f64))77 }78 }79 Ok(out.into())80}8182#[builtin]83pub fn builtin_parse_int(raw: IStr) -> Result<f64> {84 let mut chars = raw.chars();85 if let Some(first_char) = chars.next() {86 if first_char == '-' {87 let remaining = chars.as_str();88 if remaining.is_empty() {89 throw!("Integer only consists of a minus");90 }91 parse_nat::<10>(remaining).map(|value| -value)92 } else {93 parse_nat::<10>(raw.as_str())94 }95 } else {96 throw!("Empty decimal integer",);97 }98}99100#[builtin]101pub fn builtin_parse_octal(raw: IStr) -> Result<f64> {102 if raw.is_empty() {103 throw!("Empty octal integer");104 }105106 parse_nat::<8>(raw.as_str())107}108109#[builtin]110pub fn builtin_parse_hex(raw: IStr) -> Result<f64> {111 if raw.is_empty() {112 throw!("Empty hexadecimal integer");113 }114115 parse_nat::<16>(raw.as_str())116}117118fn parse_nat<const BASE: u32>(raw: &str) -> Result<f64> {119 debug_assert!(120 1 <= BASE && BASE <= 16,121 "integer base should be between 1 and 16"122 );123124 const ZERO_CODE: u32 = '0' as u32;125 const UPPER_A_CODE: u32 = 'A' as u32;126 const LOWER_A_CODE: u32 = 'a' as u32;127128 #[inline]129 fn checked_sub_if(condition: bool, lhs: u32, rhs: u32) -> Option<u32> {130 if condition {131 lhs.checked_sub(rhs)132 } else {133 None134 }135 }136137 let base = BASE as f64;138139 raw.chars().try_fold(0f64, |aggregate, digit| {140 let digit = digit as u32;141 let digit = if let Some(digit) = checked_sub_if(BASE > 10, digit, LOWER_A_CODE) {142 digit + 10143 } else if let Some(digit) = checked_sub_if(BASE > 10, digit, UPPER_A_CODE) {144 digit + 10145 } else {146 digit.checked_sub(ZERO_CODE).unwrap_or(BASE)147 };148149 if digit < BASE {150 Ok(base * aggregate + digit as f64)151 } else {152 throw!("{raw:?} is not a base {BASE} integer",);153 }154 })155}156157#[cfg(test)]158mod tests {159 use super::*;160161 #[test]162 fn parse_nat_base_10() {163 assert_eq!(parse_nat::<10>("0").unwrap(), 0.);164 assert_eq!(parse_nat::<10>("3").unwrap(), 3.);165 assert_eq!(parse_nat::<10>("27").unwrap(), 10. * 2. + 7.);166 assert_eq!(parse_nat::<10>("123").unwrap(), 10. * (10. * 1. + 2.) + 3.);167 }168}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 if let Some(raw) = raw.strip_prefix('-') {81 if raw.is_empty() {82 throw!("integer only consists of a minus")83 }8485 parse_nat::<10>(raw).map(|value| -value)86 } else {87 if raw.is_empty() {88 throw!("empty integer")89 }9091 parse_nat::<10>(raw.as_str())92 }93}9495#[builtin]96pub fn builtin_parse_octal(raw: IStr) -> Result<f64> {97 if raw.is_empty() {98 throw!("Empty octal integer");99 }100101 parse_nat::<8>(raw.as_str())102}103104#[builtin]105pub fn builtin_parse_hex(raw: IStr) -> Result<f64> {106 if raw.is_empty() {107 throw!("Empty hexadecimal integer");108 }109110 parse_nat::<16>(raw.as_str())111}112113fn parse_nat<const BASE: u32>(raw: &str) -> Result<f64> {114 debug_assert!(115 1 <= BASE && BASE <= 16,116 "integer base should be between 1 and 16"117 );118119 const ZERO_CODE: u32 = '0' as u32;120 const UPPER_A_CODE: u32 = 'A' as u32;121 const LOWER_A_CODE: u32 = 'a' as u32;122123 #[inline]124 fn checked_sub_if(condition: bool, lhs: u32, rhs: u32) -> Option<u32> {125 if condition {126 lhs.checked_sub(rhs)127 } else {128 None129 }130 }131132 let base = BASE as f64;133134 raw.chars().try_fold(0f64, |aggregate, digit| {135 let digit = digit as u32;136 let digit = if let Some(digit) = checked_sub_if(BASE > 10, digit, LOWER_A_CODE) {137 digit + 10138 } else if let Some(digit) = checked_sub_if(BASE > 10, digit, UPPER_A_CODE) {139 digit + 10140 } else {141 digit.checked_sub(ZERO_CODE).unwrap_or(BASE)142 };143144 if digit < BASE {145 Ok(base * aggregate + digit as f64)146 } else {147 throw!("{raw:?} is not a base {BASE} integer",);148 }149 })150}151152#[cfg(test)]153mod tests {154 use super::*;155156 #[test]157 fn parse_nat_base_10() {158 assert_eq!(parse_nat::<10>("0").unwrap(), 0.);159 assert_eq!(parse_nat::<10>("3").unwrap(), 3.);160 assert_eq!(parse_nat::<10>("27").unwrap(), 10. * 2. + 7.);161 assert_eq!(parse_nat::<10>("123").unwrap(), 10. * (10. * 1. + 2.) + 3.);162 }163}