1use crate::compiler::prelude::*;
2use std::collections::BTreeMap;
3
4fn filter<T>(value: Value, ctx: &mut Context, runner: &closure::Runner<T>) -> Resolved
5where
6 T: Fn(&mut Context) -> Resolved,
7{
8 match value {
9 Value::Object(object) => object
10 .into_iter()
11 .filter_map(
12 |(key, value)| match runner.run_key_value(ctx, &key, &value) {
13 Ok(v) => v
14 .as_boolean()
15 .expect("compiler guarantees boolean return type")
16 .then_some(Ok((key, value))),
17 Err(err) => Some(Err(err)),
18 },
19 )
20 .collect::<ExpressionResult<BTreeMap<_, _>>>()
21 .map(Into::into),
22
23 Value::Array(array) => array
24 .into_iter()
25 .enumerate()
26 .filter_map(
27 |(index, value)| match runner.run_index_value(ctx, index, &value) {
28 Ok(v) => v
29 .as_boolean()
30 .expect("compiler guarantees boolean return type")
31 .then_some(Ok(value)),
32 Err(err) => Some(Err(err)),
33 },
34 )
35 .collect::<ExpressionResult<Vec<_>>>()
36 .map(Into::into),
37
38 _ => Err("function requires collection types as input".into()),
39 }
40}
41
42#[derive(Clone, Copy, Debug)]
43pub struct Filter;
44
45impl Function for Filter {
46 fn identifier(&self) -> &'static str {
47 "filter"
48 }
49
50 fn usage(&self) -> &'static str {
51 indoc! {"
52 Filter elements from a collection.
53
54 This function currently *does not* support recursive iteration.
55
56 The function uses the function closure syntax to allow reading
57 the key-value or index-value combination for each item in the
58 collection.
59
60 The same scoping rules apply to closure blocks as they do for
61 regular blocks. This means that any variable defined in parent scopes
62 is accessible, and mutations to those variables are preserved,
63 but any new variables instantiated in the closure block are
64 unavailable outside of the block.
65
66 See the examples below to learn about the closure syntax.
67 "}
68 }
69
70 fn parameters(&self) -> &'static [Parameter] {
71 &[Parameter {
72 keyword: "value",
73 kind: kind::OBJECT | kind::ARRAY,
74 required: true,
75 }]
76 }
77
78 fn examples(&self) -> &'static [Example] {
79 &[
80 example! {
81 title: "Filter elements",
82 source: indoc! {r#"
83 . = { "tags": ["foo", "bar", "foo", "baz"] }
84 filter(array(.tags)) -> |_index, value| {
85 value != "foo"
86 }
87 "#},
88 result: Ok(r#"["bar", "baz"]"#),
89 },
90 example! {
91 title: "Filter object",
92 source: r#"filter({ "a": 1, "b": 2 }) -> |key, _value| { key == "a" }"#,
93 result: Ok(r#"{ "a": 1 }"#),
94 },
95 example! {
96 title: "Filter array",
97 source: "filter([1, 2]) -> |_index, value| { value < 2 }",
98 result: Ok("[1]"),
99 },
100 ]
101 }
102
103 fn compile(
104 &self,
105 _state: &state::TypeState,
106 _ctx: &mut FunctionCompileContext,
107 arguments: ArgumentList,
108 ) -> Compiled {
109 let value = arguments.required("value");
110 let closure = arguments.required_closure()?;
111
112 Ok(FilterFn { value, closure }.as_expr())
113 }
114
115 fn closure(&self) -> Option<closure::Definition> {
116 use closure::{Definition, Input, Output, Variable, VariableKind};
117
118 Some(Definition {
119 inputs: vec![Input {
120 parameter_keyword: "value",
121 kind: Kind::object(Collection::any()).or_array(Collection::any()),
122 variables: vec![
123 Variable {
124 kind: VariableKind::TargetInnerKey,
125 },
126 Variable {
127 kind: VariableKind::TargetInnerValue,
128 },
129 ],
130 output: Output::Kind(Kind::boolean()),
131 example: example! {
132 title: "filter array",
133 source: "filter([1, 2]) -> |index, _value| { index == 0 }",
134 result: Ok("[1]"),
135 },
136 }],
137 is_iterator: true,
138 })
139 }
140}
141
142#[derive(Debug, Clone)]
143struct FilterFn {
144 value: Box<dyn Expression>,
145 closure: Closure,
146}
147
148impl FunctionExpression for FilterFn {
149 fn resolve(&self, ctx: &mut Context) -> ExpressionResult<Value> {
150 let value = self.value.resolve(ctx)?;
151 let Closure {
152 variables,
153 block,
154 block_type_def: _,
155 } = &self.closure;
156 let runner = closure::Runner::new(variables, |ctx| block.resolve(ctx));
157
158 filter(value, ctx, &runner)
159 }
160
161 fn type_def(&self, ctx: &state::TypeState) -> TypeDef {
162 let mut type_def = self.value.type_def(ctx);
163
164 if type_def.contains_array() {
167 type_def.kind_mut().add_array(Collection::any());
168 }
169
170 if type_def.contains_object() {
171 type_def.kind_mut().add_object(Collection::any());
172 }
173
174 type_def
175 }
176}