mirror of
https://github.com/microsoft/regorus.git
synced 2026-08-05 02:16:11 +00:00
Policy evaluation at scale needs to be able to set memory limits so that a bad policy does not hog memory or to ensure that policy evaluation itself does not use too much memory which could cause other components to suffer. This PR introduces capability to set and enforce global memory limits. It also lays the groundwork for enabling per evaluation limits in future. Once a global memory limit is set, Regorus maintains per thread counters to track memory activity (allocation, deallocation) of a thread. These counters are periodically flushed to global memory counters. Per thread counters avoid the contention that updating global counters on each alloc/free would cause. Policy evaluation periodically checks these counters and raises errors if allocated memory has exceeded the configured limit. Currently memory limit capability is exposed only to FFI and C#. Also update mimalloc to v2.2.6 Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
678 lines
24 KiB
Rust
678 lines
24 KiB
Rust
// Copyright (c) Microsoft Corporation.
|
|
// Licensed under the MIT License.
|
|
|
|
#![allow(
|
|
clippy::unseparated_literal_suffix,
|
|
clippy::as_conversions,
|
|
clippy::pattern_type_mismatch
|
|
)]
|
|
|
|
use crate::ast::{Expr, Ref};
|
|
use crate::builtins;
|
|
use crate::builtins::utils::{
|
|
enforce_limit, ensure_args_count, ensure_array, ensure_numeric, ensure_object, ensure_string,
|
|
ensure_string_collection,
|
|
};
|
|
use crate::lexer::Span;
|
|
use crate::number::Number;
|
|
use crate::value::Value;
|
|
use crate::*;
|
|
|
|
use anyhow::{bail, Result};
|
|
|
|
pub fn register(m: &mut builtins::BuiltinsMap<&'static str, builtins::BuiltinFcn>) {
|
|
m.insert("concat", (concat, 2));
|
|
m.insert("contains", (contains, 2));
|
|
m.insert("endswith", (endswith, 2));
|
|
m.insert("format_int", (format_int, 2));
|
|
m.insert("indexof", (indexof, 2));
|
|
m.insert("indexof_n", (indexof_n, 2));
|
|
m.insert("lower", (lower, 1));
|
|
m.insert("replace", (replace, 3));
|
|
m.insert("split", (split, 2));
|
|
m.insert("sprintf", (sprintf, 2));
|
|
m.insert("startswith", (startswith, 2));
|
|
m.insert("strings.any_prefix_match", (any_prefix_match, 2));
|
|
m.insert("strings.any_suffix_match", (any_suffix_match, 2));
|
|
m.insert("strings.count", (strings_count, 2));
|
|
m.insert("strings.replace_n", (replace_n, 2));
|
|
m.insert("strings.reverse", (reverse, 1));
|
|
m.insert("substring", (substring, 3));
|
|
m.insert("trim", (trim, 2));
|
|
m.insert("trim_left", (trim_left, 2));
|
|
m.insert("trim_prefix", (trim_prefix, 2));
|
|
m.insert("trim_right", (trim_right, 2));
|
|
m.insert("trim_space", (trim_space, 1));
|
|
m.insert("trim_suffix", (trim_suffix, 2));
|
|
m.insert("upper", (upper, 1));
|
|
}
|
|
|
|
fn concat(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
|
let name = "concat";
|
|
ensure_args_count(span, name, params, args, 2)?;
|
|
let delimiter = ensure_string(name, ¶ms[0], &args[0])?;
|
|
let collection = ensure_string_collection(name, ¶ms[1], &args[1])?;
|
|
Ok(Value::String(collection.join(&delimiter).into()))
|
|
}
|
|
|
|
fn contains(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
|
let name = "contains";
|
|
ensure_args_count(span, name, params, args, 2)?;
|
|
let s1 = ensure_string(name, ¶ms[0], &args[0])?;
|
|
let s2 = ensure_string(name, ¶ms[1], &args[1])?;
|
|
Ok(Value::Bool(s1.contains(s2.as_ref())))
|
|
}
|
|
|
|
fn endswith(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
|
let name = "endswith";
|
|
ensure_args_count(span, name, params, args, 2)?;
|
|
let s1 = ensure_string(name, ¶ms[0], &args[0])?;
|
|
let s2 = ensure_string(name, ¶ms[1], &args[1])?;
|
|
Ok(Value::Bool(s1.ends_with(s2.as_ref())))
|
|
}
|
|
|
|
fn format_int(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) -> Result<Value> {
|
|
let name = "format_int";
|
|
ensure_args_count(span, name, params, args, 2)?;
|
|
let mut n = ensure_numeric(name, ¶ms[0], &args[0])?;
|
|
let mut sign = "";
|
|
if n < Number::from(0u64) {
|
|
n = n.abs();
|
|
sign = "-";
|
|
}
|
|
let n = n.floor();
|
|
|
|
let base = ensure_numeric(name, ¶ms[1], &args[1])?;
|
|
|
|
let num = match base.as_u64() {
|
|
Some(2) => n.format_bin(),
|
|
Some(8) => n.format_octal(),
|
|
Some(10) => n.format_decimal(),
|
|
Some(16) => n.format_hex(),
|
|
_ => {
|
|
if strict {
|
|
let span = params[1].span();
|
|
bail!(span.error(&format!("`{name}` expects base to be one of 2, 8, 10, 16")));
|
|
}
|
|
|
|
return Ok(Value::Undefined);
|
|
}
|
|
};
|
|
|
|
Ok(Value::String((sign.to_owned() + &num).into()))
|
|
}
|
|
|
|
fn indexof(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
|
let name = "indexof";
|
|
ensure_args_count(span, name, params, args, 2)?;
|
|
let s1 = ensure_string(name, ¶ms[0], &args[0])?;
|
|
let s2 = ensure_string(name, ¶ms[1], &args[1])?;
|
|
for (pos, (idx, _)) in s1.char_indices().enumerate() {
|
|
if s1[idx..].starts_with(s2.as_ref()) {
|
|
return Ok(Value::from(Number::from(pos)));
|
|
}
|
|
}
|
|
Ok(Value::from(Number::from(-1i64)))
|
|
}
|
|
|
|
fn indexof_n(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
|
let name = "indexof_n";
|
|
ensure_args_count(span, name, params, args, 2)?;
|
|
let s1 = ensure_string(name, ¶ms[0], &args[0])?;
|
|
let s2 = ensure_string(name, ¶ms[1], &args[1])?;
|
|
|
|
let mut positions = vec![];
|
|
for (pos, (idx, _)) in s1.char_indices().enumerate() {
|
|
if s1[idx..].starts_with(s2.as_ref()) {
|
|
positions.push(Value::from(Number::from(pos)));
|
|
// Guard position vector growth while tracking matches.
|
|
enforce_limit()?;
|
|
}
|
|
}
|
|
Ok(Value::from_array(positions))
|
|
}
|
|
|
|
fn lower(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
|
let name = "lower";
|
|
ensure_args_count(span, name, params, args, 1)?;
|
|
let s = ensure_string(name, ¶ms[0], &args[0])?;
|
|
Ok(Value::String(s.to_lowercase().into()))
|
|
}
|
|
|
|
fn replace(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
|
let name = "replace";
|
|
ensure_args_count(span, name, params, args, 3)?;
|
|
let s = ensure_string(name, ¶ms[0], &args[0])?;
|
|
let old = ensure_string(name, ¶ms[1], &args[1])?;
|
|
let new = ensure_string(name, ¶ms[2], &args[2])?;
|
|
Ok(Value::String(s.replace(old.as_ref(), new.as_ref()).into()))
|
|
}
|
|
|
|
fn split(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
|
let name = "replace";
|
|
ensure_args_count(span, name, params, args, 2)?;
|
|
let s = ensure_string(name, ¶ms[0], &args[0])?;
|
|
let delimiter = ensure_string(name, ¶ms[1], &args[1])?;
|
|
|
|
// Handle https://github.com/microsoft/regorus/issues/291
|
|
let parts: Vec<Value> = if delimiter.as_ref() == "" {
|
|
// If delimiter is "", str::split returns a leading and trailing "" whereas Golang's split doesn't.
|
|
// Therefore avoid str::split and instead return each char as a Value::String.
|
|
s.chars()
|
|
.map(|c| {
|
|
let value = Value::from(c.to_string());
|
|
// Guard part accumulation when splitting into characters.
|
|
enforce_limit()?;
|
|
Ok(value)
|
|
})
|
|
.collect::<Result<Vec<Value>>>()?
|
|
} else {
|
|
s.split(delimiter.as_ref())
|
|
.map(|s| {
|
|
let value = Value::String(s.into());
|
|
// Guard part accumulation when splitting by delimiter.
|
|
enforce_limit()?;
|
|
Ok(value)
|
|
})
|
|
.collect::<Result<Vec<Value>>>()?
|
|
};
|
|
|
|
Ok(Value::from(parts))
|
|
}
|
|
|
|
fn to_string(v: &Value, unescape: bool) -> String {
|
|
match v {
|
|
Value::Null => "null".to_owned(),
|
|
Value::Bool(b) => b.to_string(),
|
|
Value::String(s) if unescape => {
|
|
serde_json::to_string(s.as_ref()).unwrap_or(s.as_ref().to_string())
|
|
}
|
|
Value::String(s) => s.as_ref().to_string(),
|
|
Value::Number(n) => n.format_decimal(),
|
|
Value::Array(a) => {
|
|
"[".to_owned()
|
|
+ &a.iter()
|
|
.map(|e| to_string(e, true))
|
|
.collect::<Vec<String>>()
|
|
.join(", ")
|
|
+ "]"
|
|
}
|
|
Value::Set(s) => {
|
|
"{".to_owned()
|
|
+ &s.iter()
|
|
.map(|e| to_string(e, true))
|
|
.collect::<Vec<String>>()
|
|
.join(", ")
|
|
+ "}"
|
|
}
|
|
Value::Object(o) => {
|
|
"{".to_owned()
|
|
+ &o.iter()
|
|
.map(|(k, v)| to_string(k, true) + ": " + &to_string(v, true))
|
|
.collect::<Vec<String>>()
|
|
.join(", ")
|
|
+ "}"
|
|
}
|
|
Value::Undefined => "#undefined".to_string(),
|
|
}
|
|
}
|
|
|
|
enum Width {
|
|
None,
|
|
LeadingZeros(usize),
|
|
Cell(usize),
|
|
Decimals(usize),
|
|
}
|
|
|
|
fn apply_width(w: Width, s: String) -> String {
|
|
match w {
|
|
Width::LeadingZeros(n) if n > s.len() => "0".repeat(n - s.len()) + &s,
|
|
Width::Cell(n) if n > s.len() => " ".repeat(n - s.len()) + &s,
|
|
_ => s,
|
|
}
|
|
}
|
|
|
|
fn sprintf(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
|
let name = "sprintf";
|
|
ensure_args_count(span, name, params, args, 2)?;
|
|
let fmt = ensure_string(name, ¶ms[0], &args[0])?;
|
|
let args = ensure_array(name, ¶ms[1], args[1].clone())?;
|
|
|
|
let mut s = String::default();
|
|
let mut args_idx = 0usize;
|
|
let mut chars = fmt.chars().peekable();
|
|
let args_span = params[1].span();
|
|
loop {
|
|
let (verb, width) = match chars.next() {
|
|
Some('%') => match chars.next() {
|
|
Some('%') => {
|
|
s.push('%');
|
|
continue;
|
|
}
|
|
Some(c) if c == '.' || c.is_numeric() => {
|
|
let first_char = c;
|
|
let mut w = 0;
|
|
if c != '.' {
|
|
w = c.to_digit(10).expect("could not get digit from char");
|
|
}
|
|
|
|
while chars.peek().map(|c| c.is_numeric()) == Some(true) {
|
|
w = w * 10
|
|
+ chars
|
|
.next()
|
|
.expect("could not get next digit")
|
|
.to_digit(10)
|
|
.expect("could not get digit from char");
|
|
}
|
|
let width = match first_char {
|
|
'0' => Width::LeadingZeros(w as usize),
|
|
'.' => Width::Decimals(w as usize),
|
|
_ => Width::Cell(w as usize),
|
|
};
|
|
match chars.next() {
|
|
Some(c) => (c, width),
|
|
_ => {
|
|
let span = params[0].span();
|
|
bail!(span.error(
|
|
"missing format verb after `%width` at end of format string"
|
|
));
|
|
}
|
|
}
|
|
}
|
|
Some(c) => (c, Width::None),
|
|
None => {
|
|
let span = params[0].span();
|
|
bail!(span.error("missing format verb after `%` at end of format string"));
|
|
}
|
|
},
|
|
Some(c) => {
|
|
s.push(c);
|
|
continue;
|
|
}
|
|
None => break,
|
|
};
|
|
|
|
if args_idx >= args.len() {
|
|
bail!(args_span
|
|
.error(format!("no argument specified for format verb {args_idx}").as_str()));
|
|
}
|
|
let arg = &args[args_idx];
|
|
args_idx += 1;
|
|
|
|
// Handle Golang flags.
|
|
let emit_sign = false;
|
|
let leave_space_for_elided_sign = false;
|
|
// Note: Golang flags come BEFORE the format verb, not after.
|
|
// This code was incorrectly consuming characters after the verb.
|
|
// Removing the incorrect flag handling to fix sprintf spacing.
|
|
|
|
let get_sign_value = |f: &Number| match (emit_sign, f) {
|
|
(_, v) if v < &Number::from(0.0) => ("-", v.clone()),
|
|
(true, v) => ("+", v.clone()),
|
|
(false, v) if leave_space_for_elided_sign => (" ", v.clone()),
|
|
(false, v) => ("", v.clone()),
|
|
};
|
|
|
|
// Handle Golang printing verbs.
|
|
// https://pkg.go.dev/fmt
|
|
match (verb, arg) {
|
|
('s', Value::String(sv)) => s += sv.as_ref(),
|
|
('s', v) => s += &to_string(v, false),
|
|
|
|
('v', _) => s += &to_string(arg, false),
|
|
('b', Value::Number(f)) if f.is_integer() => {
|
|
let (sign, v) = get_sign_value(f);
|
|
s += sign;
|
|
s += v.format_bin().as_str()
|
|
}
|
|
('c', Value::Number(f)) if f.is_integer() => {
|
|
// TODO: range error
|
|
let ch_opt = f.as_u64().map(|ival| char::from_u32(ival as u32));
|
|
match ch_opt {
|
|
Some(Some(c)) => s.push(c),
|
|
_ => {
|
|
bail!(args_span.error(
|
|
format!("invalid value {} for format verb c.", f.format_decimal())
|
|
.as_str()
|
|
))
|
|
}
|
|
}
|
|
}
|
|
('d', Value::Number(f)) if f.is_integer() => {
|
|
let (sign, v) = get_sign_value(f);
|
|
s += sign;
|
|
s += apply_width(width, v.format_decimal()).as_str()
|
|
}
|
|
('o', Value::Number(f)) if f.is_integer() => {
|
|
let (sign, v) = get_sign_value(f);
|
|
s += sign;
|
|
s += apply_width(width, "0O".to_owned() + &v.format_octal()).as_str()
|
|
}
|
|
('O', Value::Number(f)) if f.is_integer() => {
|
|
let (sign, v) = get_sign_value(f);
|
|
s += sign;
|
|
s += apply_width(width, "0o".to_owned() + &v.format_octal()).as_str()
|
|
}
|
|
('x', Value::Number(f)) if f.is_integer() => {
|
|
let (sign, v) = get_sign_value(f);
|
|
s += sign;
|
|
s += apply_width(width, v.format_hex()).as_str()
|
|
}
|
|
('X', Value::Number(f)) if f.is_integer() => {
|
|
let (sign, v) = get_sign_value(f);
|
|
s += sign;
|
|
s += apply_width(width, v.format_big_hex()).as_str()
|
|
}
|
|
('e', Value::Number(f)) => s += &f.format_scientific(),
|
|
('E', Value::Number(f)) => s += &f.format_scientific().replace('e', "E"),
|
|
('f' | 'F', Value::Number(f)) => {
|
|
s += &match width {
|
|
Width::Decimals(d) => f.format_decimal_with_width(d as u32),
|
|
_ => apply_width(width, f.format_decimal()),
|
|
}
|
|
}
|
|
('g', Value::Number(f)) => {
|
|
let (sign, v) = get_sign_value(f);
|
|
let v = match v.as_f64() {
|
|
Some(v) => v,
|
|
_ => bail!(span.error("cannot print large float using g format specified")),
|
|
};
|
|
s += sign;
|
|
let bits = v.to_bits();
|
|
let exponent = (bits >> 52) & 0x7ff;
|
|
// TODO: what is large exponent?
|
|
if exponent > 32 {
|
|
s += format!("{v:e}").as_str()
|
|
} else {
|
|
s += format!("{v}").as_str()
|
|
}
|
|
}
|
|
('G', Value::Number(f)) => {
|
|
let (sign, v) = get_sign_value(f);
|
|
let v = match v.as_f64() {
|
|
Some(v) => v,
|
|
_ => bail!("cannot print large float using g format specified"),
|
|
};
|
|
s += sign;
|
|
let bits = v.to_bits();
|
|
let exponent = (bits >> 52) & 0x7ff;
|
|
// TODO: what is large exponent?
|
|
if exponent > 32 {
|
|
s += format!("{v:E}").as_str()
|
|
} else {
|
|
s += format!("{v}").as_str()
|
|
}
|
|
}
|
|
(_, Value::Number(_)) => {
|
|
bail!(args_span.error(&format!("number specified for format verb {verb}.")));
|
|
}
|
|
|
|
('+', _) if chars.next() == Some('v') => {
|
|
bail!(args_span.error("Go-syntax fields names format verm %#v is not supported."));
|
|
}
|
|
('T', _) | ('#', _) | ('q', _) | ('p', _) => {
|
|
bail!(
|
|
args_span.error("Go-syntax format verbs %#v. %q, %p and %T are not supported.")
|
|
);
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
if args_idx < args.len() {
|
|
bail!(args_span.error(
|
|
format!(
|
|
"extra arguments ({}) specified for {args_idx} format verbs.",
|
|
args.len()
|
|
)
|
|
.as_str()
|
|
));
|
|
}
|
|
|
|
Ok(Value::String(s.into()))
|
|
}
|
|
|
|
fn any_prefix_match(
|
|
span: &Span,
|
|
params: &[Ref<Expr>],
|
|
args: &[Value],
|
|
strict: bool,
|
|
) -> Result<Value> {
|
|
let name = "strings.any_prefix_match";
|
|
ensure_args_count(span, name, params, args, 2)?;
|
|
|
|
let search = match &args[0] {
|
|
Value::String(s) => vec![s.as_ref()],
|
|
Value::Array(_) | Value::Set(_) => {
|
|
match ensure_string_collection(name, ¶ms[0], &args[0]) {
|
|
Ok(c) => c,
|
|
Err(e) if strict => return Err(e),
|
|
_ => return Ok(Value::Undefined),
|
|
}
|
|
}
|
|
_ if strict => {
|
|
let span = params[0].span();
|
|
bail!(span.error(
|
|
format!("`{name}` expects string/array[string]/set[string] argument.").as_str()
|
|
));
|
|
}
|
|
_ => return Ok(Value::Undefined),
|
|
};
|
|
|
|
let base = match &args[1] {
|
|
Value::String(s) => vec![s.as_ref()],
|
|
Value::Array(_) | Value::Set(_) => {
|
|
match ensure_string_collection(name, ¶ms[1], &args[1]) {
|
|
Ok(c) => c,
|
|
Err(e) if strict => return Err(e),
|
|
_ => return Ok(Value::Undefined),
|
|
}
|
|
}
|
|
_ if strict => {
|
|
let span = params[0].span();
|
|
bail!(span.error(
|
|
format!("`{name}` expects string/array[string]/set[string] argument.").as_str()
|
|
));
|
|
}
|
|
_ => return Ok(Value::Undefined),
|
|
};
|
|
|
|
Ok(Value::Bool(
|
|
search.iter().any(|s| base.iter().any(|b| s.starts_with(b))),
|
|
))
|
|
}
|
|
|
|
fn any_suffix_match(
|
|
span: &Span,
|
|
params: &[Ref<Expr>],
|
|
args: &[Value],
|
|
strict: bool,
|
|
) -> Result<Value> {
|
|
let name = "strings.any_suffix_match";
|
|
ensure_args_count(span, name, params, args, 2)?;
|
|
|
|
let search = match &args[0] {
|
|
Value::String(s) => vec![s.as_ref()],
|
|
Value::Array(_) | Value::Set(_) => {
|
|
match ensure_string_collection(name, ¶ms[0], &args[0]) {
|
|
Ok(c) => c,
|
|
Err(e) if strict => return Err(e),
|
|
_ => return Ok(Value::Undefined),
|
|
}
|
|
}
|
|
_ if strict => {
|
|
let span = params[0].span();
|
|
bail!(span.error(
|
|
format!("`{name}` expects string/array[string]/set[string] argument.").as_str()
|
|
));
|
|
}
|
|
_ => return Ok(Value::Undefined),
|
|
};
|
|
|
|
let base = match &args[1] {
|
|
Value::String(s) => vec![s.as_ref()],
|
|
Value::Array(_) | Value::Set(_) => {
|
|
match ensure_string_collection(name, ¶ms[1], &args[1]) {
|
|
Ok(c) => c,
|
|
Err(e) if strict => return Err(e),
|
|
_ => return Ok(Value::Undefined),
|
|
}
|
|
}
|
|
_ if strict => {
|
|
let span = params[0].span();
|
|
bail!(span.error(
|
|
format!("`{name}` expects string/array[string]/set[string] argument.").as_str()
|
|
));
|
|
}
|
|
_ => return Ok(Value::Undefined),
|
|
};
|
|
|
|
Ok(Value::Bool(
|
|
search.iter().any(|s| base.iter().any(|b| s.ends_with(b))),
|
|
))
|
|
}
|
|
|
|
fn strings_count(
|
|
span: &Span,
|
|
params: &[Ref<Expr>],
|
|
args: &[Value],
|
|
_strict: bool,
|
|
) -> Result<Value> {
|
|
let name = "strings.count";
|
|
ensure_args_count(span, name, params, args, 2)?;
|
|
|
|
let search = ensure_string(name, ¶ms[0], &args[0])?;
|
|
let substring = ensure_string(name, ¶ms[0], &args[1])?;
|
|
|
|
Ok(Value::from(
|
|
search
|
|
.as_bytes()
|
|
.windows(substring.len())
|
|
.filter(|&w| w == substring.as_bytes())
|
|
.count(),
|
|
))
|
|
}
|
|
|
|
fn startswith(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
|
let name = "startswith";
|
|
ensure_args_count(span, name, params, args, 2)?;
|
|
let s1 = ensure_string(name, ¶ms[0], &args[0])?;
|
|
let s2 = ensure_string(name, ¶ms[1], &args[1])?;
|
|
Ok(Value::Bool(s1.starts_with(s2.as_ref())))
|
|
}
|
|
|
|
fn replace_n(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
|
let name = "trim";
|
|
ensure_args_count(span, name, params, args, 2)?;
|
|
let obj = ensure_object(name, ¶ms[0], args[0].clone())?;
|
|
let mut s = ensure_string(name, ¶ms[1], &args[1])?;
|
|
|
|
let span = params[0].span();
|
|
for item in obj.as_ref().iter() {
|
|
match item {
|
|
(Value::String(k), Value::String(v)) => {
|
|
s = s.replace(k.as_ref(), v.as_ref()).into();
|
|
}
|
|
_ => {
|
|
bail!(span.error(
|
|
format!("`{name}` expects string keys and values in pattern object.").as_str()
|
|
))
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(Value::String(s.clone()))
|
|
}
|
|
|
|
fn reverse(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
|
let name = "reverse";
|
|
ensure_args_count(span, name, params, args, 1)?;
|
|
let s = ensure_string(name, ¶ms[0], &args[0])?;
|
|
Ok(Value::String(s.chars().rev().collect::<String>().into()))
|
|
}
|
|
|
|
fn substring(span: &Span, params: &[Ref<Expr>], args: &[Value], strict: bool) -> Result<Value> {
|
|
let name = "substring";
|
|
ensure_args_count(span, name, params, args, 3)?;
|
|
let s = ensure_string(name, ¶ms[0], &args[0])?;
|
|
let offset = ensure_numeric(name, ¶ms[1], &args[1])?;
|
|
let length = ensure_numeric(name, ¶ms[2], &args[2])?;
|
|
|
|
match (offset.as_i64(), length.as_i64()) {
|
|
(Some(offset), _) if offset < 0 && strict => {
|
|
bail!(params[1].span().error("negative offset"))
|
|
}
|
|
(Some(offset), _) if offset < 0 => Ok(Value::Undefined),
|
|
(Some(offset), Some(length)) => {
|
|
let start = s.chars().skip(offset as usize);
|
|
let length = if length < 0 { s.len() } else { length as usize };
|
|
Ok(Value::String(start.take(length).collect::<String>().into()))
|
|
}
|
|
_ => Ok(Value::String("".into())),
|
|
}
|
|
}
|
|
|
|
fn trim(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
|
let name = "trim";
|
|
ensure_args_count(span, name, params, args, 2)?;
|
|
let s1 = ensure_string(name, ¶ms[0], &args[0])?;
|
|
let s2 = ensure_string(name, ¶ms[1], &args[1])?;
|
|
Ok(Value::String(s1.trim_matches(|c| s2.contains(c)).into()))
|
|
}
|
|
|
|
fn trim_left(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
|
let name = "trim_left";
|
|
ensure_args_count(span, name, params, args, 2)?;
|
|
let s1 = ensure_string(name, ¶ms[0], &args[0])?;
|
|
let s2 = ensure_string(name, ¶ms[1], &args[1])?;
|
|
Ok(Value::String(
|
|
s1.trim_start_matches(|c| s2.contains(c)).into(),
|
|
))
|
|
}
|
|
|
|
fn trim_prefix(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
|
let name = "trim_prefix";
|
|
ensure_args_count(span, name, params, args, 2)?;
|
|
let s1 = ensure_string(name, ¶ms[0], &args[0])?;
|
|
let s2 = ensure_string(name, ¶ms[1], &args[1])?;
|
|
Ok(Value::String(match s1.strip_prefix(s2.as_ref()) {
|
|
Some(s) => s.into(),
|
|
_ => s1,
|
|
}))
|
|
}
|
|
|
|
fn trim_right(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
|
let name = "trim_right";
|
|
ensure_args_count(span, name, params, args, 2)?;
|
|
let s1 = ensure_string(name, ¶ms[0], &args[0])?;
|
|
let s2 = ensure_string(name, ¶ms[1], &args[1])?;
|
|
Ok(Value::String(
|
|
s1.trim_end_matches(|c| s2.contains(c)).into(),
|
|
))
|
|
}
|
|
|
|
fn trim_space(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
|
let name = "trim_space";
|
|
ensure_args_count(span, name, params, args, 1)?;
|
|
let s = ensure_string(name, ¶ms[0], &args[0])?;
|
|
Ok(Value::String(s.trim().into()))
|
|
}
|
|
|
|
fn trim_suffix(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
|
let name = "trim_suffix";
|
|
ensure_args_count(span, name, params, args, 2)?;
|
|
let s1 = ensure_string(name, ¶ms[0], &args[0])?;
|
|
let s2 = ensure_string(name, ¶ms[1], &args[1])?;
|
|
Ok(Value::String(match s1.strip_suffix(s2.as_ref()) {
|
|
Some(s) => s.into(),
|
|
_ => s1,
|
|
}))
|
|
}
|
|
|
|
fn upper(span: &Span, params: &[Ref<Expr>], args: &[Value], _strict: bool) -> Result<Value> {
|
|
let name = "upper";
|
|
ensure_args_count(span, name, params, args, 1)?;
|
|
let s = ensure_string(name, ¶ms[0], &args[0])?;
|
|
Ok(Value::String(s.to_uppercase().into()))
|
|
}
|