1use crate::edit::{ExprSpec, FunctionSpec, StepSpec};
19use crate::node::{BinOp, Param, Produces};
20use crate::ty::{Confidence, Type};
21use std::collections::BTreeSet;
22
23fn r(n: &str) -> ExprSpec {
24 ExprSpec::Ref(n.into())
25}
26fn num(n: i64) -> ExprSpec {
27 ExprSpec::Lit(n)
28}
29fn add1(e: ExprSpec) -> ExprSpec {
30 ExprSpec::BinOp {
31 op: BinOp::Add,
32 lhs: Box::new(e),
33 rhs: Box::new(num(1)),
34 }
35}
36fn eq(a: ExprSpec, b: ExprSpec) -> ExprSpec {
37 ExprSpec::BinOp {
38 op: BinOp::Eq,
39 lhs: Box::new(a),
40 rhs: Box::new(b),
41 }
42}
43fn call(f: &str, args: Vec<ExprSpec>) -> ExprSpec {
44 ExprSpec::Call {
45 func: f.into(),
46 args,
47 }
48}
49fn callv(callee: ExprSpec, args: Vec<ExprSpec>) -> ExprSpec {
50 ExprSpec::CallValue {
51 callee: Box::new(callee),
52 args,
53 }
54}
55fn iff(c: ExprSpec, t: ExprSpec, e: ExprSpec) -> ExprSpec {
56 ExprSpec::If {
57 cond: Box::new(c),
58 then_branch: Box::new(t),
59 else_branch: Box::new(e),
60 }
61}
62fn lget(list: ExprSpec, index: ExprSpec) -> ExprSpec {
63 ExprSpec::ListGet {
64 list: Box::new(list),
65 index: Box::new(index),
66 }
67}
68fn llen(e: ExprSpec) -> ExprSpec {
69 ExprSpec::ListLen(Box::new(e))
70}
71fn cons(head: ExprSpec, tail: ExprSpec) -> ExprSpec {
72 ExprSpec::ListCons {
73 head: Box::new(head),
74 tail: Box::new(tail),
75 }
76}
77fn empty(elem: Type) -> ExprSpec {
78 ExprSpec::ListEmpty { elem }
79}
80fn boolean(b: bool) -> ExprSpec {
81 ExprSpec::Bool(b)
82}
83fn some(v: ExprSpec) -> ExprSpec {
84 ExprSpec::OptionSome(Box::new(v))
85}
86fn none(elem: Type) -> ExprSpec {
87 ExprSpec::OptionNone { elem }
88}
89fn p(name: &str, ty: Type) -> Param {
90 Param {
91 name: name.into(),
92 ty,
93 min_confidence: Confidence::External,
94 }
95}
96fn ext(ty: Type) -> Produces {
97 Produces {
98 ty,
99 confidence: Confidence::External,
100 }
101}
102fn step(b: &str, v: ExprSpec) -> StepSpec {
103 StepSpec {
104 binding: b.into(),
105 value: v,
106 }
107}
108fn var(n: &str) -> Type {
109 Type::Var(n.into())
110}
111fn list(t: Type) -> Type {
112 Type::List(Box::new(t))
113}
114fn fnt(params: Vec<Type>, ret: Type) -> Type {
116 Type::Fn {
117 params,
118 ret: Box::new(ret),
119 effects: BTreeSet::new(),
120 }
121}
122fn tp(names: &[&str]) -> Vec<String> {
123 names.iter().map(|s| (*s).to_string()).collect()
124}
125
126pub fn list_functions() -> Vec<FunctionSpec> {
128 let fold_at = FunctionSpec {
131 name: "fold_at".into(),
132 type_params: tp(&["T", "A"]),
133 params: vec![
134 p("f", fnt(vec![var("A"), var("T")], var("A"))),
135 p("acc", var("A")),
136 p("xs", list(var("T"))),
137 p("i", Type::Number),
138 ],
139 produces: ext(var("A")),
140 requires: BTreeSet::new(),
141 on_failure: vec![],
142 steps: vec![step("n", llen(r("xs")))],
143 result: iff(
144 eq(r("i"), r("n")),
145 r("acc"),
146 call(
147 "fold_at",
148 vec![
149 r("f"),
150 callv(r("f"), vec![r("acc"), lget(r("xs"), r("i"))]),
151 r("xs"),
152 add1(r("i")),
153 ],
154 ),
155 ),
156 };
157 let fold = FunctionSpec {
158 name: "fold".into(),
159 type_params: tp(&["T", "A"]),
160 params: vec![
161 p("f", fnt(vec![var("A"), var("T")], var("A"))),
162 p("acc", var("A")),
163 p("xs", list(var("T"))),
164 ],
165 produces: ext(var("A")),
166 requires: BTreeSet::new(),
167 on_failure: vec![],
168 steps: vec![],
169 result: call("fold_at", vec![r("f"), r("acc"), r("xs"), num(0)]),
170 };
171
172 let map_at = FunctionSpec {
175 name: "map_at".into(),
176 type_params: tp(&["T", "U"]),
177 params: vec![
178 p("f", fnt(vec![var("T")], var("U"))),
179 p("xs", list(var("T"))),
180 p("i", Type::Number),
181 ],
182 produces: ext(list(var("U"))),
183 requires: BTreeSet::new(),
184 on_failure: vec![],
185 steps: vec![step("n", llen(r("xs")))],
186 result: iff(
187 eq(r("i"), r("n")),
188 empty(var("U")),
189 cons(
190 callv(r("f"), vec![lget(r("xs"), r("i"))]),
191 call("map_at", vec![r("f"), r("xs"), add1(r("i"))]),
192 ),
193 ),
194 };
195 let map = FunctionSpec {
196 name: "map".into(),
197 type_params: tp(&["T", "U"]),
198 params: vec![
199 p("f", fnt(vec![var("T")], var("U"))),
200 p("xs", list(var("T"))),
201 ],
202 produces: ext(list(var("U"))),
203 requires: BTreeSet::new(),
204 on_failure: vec![],
205 steps: vec![],
206 result: call("map_at", vec![r("f"), r("xs"), num(0)]),
207 };
208
209 let filter_at = FunctionSpec {
212 name: "filter_at".into(),
213 type_params: tp(&["T"]),
214 params: vec![
215 p("f", fnt(vec![var("T")], Type::Bool)),
216 p("xs", list(var("T"))),
217 p("i", Type::Number),
218 ],
219 produces: ext(list(var("T"))),
220 requires: BTreeSet::new(),
221 on_failure: vec![],
222 steps: vec![step("n", llen(r("xs")))],
223 result: iff(
224 eq(r("i"), r("n")),
225 empty(var("T")),
226 iff(
227 callv(r("f"), vec![lget(r("xs"), r("i"))]),
228 cons(
229 lget(r("xs"), r("i")),
230 call("filter_at", vec![r("f"), r("xs"), add1(r("i"))]),
231 ),
232 call("filter_at", vec![r("f"), r("xs"), add1(r("i"))]),
233 ),
234 ),
235 };
236 let filter = FunctionSpec {
237 name: "filter".into(),
238 type_params: tp(&["T"]),
239 params: vec![
240 p("f", fnt(vec![var("T")], Type::Bool)),
241 p("xs", list(var("T"))),
242 ],
243 produces: ext(list(var("T"))),
244 requires: BTreeSet::new(),
245 on_failure: vec![],
246 steps: vec![],
247 result: call("filter_at", vec![r("f"), r("xs"), num(0)]),
248 };
249
250 let range_at = FunctionSpec {
252 name: "range_at".into(),
253 type_params: vec![],
254 params: vec![p("lo", Type::Number), p("hi", Type::Number)],
255 produces: ext(list(Type::Number)),
256 requires: BTreeSet::new(),
257 on_failure: vec![],
258 steps: vec![],
259 result: iff(
260 eq(r("lo"), r("hi")),
261 empty(Type::Number),
262 cons(
263 r("lo"),
264 call("range_at", vec![add1(r("lo")), r("hi")]),
265 ),
266 ),
267 };
268 let range = FunctionSpec {
269 name: "range".into(),
270 type_params: vec![],
271 params: vec![p("lo", Type::Number), p("hi", Type::Number)],
272 produces: ext(list(Type::Number)),
273 requires: BTreeSet::new(),
274 on_failure: vec![],
275 steps: vec![],
276 result: call("range_at", vec![r("lo"), r("hi")]),
277 };
278
279 let any_at = FunctionSpec {
282 name: "any_at".into(),
283 type_params: tp(&["T"]),
284 params: vec![
285 p("f", fnt(vec![var("T")], Type::Bool)),
286 p("xs", list(var("T"))),
287 p("i", Type::Number),
288 ],
289 produces: ext(Type::Bool),
290 requires: BTreeSet::new(),
291 on_failure: vec![],
292 steps: vec![step("n", llen(r("xs")))],
293 result: iff(
294 eq(r("i"), r("n")),
295 boolean(false),
296 iff(
297 callv(r("f"), vec![lget(r("xs"), r("i"))]),
298 boolean(true),
299 call("any_at", vec![r("f"), r("xs"), add1(r("i"))]),
300 ),
301 ),
302 };
303 let any = FunctionSpec {
304 name: "any".into(),
305 type_params: tp(&["T"]),
306 params: vec![
307 p("f", fnt(vec![var("T")], Type::Bool)),
308 p("xs", list(var("T"))),
309 ],
310 produces: ext(Type::Bool),
311 requires: BTreeSet::new(),
312 on_failure: vec![],
313 steps: vec![],
314 result: call("any_at", vec![r("f"), r("xs"), num(0)]),
315 };
316
317 let all_at = FunctionSpec {
320 name: "all_at".into(),
321 type_params: tp(&["T"]),
322 params: vec![
323 p("f", fnt(vec![var("T")], Type::Bool)),
324 p("xs", list(var("T"))),
325 p("i", Type::Number),
326 ],
327 produces: ext(Type::Bool),
328 requires: BTreeSet::new(),
329 on_failure: vec![],
330 steps: vec![step("n", llen(r("xs")))],
331 result: iff(
332 eq(r("i"), r("n")),
333 boolean(true),
334 iff(
335 callv(r("f"), vec![lget(r("xs"), r("i"))]),
336 call("all_at", vec![r("f"), r("xs"), add1(r("i"))]),
337 boolean(false),
338 ),
339 ),
340 };
341 let all = FunctionSpec {
342 name: "all".into(),
343 type_params: tp(&["T"]),
344 params: vec![
345 p("f", fnt(vec![var("T")], Type::Bool)),
346 p("xs", list(var("T"))),
347 ],
348 produces: ext(Type::Bool),
349 requires: BTreeSet::new(),
350 on_failure: vec![],
351 steps: vec![],
352 result: call("all_at", vec![r("f"), r("xs"), num(0)]),
353 };
354
355 let find_at = FunctionSpec {
358 name: "find_at".into(),
359 type_params: tp(&["T"]),
360 params: vec![
361 p("f", fnt(vec![var("T")], Type::Bool)),
362 p("xs", list(var("T"))),
363 p("i", Type::Number),
364 ],
365 produces: ext(Type::Option(Box::new(var("T")))),
366 requires: BTreeSet::new(),
367 on_failure: vec![],
368 steps: vec![step("n", llen(r("xs")))],
369 result: iff(
370 eq(r("i"), r("n")),
371 none(var("T")),
372 iff(
373 callv(r("f"), vec![lget(r("xs"), r("i"))]),
374 some(lget(r("xs"), r("i"))),
375 call("find_at", vec![r("f"), r("xs"), add1(r("i"))]),
376 ),
377 ),
378 };
379 let find = FunctionSpec {
380 name: "find".into(),
381 type_params: tp(&["T"]),
382 params: vec![
383 p("f", fnt(vec![var("T")], Type::Bool)),
384 p("xs", list(var("T"))),
385 ],
386 produces: ext(Type::Option(Box::new(var("T")))),
387 requires: BTreeSet::new(),
388 on_failure: vec![],
389 steps: vec![],
390 result: call("find_at", vec![r("f"), r("xs"), num(0)]),
391 };
392
393 let concat_at = FunctionSpec {
396 name: "concat_at".into(),
397 type_params: tp(&["T"]),
398 params: vec![
399 p("a", list(var("T"))),
400 p("b", list(var("T"))),
401 p("i", Type::Number),
402 ],
403 produces: ext(list(var("T"))),
404 requires: BTreeSet::new(),
405 on_failure: vec![],
406 steps: vec![step("n", llen(r("a")))],
407 result: iff(
408 eq(r("i"), r("n")),
409 r("b"),
410 cons(
411 lget(r("a"), r("i")),
412 call("concat_at", vec![r("a"), r("b"), add1(r("i"))]),
413 ),
414 ),
415 };
416 let concat = FunctionSpec {
417 name: "concat".into(),
418 type_params: tp(&["T"]),
419 params: vec![p("a", list(var("T"))), p("b", list(var("T")))],
420 produces: ext(list(var("T"))),
421 requires: BTreeSet::new(),
422 on_failure: vec![],
423 steps: vec![],
424 result: call("concat_at", vec![r("a"), r("b"), num(0)]),
425 };
426
427 let s_str = || ExprSpec::Str("0".into());
429 let strlen = |e: ExprSpec| ExprSpec::StrLen(Box::new(e));
430 let scat = |a: ExprSpec, b: ExprSpec| {
431 ExprSpec::StrConcat(Box::new(a), Box::new(b))
432 };
433 let n2s = |e: ExprSpec| ExprSpec::NumberToStr(Box::new(e));
434 let bop = |op: BinOp, a: ExprSpec, b: ExprSpec| ExprSpec::BinOp {
435 op,
436 lhs: Box::new(a),
437 rhs: Box::new(b),
438 };
439 let lpad0 = FunctionSpec {
440 name: "lpad0".into(),
441 type_params: vec![],
442 params: vec![p("s", Type::String), p("w", Type::Number)],
443 produces: ext(Type::String),
444 requires: BTreeSet::new(),
445 on_failure: vec![],
446 steps: vec![step("ls", strlen(r("s")))],
447 result: iff(
448 bop(BinOp::Ge, r("ls"), r("w")),
449 r("s"),
450 call("lpad0", vec![scat(s_str(), r("s")), r("w")]),
451 ),
452 };
453 let decimal_to_str = FunctionSpec {
458 name: "decimal_to_str".into(),
459 type_params: vec![],
460 params: vec![p("d", Type::Decimal)],
461 produces: ext(Type::String),
462 requires: BTreeSet::new(),
463 on_failure: vec![],
464 steps: vec![
465 step("raw", ExprSpec::DecimalRaw(Box::new(r("d")))),
466 step(
467 "m",
468 iff(
469 bop(BinOp::Lt, r("raw"), num(0)),
470 bop(BinOp::Sub, num(0), r("raw")),
471 r("raw"),
472 ),
473 ),
474 step("whole", bop(BinOp::Div, r("m"), num(10_000))),
475 step("frac", bop(BinOp::Mod, r("m"), num(10_000))),
476 ],
477 result: scat(
478 scat(
479 scat(
480 iff(
481 bop(BinOp::Lt, r("raw"), num(0)),
482 ExprSpec::Str("-".into()),
483 ExprSpec::Str("".into()),
484 ),
485 n2s(r("whole")),
486 ),
487 ExprSpec::Str(".".into()),
488 ),
489 call("lpad0", vec![n2s(r("frac")), num(4)]),
490 ),
491 };
492
493 let take_at = FunctionSpec {
496 name: "take_at".into(),
497 type_params: tp(&["T"]),
498 params: vec![
499 p("xs", list(var("T"))),
500 p("k", Type::Number),
501 p("i", Type::Number),
502 ],
503 produces: ext(list(var("T"))),
504 requires: BTreeSet::new(),
505 on_failure: vec![],
506 steps: vec![step("n", llen(r("xs")))],
507 result: iff(
508 bop(BinOp::Or, bop(BinOp::Ge, r("i"), r("k")), eq(r("i"), r("n"))),
509 empty(var("T")),
510 cons(
511 lget(r("xs"), r("i")),
512 call("take_at", vec![r("xs"), r("k"), add1(r("i"))]),
513 ),
514 ),
515 };
516 let take = FunctionSpec {
517 name: "take".into(),
518 type_params: tp(&["T"]),
519 params: vec![p("xs", list(var("T"))), p("k", Type::Number)],
520 produces: ext(list(var("T"))),
521 requires: BTreeSet::new(),
522 on_failure: vec![],
523 steps: vec![],
524 result: call("take_at", vec![r("xs"), r("k"), num(0)]),
525 };
526 let drop_at = FunctionSpec {
530 name: "drop_at".into(),
531 type_params: tp(&["T"]),
532 params: vec![
533 p("xs", list(var("T"))),
534 p("k", Type::Number),
535 p("i", Type::Number),
536 ],
537 produces: ext(list(var("T"))),
538 requires: BTreeSet::new(),
539 on_failure: vec![],
540 steps: vec![step("n", llen(r("xs")))],
541 result: iff(
542 eq(r("i"), r("n")),
543 empty(var("T")),
544 iff(
545 bop(BinOp::Lt, r("i"), r("k")),
546 call("drop_at", vec![r("xs"), r("k"), add1(r("i"))]),
547 cons(
548 lget(r("xs"), r("i")),
549 call("drop_at", vec![r("xs"), r("k"), add1(r("i"))]),
550 ),
551 ),
552 ),
553 };
554 let drop = FunctionSpec {
555 name: "drop".into(),
556 type_params: tp(&["T"]),
557 params: vec![p("xs", list(var("T"))), p("k", Type::Number)],
558 produces: ext(list(var("T"))),
559 requires: BTreeSet::new(),
560 on_failure: vec![],
561 steps: vec![],
562 result: call("drop_at", vec![r("xs"), r("k"), num(0)]),
563 };
564
565 let rev_at = FunctionSpec {
567 name: "rev_at".into(),
568 type_params: tp(&["T"]),
569 params: vec![p("xs", list(var("T"))), p("i", Type::Number)],
570 produces: ext(list(var("T"))),
571 requires: BTreeSet::new(),
572 on_failure: vec![],
573 steps: vec![],
574 result: iff(
575 bop(BinOp::Lt, r("i"), num(0)),
576 empty(var("T")),
577 cons(
578 lget(r("xs"), r("i")),
579 call(
580 "rev_at",
581 vec![r("xs"), bop(BinOp::Sub, r("i"), num(1))],
582 ),
583 ),
584 ),
585 };
586 let reverse = FunctionSpec {
587 name: "reverse".into(),
588 type_params: tp(&["T"]),
589 params: vec![p("xs", list(var("T")))],
590 produces: ext(list(var("T"))),
591 requires: BTreeSet::new(),
592 on_failure: vec![],
593 steps: vec![],
594 result: call(
595 "rev_at",
596 vec![r("xs"), bop(BinOp::Sub, llen(r("xs")), num(1))],
597 ),
598 };
599 let flat_map_at = FunctionSpec {
602 name: "flat_map_at".into(),
603 type_params: tp(&["T", "U"]),
604 params: vec![
605 p("f", fnt(vec![var("T")], list(var("U")))),
606 p("xs", list(var("T"))),
607 p("i", Type::Number),
608 ],
609 produces: ext(list(var("U"))),
610 requires: BTreeSet::new(),
611 on_failure: vec![],
612 steps: vec![step("n", llen(r("xs")))],
613 result: iff(
614 eq(r("i"), r("n")),
615 empty(var("U")),
616 call(
617 "concat",
618 vec![
619 callv(r("f"), vec![lget(r("xs"), r("i"))]),
620 call("flat_map_at", vec![r("f"), r("xs"), add1(r("i"))]),
621 ],
622 ),
623 ),
624 };
625 let flat_map = FunctionSpec {
626 name: "flat_map".into(),
627 type_params: tp(&["T", "U"]),
628 params: vec![
629 p("f", fnt(vec![var("T")], list(var("U")))),
630 p("xs", list(var("T"))),
631 ],
632 produces: ext(list(var("U"))),
633 requires: BTreeSet::new(),
634 on_failure: vec![],
635 steps: vec![],
636 result: call("flat_map_at", vec![r("f"), r("xs"), num(0)]),
637 };
638
639 let ins = FunctionSpec {
643 name: "ins".into(),
644 type_params: tp(&["T"]),
645 params: vec![
646 p("le", fnt(vec![var("T"), var("T")], Type::Bool)),
647 p("x", var("T")),
648 p("ys", list(var("T"))),
649 ],
650 produces: ext(list(var("T"))),
651 requires: BTreeSet::new(),
652 on_failure: vec![],
653 steps: vec![step("n", llen(r("ys")))],
654 result: iff(
655 eq(r("n"), num(0)),
656 cons(r("x"), empty(var("T"))),
657 iff(
658 callv(r("le"), vec![r("x"), lget(r("ys"), num(0))]),
659 cons(r("x"), r("ys")),
660 cons(
661 lget(r("ys"), num(0)),
662 call(
663 "ins",
664 vec![
665 r("le"),
666 r("x"),
667 call("drop", vec![r("ys"), num(1)]),
668 ],
669 ),
670 ),
671 ),
672 ),
673 };
674 let sort_at = FunctionSpec {
677 name: "sort_at".into(),
678 type_params: tp(&["T"]),
679 params: vec![
680 p("le", fnt(vec![var("T"), var("T")], Type::Bool)),
681 p("xs", list(var("T"))),
682 p("i", Type::Number),
683 p("acc", list(var("T"))),
684 ],
685 produces: ext(list(var("T"))),
686 requires: BTreeSet::new(),
687 on_failure: vec![],
688 steps: vec![step("n", llen(r("xs")))],
689 result: iff(
690 eq(r("i"), r("n")),
691 r("acc"),
692 call(
693 "sort_at",
694 vec![
695 r("le"),
696 r("xs"),
697 add1(r("i")),
698 call("ins", vec![r("le"), lget(r("xs"), r("i")), r("acc")]),
699 ],
700 ),
701 ),
702 };
703 let sort_by = FunctionSpec {
704 name: "sort_by".into(),
705 type_params: tp(&["T"]),
706 params: vec![
707 p("le", fnt(vec![var("T"), var("T")], Type::Bool)),
708 p("xs", list(var("T"))),
709 ],
710 produces: ext(list(var("T"))),
711 requires: BTreeSet::new(),
712 on_failure: vec![],
713 steps: vec![],
714 result: call(
715 "sort_at",
716 vec![r("le"), r("xs"), num(0), empty(var("T"))],
717 ),
718 };
719
720 let zip_at = FunctionSpec {
723 name: "zip_at".into(),
724 type_params: tp(&["A", "B", "C"]),
725 params: vec![
726 p("f", fnt(vec![var("A"), var("B")], var("C"))),
727 p("as", list(var("A"))),
728 p("bs", list(var("B"))),
729 p("i", Type::Number),
730 ],
731 produces: ext(list(var("C"))),
732 requires: BTreeSet::new(),
733 on_failure: vec![],
734 steps: vec![
735 step("na", llen(r("as"))),
736 step("nb", llen(r("bs"))),
737 ],
738 result: iff(
739 bop(
740 BinOp::Or,
741 bop(BinOp::Ge, r("i"), r("na")),
742 bop(BinOp::Ge, r("i"), r("nb")),
743 ),
744 empty(var("C")),
745 cons(
746 callv(
747 r("f"),
748 vec![lget(r("as"), r("i")), lget(r("bs"), r("i"))],
749 ),
750 call("zip_at", vec![r("f"), r("as"), r("bs"), add1(r("i"))]),
751 ),
752 ),
753 };
754 let zip_with = FunctionSpec {
755 name: "zip_with".into(),
756 type_params: tp(&["A", "B", "C"]),
757 params: vec![
758 p("f", fnt(vec![var("A"), var("B")], var("C"))),
759 p("as", list(var("A"))),
760 p("bs", list(var("B"))),
761 ],
762 produces: ext(list(var("C"))),
763 requires: BTreeSet::new(),
764 on_failure: vec![],
765 steps: vec![],
766 result: call("zip_at", vec![r("f"), r("as"), r("bs"), num(0)]),
767 };
768 let dd_at = FunctionSpec {
771 name: "dd_at".into(),
772 type_params: tp(&["T"]),
773 params: vec![
774 p("eq", fnt(vec![var("T"), var("T")], Type::Bool)),
775 p("xs", list(var("T"))),
776 p("i", Type::Number),
777 ],
778 produces: ext(list(var("T"))),
779 requires: BTreeSet::new(),
780 on_failure: vec![],
781 steps: vec![step("n", llen(r("xs")))],
782 result: iff(
783 eq(r("i"), r("n")),
784 empty(var("T")),
785 iff(
786 bop(BinOp::Eq, r("i"), num(0)),
787 cons(
788 lget(r("xs"), num(0)),
789 call("dd_at", vec![r("eq"), r("xs"), num(1)]),
790 ),
791 iff(
792 callv(
793 r("eq"),
794 vec![
795 lget(r("xs"), r("i")),
796 lget(r("xs"), bop(BinOp::Sub, r("i"), num(1))),
797 ],
798 ),
799 call("dd_at", vec![r("eq"), r("xs"), add1(r("i"))]),
800 cons(
801 lget(r("xs"), r("i")),
802 call("dd_at", vec![r("eq"), r("xs"), add1(r("i"))]),
803 ),
804 ),
805 ),
806 ),
807 };
808 let dedup_by = FunctionSpec {
809 name: "dedup_by".into(),
810 type_params: tp(&["T"]),
811 params: vec![
812 p("eq", fnt(vec![var("T"), var("T")], Type::Bool)),
813 p("xs", list(var("T"))),
814 ],
815 produces: ext(list(var("T"))),
816 requires: BTreeSet::new(),
817 on_failure: vec![],
818 steps: vec![],
819 result: call("dd_at", vec![r("eq"), r("xs"), num(0)]),
820 };
821
822 let join_at = FunctionSpec {
826 name: "join_at".into(),
827 type_params: vec![],
828 params: vec![
829 p("sep", Type::String),
830 p("xs", list(Type::String)),
831 p("i", Type::Number),
832 ],
833 produces: ext(Type::String),
834 requires: BTreeSet::new(),
835 on_failure: vec![],
836 steps: vec![step("n", llen(r("xs")))],
837 result: iff(
838 eq(r("i"), r("n")),
839 ExprSpec::Str(String::new()),
840 iff(
841 eq(r("i"), num(0)),
842 scat(
843 lget(r("xs"), num(0)),
844 call("join_at", vec![r("sep"), r("xs"), num(1)]),
845 ),
846 scat(
847 scat(r("sep"), lget(r("xs"), r("i"))),
848 call("join_at", vec![r("sep"), r("xs"), add1(r("i"))]),
849 ),
850 ),
851 ),
852 };
853 let str_join = FunctionSpec {
854 name: "str_join".into(),
855 type_params: vec![],
856 params: vec![p("sep", Type::String), p("xs", list(Type::String))],
857 produces: ext(Type::String),
858 requires: BTreeSet::new(),
859 on_failure: vec![],
860 steps: vec![],
861 result: call("join_at", vec![r("sep"), r("xs"), num(0)]),
862 };
863 let rep_at = FunctionSpec {
865 name: "rep_at".into(),
866 type_params: vec![],
867 params: vec![
868 p("s", Type::String),
869 p("n", Type::Number),
870 p("i", Type::Number),
871 ],
872 produces: ext(Type::String),
873 requires: BTreeSet::new(),
874 on_failure: vec![],
875 steps: vec![],
876 result: iff(
877 bop(BinOp::Ge, r("i"), r("n")),
878 ExprSpec::Str(String::new()),
879 scat(
880 r("s"),
881 call("rep_at", vec![r("s"), r("n"), add1(r("i"))]),
882 ),
883 ),
884 };
885 let str_repeat = FunctionSpec {
886 name: "str_repeat".into(),
887 type_params: vec![],
888 params: vec![p("s", Type::String), p("n", Type::Number)],
889 produces: ext(Type::String),
890 requires: BTreeSet::new(),
891 on_failure: vec![],
892 steps: vec![],
893 result: call("rep_at", vec![r("s"), r("n"), num(0)]),
894 };
895
896 let sl = |s: ExprSpec, a: ExprSpec, b: ExprSpec| ExprSpec::StrSlice {
897 s: Box::new(s),
898 start: Box::new(a),
899 len: Box::new(b),
900 };
901 let slen_ = |x: ExprSpec| ExprSpec::StrLen(Box::new(x));
902 let sidx = |h: ExprSpec, n: ExprSpec| ExprSpec::StrIndexOf {
903 haystack: Box::new(h),
904 needle: Box::new(n),
905 };
906 let seq = |a: ExprSpec, b: ExprSpec| ExprSpec::StrEq(Box::new(a), Box::new(b));
907 let sub1 = |x: ExprSpec| bop(BinOp::Sub, x, num(1));
908 let is_ws = FunctionSpec {
910 name: "is_ws".into(),
911 type_params: vec![],
912 params: vec![p("c", Type::String)],
913 produces: ext(Type::Bool),
914 requires: BTreeSet::new(),
915 on_failure: vec![],
916 steps: vec![],
917 result: bop(
918 BinOp::Or,
919 bop(
920 BinOp::Or,
921 seq(r("c"), ExprSpec::Str(" ".into())),
922 seq(r("c"), ExprSpec::Str("\t".into())),
923 ),
924 bop(
925 BinOp::Or,
926 seq(r("c"), ExprSpec::Str("\n".into())),
927 seq(r("c"), ExprSpec::Str("\r".into())),
928 ),
929 ),
930 };
931 let lead = FunctionSpec {
933 name: "lead".into(),
934 type_params: vec![],
935 params: vec![p("s", Type::String), p("i", Type::Number)],
936 produces: ext(Type::Number),
937 requires: BTreeSet::new(),
938 on_failure: vec![],
939 steps: vec![step("n", slen_(r("s")))],
940 result: iff(
941 bop(BinOp::Ge, r("i"), r("n")),
942 r("n"),
943 iff(
944 call("is_ws", vec![sl(r("s"), r("i"), num(1))]),
945 call("lead", vec![r("s"), add1(r("i"))]),
946 r("i"),
947 ),
948 ),
949 };
950 let tend = FunctionSpec {
952 name: "tend".into(),
953 type_params: vec![],
954 params: vec![p("s", Type::String), p("i", Type::Number)],
955 produces: ext(Type::Number),
956 requires: BTreeSet::new(),
957 on_failure: vec![],
958 steps: vec![],
959 result: iff(
960 bop(BinOp::Lt, r("i"), num(0)),
961 num(0),
962 iff(
963 call("is_ws", vec![sl(r("s"), r("i"), num(1))]),
964 call("tend", vec![r("s"), sub1(r("i"))]),
965 add1(r("i")),
966 ),
967 ),
968 };
969 let str_trim = FunctionSpec {
970 name: "str_trim".into(),
971 type_params: vec![],
972 params: vec![p("s", Type::String)],
973 produces: ext(Type::String),
974 requires: BTreeSet::new(),
975 on_failure: vec![],
976 steps: vec![
977 step("st", call("lead", vec![r("s"), num(0)])),
978 step("en", call("tend", vec![r("s"), sub1(slen_(r("s")))])),
979 ],
980 result: iff(
981 bop(BinOp::Ge, r("st"), r("en")),
982 ExprSpec::Str(String::new()),
983 sl(r("s"), r("st"), bop(BinOp::Sub, r("en"), r("st"))),
984 ),
985 };
986 let rep = FunctionSpec {
989 name: "rep".into(),
990 type_params: vec![],
991 params: vec![
992 p("s", Type::String),
993 p("from", Type::String),
994 p("to", Type::String),
995 ],
996 produces: ext(Type::String),
997 requires: BTreeSet::new(),
998 on_failure: vec![],
999 steps: vec![
1000 step("fl", slen_(r("from"))),
1001 step("ix", sidx(r("s"), r("from"))),
1002 ],
1003 result: iff(
1004 bop(BinOp::Eq, r("fl"), num(0)),
1005 r("s"),
1006 iff(
1007 bop(BinOp::Lt, r("ix"), num(0)),
1008 r("s"),
1009 scat(
1010 scat(sl(r("s"), num(0), r("ix")), r("to")),
1011 call(
1012 "rep",
1013 vec![
1014 sl(
1015 r("s"),
1016 bop(BinOp::Add, r("ix"), r("fl")),
1017 bop(
1018 BinOp::Sub,
1019 slen_(r("s")),
1020 bop(BinOp::Add, r("ix"), r("fl")),
1021 ),
1022 ),
1023 r("from"),
1024 r("to"),
1025 ],
1026 ),
1027 ),
1028 ),
1029 ),
1030 };
1031 let str_replace = FunctionSpec {
1032 name: "str_replace".into(),
1033 type_params: vec![],
1034 params: vec![
1035 p("s", Type::String),
1036 p("from", Type::String),
1037 p("to", Type::String),
1038 ],
1039 produces: ext(Type::String),
1040 requires: BTreeSet::new(),
1041 on_failure: vec![],
1042 steps: vec![],
1043 result: call("rep", vec![r("s"), r("from"), r("to")]),
1044 };
1045
1046 let first = FunctionSpec {
1052 name: "first".into(),
1053 type_params: tp(&["T"]),
1054 params: vec![p("xs", list(var("T")))],
1055 produces: ext(Type::Option(Box::new(var("T")))),
1056 requires: BTreeSet::new(),
1057 on_failure: vec![],
1058 steps: vec![step("n", llen(r("xs")))],
1059 result: iff(
1060 eq(r("n"), num(0)),
1061 none(var("T")),
1062 some(lget(r("xs"), num(0))),
1063 ),
1064 };
1065 let last = FunctionSpec {
1066 name: "last".into(),
1067 type_params: tp(&["T"]),
1068 params: vec![p("xs", list(var("T")))],
1069 produces: ext(Type::Option(Box::new(var("T")))),
1070 requires: BTreeSet::new(),
1071 on_failure: vec![],
1072 steps: vec![step("n", llen(r("xs")))],
1073 result: iff(
1074 eq(r("n"), num(0)),
1075 none(var("T")),
1076 some(lget(r("xs"), bop(BinOp::Sub, r("n"), num(1)))),
1077 ),
1078 };
1079
1080 let omatch = |o: ExprSpec, sb: ExprSpec, nb: ExprSpec| {
1083 ExprSpec::OptionMatch {
1084 opt: Box::new(o),
1085 some_bind: "v".into(),
1086 some_body: Box::new(sb),
1087 none_body: Box::new(nb),
1088 }
1089 };
1090 let opt_map = FunctionSpec {
1091 name: "opt_map".into(),
1092 type_params: tp(&["T", "U"]),
1093 params: vec![
1094 p("f", fnt(vec![var("T")], var("U"))),
1095 p("o", Type::Option(Box::new(var("T")))),
1096 ],
1097 produces: ext(Type::Option(Box::new(var("U")))),
1098 requires: BTreeSet::new(),
1099 on_failure: vec![],
1100 steps: vec![],
1101 result: omatch(
1102 r("o"),
1103 some(callv(r("f"), vec![r("v")])),
1104 none(var("U")),
1105 ),
1106 };
1107 let opt_then = FunctionSpec {
1108 name: "opt_then".into(),
1109 type_params: tp(&["T", "U"]),
1110 params: vec![
1111 p("f", fnt(vec![var("T")], Type::Option(Box::new(var("U"))))),
1112 p("o", Type::Option(Box::new(var("T")))),
1113 ],
1114 produces: ext(Type::Option(Box::new(var("U")))),
1115 requires: BTreeSet::new(),
1116 on_failure: vec![],
1117 steps: vec![],
1118 result: omatch(
1119 r("o"),
1120 callv(r("f"), vec![r("v")]),
1121 none(var("U")),
1122 ),
1123 };
1124
1125 let nf0 = |name: &str, params: Vec<Param>, result: ExprSpec| FunctionSpec {
1129 name: name.into(),
1130 type_params: vec![],
1131 params,
1132 produces: ext(Type::Number),
1133 requires: BTreeSet::new(),
1134 on_failure: vec![],
1135 steps: vec![],
1136 result,
1137 };
1138 let iabs = nf0(
1139 "iabs",
1140 vec![p("x", Type::Number)],
1141 iff(
1142 bop(BinOp::Lt, r("x"), num(0)),
1143 bop(BinOp::Sub, num(0), r("x")),
1144 r("x"),
1145 ),
1146 );
1147 let imin = nf0(
1148 "imin",
1149 vec![p("a", Type::Number), p("b", Type::Number)],
1150 iff(bop(BinOp::Lt, r("a"), r("b")), r("a"), r("b")),
1151 );
1152 let imax = nf0(
1153 "imax",
1154 vec![p("a", Type::Number), p("b", Type::Number)],
1155 iff(bop(BinOp::Gt, r("a"), r("b")), r("a"), r("b")),
1156 );
1157 let clamp = nf0(
1158 "clamp",
1159 vec![
1160 p("x", Type::Number),
1161 p("lo", Type::Number),
1162 p("hi", Type::Number),
1163 ],
1164 call(
1165 "imax",
1166 vec![r("lo"), call("imin", vec![r("x"), r("hi")])],
1167 ),
1168 );
1169
1170 let ipow = nf0(
1173 "ipow",
1174 vec![p("base", Type::Number), p("exp", Type::Number)],
1175 iff(
1176 bop(BinOp::Le, r("exp"), num(0)),
1177 num(1),
1178 bop(
1179 BinOp::Mul,
1180 r("base"),
1181 call(
1182 "ipow",
1183 vec![r("base"), bop(BinOp::Sub, r("exp"), num(1))],
1184 ),
1185 ),
1186 ),
1187 );
1188 let gcd = nf0(
1189 "gcd",
1190 vec![p("a", Type::Number), p("b", Type::Number)],
1191 iff(
1192 bop(BinOp::Eq, r("b"), num(0)),
1193 call("iabs", vec![r("a")]),
1194 call(
1195 "gcd",
1196 vec![r("b"), bop(BinOp::Mod, r("a"), r("b"))],
1197 ),
1198 ),
1199 );
1200
1201 let dr = FunctionSpec {
1210 name: "decimal_round_str".into(),
1211 type_params: vec![],
1212 params: vec![p("d", Type::Decimal), p("places", Type::Number)],
1213 produces: ext(Type::String),
1214 requires: BTreeSet::new(),
1215 on_failure: vec![],
1216 steps: vec![
1217 step("raw", ExprSpec::DecimalRaw(Box::new(r("d")))),
1218 step("m", call("iabs", vec![r("raw")])),
1219 step("pl", call("clamp", vec![r("places"), num(0), num(4)])),
1220 step(
1221 "f",
1222 call(
1223 "ipow",
1224 vec![num(10), bop(BinOp::Sub, num(4), r("pl"))],
1225 ),
1226 ),
1227 step(
1229 "rnd",
1230 bop(
1231 BinOp::Mul,
1232 bop(
1233 BinOp::Div,
1234 bop(
1235 BinOp::Add,
1236 r("m"),
1237 bop(BinOp::Div, r("f"), num(2)),
1238 ),
1239 r("f"),
1240 ),
1241 r("f"),
1242 ),
1243 ),
1244 step("whole", bop(BinOp::Div, r("rnd"), num(10_000))),
1245 step(
1246 "frac",
1247 bop(
1248 BinOp::Div,
1249 bop(BinOp::Mod, r("rnd"), num(10_000)),
1250 r("f"),
1251 ),
1252 ),
1253 step(
1254 "sign",
1255 iff(
1256 bop(BinOp::Lt, r("raw"), num(0)),
1257 ExprSpec::Str("-".into()),
1258 ExprSpec::Str(String::new()),
1259 ),
1260 ),
1261 ],
1262 result: scat(
1263 scat(r("sign"), n2s(r("whole"))),
1264 iff(
1265 bop(BinOp::Eq, r("pl"), num(0)),
1266 ExprSpec::Str(String::new()),
1267 scat(
1268 ExprSpec::Str(".".into()),
1269 call("lpad0", vec![n2s(r("frac")), r("pl")]),
1270 ),
1271 ),
1272 ),
1273 };
1274
1275 vec![
1276 fold_at, fold, map_at, map, filter_at, filter, range_at, range,
1277 any_at, any, all_at, all, find_at, find, concat_at, concat,
1278 lpad0, decimal_to_str, take_at, take, drop_at, drop, rev_at,
1279 reverse, flat_map_at, flat_map, ins, sort_at, sort_by, zip_at,
1280 zip_with, dd_at, dedup_by, join_at, str_join, rep_at, str_repeat,
1281 is_ws, lead, tend, str_trim, rep, str_replace, first, last,
1282 opt_map, opt_then, iabs, imin, imax, clamp, ipow, gcd, dr,
1283 ]
1284}
1285
1286#[cfg(test)]
1287mod tests {
1288 use super::*;
1289 use crate::edit::{Editor, ModuleSpec};
1290 use crate::store::Store;
1291 use crate::wasm::{lower, run_i64};
1292
1293 #[test]
1297 fn decimal_to_str_formats() {
1298 let probe = |name: &str, v: f64, want: &str| FunctionSpec {
1299 name: name.into(),
1300 type_params: vec![],
1301 params: vec![],
1302 produces: ext(Type::Bool),
1303 requires: BTreeSet::new(),
1304 on_failure: vec![],
1305 steps: vec![],
1306 result: ExprSpec::StrEq(
1307 Box::new(call(
1308 "decimal_to_str",
1309 vec![ExprSpec::Decimal(v)],
1310 )),
1311 Box::new(ExprSpec::Str(want.into())),
1312 ),
1313 };
1314 let cases = [
1315 ("a", 1.25, "1.2500"),
1316 ("b", 19.99, "19.9900"),
1317 ("c", -0.5, "-0.5000"),
1318 ("d", 0.0, "0.0000"),
1319 ("e", 1000.0, "1000.0000"),
1320 ("f", -12.3456, "-12.3456"),
1321 ];
1322 let mut fns = list_functions();
1323 fns.extend(cases.iter().map(|(n, v, w)| probe(n, *v, w)));
1324 let e = Editor::new(Store::open_in_memory().unwrap());
1325 let (m, report) = e
1326 .apply_module(&ModuleSpec {
1327 name: "dec".into(),
1328 types: vec![],
1329 functions: fns,
1330 })
1331 .unwrap();
1332 assert!(report.ok(), "violations: {:?}", report.violations);
1333 let wasm = lower(e.store(), &m).unwrap();
1334 for (n, _, w) in cases {
1335 assert_eq!(
1336 run_i64(&wasm, n, &[]).unwrap(),
1337 1,
1338 "decimal_to_str should produce {w}"
1339 );
1340 }
1341 }
1342
1343 #[test]
1346 fn the_stdlib_maps_filters_and_folds() {
1347 let lam = |params: Vec<(&str, Type)>, body: ExprSpec| ExprSpec::Lambda {
1350 params: params
1351 .into_iter()
1352 .map(|(n, t)| (n.to_string(), t))
1353 .collect(),
1354 body: Box::new(body),
1355 };
1356 let bin = |op, a: ExprSpec, b: ExprSpec| ExprSpec::BinOp {
1357 op,
1358 lhs: Box::new(a),
1359 rhs: Box::new(b),
1360 };
1361
1362 let is_even = lam(
1363 vec![("x", Type::Number)],
1364 bin(BinOp::Eq, bin(BinOp::Mod, r("x"), num(2)), num(0)),
1365 );
1366 let double = lam(vec![("x", Type::Number)], bin(BinOp::Mul, r("x"), num(2)));
1367 let plus = lam(
1368 vec![("a", Type::Number), ("x", Type::Number)],
1369 bin(BinOp::Add, r("a"), r("x")),
1370 );
1371
1372 let xs = ExprSpec::List((1..=6).map(num).collect());
1375 let caller = FunctionSpec {
1376 name: "run".into(),
1377 type_params: vec![],
1378 params: vec![],
1379 produces: ext(Type::Number),
1380 requires: BTreeSet::new(),
1381 on_failure: vec![],
1382 steps: vec![],
1383 result: call(
1384 "fold",
1385 vec![
1386 plus,
1387 num(0),
1388 call("map", vec![double, call("filter", vec![is_even, xs])]),
1389 ],
1390 ),
1391 };
1392
1393 let e = Editor::new(Store::open_in_memory().unwrap());
1394 let mut fns = list_functions();
1395 fns.push(caller);
1396 let (m, report) = e
1397 .apply_module(&ModuleSpec {
1398 name: "stdtest".into(),
1399 types: vec![],
1400 functions: fns,
1401 })
1402 .unwrap();
1403 assert!(report.ok(), "violations: {:?}", report.violations);
1404
1405 let wasm = lower(e.store(), &m).unwrap();
1406 assert_eq!(run_i64(&wasm, "run", &[]).unwrap(), 24);
1407 }
1408
1409 #[test]
1412 fn the_stdlib_ranges_searches_and_quantifies() {
1413 let lam = |params: Vec<(&str, Type)>, body: ExprSpec| ExprSpec::Lambda {
1414 params: params
1415 .into_iter()
1416 .map(|(n, t)| (n.to_string(), t))
1417 .collect(),
1418 body: Box::new(body),
1419 };
1420 let bin = |op, a: ExprSpec, b: ExprSpec| ExprSpec::BinOp {
1421 op,
1422 lhs: Box::new(a),
1423 rhs: Box::new(b),
1424 };
1425 let nums = |vs: &[i64]| ExprSpec::List(vs.iter().copied().map(num).collect());
1426 let nfn = |name: &str, ret: Type, result: ExprSpec| FunctionSpec {
1427 name: name.into(),
1428 type_params: vec![],
1429 params: vec![],
1430 produces: ext(ret),
1431 requires: BTreeSet::new(),
1432 on_failure: vec![],
1433 steps: vec![],
1434 result,
1435 };
1436 let nullary = |name: &str, result: ExprSpec| nfn(name, Type::Number, result);
1437 let plus = || {
1438 lam(
1439 vec![("a", Type::Number), ("x", Type::Number)],
1440 bin(BinOp::Add, r("a"), r("x")),
1441 )
1442 };
1443 let find_or = |pred: ExprSpec, xs: ExprSpec| ExprSpec::OptionMatch {
1444 opt: Box::new(call("find", vec![pred, xs])),
1445 some_bind: "v".into(),
1446 some_body: Box::new(r("v")),
1447 none_body: Box::new(num(-1)),
1448 };
1449
1450 let mut fns = list_functions();
1451 fns.extend([
1452 nullary(
1454 "r_sum",
1455 call("fold", vec![plus(), num(0), call("range", vec![num(1), num(5)])]),
1456 ),
1457 nfn(
1459 "anyt",
1460 Type::Bool,
1461 call(
1462 "any",
1463 vec![
1464 lam(vec![("x", Type::Number)], bin(BinOp::Eq, r("x"), num(3))),
1465 nums(&[1, 2, 3]),
1466 ],
1467 ),
1468 ),
1469 nfn(
1471 "allt",
1472 Type::Bool,
1473 call(
1474 "all",
1475 vec![
1476 lam(vec![("x", Type::Number)], bin(BinOp::Gt, r("x"), num(0))),
1477 nums(&[1, 2, 3]),
1478 ],
1479 ),
1480 ),
1481 nfn(
1482 "allf",
1483 Type::Bool,
1484 call(
1485 "all",
1486 vec![
1487 lam(vec![("x", Type::Number)], bin(BinOp::Gt, r("x"), num(1))),
1488 nums(&[1, 2, 3]),
1489 ],
1490 ),
1491 ),
1492 nullary(
1494 "find_hit",
1495 find_or(
1496 lam(vec![("x", Type::Number)], bin(BinOp::Gt, r("x"), num(2))),
1497 nums(&[1, 2, 3, 4]),
1498 ),
1499 ),
1500 nullary(
1501 "find_miss",
1502 find_or(
1503 lam(vec![("x", Type::Number)], bin(BinOp::Gt, r("x"), num(9))),
1504 nums(&[1, 2, 3, 4]),
1505 ),
1506 ),
1507 ]);
1508
1509 let e = Editor::new(Store::open_in_memory().unwrap());
1510 let (m, report) = e
1511 .apply_module(&ModuleSpec {
1512 name: "stdtest2".into(),
1513 types: vec![],
1514 functions: fns,
1515 })
1516 .unwrap();
1517 assert!(report.ok(), "violations: {:?}", report.violations);
1518 let wasm = lower(e.store(), &m).unwrap();
1519 assert_eq!(run_i64(&wasm, "r_sum", &[]).unwrap(), 10);
1520 assert_eq!(run_i64(&wasm, "anyt", &[]).unwrap(), 1);
1521 assert_eq!(run_i64(&wasm, "allt", &[]).unwrap(), 1);
1522 assert_eq!(run_i64(&wasm, "allf", &[]).unwrap(), 0);
1523 assert_eq!(run_i64(&wasm, "find_hit", &[]).unwrap(), 3);
1524 assert_eq!(run_i64(&wasm, "find_miss", &[]).unwrap(), -1);
1525 }
1526
1527 #[test]
1530 fn the_stdlib_takes_and_drops() {
1531 let lam = |params: Vec<(&str, Type)>, body: ExprSpec| ExprSpec::Lambda {
1532 params: params
1533 .into_iter()
1534 .map(|(n, t)| (n.to_string(), t))
1535 .collect(),
1536 body: Box::new(body),
1537 };
1538 let bin = |op, a: ExprSpec, b: ExprSpec| ExprSpec::BinOp {
1539 op,
1540 lhs: Box::new(a),
1541 rhs: Box::new(b),
1542 };
1543 let xs = || ExprSpec::List([10, 20, 30, 40, 50].iter().copied().map(num).collect());
1544 let plus = || {
1545 lam(
1546 vec![("a", Type::Number), ("x", Type::Number)],
1547 bin(BinOp::Add, r("a"), r("x")),
1548 )
1549 };
1550 let sum = |l: ExprSpec| call("fold", vec![plus(), num(0), l]);
1551 let nf = |name: &str, result: ExprSpec| FunctionSpec {
1552 name: name.into(),
1553 type_params: vec![],
1554 params: vec![],
1555 produces: ext(Type::Number),
1556 requires: BTreeSet::new(),
1557 on_failure: vec![],
1558 steps: vec![],
1559 result,
1560 };
1561 let mut fns = list_functions();
1562 fns.extend([
1563 nf("t_take2", sum(call("take", vec![xs(), num(2)]))), nf("t_drop2", sum(call("drop", vec![xs(), num(2)]))), nf("t_take_over", llen(call("take", vec![xs(), num(99)]))), nf("t_take_zero", llen(call("take", vec![xs(), num(0)]))), nf("t_drop_zero", llen(call("drop", vec![xs(), num(0)]))), nf("t_drop_over", llen(call("drop", vec![xs(), num(99)]))), ]);
1570 let e = Editor::new(Store::open_in_memory().unwrap());
1571 let (m, report) = e
1572 .apply_module(&ModuleSpec {
1573 name: "stdtake".into(),
1574 types: vec![],
1575 functions: fns,
1576 })
1577 .unwrap();
1578 assert!(report.ok(), "violations: {:?}", report.violations);
1579 let wasm = lower(e.store(), &m).unwrap();
1580 assert_eq!(run_i64(&wasm, "t_take2", &[]).unwrap(), 30);
1581 assert_eq!(run_i64(&wasm, "t_drop2", &[]).unwrap(), 120);
1582 assert_eq!(run_i64(&wasm, "t_take_over", &[]).unwrap(), 5);
1583 assert_eq!(run_i64(&wasm, "t_take_zero", &[]).unwrap(), 0);
1584 assert_eq!(run_i64(&wasm, "t_drop_zero", &[]).unwrap(), 5);
1585 assert_eq!(run_i64(&wasm, "t_drop_over", &[]).unwrap(), 0);
1586 }
1587
1588 #[test]
1591 fn the_stdlib_reverses_and_flat_maps() {
1592 let lam = |params: Vec<(&str, Type)>, body: ExprSpec| ExprSpec::Lambda {
1593 params: params
1594 .into_iter()
1595 .map(|(n, t)| (n.to_string(), t))
1596 .collect(),
1597 body: Box::new(body),
1598 };
1599 let bin = |op, a: ExprSpec, b: ExprSpec| ExprSpec::BinOp {
1600 op,
1601 lhs: Box::new(a),
1602 rhs: Box::new(b),
1603 };
1604 let nums = |vs: &[i64]| ExprSpec::List(vs.iter().copied().map(num).collect());
1605 let nf = |name: &str, result: ExprSpec| FunctionSpec {
1606 name: name.into(),
1607 type_params: vec![],
1608 params: vec![],
1609 produces: ext(Type::Number),
1610 requires: BTreeSet::new(),
1611 on_failure: vec![],
1612 steps: vec![],
1613 result,
1614 };
1615 let horner = || {
1618 lam(
1619 vec![("a", Type::Number), ("x", Type::Number)],
1620 bin(BinOp::Add, bin(BinOp::Mul, r("a"), num(10)), r("x")),
1621 )
1622 };
1623 let dig = |l: ExprSpec| call("fold", vec![horner(), num(0), l]);
1624 let dup = || {
1625 lam(
1626 vec![("x", Type::Number)],
1627 ExprSpec::List(vec![r("x"), r("x")]),
1628 )
1629 };
1630 let mut fns = list_functions();
1631 fns.extend([
1632 nf("rev3", dig(call("reverse", vec![nums(&[1, 2, 3])]))), nf(
1634 "rev_empty",
1635 llen(call("reverse", vec![empty(Type::Number)])),
1636 ), nf(
1638 "fm_dup",
1639 dig(call("flat_map", vec![dup(), nums(&[1, 2, 3])])),
1640 ), nf(
1642 "fm_len",
1643 llen(call("flat_map", vec![dup(), nums(&[4, 5])])),
1644 ), ]);
1646 let e = Editor::new(Store::open_in_memory().unwrap());
1647 let (m, report) = e
1648 .apply_module(&ModuleSpec {
1649 name: "stdrev".into(),
1650 types: vec![],
1651 functions: fns,
1652 })
1653 .unwrap();
1654 assert!(report.ok(), "violations: {:?}", report.violations);
1655 let wasm = lower(e.store(), &m).unwrap();
1656 assert_eq!(run_i64(&wasm, "rev3", &[]).unwrap(), 321);
1657 assert_eq!(run_i64(&wasm, "rev_empty", &[]).unwrap(), 0);
1658 assert_eq!(run_i64(&wasm, "fm_dup", &[]).unwrap(), 112233);
1659 assert_eq!(run_i64(&wasm, "fm_len", &[]).unwrap(), 4);
1660 }
1661
1662 #[test]
1666 fn the_stdlib_sorts_by_a_comparator() {
1667 let lam = |params: Vec<(&str, Type)>, body: ExprSpec| ExprSpec::Lambda {
1668 params: params
1669 .into_iter()
1670 .map(|(n, t)| (n.to_string(), t))
1671 .collect(),
1672 body: Box::new(body),
1673 };
1674 let bin = |op, a: ExprSpec, b: ExprSpec| ExprSpec::BinOp {
1675 op,
1676 lhs: Box::new(a),
1677 rhs: Box::new(b),
1678 };
1679 let nums = |vs: &[i64]| ExprSpec::List(vs.iter().copied().map(num).collect());
1680 let nf = |name: &str, result: ExprSpec| FunctionSpec {
1681 name: name.into(),
1682 type_params: vec![],
1683 params: vec![],
1684 produces: ext(Type::Number),
1685 requires: BTreeSet::new(),
1686 on_failure: vec![],
1687 steps: vec![],
1688 result,
1689 };
1690 let le = || {
1691 lam(
1692 vec![("a", Type::Number), ("b", Type::Number)],
1693 bin(BinOp::Le, r("a"), r("b")),
1694 )
1695 };
1696 let ge = || {
1697 lam(
1698 vec![("a", Type::Number), ("b", Type::Number)],
1699 bin(BinOp::Ge, r("a"), r("b")),
1700 )
1701 };
1702 let horner = || {
1703 lam(
1704 vec![("a", Type::Number), ("x", Type::Number)],
1705 bin(BinOp::Add, bin(BinOp::Mul, r("a"), num(10)), r("x")),
1706 )
1707 };
1708 let dig = |l: ExprSpec| call("fold", vec![horner(), num(0), l]);
1709 let mut fns = list_functions();
1710 fns.extend([
1711 nf("asc", dig(call("sort_by", vec![le(), nums(&[3, 1, 2])]))), nf("desc", dig(call("sort_by", vec![ge(), nums(&[3, 1, 2])]))), nf(
1714 "already",
1715 dig(call("sort_by", vec![le(), nums(&[1, 2, 3])])),
1716 ), nf(
1718 "sort_empty",
1719 llen(call("sort_by", vec![le(), empty(Type::Number)])),
1720 ), nf(
1722 "asc5",
1723 dig(call("sort_by", vec![le(), nums(&[5, 4, 3, 2, 1])])),
1724 ), ]);
1726 let e = Editor::new(Store::open_in_memory().unwrap());
1727 let (m, report) = e
1728 .apply_module(&ModuleSpec {
1729 name: "stdsort".into(),
1730 types: vec![],
1731 functions: fns,
1732 })
1733 .unwrap();
1734 assert!(report.ok(), "violations: {:?}", report.violations);
1735 let wasm = lower(e.store(), &m).unwrap();
1736 assert_eq!(run_i64(&wasm, "asc", &[]).unwrap(), 123);
1737 assert_eq!(run_i64(&wasm, "desc", &[]).unwrap(), 321);
1738 assert_eq!(run_i64(&wasm, "already", &[]).unwrap(), 123);
1739 assert_eq!(run_i64(&wasm, "sort_empty", &[]).unwrap(), 0);
1740 assert_eq!(run_i64(&wasm, "asc5", &[]).unwrap(), 12345);
1741 }
1742
1743 #[test]
1746 fn the_stdlib_zips_and_dedups() {
1747 let lam = |params: Vec<(&str, Type)>, body: ExprSpec| ExprSpec::Lambda {
1748 params: params
1749 .into_iter()
1750 .map(|(n, t)| (n.to_string(), t))
1751 .collect(),
1752 body: Box::new(body),
1753 };
1754 let bin = |op, a: ExprSpec, b: ExprSpec| ExprSpec::BinOp {
1755 op,
1756 lhs: Box::new(a),
1757 rhs: Box::new(b),
1758 };
1759 let nums = |vs: &[i64]| ExprSpec::List(vs.iter().copied().map(num).collect());
1760 let nf = |name: &str, result: ExprSpec| FunctionSpec {
1761 name: name.into(),
1762 type_params: vec![],
1763 params: vec![],
1764 produces: ext(Type::Number),
1765 requires: BTreeSet::new(),
1766 on_failure: vec![],
1767 steps: vec![],
1768 result,
1769 };
1770 let plus = || {
1771 lam(
1772 vec![("a", Type::Number), ("x", Type::Number)],
1773 bin(BinOp::Add, r("a"), r("x")),
1774 )
1775 };
1776 let sum = |l: ExprSpec| call("fold", vec![plus(), num(0), l]);
1777 let addf = || {
1778 lam(
1779 vec![("a", Type::Number), ("b", Type::Number)],
1780 bin(BinOp::Add, r("a"), r("b")),
1781 )
1782 };
1783 let eqf = || {
1784 lam(
1785 vec![("a", Type::Number), ("b", Type::Number)],
1786 bin(BinOp::Eq, r("a"), r("b")),
1787 )
1788 };
1789 let horner = || {
1790 lam(
1791 vec![("a", Type::Number), ("x", Type::Number)],
1792 bin(BinOp::Add, bin(BinOp::Mul, r("a"), num(10)), r("x")),
1793 )
1794 };
1795 let dig = |l: ExprSpec| call("fold", vec![horner(), num(0), l]);
1796 let mut fns = list_functions();
1797 fns.extend([
1798 nf(
1800 "zip_sum",
1801 sum(call(
1802 "zip_with",
1803 vec![addf(), nums(&[1, 2, 3]), nums(&[10, 20, 30])],
1804 )),
1805 ),
1806 nf(
1808 "zip_uneven_len",
1809 llen(call(
1810 "zip_with",
1811 vec![addf(), nums(&[1, 2]), nums(&[10, 20, 30])],
1812 )),
1813 ),
1814 nf(
1816 "dd",
1817 dig(call(
1818 "dedup_by",
1819 vec![eqf(), nums(&[1, 1, 2, 2, 2, 3, 1])],
1820 )),
1821 ),
1822 nf(
1823 "dd_empty",
1824 llen(call("dedup_by", vec![eqf(), empty(Type::Number)])),
1825 ),
1826 ]);
1827 let e = Editor::new(Store::open_in_memory().unwrap());
1828 let (m, report) = e
1829 .apply_module(&ModuleSpec {
1830 name: "stdzip".into(),
1831 types: vec![],
1832 functions: fns,
1833 })
1834 .unwrap();
1835 assert!(report.ok(), "violations: {:?}", report.violations);
1836 let wasm = lower(e.store(), &m).unwrap();
1837 assert_eq!(run_i64(&wasm, "zip_sum", &[]).unwrap(), 66);
1838 assert_eq!(run_i64(&wasm, "zip_uneven_len", &[]).unwrap(), 2);
1839 assert_eq!(run_i64(&wasm, "dd", &[]).unwrap(), 1231);
1840 assert_eq!(run_i64(&wasm, "dd_empty", &[]).unwrap(), 0);
1841 }
1842
1843 #[test]
1846 fn the_stdlib_joins_and_repeats() {
1847 let strs = |vs: &[&str]| {
1848 ExprSpec::List(vs.iter().map(|s| ExprSpec::Str((*s).into())).collect())
1849 };
1850 let probe = |name: &str, got: ExprSpec, want: &str| FunctionSpec {
1851 name: name.into(),
1852 type_params: vec![],
1853 params: vec![],
1854 produces: ext(Type::Bool),
1855 requires: BTreeSet::new(),
1856 on_failure: vec![],
1857 steps: vec![],
1858 result: ExprSpec::StrEq(
1859 Box::new(got),
1860 Box::new(ExprSpec::Str(want.into())),
1861 ),
1862 };
1863 let mut fns = list_functions();
1864 fns.extend([
1865 probe(
1866 "j_abc",
1867 call(
1868 "str_join",
1869 vec![ExprSpec::Str(", ".into()), strs(&["a", "b", "c"])],
1870 ),
1871 "a, b, c",
1872 ),
1873 probe(
1874 "j_empty",
1875 call(
1876 "str_join",
1877 vec![
1878 ExprSpec::Str("-".into()),
1879 ExprSpec::ListEmpty { elem: Type::String },
1880 ],
1881 ),
1882 "",
1883 ),
1884 probe(
1885 "j_one",
1886 call(
1887 "str_join",
1888 vec![ExprSpec::Str("-".into()), strs(&["x"])],
1889 ),
1890 "x",
1891 ),
1892 probe(
1893 "r_ab3",
1894 call(
1895 "str_repeat",
1896 vec![ExprSpec::Str("ab".into()), num(3)],
1897 ),
1898 "ababab",
1899 ),
1900 probe(
1901 "r_zero",
1902 call(
1903 "str_repeat",
1904 vec![ExprSpec::Str("z".into()), num(0)],
1905 ),
1906 "",
1907 ),
1908 ]);
1909 let e = Editor::new(Store::open_in_memory().unwrap());
1910 let (m, report) = e
1911 .apply_module(&ModuleSpec {
1912 name: "stdjoin".into(),
1913 types: vec![],
1914 functions: fns,
1915 })
1916 .unwrap();
1917 assert!(report.ok(), "violations: {:?}", report.violations);
1918 let wasm = lower(e.store(), &m).unwrap();
1919 for n in ["j_abc", "j_empty", "j_one", "r_ab3", "r_zero"] {
1920 assert_eq!(run_i64(&wasm, n, &[]).unwrap(), 1, "{n}");
1921 }
1922 }
1923
1924 #[test]
1927 fn the_stdlib_trims_and_replaces() {
1928 let probe = |name: &str, got: ExprSpec, want: &str| FunctionSpec {
1929 name: name.into(),
1930 type_params: vec![],
1931 params: vec![],
1932 produces: ext(Type::Bool),
1933 requires: BTreeSet::new(),
1934 on_failure: vec![],
1935 steps: vec![],
1936 result: ExprSpec::StrEq(
1937 Box::new(got),
1938 Box::new(ExprSpec::Str(want.into())),
1939 ),
1940 };
1941 let s = |x: &str| ExprSpec::Str(x.into());
1942 let tr = |x: &str| call("str_trim", vec![s(x)]);
1943 let rp = |a: &str, b: &str, c: &str| {
1944 call("str_replace", vec![s(a), s(b), s(c)])
1945 };
1946 let mut fns = list_functions();
1947 fns.extend([
1948 probe("t_pad", tr(" hi "), "hi"),
1949 probe("t_id", tr("x"), "x"),
1950 probe("t_allws", tr(" "), ""),
1951 probe("t_mixed", tr("\t a \n"), "a"),
1952 probe("t_empty", tr(""), ""),
1953 probe("rp_dots", rp("a.b.c", ".", "-"), "a-b-c"),
1954 probe("rp_grow", rp("aaa", "a", "bb"), "bbbbbb"),
1955 probe("rp_none", rp("none", "x", "y"), "none"),
1956 probe("rp_empty_from", rp("abc", "", "-"), "abc"),
1957 ]);
1958 let e = Editor::new(Store::open_in_memory().unwrap());
1959 let (m, report) = e
1960 .apply_module(&ModuleSpec {
1961 name: "stdtrim".into(),
1962 types: vec![],
1963 functions: fns,
1964 })
1965 .unwrap();
1966 assert!(report.ok(), "violations: {:?}", report.violations);
1967 let wasm = lower(e.store(), &m).unwrap();
1968 for n in [
1969 "t_pad",
1970 "t_id",
1971 "t_allws",
1972 "t_mixed",
1973 "t_empty",
1974 "rp_dots",
1975 "rp_grow",
1976 "rp_none",
1977 "rp_empty_from",
1978 ] {
1979 assert_eq!(run_i64(&wasm, n, &[]).unwrap(), 1, "{n}");
1980 }
1981 }
1982
1983 #[test]
1986 fn the_stdlib_first_and_last() {
1987 let nums = |vs: &[i64]| ExprSpec::List(vs.iter().copied().map(num).collect());
1988 let opt_or = |call_e: ExprSpec| ExprSpec::OptionMatch {
1989 opt: Box::new(call_e),
1990 some_bind: "v".into(),
1991 some_body: Box::new(r("v")),
1992 none_body: Box::new(num(-1)),
1993 };
1994 let nf = |name: &str, result: ExprSpec| FunctionSpec {
1995 name: name.into(),
1996 type_params: vec![],
1997 params: vec![],
1998 produces: ext(Type::Number),
1999 requires: BTreeSet::new(),
2000 on_failure: vec![],
2001 steps: vec![],
2002 result,
2003 };
2004 let mut fns = list_functions();
2005 fns.extend([
2006 nf("f_hit", opt_or(call("first", vec![nums(&[7, 8, 9])]))), nf("f_miss", opt_or(call("first", vec![empty(Type::Number)]))), nf("l_hit", opt_or(call("last", vec![nums(&[7, 8, 9])]))), nf("l_one", opt_or(call("last", vec![nums(&[5])]))), nf("l_miss", opt_or(call("last", vec![empty(Type::Number)]))), ]);
2012 let e = Editor::new(Store::open_in_memory().unwrap());
2013 let (m, report) = e
2014 .apply_module(&ModuleSpec {
2015 name: "stdopt".into(),
2016 types: vec![],
2017 functions: fns,
2018 })
2019 .unwrap();
2020 assert!(report.ok(), "violations: {:?}", report.violations);
2021 let wasm = lower(e.store(), &m).unwrap();
2022 assert_eq!(run_i64(&wasm, "f_hit", &[]).unwrap(), 7);
2023 assert_eq!(run_i64(&wasm, "f_miss", &[]).unwrap(), -1);
2024 assert_eq!(run_i64(&wasm, "l_hit", &[]).unwrap(), 9);
2025 assert_eq!(run_i64(&wasm, "l_one", &[]).unwrap(), 5);
2026 assert_eq!(run_i64(&wasm, "l_miss", &[]).unwrap(), -1);
2027 }
2028
2029 #[test]
2031 fn the_stdlib_opt_map_and_then() {
2032 let lam = |params: Vec<(&str, Type)>, body: ExprSpec| ExprSpec::Lambda {
2033 params: params
2034 .into_iter()
2035 .map(|(n, t)| (n.to_string(), t))
2036 .collect(),
2037 body: Box::new(body),
2038 };
2039 let bin = |op, a: ExprSpec, b: ExprSpec| ExprSpec::BinOp {
2040 op,
2041 lhs: Box::new(a),
2042 rhs: Box::new(b),
2043 };
2044 let som = |v: ExprSpec| ExprSpec::OptionSome(Box::new(v));
2045 let non = || ExprSpec::OptionNone { elem: Type::Number };
2046 let unwrap = |o: ExprSpec| ExprSpec::OptionMatch {
2047 opt: Box::new(o),
2048 some_bind: "v".into(),
2049 some_body: Box::new(r("v")),
2050 none_body: Box::new(num(-1)),
2051 };
2052 let dbl = || lam(vec![("x", Type::Number)], bin(BinOp::Mul, r("x"), num(2)));
2053 let pos10 = || {
2055 lam(
2056 vec![("x", Type::Number)],
2057 ExprSpec::If {
2058 cond: Box::new(bin(BinOp::Gt, r("x"), num(0))),
2059 then_branch: Box::new(som(bin(BinOp::Mul, r("x"), num(10)))),
2060 else_branch: Box::new(non()),
2061 },
2062 )
2063 };
2064 let nf = |name: &str, result: ExprSpec| FunctionSpec {
2065 name: name.into(),
2066 type_params: vec![],
2067 params: vec![],
2068 produces: ext(Type::Number),
2069 requires: BTreeSet::new(),
2070 on_failure: vec![],
2071 steps: vec![],
2072 result,
2073 };
2074 let mut fns = list_functions();
2075 fns.extend([
2076 nf("m_some", unwrap(call("opt_map", vec![dbl(), som(num(5))]))), nf("m_none", unwrap(call("opt_map", vec![dbl(), non()]))), nf("t_some", unwrap(call("opt_then", vec![pos10(), som(num(3))]))), nf("t_zero", unwrap(call("opt_then", vec![pos10(), som(num(-1))]))), nf("t_none", unwrap(call("opt_then", vec![pos10(), non()]))), ]);
2082 let e = Editor::new(Store::open_in_memory().unwrap());
2083 let (m, report) = e
2084 .apply_module(&ModuleSpec {
2085 name: "stdoptm".into(),
2086 types: vec![],
2087 functions: fns,
2088 })
2089 .unwrap();
2090 assert!(report.ok(), "violations: {:?}", report.violations);
2091 let wasm = lower(e.store(), &m).unwrap();
2092 assert_eq!(run_i64(&wasm, "m_some", &[]).unwrap(), 10);
2093 assert_eq!(run_i64(&wasm, "m_none", &[]).unwrap(), -1);
2094 assert_eq!(run_i64(&wasm, "t_some", &[]).unwrap(), 30);
2095 assert_eq!(run_i64(&wasm, "t_zero", &[]).unwrap(), -1);
2096 assert_eq!(run_i64(&wasm, "t_none", &[]).unwrap(), -1);
2097 }
2098
2099 #[test]
2101 fn the_stdlib_integer_helpers() {
2102 let nf = |name: &str, result: ExprSpec| FunctionSpec {
2103 name: name.into(),
2104 type_params: vec![],
2105 params: vec![],
2106 produces: ext(Type::Number),
2107 requires: BTreeSet::new(),
2108 on_failure: vec![],
2109 steps: vec![],
2110 result,
2111 };
2112 let mut fns = list_functions();
2113 fns.extend([
2114 nf("ab_neg", call("iabs", vec![num(-7)])), nf("ab_pos", call("iabs", vec![num(3)])), nf("mn", call("imin", vec![num(2), num(5)])), nf("mx", call("imax", vec![num(2), num(5)])), nf("c_hi", call("clamp", vec![num(10), num(0), num(5)])), nf("c_lo", call("clamp", vec![num(-3), num(0), num(5)])), nf("c_in", call("clamp", vec![num(3), num(0), num(5)])), ]);
2122 let e = Editor::new(Store::open_in_memory().unwrap());
2123 let (m, report) = e
2124 .apply_module(&ModuleSpec {
2125 name: "stdnum".into(),
2126 types: vec![],
2127 functions: fns,
2128 })
2129 .unwrap();
2130 assert!(report.ok(), "violations: {:?}", report.violations);
2131 let wasm = lower(e.store(), &m).unwrap();
2132 for (n, want) in [
2133 ("ab_neg", 7),
2134 ("ab_pos", 3),
2135 ("mn", 2),
2136 ("mx", 5),
2137 ("c_hi", 5),
2138 ("c_lo", 0),
2139 ("c_in", 3),
2140 ] {
2141 assert_eq!(run_i64(&wasm, n, &[]).unwrap(), want, "{n}");
2142 }
2143 }
2144
2145 #[test]
2147 fn the_stdlib_ipow_and_gcd() {
2148 let nf = |name: &str, result: ExprSpec| FunctionSpec {
2149 name: name.into(),
2150 type_params: vec![],
2151 params: vec![],
2152 produces: ext(Type::Number),
2153 requires: BTreeSet::new(),
2154 on_failure: vec![],
2155 steps: vec![],
2156 result,
2157 };
2158 let mut fns = list_functions();
2159 fns.extend([
2160 nf("p_2_10", call("ipow", vec![num(2), num(10)])), nf("p_5_0", call("ipow", vec![num(5), num(0)])), nf("p_3_3", call("ipow", vec![num(3), num(3)])), nf("p_neg", call("ipow", vec![num(7), num(-1)])), nf("g_12_8", call("gcd", vec![num(12), num(8)])), nf("g_54_24", call("gcd", vec![num(54), num(24)])), nf("g_7_0", call("gcd", vec![num(7), num(0)])), nf("g_0_0", call("gcd", vec![num(0), num(0)])), nf("g_neg", call("gcd", vec![num(-12), num(8)])), ]);
2170 let e = Editor::new(Store::open_in_memory().unwrap());
2171 let (m, report) = e
2172 .apply_module(&ModuleSpec {
2173 name: "stdpow".into(),
2174 types: vec![],
2175 functions: fns,
2176 })
2177 .unwrap();
2178 assert!(report.ok(), "violations: {:?}", report.violations);
2179 let wasm = lower(e.store(), &m).unwrap();
2180 for (n, want) in [
2181 ("p_2_10", 1024),
2182 ("p_5_0", 1),
2183 ("p_3_3", 27),
2184 ("p_neg", 1),
2185 ("g_12_8", 4),
2186 ("g_54_24", 6),
2187 ("g_7_0", 7),
2188 ("g_0_0", 0),
2189 ("g_neg", 4),
2190 ] {
2191 assert_eq!(run_i64(&wasm, n, &[]).unwrap(), want, "{n}");
2192 }
2193 }
2194
2195 #[test]
2198 fn decimal_round_str_formats() {
2199 let probe = |name: &str, v: f64, places: i64, want: &str| FunctionSpec {
2200 name: name.into(),
2201 type_params: vec![],
2202 params: vec![],
2203 produces: ext(Type::Bool),
2204 requires: BTreeSet::new(),
2205 on_failure: vec![],
2206 steps: vec![],
2207 result: ExprSpec::StrEq(
2208 Box::new(call(
2209 "decimal_round_str",
2210 vec![ExprSpec::Decimal(v), num(places)],
2211 )),
2212 Box::new(ExprSpec::Str(want.into())),
2213 ),
2214 };
2215 let cases = [
2216 ("a", 1.25, 1, "1.3"),
2217 ("b", 1.24, 1, "1.2"),
2218 ("c", 19.99, 1, "20.0"),
2219 ("d", 1.255, 2, "1.26"),
2220 ("e", -1.255, 2, "-1.26"),
2221 ("f", 2.5, 0, "3"),
2222 ("g", 1.4, 0, "1"),
2223 ("h", 1.2345, 4, "1.2345"),
2224 ("i", 0.0, 2, "0.00"),
2225 ];
2226 let mut fns = list_functions();
2227 fns.extend(cases.iter().map(|(n, v, pl, w)| probe(n, *v, *pl, w)));
2228 let e = Editor::new(Store::open_in_memory().unwrap());
2229 let (m, report) = e
2230 .apply_module(&ModuleSpec {
2231 name: "stddr".into(),
2232 types: vec![],
2233 functions: fns,
2234 })
2235 .unwrap();
2236 assert!(report.ok(), "violations: {:?}", report.violations);
2237 let wasm = lower(e.store(), &m).unwrap();
2238 for (n, _, _, w) in cases {
2239 assert_eq!(
2240 run_i64(&wasm, n, &[]).unwrap(),
2241 1,
2242 "decimal_round_str should produce {w}"
2243 );
2244 }
2245 }
2246}