git.delta.rocks / jrsonnet / refs/commits / 398f421f2a20

difftreelog

feat add intrinsics for numeric parsing

Petr Portnov2022-11-23parent: #15d127a.patch.diff
in: master

2 files changed

modifiedcrates/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
modifiedcrates/jrsonnet-stdlib/src/strings.rsdiffbeforeafterboth
1use jrsonnet_evaluator::{1use jrsonnet_evaluator::{
2 error::{ErrorKind::*, Result},2 error::{ErrorKind::*, Result},
3 function::builtin,3 function::builtin,
4 throw,
4 typed::{Either2, VecVal, M1},5 typed::{Either2, VecVal, M1},
5 val::ArrValue,6 val::ArrValue,
6 Either, IStr, Val,7 Either, IStr, Val,
74 Ok(out.into())75 Ok(out.into())
75}76}
77
78#[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}
95
96#[builtin]
97pub fn builtin_parse_octal(raw: IStr) -> Result<f64> {
98 if raw.is_empty() {
99 throw!("Not an octal number: \"\"");
100 }
101
102 parse_nat::<8>(raw.as_str())
103}
104
105#[builtin]
106pub fn builtin_parse_hex(raw: IStr) -> Result<f64> {
107 if raw.is_empty() {
108 throw!("Not hexadecimal: \"\"");
109 }
110
111 parse_nat::<16>(raw.as_str())
112}
113
114fn 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 );
119
120 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;
123
124 #[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 None
130 }
131 }
132
133 let base = BASE as f64;
134
135 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 + 10
139 } else if let Some(digit) = checked_sub_if(BASE > 10, digit, UPPER_A_CODE) {
140 digit + 10
141 } else {
142 digit.checked_sub(ZERO_CODE).unwrap_or(BASE)
143 };
144
145 if digit < BASE {
146 Ok(base * aggregate + digit as f64)
147 } else {
148 throw!("{raw} is not a base {BASE} integer",);
149 }
150 })
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[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}
76165