expr/functions/
array.rs

1use indexmap::IndexMap;
2use crate::{bail, Parser, Value};
3
4pub fn add_array_functions(p: &mut Parser) {
5    p.add_function("all", |c| {
6        if c.args.len() != 1 {
7            bail!("all() takes exactly one argument and a predicate");
8        }
9        if let (Value::Array(a), Some(predicate)) = (&c.args[0], c.predicate) {
10            for value in a {
11                let mut ctx = c.ctx.clone();
12                ctx.insert("#".to_string(), value.clone());
13                if let Value::Bool(false) = c.parser.run(predicate.clone(), &ctx)? {
14                    return Ok(false.into());
15                }
16            }
17            Ok(true.into())
18        } else {
19            bail!("all() takes an array as the first argument");
20        }
21    });
22
23    p.add_function("any", |c| {
24        if c.args.len() != 1 {
25            bail!("any() takes exactly one argument and a predicate");
26        }
27        if let (Value::Array(a), Some(predicate)) = (&c.args[0], c.predicate) {
28            for value in a {
29                let mut ctx = c.ctx.clone();
30                ctx.insert("#".to_string(), value.clone());
31                if let Value::Bool(true) = c.parser.run(predicate.clone(), &ctx)? {
32                    return Ok(true.into());
33                }
34            }
35            Ok(false.into())
36        } else {
37            bail!("any() takes an array as the first argument");
38        }
39    });
40
41    p.add_function("one", |c| {
42        if c.args.len() != 1 {
43            bail!("one() takes exactly one argument and a predicate");
44        }
45        if let (Value::Array(a), Some(predicate)) = (&c.args[0], c.predicate) {
46            let mut found = false;
47            for value in a {
48                let mut ctx = c.ctx.clone();
49                ctx.insert("#".to_string(), value.clone());
50                if let Value::Bool(true) = c.parser.run(predicate.clone(), &ctx)? {
51                    if found {
52                        return Ok(false.into());
53                    }
54                    found = true;
55                }
56            }
57            Ok(found.into())
58        } else {
59            bail!("one() takes an array as the first argument");
60        }
61    });
62
63    p.add_function("none", |c| {
64        if c.args.len() != 1 {
65            bail!("none() takes exactly one argument and a predicate");
66        }
67        if let (Value::Array(a), Some(predicate)) = (&c.args[0], c.predicate) {
68            for value in a {
69                let mut ctx = c.ctx.clone();
70                ctx.insert("#".to_string(), value.clone());
71                if let Value::Bool(true) = c.parser.run(predicate.clone(), &ctx)? {
72                    return Ok(false.into());
73                }
74            }
75            Ok(true.into())
76        } else {
77            bail!("none() takes an array as the first argument");
78        }
79    });
80
81    p.add_function("map", |c| {
82        let mut result = Vec::new();
83        if c.args.len() != 1 {
84            bail!("map() takes exactly one argument and a predicate");
85        }
86        if let (Value::Array(a), Some(predicate)) = (&c.args[0], c.predicate) {
87            for value in a {
88                let mut ctx = c.ctx.clone();
89                ctx.insert("#".to_string(), value.clone());
90                result.push(c.parser.run(predicate.clone(), &ctx)?);
91            }
92        } else {
93            bail!("map() takes an array as the first argument");
94        }
95        Ok(result.into())
96    });
97
98    p.add_function("filter", |c| {
99        let mut result = Vec::new();
100        if c.args.len() != 1 {
101            bail!("filter() takes exactly one argument and a predicate");
102        }
103        if let (Value::Array(a), Some(predicate)) = (&c.args[0], c.predicate) {
104            for value in a {
105                let mut ctx = c.ctx.clone();
106                ctx.insert("#".to_string(), value.clone());
107                if let Value::Bool(true) = c.parser.run(predicate.clone(), &ctx)? {
108                    result.push(value.clone());
109                }
110            }
111        } else {
112            bail!("filter() takes an array as the first argument");
113        }
114        Ok(result.into())
115    });
116
117    p.add_function("find", |c| {
118        if c.args.len() != 1 {
119            bail!("find() takes exactly one argument and a predicate");
120        }
121        if let (Value::Array(a), Some(predicate)) = (&c.args[0], c.predicate) {
122            for value in a {
123                let mut ctx = c.ctx.clone();
124                ctx.insert("#".to_string(), value.clone());
125                if let Value::Bool(true) = c.parser.run(predicate.clone(), &ctx)? {
126                    return Ok(value.clone());
127                }
128            }
129            Ok(Value::Nil)
130        } else {
131            bail!("find() takes an array as the first argument");
132        }
133    });
134
135    p.add_function("findIndex", |c| {
136        if c.args.len() != 1 {
137            bail!("findIndex() takes exactly one argument and a predicate");
138        }
139        if let (Value::Array(a), Some(predicate)) = (&c.args[0], c.predicate) {
140            for (i, value) in a.iter().enumerate() {
141                let mut ctx = c.ctx.clone();
142                ctx.insert("#".to_string(), value.clone());
143                if let Value::Bool(true) = c.parser.run(predicate.clone(), &ctx)? {
144                    return Ok(i.into());
145                }
146            }
147            Ok(Value::Number(-1))
148        } else {
149            bail!("findIndex() takes an array as the first argument");
150        }
151    });
152
153    p.add_function("findLast", |c| {
154        if c.args.len() != 1 {
155            bail!("findLast() takes exactly one argument and a predicate");
156        }
157        if let (Value::Array(a), Some(predicate)) = (&c.args[0], c.predicate) {
158            for value in a.iter().rev() {
159                let mut ctx = c.ctx.clone();
160                ctx.insert("#".to_string(), value.clone());
161                if let Value::Bool(true) = c.parser.run(predicate.clone(), &ctx)? {
162                    return Ok(value.clone());
163                }
164            }
165            Ok(Value::Nil)
166        } else {
167            bail!("findLast() takes an array as the first argument");
168        }
169    });
170
171    p.add_function("findLastIndex", |c| {
172        if c.args.len() != 1 {
173            bail!("findLastIndex() takes exactly one argument and a predicate");
174        }
175        if let (Value::Array(a), Some(predicate)) = (&c.args[0], c.predicate) {
176            for (i, value) in a.iter().enumerate().rev() {
177                let mut ctx = c.ctx.clone();
178                ctx.insert("#".to_string(), value.clone());
179                if let Value::Bool(true) = c.parser.run(predicate.clone(), &ctx)? {
180                    return Ok(i.into());
181                }
182            }
183            Ok(Value::Number(-1))
184        } else {
185            bail!("findLastIndex() takes an array as the first argument");
186        }
187    });
188    p.add_function("groupBy", |c| {
189        if c.args.len() != 1 {
190            bail!("groupBy() takes exactly two arguments");
191        }
192        if let (Value::Array(a), Some(predicate)) = (&c.args[0], c.predicate) {
193            let mut groups = IndexMap::new();
194            for value in a {
195                let mut ctx = c.ctx.clone();
196                ctx.insert("#".to_string(), value.clone());
197                if let Some(key) = c.parser.run(predicate.clone(), &ctx)?.as_string() {
198                    groups.entry(key.to_string()).or_insert_with(Vec::new).push(value.clone());
199                } else {
200                    bail!("groupBy() predicate must return a string");
201                }
202            }
203            Ok(Value::Map(groups.into_iter().map(|(k, group)| (k, group.into())).collect()))
204        } else {
205            bail!("groupBy() takes an array as the first argument and a predicate as the second argument");
206        }
207    });
208}