///|
/// Pure JSON operations cannot invoke filters. Arguments have already been
/// expanded by the machine according to the builtin registry.
fn pure_builtin(
name : String,
input : Json,
args : Array[Json],
) -> Iter[Json] raise PrimitiveError {
match name {
"length" => eval_length(input)
"sort" => eval_sort(input)
"reverse" => eval_reverse(input)
"unique" => eval_unique(input)
"transpose" => eval_transpose(input)
"add" => eval_add(input)
"floor" => eval_floor(input)
"sqrt" => eval_sqrt(input)
"min" => eval_min(input)
"max" => eval_max(input)
"round" => eval_round(input)
"ceil" => eval_ceil(input)
"abs" => eval_abs(input)
"to_entries" => eval_to_entries(input)
"from_entries" => eval_from_entries(input)
"ascii_upcase" => eval_ascii_upcase(input)
"ascii_downcase" => eval_ascii_downcase(input)
"explode" => eval_explode(input)
"implode" => eval_implode(input)
"tojson" => eval_to_json_string(input)
"fromjson" => eval_from_json_string(input)
"log" => eval_log(input)
"exp" => eval_exp(input)
"sin" => eval_sin(input)
"cos" => eval_cos(input)
"tan" => eval_tan(input)
"asin" => eval_asin(input)
"acos" => eval_acos(input)
"atan" => eval_atan(input)
"type" => eval_type(input)
"not" => eval_not(input)
"split" => eval_split(string_arg(args[0]), input)
"join" => eval_join(string_arg(args[0]), input)
"startswith" => eval_starts_with(string_arg(args[0]), input)
"endswith" => eval_ends_with(string_arg(args[0]), input)
"ltrimstr" => eval_ltrim_str(string_arg(args[0]), input)
"rtrimstr" => eval_rtrim_str(string_arg(args[0]), input)
"test" => eval_test(string_arg(args[0]), input)
"match" => eval_match(string_arg(args[0]), input)
"capture" => eval_capture(string_arg(args[0]), input)
"scan" => eval_scan(string_arg(args[0]), input)
"splits" => eval_splits(string_arg(args[0]), input)
"sub" => eval_sub(string_arg(args[0]), string_arg(args[1]), input)
"gsub" => eval_gsub(string_arg(args[0]), string_arg(args[1]), input)
"flatten" =>
eval_flatten(
if args.is_empty() {
None
} else {
Some(numeric_arg(args[0]).to_int())
},
input,
)
"tostring" => Iter::singleton(Json::string(json_to_display_string(input)))
"numbers"
| "strings"
| "scalars"
| "booleans"
| "nulls"
| "arrays"
| "objects"
| "iterables" => {
let keep = match name {
"numbers" => input is Number(_, ..)
"strings" => input is String(_)
"booleans" => input is (True | False)
"nulls" => input is Null
"arrays" => input is Array(_)
"objects" => input is Object(_)
"iterables" => input is (Array(_) | Object(_))
_ => !(input is (Array(_) | Object(_)))
}
if keep {
Iter::singleton(input)
} else {
Iter::empty()
}
}
"empty" => Iter::empty()
"values" =>
if input is Null {
Iter::empty()
} else {
Iter::singleton(input)
}
"keys" | "keys_unsorted" =>
match input {
Object(obj) => {
let keys = obj.keys().collect()
if name == "keys" {
keys.sort_by(compare_code_points)
}
Iter::singleton(Json::array(keys.map(Json::string)))
}
_ => eval_keys(input)
}
"contains" => Iter::singleton(Json::boolean(contains_value(input, args[0])))
"inside" => Iter::singleton(Json::boolean(contains_value(args[0], input)))
"has" => Iter::singleton(Json::boolean(has_value(input, args[0])))
"in" => Iter::singleton(Json::boolean(has_value(args[0], input)))
"indices" | "index" | "rindex" => {
let indices = match (input, args[0]) {
(String(s), String(needle)) => indices_of_string(s, needle)
(Array(arr), needle) => {
let pattern = match needle {
Array(a) => a
_ => [needle]
}
let out : Array[Json] = []
for i in 0..<(arr.length() - pattern.length() + 1) {
if !pattern.is_empty() && arr[i:i + pattern.length()] == pattern {
out.push(Json::number(i.to_double()))
}
}
out
}
_ =>
raise TypeMismatch(
"array/string",
@ast_internal.json_type_name(input),
)
}
Iter::singleton(
if name == "indices" {
Json::array(indices)
} else if name == "index" {
indices.get(0).unwrap_or(null)
} else {
indices.last().unwrap_or(null)
},
)
}
"setpath" => {
guard args[0] is Array(path) else {
raise InvalidOperation("Path must be specified as an array")
}
Iter::singleton(set_at_path(input, path, args[1]))
}
"delpaths" => {
guard args[0] is Array(paths) else {
raise InvalidOperation("Paths must be specified as an array")
}
let paths : Array[ArrayView[Json]] = paths.map(p => {
match p {
Array(a) => a
_ => raise InvalidOperation("Path must be specified as an array")
}
})
Iter::singleton(delete_paths(input, paths))
}
"pow" => {
let base = if args.length() == 1 {
numeric_arg(input)
} else {
numeric_arg(args[0])
}
let exp = numeric_arg(args[args.length() - 1])
Iter::singleton(Json::number(@math.pow(base, exp)))
}
_ => raise InvalidOperation("Unknown value operation: " + name)
}
}
///|
fn string_arg(json : Json) -> String raise PrimitiveError {
match json {
String(s) => s
_ => raise TypeMismatch("string", @ast_internal.json_type_name(json))
}
}
///|
fn numeric_arg(json : Json) -> Double raise PrimitiveError {
match json {
Number(n, ..) => n
_ => raise TypeMismatch("number", @ast_internal.json_type_name(json))
}
}
///|
fn has_value(input : Json, key : Json) -> Bool raise PrimitiveError {
match (input, key) {
(Null, _) => false
(Object(obj), String(key)) => obj.contains(key)
(Array(arr), Number(n, ..)) =>
n >= 0 && n < arr.length().to_double() && n == n.floor()
_ => raise InvalidOperation("Cannot check membership with these types")
}
}
///|
fn contains_value(container : Json, item : Json) -> Bool raise PrimitiveError {
if json_type_rank(container) != json_type_rank(item) {
raise InvalidOperation("Cannot check containment of different types")
}
contains_nested(container, item)
}
///|
/// Nested type mismatches are non-matches, so array searches can continue.
fn contains_nested(container : Json, item : Json) -> Bool {
match (container, item) {
(String(a), String(b)) => a.contains(b)
(Array(a), Array(b)) =>
b.iter().all(v => a.iter().any(x => contains_nested(x, v)))
(Object(a), Object(b)) =>
b
.iter()
.all(pair => {
a.get(pair.0).map(v => contains_nested(v, pair.1)).unwrap_or(false)
})
_ => container == item
}
}