1use crate::module_exports::{ModuleContext, ModuleExports, ModuleFunction, ModuleParam};
9use shape_value::ValueWord;
10
11fn empty_set() -> ValueWord {
13 ValueWord::from_hashmap_pairs(Vec::new(), Vec::new())
14}
15
16fn set_insert(set: &ValueWord, item: &ValueWord) -> Result<ValueWord, String> {
19 let data = set
20 .as_hashmap_data()
21 .ok_or_else(|| "set: expected a set (HashMap)".to_string())?;
22
23 if data.find_key(item).is_some() {
25 return Ok(set.clone());
26 }
27
28 let mut keys = data.keys.clone();
29 let mut values = data.values.clone();
30 keys.push(item.clone());
31 values.push(ValueWord::from_bool(true));
32 Ok(ValueWord::from_hashmap_pairs(keys, values))
33}
34
35pub fn create_set_module() -> ModuleExports {
37 let mut module = ModuleExports::new("set");
38 module.description = "Unordered collection of unique elements".to_string();
39
40 module.add_function_with_schema(
42 "new",
43 |_args: &[ValueWord], _ctx: &ModuleContext| Ok(empty_set()),
44 ModuleFunction {
45 description: "Create a new empty set".to_string(),
46 params: vec![],
47 return_type: Some("HashMap".to_string()),
48 },
49 );
50
51 module.add_function_with_schema(
53 "from_array",
54 |args: &[ValueWord], _ctx: &ModuleContext| {
55 let arr = args
56 .first()
57 .and_then(|a| a.as_any_array())
58 .ok_or_else(|| "set.from_array() requires an array argument".to_string())?;
59
60 let items = arr.to_generic();
61 let mut result = empty_set();
62 for item in items.iter() {
63 result = set_insert(&result, item)?;
64 }
65 Ok(result)
66 },
67 ModuleFunction {
68 description: "Create a set from an array (deduplicates)".to_string(),
69 params: vec![ModuleParam {
70 name: "arr".to_string(),
71 type_name: "Array".to_string(),
72 required: true,
73 description: "Array of items to add to the set".to_string(),
74 ..Default::default()
75 }],
76 return_type: Some("HashMap".to_string()),
77 },
78 );
79
80 module.add_function_with_schema(
82 "add",
83 |args: &[ValueWord], _ctx: &ModuleContext| {
84 let s = args
85 .first()
86 .ok_or_else(|| "set.add() requires a set argument".to_string())?;
87 let item = args
88 .get(1)
89 .ok_or_else(|| "set.add() requires an item argument".to_string())?;
90
91 set_insert(s, item)
92 },
93 ModuleFunction {
94 description: "Add an item to the set, returns new set".to_string(),
95 params: vec![
96 ModuleParam {
97 name: "s".to_string(),
98 type_name: "HashMap".to_string(),
99 required: true,
100 description: "The set".to_string(),
101 ..Default::default()
102 },
103 ModuleParam {
104 name: "item".to_string(),
105 type_name: "any".to_string(),
106 required: true,
107 description: "Item to add".to_string(),
108 ..Default::default()
109 },
110 ],
111 return_type: Some("HashMap".to_string()),
112 },
113 );
114
115 module.add_function_with_schema(
117 "remove",
118 |args: &[ValueWord], _ctx: &ModuleContext| {
119 let s = args
120 .first()
121 .ok_or_else(|| "set.remove() requires a set argument".to_string())?;
122 let item = args
123 .get(1)
124 .ok_or_else(|| "set.remove() requires an item argument".to_string())?;
125
126 let data = s
127 .as_hashmap_data()
128 .ok_or_else(|| "set.remove(): expected a set (HashMap)".to_string())?;
129
130 if let Some(idx) = data.find_key(item) {
131 let mut keys = data.keys.clone();
132 let mut values = data.values.clone();
133 keys.remove(idx);
134 values.remove(idx);
135 Ok(ValueWord::from_hashmap_pairs(keys, values))
136 } else {
137 Ok(s.clone())
138 }
139 },
140 ModuleFunction {
141 description: "Remove an item from the set, returns new set".to_string(),
142 params: vec![
143 ModuleParam {
144 name: "s".to_string(),
145 type_name: "HashMap".to_string(),
146 required: true,
147 description: "The set".to_string(),
148 ..Default::default()
149 },
150 ModuleParam {
151 name: "item".to_string(),
152 type_name: "any".to_string(),
153 required: true,
154 description: "Item to remove".to_string(),
155 ..Default::default()
156 },
157 ],
158 return_type: Some("HashMap".to_string()),
159 },
160 );
161
162 module.add_function_with_schema(
164 "contains",
165 |args: &[ValueWord], _ctx: &ModuleContext| {
166 let s = args
167 .first()
168 .ok_or_else(|| "set.contains() requires a set argument".to_string())?;
169 let item = args
170 .get(1)
171 .ok_or_else(|| "set.contains() requires an item argument".to_string())?;
172
173 let data = s
174 .as_hashmap_data()
175 .ok_or_else(|| "set.contains(): expected a set (HashMap)".to_string())?;
176
177 Ok(ValueWord::from_bool(data.find_key(item).is_some()))
178 },
179 ModuleFunction {
180 description: "Check if set contains an item".to_string(),
181 params: vec![
182 ModuleParam {
183 name: "s".to_string(),
184 type_name: "HashMap".to_string(),
185 required: true,
186 description: "The set".to_string(),
187 ..Default::default()
188 },
189 ModuleParam {
190 name: "item".to_string(),
191 type_name: "any".to_string(),
192 required: true,
193 description: "Item to check".to_string(),
194 ..Default::default()
195 },
196 ],
197 return_type: Some("bool".to_string()),
198 },
199 );
200
201 module.add_function_with_schema(
203 "union",
204 |args: &[ValueWord], _ctx: &ModuleContext| {
205 let a = args
206 .first()
207 .ok_or_else(|| "set.union() requires two set arguments".to_string())?;
208 let b = args
209 .get(1)
210 .ok_or_else(|| "set.union() requires two set arguments".to_string())?;
211
212 let a_data = a
213 .as_hashmap_data()
214 .ok_or_else(|| "set.union(): first argument must be a set".to_string())?;
215 let b_data = b
216 .as_hashmap_data()
217 .ok_or_else(|| "set.union(): second argument must be a set".to_string())?;
218
219 let mut result = a.clone();
220 for key in &b_data.keys {
221 if a_data.find_key(key).is_none() {
222 result = set_insert(&result, key)?;
223 }
224 }
225 Ok(result)
226 },
227 ModuleFunction {
228 description: "Union of two sets".to_string(),
229 params: vec![
230 ModuleParam {
231 name: "a".to_string(),
232 type_name: "HashMap".to_string(),
233 required: true,
234 description: "First set".to_string(),
235 ..Default::default()
236 },
237 ModuleParam {
238 name: "b".to_string(),
239 type_name: "HashMap".to_string(),
240 required: true,
241 description: "Second set".to_string(),
242 ..Default::default()
243 },
244 ],
245 return_type: Some("HashMap".to_string()),
246 },
247 );
248
249 module.add_function_with_schema(
251 "intersection",
252 |args: &[ValueWord], _ctx: &ModuleContext| {
253 let a = args
254 .first()
255 .ok_or_else(|| "set.intersection() requires two set arguments".to_string())?;
256 let b = args
257 .get(1)
258 .ok_or_else(|| "set.intersection() requires two set arguments".to_string())?;
259
260 let a_data = a
261 .as_hashmap_data()
262 .ok_or_else(|| "set.intersection(): first argument must be a set".to_string())?;
263 let b_data = b
264 .as_hashmap_data()
265 .ok_or_else(|| "set.intersection(): second argument must be a set".to_string())?;
266
267 let mut result = empty_set();
268 for key in &a_data.keys {
269 if b_data.find_key(key).is_some() {
270 result = set_insert(&result, key)?;
271 }
272 }
273 Ok(result)
274 },
275 ModuleFunction {
276 description: "Intersection of two sets".to_string(),
277 params: vec![
278 ModuleParam {
279 name: "a".to_string(),
280 type_name: "HashMap".to_string(),
281 required: true,
282 description: "First set".to_string(),
283 ..Default::default()
284 },
285 ModuleParam {
286 name: "b".to_string(),
287 type_name: "HashMap".to_string(),
288 required: true,
289 description: "Second set".to_string(),
290 ..Default::default()
291 },
292 ],
293 return_type: Some("HashMap".to_string()),
294 },
295 );
296
297 module.add_function_with_schema(
299 "difference",
300 |args: &[ValueWord], _ctx: &ModuleContext| {
301 let a = args
302 .first()
303 .ok_or_else(|| "set.difference() requires two set arguments".to_string())?;
304 let b = args
305 .get(1)
306 .ok_or_else(|| "set.difference() requires two set arguments".to_string())?;
307
308 let a_data = a
309 .as_hashmap_data()
310 .ok_or_else(|| "set.difference(): first argument must be a set".to_string())?;
311 let b_data = b
312 .as_hashmap_data()
313 .ok_or_else(|| "set.difference(): second argument must be a set".to_string())?;
314
315 let mut result = empty_set();
316 for key in &a_data.keys {
317 if b_data.find_key(key).is_none() {
318 result = set_insert(&result, key)?;
319 }
320 }
321 Ok(result)
322 },
323 ModuleFunction {
324 description: "Difference (a - b)".to_string(),
325 params: vec![
326 ModuleParam {
327 name: "a".to_string(),
328 type_name: "HashMap".to_string(),
329 required: true,
330 description: "First set".to_string(),
331 ..Default::default()
332 },
333 ModuleParam {
334 name: "b".to_string(),
335 type_name: "HashMap".to_string(),
336 required: true,
337 description: "Second set".to_string(),
338 ..Default::default()
339 },
340 ],
341 return_type: Some("HashMap".to_string()),
342 },
343 );
344
345 module.add_function_with_schema(
347 "to_array",
348 |args: &[ValueWord], _ctx: &ModuleContext| {
349 let s = args
350 .first()
351 .ok_or_else(|| "set.to_array() requires a set argument".to_string())?;
352
353 let data = s
354 .as_hashmap_data()
355 .ok_or_else(|| "set.to_array(): expected a set (HashMap)".to_string())?;
356
357 Ok(ValueWord::from_array(std::sync::Arc::new(
358 data.keys.clone(),
359 )))
360 },
361 ModuleFunction {
362 description: "Convert set to array".to_string(),
363 params: vec![ModuleParam {
364 name: "s".to_string(),
365 type_name: "HashMap".to_string(),
366 required: true,
367 description: "The set".to_string(),
368 ..Default::default()
369 }],
370 return_type: Some("Array".to_string()),
371 },
372 );
373
374 module.add_function_with_schema(
376 "size",
377 |args: &[ValueWord], _ctx: &ModuleContext| {
378 let s = args
379 .first()
380 .ok_or_else(|| "set.size() requires a set argument".to_string())?;
381
382 let data = s
383 .as_hashmap_data()
384 .ok_or_else(|| "set.size(): expected a set (HashMap)".to_string())?;
385
386 Ok(ValueWord::from_i64(data.keys.len() as i64))
387 },
388 ModuleFunction {
389 description: "Get the number of elements".to_string(),
390 params: vec![ModuleParam {
391 name: "s".to_string(),
392 type_name: "HashMap".to_string(),
393 required: true,
394 description: "The set".to_string(),
395 ..Default::default()
396 }],
397 return_type: Some("int".to_string()),
398 },
399 );
400
401 module
402}