difftreelog
refactor drop runtime_error! usage
in: master
4 files changed
crates/jrsonnet-stdlib/src/arrays.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/arrays.rs
+++ b/crates/jrsonnet-stdlib/src/arrays.rs
@@ -3,7 +3,6 @@
use jrsonnet_evaluator::{
Either, IStr, ObjValue, ObjValueBuilder, Result, ResultExt, Thunk, Val, bail, error,
function::{NativeFn, builtin},
- runtime_error,
typed::{BoundedUsize, Either2, FromUntyped},
val::{ArrValue, IndexableVal, equals},
};
@@ -40,8 +39,7 @@
Ok(match what {
Either2::A(s) => Val::string(s.repeat(count as usize)),
Either2::B(arr) => Val::Arr(
- ArrValue::repeated(arr, count)
- .ok_or_else(|| runtime_error!("repeated length overflow"))?,
+ ArrValue::repeated(arr, count).ok_or_else(|| error!("repeated length overflow"))?,
),
})
}
crates/jrsonnet-stdlib/src/encoding.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/encoding.rs
+++ b/crates/jrsonnet-stdlib/src/encoding.rs
@@ -1,8 +1,7 @@
use base64::{Engine, engine::general_purpose::STANDARD};
use jrsonnet_evaluator::{
- IBytes, IStr, Result, bail,
+ IBytes, IStr, Result, bail, error,
function::builtin,
- runtime_error,
typed::{Either, Either2},
};
@@ -35,7 +34,7 @@
pub fn builtin_base64_decode_bytes(str: IStr) -> Result<IBytes> {
Ok(STANDARD
.decode(str.as_bytes())
- .map_err(|e| runtime_error!("invalid base64: {e}"))?
+ .map_err(|e| error!("invalid base64: {e}"))?
.as_slice()
.into())
}
@@ -44,10 +43,10 @@
pub fn builtin_base64_decode(str: IStr, #[default(false)] lossy: bool) -> Result<String> {
let bytes = STANDARD
.decode(str.as_bytes())
- .map_err(|e| runtime_error!("invalid base64: {e}"))?;
+ .map_err(|e| error!("invalid base64: {e}"))?;
if lossy {
Ok(String::from_utf8_lossy(&bytes).to_string())
} else {
- String::from_utf8(bytes).map_err(|e| runtime_error!("bad utf8: {e}"))
+ String::from_utf8(bytes).map_err(|e| error!("bad utf8: {e}"))
}
}
crates/jrsonnet-stdlib/src/parse.rsdiffbeforeafterboth--- a/crates/jrsonnet-stdlib/src/parse.rs
+++ b/crates/jrsonnet-stdlib/src/parse.rs
@@ -1,10 +1,9 @@
-use jrsonnet_evaluator::{IStr, Result, Val, function::builtin, runtime_error};
+use jrsonnet_evaluator::{IStr, Result, Val, error, function::builtin};
use serde_saphyr::options;
#[builtin]
pub fn builtin_parse_json(str: IStr) -> Result<Val> {
- let value: Val =
- serde_json::from_str(&str).map_err(|e| runtime_error!("failed to parse json: {e}"))?;
+ let value: Val = serde_json::from_str(&str).map_err(|e| error!("failed to parse json: {e}"))?;
Ok(value)
}
@@ -21,7 +20,7 @@
budget: None,
},
)
- .map_err(|e| runtime_error!("failed to parse yaml: {e}"))?;
+ .map_err(|e| error!("failed to parse yaml: {e}"))?;
// saphyr and other yaml implementations disagree on how to handle an empty document in multi-document stream.
// Saphyr only considers document started after anything is emitted after the document delimiter
crates/jrsonnet-stdlib/src/strings.rsdiffbeforeafterboth1use std::collections::BTreeSet;23use jrsonnet_evaluator::{4 Either, IStr, Val, bail,5 error::{ErrorKind::*, Result},6 function::builtin,7 typed::{Either2, FromUntyped, M1},8 val::{ArrValue, IndexableVal},9};1011#[builtin]12pub const fn builtin_codepoint(str: char) -> u32 {13 str as u3214}1516#[builtin]17pub fn builtin_substr(str: IStr, from: usize, len: usize) -> String {18 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 if from.is_empty() {29 bail!("`from` string must not be zero length");30 }31 Ok(str.replace(&from as &str, &to as &str))32}3334#[builtin]35pub fn builtin_escape_string_bash(str_: String) -> String {36 const QUOTE: char = '\'';37 let mut out = str_.replace(QUOTE, "'\"'\"'");38 out.insert(0, QUOTE);39 out.push(QUOTE);40 out41}4243#[builtin]44pub fn builtin_escape_string_dollars(str_: String) -> String {45 str_.replace('$', "$$")46}4748#[builtin]49pub fn builtin_is_empty(str: String) -> bool {50 str.is_empty()51}5253#[builtin]54pub fn builtin_equals_ignore_case(str1: String, str2: String) -> bool {55 str1.eq_ignore_ascii_case(&str2)56}5758#[builtin]59pub fn builtin_splitlimit(str: IStr, c: IStr, maxsplits: Either![usize, M1]) -> ArrValue {60 use Either2::*;61 match maxsplits {62 A(n) => str.splitn(n + 1, &c as &str).map(Val::string).collect(),63 B(_) => str.split(&c as &str).map(Val::string).collect(),64 }65}6667#[builtin]68pub fn builtin_splitlimitr(str: IStr, c: IStr, maxsplits: Either![usize, M1]) -> ArrValue {69 use Either2::*;70 match maxsplits {71 A(n) =>72 // rsplitn does not implement DoubleEndedIterator so collect into73 // a temporary vec74 {75 str.rsplitn(n + 1, &c as &str)76 .map(Val::string)77 .collect::<Vec<_>>()78 .into_iter()79 .rev()80 .collect()81 }82 B(_) => str.split(&c as &str).map(Val::string).collect(),83 }84}8586#[builtin]87pub fn builtin_split(str: IStr, c: IStr) -> ArrValue {88 use Either2::*;89 builtin_splitlimit(str, c, B(M1))90}9192#[builtin]93pub fn builtin_ascii_upper(str: IStr) -> String {94 str.to_ascii_uppercase()95}9697#[builtin]98pub fn builtin_ascii_lower(str: IStr) -> String {99 str.to_ascii_lowercase()100}101102#[builtin]103pub fn builtin_find_substr(pat: IStr, str: IStr) -> ArrValue {104 if pat.is_empty() || str.is_empty() || pat.len() > str.len() {105 return ArrValue::empty();106 }107108 let str = str.as_str();109 let pat = pat.as_bytes();110 let strb = str.as_bytes();111112 let max_pos = str.len() - pat.len();113114 let mut out: Vec<Val> = Vec::new();115 for (ch_idx, (i, _)) in str116 .char_indices()117 .take_while(|(i, _)| i <= &max_pos)118 .enumerate()119 {120 if &strb[i..i + pat.len()] == pat {121 out.push(Val::Num(122 ch_idx.try_into().expect("unrealisticly long string"),123 ));124 }125 }126 out.into()127}128129#[builtin]130pub fn builtin_parse_int(str: IStr) -> Result<f64> {131 if let Some(raw) = str.strip_prefix('-') {132 if raw.is_empty() {133 bail!("integer only consists of a minus")134 }135136 parse_nat::<10>(raw).map(|value| -value)137 } else {138 if str.is_empty() {139 bail!("empty integer")140 }141142 parse_nat::<10>(str.as_str())143 }144}145146#[builtin]147pub fn builtin_parse_octal(str: IStr) -> Result<f64> {148 if str.is_empty() {149 bail!("empty octal integer");150 }151152 parse_nat::<8>(str.as_str())153}154155#[builtin]156pub fn builtin_parse_hex(str: IStr) -> Result<f64> {157 if str.is_empty() {158 bail!("empty hexadecimal integer");159 }160161 parse_nat::<16>(str.as_str())162}163164fn parse_nat<const BASE: u32>(raw: &str) -> Result<f64> {165 const ZERO_CODE: u32 = '0' as u32;166 const UPPER_A_CODE: u32 = 'A' as u32;167 const LOWER_A_CODE: u32 = 'a' as u32;168169 #[inline]170 fn checked_sub_if(condition: bool, lhs: u32, rhs: u32) -> Option<u32> {171 if condition {172 lhs.checked_sub(rhs)173 } else {174 None175 }176 }177178 debug_assert!(179 1 <= BASE && BASE <= 16,180 "integer base should be between 1 and 16"181 );182183 let base = f64::from(BASE);184185 raw.chars().try_fold(0f64, |aggregate, digit| {186 let digit = digit as u32;187 // if-let-else looks better here than Option combinators188 #[allow(clippy::option_if_let_else)]189 let digit = if let Some(digit) = checked_sub_if(BASE > 10, digit, LOWER_A_CODE) {190 digit + 10191 } else if let Some(digit) = checked_sub_if(BASE > 10, digit, UPPER_A_CODE) {192 digit + 10193 } else {194 digit.checked_sub(ZERO_CODE).unwrap_or(BASE)195 };196197 if digit < BASE {198 Ok(base.mul_add(aggregate, f64::from(digit)))199 } else {200 bail!("{raw:?} is not a base {BASE} integer");201 }202 })203}204205#[cfg(feature = "exp-bigint")]206#[builtin]207pub fn builtin_bigint(v: Either![f64, IStr]) -> Result<Val> {208 use Either2::*;209 use jrsonnet_evaluator::runtime_error;210 Ok(match v {211 A(a) => {212 Val::BigInt(Box::new(a.to_string().parse().map_err(|e| {213 runtime_error!("number is not convertible to bigint: {e}")214 })?))215 }216 B(b) => Val::BigInt(Box::new(217 b.as_str()218 .parse()219 .map_err(|e| runtime_error!("bad bigint: {e}"))?,220 )),221 })222}223224#[builtin]225pub fn builtin_string_chars(str: IStr) -> ArrValue {226 str.chars().collect()227}228229#[builtin]230pub fn builtin_lstrip_chars(str: IStr, chars: IndexableVal) -> Result<IStr> {231 if str.is_empty() || chars.is_empty() {232 return Ok(str);233 }234235 let pattern = new_trim_pattern(chars)?;236 Ok(str.as_str().trim_start_matches(pattern).into())237}238239#[builtin]240pub fn builtin_rstrip_chars(str: IStr, chars: IndexableVal) -> Result<IStr> {241 if str.is_empty() || chars.is_empty() {242 return Ok(str);243 }244245 let pattern = new_trim_pattern(chars)?;246 Ok(str.as_str().trim_end_matches(pattern).into())247}248249#[builtin]250pub fn builtin_strip_chars(str: IStr, chars: IndexableVal) -> Result<IStr> {251 if str.is_empty() || chars.is_empty() {252 return Ok(str);253 }254255 let pattern = new_trim_pattern(chars)?;256 Ok(str.as_str().trim_matches(pattern).into())257}258259#[builtin]260pub fn builtin_trim(str: IStr) -> String {261 let filter =262 |v: char| {263 v == ' '264 || v == '\t' || v == '\n'265 || v == '\u{000c}'266 || v == '\r' || v == '\u{0085}'267 || v == '\u{00a0}'268 };269 str.as_str().trim_matches(filter).to_string()270}271272fn new_trim_pattern(chars: IndexableVal) -> Result<impl Fn(char) -> bool> {273 let chars: BTreeSet<char> = match chars {274 IndexableVal::Str(chars) => chars.chars().collect(),275 IndexableVal::Arr(chars) => chars276 .iter()277 .filter_map(|it| it.map(|it| char::from_untyped(it).ok()).transpose())278 .collect::<Result<_, _>>()?,279 };280281 Ok(move |char| chars.contains(&char))282}283284#[cfg(test)]285#[allow(clippy::float_cmp)]286mod tests {287 use super::*;288289 #[test]290 fn parse_nat_base_8() {291 assert_eq!(parse_nat::<8>("0").unwrap(), 0.);292 assert_eq!(parse_nat::<8>("5").unwrap(), 5.);293 assert_eq!(parse_nat::<8>("32").unwrap(), f64::from(0o32));294 assert_eq!(parse_nat::<8>("761").unwrap(), f64::from(0o761));295 }296297 #[test]298 fn parse_nat_base_10() {299 assert_eq!(parse_nat::<10>("0").unwrap(), 0.);300 assert_eq!(parse_nat::<10>("3").unwrap(), 3.);301 assert_eq!(parse_nat::<10>("27").unwrap(), 27.);302 assert_eq!(parse_nat::<10>("123").unwrap(), 123.);303 }304305 #[test]306 fn parse_nat_base_16() {307 assert_eq!(parse_nat::<16>("0").unwrap(), 0.);308 assert_eq!(parse_nat::<16>("A").unwrap(), 10.);309 assert_eq!(parse_nat::<16>("a9").unwrap(), f64::from(0xA9));310 assert_eq!(parse_nat::<16>("BbC").unwrap(), f64::from(0xBBC));311 }312}1use std::collections::BTreeSet;23use jrsonnet_evaluator::{4 Either, IStr, Val, bail,5 error::{ErrorKind::*, Result},6 function::builtin,7 typed::{Either2, FromUntyped, M1},8 val::{ArrValue, IndexableVal},9};1011#[builtin]12pub const fn builtin_codepoint(str: char) -> u32 {13 str as u3214}1516#[builtin]17pub fn builtin_substr(str: IStr, from: usize, len: usize) -> String {18 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 if from.is_empty() {29 bail!("`from` string must not be zero length");30 }31 Ok(str.replace(&from as &str, &to as &str))32}3334#[builtin]35pub fn builtin_escape_string_bash(str_: String) -> String {36 const QUOTE: char = '\'';37 let mut out = str_.replace(QUOTE, "'\"'\"'");38 out.insert(0, QUOTE);39 out.push(QUOTE);40 out41}4243#[builtin]44pub fn builtin_escape_string_dollars(str_: String) -> String {45 str_.replace('$', "$$")46}4748#[builtin]49pub fn builtin_is_empty(str: String) -> bool {50 str.is_empty()51}5253#[builtin]54pub fn builtin_equals_ignore_case(str1: String, str2: String) -> bool {55 str1.eq_ignore_ascii_case(&str2)56}5758#[builtin]59pub fn builtin_splitlimit(str: IStr, c: IStr, maxsplits: Either![usize, M1]) -> ArrValue {60 use Either2::*;61 match maxsplits {62 A(n) => str.splitn(n + 1, &c as &str).map(Val::string).collect(),63 B(_) => str.split(&c as &str).map(Val::string).collect(),64 }65}6667#[builtin]68pub fn builtin_splitlimitr(str: IStr, c: IStr, maxsplits: Either![usize, M1]) -> ArrValue {69 use Either2::*;70 match maxsplits {71 A(n) =>72 // rsplitn does not implement DoubleEndedIterator so collect into73 // a temporary vec74 {75 str.rsplitn(n + 1, &c as &str)76 .map(Val::string)77 .collect::<Vec<_>>()78 .into_iter()79 .rev()80 .collect()81 }82 B(_) => str.split(&c as &str).map(Val::string).collect(),83 }84}8586#[builtin]87pub fn builtin_split(str: IStr, c: IStr) -> ArrValue {88 use Either2::*;89 builtin_splitlimit(str, c, B(M1))90}9192#[builtin]93pub fn builtin_ascii_upper(str: IStr) -> String {94 str.to_ascii_uppercase()95}9697#[builtin]98pub fn builtin_ascii_lower(str: IStr) -> String {99 str.to_ascii_lowercase()100}101102#[builtin]103pub fn builtin_find_substr(pat: IStr, str: IStr) -> ArrValue {104 if pat.is_empty() || str.is_empty() || pat.len() > str.len() {105 return ArrValue::empty();106 }107108 let str = str.as_str();109 let pat = pat.as_bytes();110 let strb = str.as_bytes();111112 let max_pos = str.len() - pat.len();113114 let mut out: Vec<Val> = Vec::new();115 for (ch_idx, (i, _)) in str116 .char_indices()117 .take_while(|(i, _)| i <= &max_pos)118 .enumerate()119 {120 if &strb[i..i + pat.len()] == pat {121 out.push(Val::Num(122 ch_idx.try_into().expect("unrealisticly long string"),123 ));124 }125 }126 out.into()127}128129#[builtin]130pub fn builtin_parse_int(str: IStr) -> Result<f64> {131 if let Some(raw) = str.strip_prefix('-') {132 if raw.is_empty() {133 bail!("integer only consists of a minus")134 }135136 parse_nat::<10>(raw).map(|value| -value)137 } else {138 if str.is_empty() {139 bail!("empty integer")140 }141142 parse_nat::<10>(str.as_str())143 }144}145146#[builtin]147pub fn builtin_parse_octal(str: IStr) -> Result<f64> {148 if str.is_empty() {149 bail!("empty octal integer");150 }151152 parse_nat::<8>(str.as_str())153}154155#[builtin]156pub fn builtin_parse_hex(str: IStr) -> Result<f64> {157 if str.is_empty() {158 bail!("empty hexadecimal integer");159 }160161 parse_nat::<16>(str.as_str())162}163164fn parse_nat<const BASE: u32>(raw: &str) -> Result<f64> {165 const ZERO_CODE: u32 = '0' as u32;166 const UPPER_A_CODE: u32 = 'A' as u32;167 const LOWER_A_CODE: u32 = 'a' as u32;168169 #[inline]170 fn checked_sub_if(condition: bool, lhs: u32, rhs: u32) -> Option<u32> {171 if condition {172 lhs.checked_sub(rhs)173 } else {174 None175 }176 }177178 debug_assert!(179 1 <= BASE && BASE <= 16,180 "integer base should be between 1 and 16"181 );182183 let base = f64::from(BASE);184185 raw.chars().try_fold(0f64, |aggregate, digit| {186 let digit = digit as u32;187 // if-let-else looks better here than Option combinators188 #[allow(clippy::option_if_let_else)]189 let digit = if let Some(digit) = checked_sub_if(BASE > 10, digit, LOWER_A_CODE) {190 digit + 10191 } else if let Some(digit) = checked_sub_if(BASE > 10, digit, UPPER_A_CODE) {192 digit + 10193 } else {194 digit.checked_sub(ZERO_CODE).unwrap_or(BASE)195 };196197 if digit < BASE {198 Ok(base.mul_add(aggregate, f64::from(digit)))199 } else {200 bail!("{raw:?} is not a base {BASE} integer");201 }202 })203}204205#[cfg(feature = "exp-bigint")]206#[builtin]207pub fn builtin_bigint(v: Either![f64, IStr]) -> Result<Val> {208 use Either2::*;209 use jrsonnet_evaluator::error;210 Ok(match v {211 A(a) => Val::BigInt(Box::new(212 a.to_string()213 .parse()214 .map_err(|e| error!("number is not convertible to bigint: {e}"))?,215 )),216 B(b) => Val::BigInt(Box::new(217 b.as_str().parse().map_err(|e| error!("bad bigint: {e}"))?,218 )),219 })220}221222#[builtin]223pub fn builtin_string_chars(str: IStr) -> ArrValue {224 str.chars().collect()225}226227#[builtin]228pub fn builtin_lstrip_chars(str: IStr, chars: IndexableVal) -> Result<IStr> {229 if str.is_empty() || chars.is_empty() {230 return Ok(str);231 }232233 let pattern = new_trim_pattern(chars)?;234 Ok(str.as_str().trim_start_matches(pattern).into())235}236237#[builtin]238pub fn builtin_rstrip_chars(str: IStr, chars: IndexableVal) -> Result<IStr> {239 if str.is_empty() || chars.is_empty() {240 return Ok(str);241 }242243 let pattern = new_trim_pattern(chars)?;244 Ok(str.as_str().trim_end_matches(pattern).into())245}246247#[builtin]248pub fn builtin_strip_chars(str: IStr, chars: IndexableVal) -> Result<IStr> {249 if str.is_empty() || chars.is_empty() {250 return Ok(str);251 }252253 let pattern = new_trim_pattern(chars)?;254 Ok(str.as_str().trim_matches(pattern).into())255}256257#[builtin]258pub fn builtin_trim(str: IStr) -> String {259 let filter =260 |v: char| {261 v == ' '262 || v == '\t' || v == '\n'263 || v == '\u{000c}'264 || v == '\r' || v == '\u{0085}'265 || v == '\u{00a0}'266 };267 str.as_str().trim_matches(filter).to_string()268}269270fn new_trim_pattern(chars: IndexableVal) -> Result<impl Fn(char) -> bool> {271 let chars: BTreeSet<char> = match chars {272 IndexableVal::Str(chars) => chars.chars().collect(),273 IndexableVal::Arr(chars) => chars274 .iter()275 .filter_map(|it| it.map(|it| char::from_untyped(it).ok()).transpose())276 .collect::<Result<_, _>>()?,277 };278279 Ok(move |char| chars.contains(&char))280}281282#[cfg(test)]283#[allow(clippy::float_cmp)]284mod tests {285 use super::*;286287 #[test]288 fn parse_nat_base_8() {289 assert_eq!(parse_nat::<8>("0").unwrap(), 0.);290 assert_eq!(parse_nat::<8>("5").unwrap(), 5.);291 assert_eq!(parse_nat::<8>("32").unwrap(), f64::from(0o32));292 assert_eq!(parse_nat::<8>("761").unwrap(), f64::from(0o761));293 }294295 #[test]296 fn parse_nat_base_10() {297 assert_eq!(parse_nat::<10>("0").unwrap(), 0.);298 assert_eq!(parse_nat::<10>("3").unwrap(), 3.);299 assert_eq!(parse_nat::<10>("27").unwrap(), 27.);300 assert_eq!(parse_nat::<10>("123").unwrap(), 123.);301 }302303 #[test]304 fn parse_nat_base_16() {305 assert_eq!(parse_nat::<16>("0").unwrap(), 0.);306 assert_eq!(parse_nat::<16>("A").unwrap(), 10.);307 assert_eq!(parse_nat::<16>("a9").unwrap(), f64::from(0xA9));308 assert_eq!(parse_nat::<16>("BbC").unwrap(), f64::from(0xBBC));309 }310}