--- a/crates/jrsonnet-stdlib/src/lib.rs +++ b/crates/jrsonnet-stdlib/src/lib.rs @@ -39,6 +39,10 @@ pub use manifest::*; mod parse; pub use parse::*; +mod strings; +pub use strings::*; +mod misc; +pub use misc::*; pub fn stdlib_uncached(s: State, settings: Rc>) -> ObjValue { let mut builder = ObjValueBuilder::new(); @@ -52,7 +56,6 @@ builder.with_super(eval); for (name, builtin) in [ - ("length", builtin_length::INST), // Types ("type", builtin_type::INST), ("isString", builtin_is_string::INST), @@ -117,7 +120,7 @@ // Parsing ("parseJson", builtin_parse_json::INST), ("parseYaml", builtin_parse_yaml::INST), - // Misc + // Strings ("codepoint", builtin_codepoint::INST), ("substr", builtin_substr::INST), ("char", builtin_char::INST), @@ -126,6 +129,8 @@ ("asciiUpper", builtin_ascii_upper::INST), ("asciiLower", builtin_ascii_lower::INST), ("findSubstr", builtin_find_substr::INST), + // Misc + ("length", builtin_length::INST), ("startsWith", builtin_starts_with::INST), ("endsWith", builtin_ends_with::INST), ] @@ -342,200 +347,7 @@ } fn as_any(&self) -> &dyn std::any::Any { self - } -} - -#[builtin] -fn builtin_length(x: Either![IStr, ArrValue, ObjValue, FuncVal]) -> Result { - use Either4::*; - Ok(match x { - A(x) => x.chars().count(), - B(x) => x.len(), - C(x) => x.len(), - D(f) => f.params_len(), - }) -} - -#[builtin] -const fn builtin_codepoint(str: char) -> Result { - Ok(str as u32) -} - -#[builtin] -fn builtin_substr(str: IStr, from: usize, len: usize) -> Result { - Ok(str.chars().skip(from).take(len).collect()) -} - -#[builtin(fields( - settings: Rc>, -))] -fn builtin_ext_var(this: &builtin_ext_var, s: State, x: IStr) -> Result { - let ctx = s.create_default_context(extvar_source(&x, "")); - Ok(Any(this - .settings - .borrow() - .ext_vars - .get(&x) - .cloned() - .ok_or_else(|| UndefinedExternalVariable(x))? - .evaluate_arg(s.clone(), ctx, true)? - .evaluate(s)?)) -} - -#[builtin(fields( - settings: Rc>, -))] -fn builtin_native(this: &builtin_native, name: IStr) -> Result { - Ok(Any(this - .settings - .borrow() - .ext_natives - .get(&name) - .cloned() - .map_or(Val::Null, |v| { - Val::Func(FuncVal::Builtin(v.clone())) - }))) -} - -#[builtin] -fn builtin_char(n: u32) -> Result { - Ok(std::char::from_u32(n).ok_or_else(|| InvalidUnicodeCodepointGot(n))?) -} - -#[builtin(fields( - settings: Rc>, -))] -fn builtin_trace( - this: &builtin_trace, - s: State, - loc: CallLocation, - str: IStr, - rest: Thunk, -) -> Result { - this.settings - .borrow() - .trace_printer - .print_trace(s.clone(), loc, str); - Ok(Any(rest.evaluate(s)?)) -} - -#[builtin] -fn builtin_str_replace(str: String, from: IStr, to: IStr) -> Result { - Ok(str.replace(&from as &str, &to as &str)) -} - -#[builtin] -fn builtin_splitlimit(str: IStr, c: IStr, maxsplits: Either![usize, M1]) -> Result { - use Either2::*; - Ok(VecVal(Cc::new(match maxsplits { - A(n) => str - .splitn(n + 1, &c as &str) - .map(|s| Val::Str(s.into())) - .collect(), - B(_) => str.split(&c as &str).map(|s| Val::Str(s.into())).collect(), - }))) -} - -#[builtin] -fn builtin_ascii_upper(str: IStr) -> Result { - Ok(str.to_ascii_uppercase()) -} - -#[builtin] -fn builtin_ascii_lower(str: IStr) -> Result { - Ok(str.to_ascii_lowercase()) -} - -#[builtin] -fn builtin_find_substr(pat: IStr, str: IStr) -> Result { - if pat.is_empty() || str.is_empty() || pat.len() > str.len() { - return Ok(ArrValue::empty()); } - - let str = str.as_str(); - let pat = pat.as_bytes(); - let strb = str.as_bytes(); - - let max_pos = str.len() - pat.len(); - - let mut out: Vec = Vec::new(); - for (ch_idx, (i, _)) in str - .char_indices() - .take_while(|(i, _)| i <= &max_pos) - .enumerate() - { - if &strb[i..i + pat.len()] == pat { - out.push(Val::Num(ch_idx as f64)) - } - } - Ok(out.into()) -} - -#[allow(clippy::comparison_chain)] -#[builtin] -fn builtin_starts_with( - s: State, - a: Either![IStr, ArrValue], - b: Either![IStr, ArrValue], -) -> Result { - Ok(match (a, b) { - (Either2::A(a), Either2::A(b)) => a.starts_with(b.as_str()), - (Either2::B(a), Either2::B(b)) => { - if b.len() > a.len() { - return Ok(false); - } else if b.len() == a.len() { - return equals(s, &Val::Arr(a), &Val::Arr(b)); - } else { - for (a, b) in a - .slice(None, Some(b.len()), None) - .iter(s.clone()) - .zip(b.iter(s.clone())) - { - let a = a?; - let b = b?; - if !equals(s.clone(), &a, &b)? { - return Ok(false); - } - } - true - } - } - _ => throw!("both arguments should be of the same type"), - }) -} - -#[allow(clippy::comparison_chain)] -#[builtin] -fn builtin_ends_with( - s: State, - a: Either![IStr, ArrValue], - b: Either![IStr, ArrValue], -) -> Result { - Ok(match (a, b) { - (Either2::A(a), Either2::A(b)) => a.ends_with(b.as_str()), - (Either2::B(a), Either2::B(b)) => { - if b.len() > a.len() { - return Ok(false); - } else if b.len() == a.len() { - return equals(s, &Val::Arr(a), &Val::Arr(b)); - } else { - let a_len = a.len(); - for (a, b) in a - .slice(Some(a_len - b.len()), None, None) - .iter(s.clone()) - .zip(b.iter(s.clone())) - { - let a = a?; - let b = b?; - if !equals(s.clone(), &a, &b)? { - return Ok(false); - } - } - true - } - } - _ => throw!("both arguments should be of the same type"), - }) } pub trait StateExt { --- /dev/null +++ b/crates/jrsonnet-stdlib/src/misc.rs @@ -0,0 +1,138 @@ +use std::{cell::RefCell, rc::Rc}; + +use jrsonnet_evaluator::{ + error::{Error::*, Result}, + function::{builtin, ArgLike, CallLocation, FuncVal}, + throw, + typed::{Any, Either2, Either4}, + val::{equals, ArrValue}, + Either, IStr, ObjValue, State, Thunk, Val, +}; + +use crate::{extvar_source, Settings}; + +#[builtin] +pub fn builtin_length(x: Either![IStr, ArrValue, ObjValue, FuncVal]) -> Result { + use Either4::*; + Ok(match x { + A(x) => x.chars().count(), + B(x) => x.len(), + C(x) => x.len(), + D(f) => f.params_len(), + }) +} + +#[builtin(fields( + settings: Rc>, +))] +pub fn builtin_ext_var(this: &builtin_ext_var, s: State, x: IStr) -> Result { + let ctx = s.create_default_context(extvar_source(&x, "")); + Ok(Any(this + .settings + .borrow() + .ext_vars + .get(&x) + .cloned() + .ok_or_else(|| UndefinedExternalVariable(x))? + .evaluate_arg(s.clone(), ctx, true)? + .evaluate(s)?)) +} + +#[builtin(fields( + settings: Rc>, +))] +pub fn builtin_native(this: &builtin_native, name: IStr) -> Result { + Ok(Any(this + .settings + .borrow() + .ext_natives + .get(&name) + .cloned() + .map_or(Val::Null, |v| { + Val::Func(FuncVal::Builtin(v.clone())) + }))) +} + +#[builtin(fields( + settings: Rc>, +))] +pub fn builtin_trace( + this: &builtin_trace, + s: State, + loc: CallLocation, + str: IStr, + rest: Thunk, +) -> Result { + this.settings + .borrow() + .trace_printer + .print_trace(s.clone(), loc, str); + Ok(Any(rest.evaluate(s)?)) +} + +#[allow(clippy::comparison_chain)] +#[builtin] +pub fn builtin_starts_with( + s: State, + a: Either![IStr, ArrValue], + b: Either![IStr, ArrValue], +) -> Result { + Ok(match (a, b) { + (Either2::A(a), Either2::A(b)) => a.starts_with(b.as_str()), + (Either2::B(a), Either2::B(b)) => { + if b.len() > a.len() { + return Ok(false); + } else if b.len() == a.len() { + return equals(s, &Val::Arr(a), &Val::Arr(b)); + } else { + for (a, b) in a + .slice(None, Some(b.len()), None) + .iter(s.clone()) + .zip(b.iter(s.clone())) + { + let a = a?; + let b = b?; + if !equals(s.clone(), &a, &b)? { + return Ok(false); + } + } + true + } + } + _ => throw!("both arguments should be of the same type"), + }) +} + +#[allow(clippy::comparison_chain)] +#[builtin] +pub fn builtin_ends_with( + s: State, + a: Either![IStr, ArrValue], + b: Either![IStr, ArrValue], +) -> Result { + Ok(match (a, b) { + (Either2::A(a), Either2::A(b)) => a.ends_with(b.as_str()), + (Either2::B(a), Either2::B(b)) => { + if b.len() > a.len() { + return Ok(false); + } else if b.len() == a.len() { + return equals(s, &Val::Arr(a), &Val::Arr(b)); + } else { + let a_len = a.len(); + for (a, b) in a + .slice(Some(a_len - b.len()), None, None) + .iter(s.clone()) + .zip(b.iter(s.clone())) + { + let a = a?; + let b = b?; + if !equals(s.clone(), &a, &b)? { + return Ok(false); + } + } + true + } + } + _ => throw!("both arguments should be of the same type"), + }) +} --- /dev/null +++ b/crates/jrsonnet-stdlib/src/strings.rs @@ -0,0 +1,75 @@ +use jrsonnet_evaluator::{ + error::{Error::*, Result}, + function::builtin, + typed::{Either2, VecVal, M1}, + val::ArrValue, + Either, IStr, Val, +}; +use jrsonnet_gcmodule::Cc; + +#[builtin] +pub const fn builtin_codepoint(str: char) -> Result { + Ok(str as u32) +} + +#[builtin] +pub fn builtin_substr(str: IStr, from: usize, len: usize) -> Result { + Ok(str.chars().skip(from).take(len).collect()) +} + +#[builtin] +pub fn builtin_char(n: u32) -> Result { + Ok(std::char::from_u32(n).ok_or_else(|| InvalidUnicodeCodepointGot(n))?) +} + +#[builtin] +pub fn builtin_str_replace(str: String, from: IStr, to: IStr) -> Result { + Ok(str.replace(&from as &str, &to as &str)) +} + +#[builtin] +pub fn builtin_splitlimit(str: IStr, c: IStr, maxsplits: Either![usize, M1]) -> Result { + use Either2::*; + Ok(VecVal(Cc::new(match maxsplits { + A(n) => str + .splitn(n + 1, &c as &str) + .map(|s| Val::Str(s.into())) + .collect(), + B(_) => str.split(&c as &str).map(|s| Val::Str(s.into())).collect(), + }))) +} + +#[builtin] +pub fn builtin_ascii_upper(str: IStr) -> Result { + Ok(str.to_ascii_uppercase()) +} + +#[builtin] +pub fn builtin_ascii_lower(str: IStr) -> Result { + Ok(str.to_ascii_lowercase()) +} + +#[builtin] +pub fn builtin_find_substr(pat: IStr, str: IStr) -> Result { + if pat.is_empty() || str.is_empty() || pat.len() > str.len() { + return Ok(ArrValue::empty()); + } + + let str = str.as_str(); + let pat = pat.as_bytes(); + let strb = str.as_bytes(); + + let max_pos = str.len() - pat.len(); + + let mut out: Vec = Vec::new(); + for (ch_idx, (i, _)) in str + .char_indices() + .take_while(|(i, _)| i <= &max_pos) + .enumerate() + { + if &strb[i..i + pat.len()] == pat { + out.push(Val::Num(ch_idx as f64)) + } + } + Ok(out.into()) +}