1use crate::node::{Node, NodeHash};
18use crate::store::Store;
19use std::cell::RefCell;
20use std::collections::{HashMap, HashSet};
21use walrus::ir::{BinaryOp, InstrSeqType, LoadKind, MemArg, StoreKind, UnaryOp, Value};
22use walrus::{
23 ConstExpr, ElementItems, ElementKind, FunctionBuilder, FunctionId, GlobalId, InstrSeqBuilder,
24 LocalId, MemoryId, Module, RefType, TableId, TypeId, ValType,
25};
26
27#[derive(Debug)]
30pub enum LowerError {
31 Store(crate::store::Error),
32 NotAModule,
34 Hole,
36 Effectful(String),
38 UnknownCallee(String),
40 UnresolvedRef(String),
42 Unsupported(&'static str),
44}
45
46impl std::fmt::Display for LowerError {
47 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48 match self {
49 LowerError::Store(e) => write!(f, "store error: {e}"),
50 LowerError::NotAModule => write!(f, "lowering root must be a Module"),
51 LowerError::Hole => write!(f, "cannot lower an incomplete program (hole)"),
52 LowerError::Effectful(n) => {
53 write!(f, "function `{n}` declares effects; step 3 lowers pure functions only")
54 }
55 LowerError::UnknownCallee(n) => write!(f, "call to unknown function `{n}`"),
56 LowerError::UnresolvedRef(n) => write!(f, "unresolved reference: `{n}`"),
57 LowerError::Unsupported(w) => write!(f, "not lowerable yet: {w}"),
58 }
59 }
60}
61
62impl std::error::Error for LowerError {}
63
64impl From<crate::store::Error> for LowerError {
65 fn from(e: crate::store::Error) -> Self {
66 LowerError::Store(e)
67 }
68}
69
70type LowerResult<T> = std::result::Result<T, LowerError>;
71
72struct Ctx<'a> {
77 store: &'a Store,
78 fns: &'a HashMap<String, FunctionId>,
79 fn_table_idx: &'a HashMap<String, i64>,
81 lambdas: &'a HashMap<NodeHash, (i64, Vec<String>)>,
83 func_table: TableId,
85 indirect_types: &'a HashMap<usize, TypeId>,
88 recs: &'a HashMap<String, Vec<String>>,
89 vars: &'a HashMap<String, Vec<(String, Vec<String>)>>,
91 ftags: &'a HashMap<String, i64>,
93 fallible: &'a HashSet<String>,
96 locals: &'a RefCell<&'a mut walrus::ModuleLocals>,
99 alloc: FunctionId,
100 set: FunctionId,
101 get: FunctionId,
102 map_get: FunctionId,
103 map_try_get: FunctionId,
104 now: FunctionId,
107 log: FunctionId,
110 publish: FunctionId,
113 set_header: FunctionId,
117 rand: FunctionId,
119 disk_write: FunctionId,
122 disk_read: FunctionId,
123 net_get: FunctionId,
125 db_query: FunctionId,
127 str_concat: FunctionId,
130 str_slice: FunctionId,
131 str_lower: FunctionId,
134 str_from_code: FunctionId,
137 str_eq: FunctionId,
138 str_contains: FunctionId,
139 str_starts_with: FunctionId,
140 str_index_of: FunctionId,
141 num_to_str: FunctionId,
142 str_to_num: FunctionId,
143 str_to_num_opt: FunctionId,
145 list_cons: FunctionId,
147 list_get: FunctionId,
149 list_try_get: FunctionId,
151}
152
153#[derive(Clone, Copy)]
156enum Slot {
157 Local(LocalId),
158 Member { base: LocalId, off: i64 },
159}
160
161fn build_alloc(wasm: &mut Module, bump: GlobalId, mem: MemoryId) -> FunctionId {
164 let mut fb = FunctionBuilder::new(&mut wasm.types, &[ValType::I64], &[ValType::I64]);
165 let size = wasm.locals.add(ValType::I64);
166 let old = wasm.locals.add(ValType::I64);
167 let new = wasm.locals.add(ValType::I64);
168 let cur = wasm.locals.add(ValType::I64);
169 {
170 let mut b = fb.func_body();
171 b.global_get(bump).local_set(old);
173 b.local_get(old)
174 .local_get(size)
175 .binop(BinaryOp::I64Add)
176 .local_set(new);
177 b.local_get(new).global_set(bump);
178 b.memory_size(mem)
182 .unop(UnaryOp::I64ExtendUI32)
183 .i64_const(65536)
184 .binop(BinaryOp::I64Mul)
185 .local_set(cur);
186 b.local_get(new).local_get(cur).binop(BinaryOp::I64GtS);
187 b.if_else(
188 InstrSeqType::Simple(None),
189 |t| {
190 t.local_get(new)
192 .local_get(cur)
193 .binop(BinaryOp::I64Sub)
194 .i64_const(65535)
195 .binop(BinaryOp::I64Add)
196 .i64_const(65536)
197 .binop(BinaryOp::I64DivS)
198 .unop(UnaryOp::I32WrapI64);
199 t.memory_grow(mem);
200 t.drop(); },
202 |_| {},
203 );
204 b.local_get(old); }
206 fb.finish(vec![size], &mut wasm.funcs)
207}
208
209fn build_set(wasm: &mut Module, mem: MemoryId) -> FunctionId {
212 let mut fb = FunctionBuilder::new(
213 &mut wasm.types,
214 &[ValType::I64, ValType::I64, ValType::I64],
215 &[ValType::I64],
216 );
217 let p = wasm.locals.add(ValType::I64);
218 let off = wasm.locals.add(ValType::I64);
219 let v = wasm.locals.add(ValType::I64);
220 {
221 let mut b = fb.func_body();
222 b.local_get(p)
223 .local_get(off)
224 .binop(BinaryOp::I64Add)
225 .unop(UnaryOp::I32WrapI64) .local_get(v)
227 .store(
228 mem,
229 StoreKind::I64 { atomic: false },
230 MemArg { align: 3, offset: 0 },
231 )
232 .local_get(p); }
234 fb.finish(vec![p, off, v], &mut wasm.funcs)
235}
236
237fn build_get(wasm: &mut Module, mem: MemoryId) -> FunctionId {
239 let mut fb = FunctionBuilder::new(
240 &mut wasm.types,
241 &[ValType::I64, ValType::I64],
242 &[ValType::I64],
243 );
244 let p = wasm.locals.add(ValType::I64);
245 let off = wasm.locals.add(ValType::I64);
246 {
247 let mut b = fb.func_body();
248 b.local_get(p)
249 .local_get(off)
250 .binop(BinaryOp::I64Add)
251 .unop(UnaryOp::I32WrapI64)
252 .load(
253 mem,
254 LoadKind::I64 { atomic: false },
255 MemArg { align: 3, offset: 0 },
256 );
257 }
258 fb.finish(vec![p, off], &mut wasm.funcs)
259}
260
261fn build_map_get(wasm: &mut Module, get: FunctionId) -> FunctionId {
265 let mut fb = FunctionBuilder::new(
266 &mut wasm.types,
267 &[ValType::I64, ValType::I64],
268 &[ValType::I64],
269 );
270 let p = wasm.locals.add(ValType::I64);
271 let key = wasm.locals.add(ValType::I64);
272 let i = wasm.locals.add(ValType::I64);
273 let count = wasm.locals.add(ValType::I64);
274 let k = wasm.locals.add(ValType::I64);
275 let val = wasm.locals.add(ValType::I64);
276 {
277 let mut b = fb.func_body();
278 b.local_get(p).i64_const(0).call(get).local_set(count);
279 b.i64_const(0).local_set(i);
280 b.i64_const(0).local_set(val);
281 b.block(InstrSeqType::Simple(None), |outer| {
282 let oid = outer.id();
283 outer.loop_(InstrSeqType::Simple(None), |lp| {
284 let lid = lp.id();
285 lp.local_get(i).local_get(count).binop(BinaryOp::I64GeS);
287 lp.br_if(oid);
288 lp.local_get(p);
290 lp.local_get(i)
291 .i64_const(16)
292 .binop(BinaryOp::I64Mul)
293 .i64_const(8)
294 .binop(BinaryOp::I64Add);
295 lp.call(get);
296 lp.local_set(k);
297 lp.local_get(k).local_get(key).binop(BinaryOp::I64Eq);
299 lp.if_else(
300 InstrSeqType::Simple(None),
301 |t| {
302 t.local_get(p);
303 t.local_get(i)
304 .i64_const(16)
305 .binop(BinaryOp::I64Mul)
306 .i64_const(16)
307 .binop(BinaryOp::I64Add);
308 t.call(get);
309 t.local_set(val);
310 t.br(oid);
311 },
312 |_| {},
313 );
314 lp.local_get(i)
316 .i64_const(1)
317 .binop(BinaryOp::I64Add)
318 .local_set(i);
319 lp.br(lid);
320 });
321 });
322 b.local_get(val);
323 }
324 fb.finish(vec![p, key], &mut wasm.funcs)
325}
326
327fn build_map_try_get(
331 wasm: &mut Module,
332 get: FunctionId,
333 set: FunctionId,
334 alloc: FunctionId,
335) -> FunctionId {
336 let mut fb = FunctionBuilder::new(
337 &mut wasm.types,
338 &[ValType::I64, ValType::I64],
339 &[ValType::I64],
340 );
341 let p = wasm.locals.add(ValType::I64);
342 let key = wasm.locals.add(ValType::I64);
343 let i = wasm.locals.add(ValType::I64);
344 let count = wasm.locals.add(ValType::I64);
345 let k = wasm.locals.add(ValType::I64);
346 let val = wasm.locals.add(ValType::I64);
347 let found = wasm.locals.add(ValType::I64);
348 let opt = wasm.locals.add(ValType::I64);
349 {
350 let mut b = fb.func_body();
351 b.local_get(p).i64_const(0).call(get).local_set(count);
352 b.i64_const(0).local_set(i);
353 b.i64_const(0).local_set(found);
354 b.i64_const(0).local_set(val);
355 b.block(InstrSeqType::Simple(None), |outer| {
356 let oid = outer.id();
357 outer.loop_(InstrSeqType::Simple(None), |lp| {
358 let lid = lp.id();
359 lp.local_get(i).local_get(count).binop(BinaryOp::I64GeS);
360 lp.br_if(oid);
361 lp.local_get(p);
362 lp.local_get(i)
363 .i64_const(16)
364 .binop(BinaryOp::I64Mul)
365 .i64_const(8)
366 .binop(BinaryOp::I64Add);
367 lp.call(get);
368 lp.local_set(k);
369 lp.local_get(k).local_get(key).binop(BinaryOp::I64Eq);
370 lp.if_else(
371 InstrSeqType::Simple(None),
372 |t| {
373 t.local_get(p);
374 t.local_get(i)
375 .i64_const(16)
376 .binop(BinaryOp::I64Mul)
377 .i64_const(16)
378 .binop(BinaryOp::I64Add);
379 t.call(get);
380 t.local_set(val);
381 t.i64_const(1).local_set(found);
382 t.br(oid);
383 },
384 |_| {},
385 );
386 lp.local_get(i)
387 .i64_const(1)
388 .binop(BinaryOp::I64Add)
389 .local_set(i);
390 lp.br(lid);
391 });
392 });
393 b.i64_const(16).call(alloc).local_set(opt);
395 b.local_get(found).i64_const(1).binop(BinaryOp::I64Eq);
396 b.if_else(
397 InstrSeqType::Simple(None),
398 |t| {
399 t.local_get(opt).i64_const(0).i64_const(1).call(set).drop();
400 t.local_get(opt).i64_const(8).local_get(val).call(set).drop();
401 },
402 |e| {
403 e.local_get(opt).i64_const(0).i64_const(0).call(set).drop();
404 },
405 );
406 b.local_get(opt);
407 }
408 fb.finish(vec![p, key], &mut wasm.funcs)
409}
410
411fn build_str_concat(
417 wasm: &mut Module,
418 get: FunctionId,
419 set: FunctionId,
420 alloc: FunctionId,
421) -> FunctionId {
422 let mut fb = FunctionBuilder::new(
423 &mut wasm.types,
424 &[ValType::I64, ValType::I64],
425 &[ValType::I64],
426 );
427 let a = wasm.locals.add(ValType::I64);
428 let b = wasm.locals.add(ValType::I64);
429 let la = wasm.locals.add(ValType::I64);
430 let lb = wasm.locals.add(ValType::I64);
431 let i = wasm.locals.add(ValType::I64);
432 let dst = wasm.locals.add(ValType::I64);
433 {
434 let mut bd = fb.func_body();
435 bd.local_get(a).i64_const(0).call(get).local_set(la);
436 bd.local_get(b).i64_const(0).call(get).local_set(lb);
437 bd.local_get(la)
438 .local_get(lb)
439 .binop(BinaryOp::I64Add)
440 .i64_const(1)
441 .binop(BinaryOp::I64Add)
442 .i64_const(8)
443 .binop(BinaryOp::I64Mul)
444 .call(alloc)
445 .local_set(dst);
446 bd.local_get(dst)
447 .i64_const(0)
448 .local_get(la)
449 .local_get(lb)
450 .binop(BinaryOp::I64Add)
451 .call(set)
452 .drop();
453 bd.i64_const(0).local_set(i);
455 bd.block(InstrSeqType::Simple(None), |o| {
456 let oid = o.id();
457 o.loop_(InstrSeqType::Simple(None), |l| {
458 let lid = l.id();
459 l.local_get(i).local_get(la).binop(BinaryOp::I64GeS);
460 l.br_if(oid);
461 l.local_get(dst);
462 l.local_get(i)
463 .i64_const(1)
464 .binop(BinaryOp::I64Add)
465 .i64_const(8)
466 .binop(BinaryOp::I64Mul);
467 l.local_get(a);
468 l.local_get(i)
469 .i64_const(1)
470 .binop(BinaryOp::I64Add)
471 .i64_const(8)
472 .binop(BinaryOp::I64Mul);
473 l.call(get);
474 l.call(set);
475 l.drop();
476 l.local_get(i)
477 .i64_const(1)
478 .binop(BinaryOp::I64Add)
479 .local_set(i);
480 l.br(lid);
481 });
482 });
483 bd.i64_const(0).local_set(i);
485 bd.block(InstrSeqType::Simple(None), |o| {
486 let oid = o.id();
487 o.loop_(InstrSeqType::Simple(None), |l| {
488 let lid = l.id();
489 l.local_get(i).local_get(lb).binop(BinaryOp::I64GeS);
490 l.br_if(oid);
491 l.local_get(dst);
492 l.local_get(la)
493 .local_get(i)
494 .binop(BinaryOp::I64Add)
495 .i64_const(1)
496 .binop(BinaryOp::I64Add)
497 .i64_const(8)
498 .binop(BinaryOp::I64Mul);
499 l.local_get(b);
500 l.local_get(i)
501 .i64_const(1)
502 .binop(BinaryOp::I64Add)
503 .i64_const(8)
504 .binop(BinaryOp::I64Mul);
505 l.call(get);
506 l.call(set);
507 l.drop();
508 l.local_get(i)
509 .i64_const(1)
510 .binop(BinaryOp::I64Add)
511 .local_set(i);
512 l.br(lid);
513 });
514 });
515 bd.local_get(dst);
516 }
517 fb.finish(vec![a, b], &mut wasm.funcs)
518}
519
520fn build_str_slice(
522 wasm: &mut Module,
523 get: FunctionId,
524 set: FunctionId,
525 alloc: FunctionId,
526) -> FunctionId {
527 let mut fb = FunctionBuilder::new(
528 &mut wasm.types,
529 &[ValType::I64, ValType::I64, ValType::I64],
530 &[ValType::I64],
531 );
532 let s = wasm.locals.add(ValType::I64);
533 let start = wasm.locals.add(ValType::I64);
534 let n = wasm.locals.add(ValType::I64);
535 let i = wasm.locals.add(ValType::I64);
536 let dst = wasm.locals.add(ValType::I64);
537 let ls = wasm.locals.add(ValType::I64);
538 {
539 let mut bd = fb.func_body();
540 bd.local_get(s).i64_const(0).call(get).local_set(ls);
542 bd.local_get(start).i64_const(0).binop(BinaryOp::I64LtS);
543 bd.local_get(n).i64_const(0).binop(BinaryOp::I64LtS);
544 bd.binop(BinaryOp::I32Or);
545 bd.local_get(start)
546 .local_get(n)
547 .binop(BinaryOp::I64Add)
548 .local_get(ls)
549 .binop(BinaryOp::I64GtS);
550 bd.binop(BinaryOp::I32Or);
551 bd.if_else(
552 InstrSeqType::Simple(None),
553 |t| {
554 t.unreachable();
555 },
556 |_| {},
557 );
558 bd.local_get(n)
559 .i64_const(1)
560 .binop(BinaryOp::I64Add)
561 .i64_const(8)
562 .binop(BinaryOp::I64Mul)
563 .call(alloc)
564 .local_set(dst);
565 bd.local_get(dst)
566 .i64_const(0)
567 .local_get(n)
568 .call(set)
569 .drop();
570 bd.i64_const(0).local_set(i);
571 bd.block(InstrSeqType::Simple(None), |o| {
572 let oid = o.id();
573 o.loop_(InstrSeqType::Simple(None), |l| {
574 let lid = l.id();
575 l.local_get(i).local_get(n).binop(BinaryOp::I64GeS);
576 l.br_if(oid);
577 l.local_get(dst);
579 l.local_get(i)
580 .i64_const(1)
581 .binop(BinaryOp::I64Add)
582 .i64_const(8)
583 .binop(BinaryOp::I64Mul);
584 l.local_get(s);
585 l.local_get(start)
586 .local_get(i)
587 .binop(BinaryOp::I64Add)
588 .i64_const(1)
589 .binop(BinaryOp::I64Add)
590 .i64_const(8)
591 .binop(BinaryOp::I64Mul);
592 l.call(get);
593 l.call(set);
594 l.drop();
595 l.local_get(i)
596 .i64_const(1)
597 .binop(BinaryOp::I64Add)
598 .local_set(i);
599 l.br(lid);
600 });
601 });
602 bd.local_get(dst);
603 }
604 fb.finish(vec![s, start, n], &mut wasm.funcs)
605}
606
607fn build_str_lower(
611 wasm: &mut Module,
612 get: FunctionId,
613 set: FunctionId,
614 alloc: FunctionId,
615) -> FunctionId {
616 let mut fb = FunctionBuilder::new(
617 &mut wasm.types,
618 &[ValType::I64],
619 &[ValType::I64],
620 );
621 let s = wasm.locals.add(ValType::I64);
622 let n = wasm.locals.add(ValType::I64);
623 let i = wasm.locals.add(ValType::I64);
624 let dst = wasm.locals.add(ValType::I64);
625 let ch = wasm.locals.add(ValType::I64);
626 {
627 let mut bd = fb.func_body();
628 bd.local_get(s).i64_const(0).call(get).local_set(n);
629 bd.local_get(n)
630 .i64_const(1)
631 .binop(BinaryOp::I64Add)
632 .i64_const(8)
633 .binop(BinaryOp::I64Mul)
634 .call(alloc)
635 .local_set(dst);
636 bd.local_get(dst)
637 .i64_const(0)
638 .local_get(n)
639 .call(set)
640 .drop();
641 bd.i64_const(0).local_set(i);
642 bd.block(InstrSeqType::Simple(None), |o| {
643 let oid = o.id();
644 o.loop_(InstrSeqType::Simple(None), |l| {
645 let lid = l.id();
646 l.local_get(i).local_get(n).binop(BinaryOp::I64GeS);
647 l.br_if(oid);
648 l.local_get(s);
650 l.local_get(i)
651 .i64_const(1)
652 .binop(BinaryOp::I64Add)
653 .i64_const(8)
654 .binop(BinaryOp::I64Mul);
655 l.call(get).local_set(ch);
656 l.local_get(ch).i64_const(65).binop(BinaryOp::I64GeS);
658 l.local_get(ch).i64_const(90).binop(BinaryOp::I64LeS);
659 l.binop(BinaryOp::I32And);
660 l.if_else(
661 InstrSeqType::Simple(None),
662 |t| {
663 t.local_get(ch)
664 .i64_const(32)
665 .binop(BinaryOp::I64Add)
666 .local_set(ch);
667 },
668 |_| {},
669 );
670 l.local_get(dst);
672 l.local_get(i)
673 .i64_const(1)
674 .binop(BinaryOp::I64Add)
675 .i64_const(8)
676 .binop(BinaryOp::I64Mul);
677 l.local_get(ch);
678 l.call(set);
679 l.drop();
680 l.local_get(i)
681 .i64_const(1)
682 .binop(BinaryOp::I64Add)
683 .local_set(i);
684 l.br(lid);
685 });
686 });
687 bd.local_get(dst);
688 }
689 fb.finish(vec![s], &mut wasm.funcs)
690}
691
692fn build_str_from_code(
697 wasm: &mut Module,
698 set: FunctionId,
699 alloc: FunctionId,
700) -> FunctionId {
701 let mut fb = FunctionBuilder::new(
702 &mut wasm.types,
703 &[ValType::I64],
704 &[ValType::I64],
705 );
706 let code = wasm.locals.add(ValType::I64);
707 let dst = wasm.locals.add(ValType::I64);
708 {
709 let mut bd = fb.func_body();
710 bd.i64_const(16).call(alloc).local_set(dst);
712 bd.local_get(dst)
713 .i64_const(0)
714 .i64_const(1)
715 .call(set)
716 .drop();
717 bd.local_get(dst)
718 .i64_const(8)
719 .local_get(code)
720 .call(set)
721 .drop();
722 bd.local_get(dst);
723 }
724 fb.finish(vec![code], &mut wasm.funcs)
725}
726
727fn build_list_get(wasm: &mut Module, get: FunctionId) -> FunctionId {
734 let mut fb = FunctionBuilder::new(
735 &mut wasm.types,
736 &[ValType::I64, ValType::I64],
737 &[ValType::I64],
738 );
739 let ptr = wasm.locals.add(ValType::I64);
740 let idx = wasm.locals.add(ValType::I64);
741 let len = wasm.locals.add(ValType::I64);
742 {
743 let mut bd = fb.func_body();
744 bd.local_get(ptr).i64_const(0).call(get).local_set(len);
745 bd.local_get(idx).i64_const(0).binop(BinaryOp::I64LtS);
747 bd.local_get(idx).local_get(len).binop(BinaryOp::I64GeS);
748 bd.binop(BinaryOp::I32Or);
749 bd.if_else(
750 InstrSeqType::Simple(None),
751 |t| {
752 t.unreachable();
753 },
754 |_| {},
755 );
756 bd.local_get(ptr);
757 bd.local_get(idx)
758 .i64_const(1)
759 .binop(BinaryOp::I64Add)
760 .i64_const(8)
761 .binop(BinaryOp::I64Mul);
762 bd.call(get); }
764 fb.finish(vec![ptr, idx], &mut wasm.funcs)
765}
766
767fn build_list_try_get(
771 wasm: &mut Module,
772 get: FunctionId,
773 set: FunctionId,
774 alloc: FunctionId,
775) -> FunctionId {
776 let mut fb = FunctionBuilder::new(
777 &mut wasm.types,
778 &[ValType::I64, ValType::I64],
779 &[ValType::I64],
780 );
781 let ptr = wasm.locals.add(ValType::I64);
782 let idx = wasm.locals.add(ValType::I64);
783 let len = wasm.locals.add(ValType::I64);
784 let opt = wasm.locals.add(ValType::I64);
785 {
786 let mut bd = fb.func_body();
787 bd.local_get(ptr).i64_const(0).call(get).local_set(len);
788 bd.i64_const(16).call(alloc).local_set(opt);
789 bd.local_get(idx).i64_const(0).binop(BinaryOp::I64LtS);
790 bd.local_get(idx).local_get(len).binop(BinaryOp::I64GeS);
791 bd.binop(BinaryOp::I32Or);
792 bd.if_else(
793 InstrSeqType::Simple(None),
794 |t| {
795 t.local_get(opt).i64_const(0).i64_const(0).call(set).drop();
797 },
798 |e| {
799 e.local_get(opt).i64_const(0).i64_const(1).call(set).drop();
801 e.local_get(opt).i64_const(8);
802 e.local_get(ptr);
803 e.local_get(idx)
804 .i64_const(1)
805 .binop(BinaryOp::I64Add)
806 .i64_const(8)
807 .binop(BinaryOp::I64Mul);
808 e.call(get);
809 e.call(set).drop();
810 },
811 );
812 bd.local_get(opt);
813 }
814 fb.finish(vec![ptr, idx], &mut wasm.funcs)
815}
816
817fn build_str_eq(wasm: &mut Module, get: FunctionId) -> FunctionId {
819 let mut fb = FunctionBuilder::new(
820 &mut wasm.types,
821 &[ValType::I64, ValType::I64],
822 &[ValType::I64],
823 );
824 let a = wasm.locals.add(ValType::I64);
825 let b = wasm.locals.add(ValType::I64);
826 let la = wasm.locals.add(ValType::I64);
827 let lb = wasm.locals.add(ValType::I64);
828 let i = wasm.locals.add(ValType::I64);
829 let res = wasm.locals.add(ValType::I64);
830 {
831 let mut bd = fb.func_body();
832 bd.local_get(a).i64_const(0).call(get).local_set(la);
833 bd.local_get(b).i64_const(0).call(get).local_set(lb);
834 bd.i64_const(0).local_set(i);
835 bd.local_get(la).local_get(lb).binop(BinaryOp::I64Ne);
836 bd.if_else(
837 InstrSeqType::Simple(None),
838 |t| {
839 t.i64_const(0).local_set(res);
840 },
841 |e| {
842 e.i64_const(1).local_set(res);
843 e.block(InstrSeqType::Simple(None), |o| {
844 let oid = o.id();
845 o.loop_(InstrSeqType::Simple(None), |l| {
846 let lid = l.id();
847 l.local_get(i).local_get(la).binop(BinaryOp::I64GeS);
848 l.br_if(oid);
849 l.local_get(a);
850 l.local_get(i)
851 .i64_const(1)
852 .binop(BinaryOp::I64Add)
853 .i64_const(8)
854 .binop(BinaryOp::I64Mul);
855 l.call(get);
856 l.local_get(b);
857 l.local_get(i)
858 .i64_const(1)
859 .binop(BinaryOp::I64Add)
860 .i64_const(8)
861 .binop(BinaryOp::I64Mul);
862 l.call(get);
863 l.binop(BinaryOp::I64Ne);
864 l.if_else(
865 InstrSeqType::Simple(None),
866 |t2| {
867 t2.i64_const(0).local_set(res);
868 t2.br(oid);
869 },
870 |_| {},
871 );
872 l.local_get(i)
873 .i64_const(1)
874 .binop(BinaryOp::I64Add)
875 .local_set(i);
876 l.br(lid);
877 });
878 });
879 },
880 );
881 bd.local_get(res);
882 }
883 fb.finish(vec![a, b], &mut wasm.funcs)
884}
885
886fn build_str_contains(wasm: &mut Module, get: FunctionId) -> FunctionId {
888 let mut fb = FunctionBuilder::new(
889 &mut wasm.types,
890 &[ValType::I64, ValType::I64],
891 &[ValType::I64],
892 );
893 let h = wasm.locals.add(ValType::I64);
894 let n = wasm.locals.add(ValType::I64);
895 let lh = wasm.locals.add(ValType::I64);
896 let ln = wasm.locals.add(ValType::I64);
897 let i = wasm.locals.add(ValType::I64);
898 let j = wasm.locals.add(ValType::I64);
899 let m = wasm.locals.add(ValType::I64);
900 let res = wasm.locals.add(ValType::I64);
901 {
902 let mut bd = fb.func_body();
903 bd.local_get(h).i64_const(0).call(get).local_set(lh);
904 bd.local_get(n).i64_const(0).call(get).local_set(ln);
905 bd.local_get(ln).i64_const(0).binop(BinaryOp::I64Eq);
907 bd.if_else(
908 InstrSeqType::Simple(None),
909 |t| {
910 t.i64_const(1).local_set(res);
912 },
913 |e| {
914 e.local_get(ln).local_get(lh).binop(BinaryOp::I64LeS);
916 e.if_else(
917 InstrSeqType::Simple(None),
918 |s2| {
919 s2.i64_const(0).local_set(i);
920 s2.block(InstrSeqType::Simple(None), |o| {
921 let oid = o.id();
922 o.loop_(InstrSeqType::Simple(None), |l| {
923 let lid = l.id();
924 l.local_get(i)
926 .local_get(ln)
927 .binop(BinaryOp::I64Add)
928 .local_get(lh)
929 .binop(BinaryOp::I64GtS);
930 l.br_if(oid);
931 l.i64_const(1).local_set(m);
933 l.i64_const(0).local_set(j);
934 l.block(InstrSeqType::Simple(None), |ib| {
935 let ibid = ib.id();
936 ib.loop_(InstrSeqType::Simple(None), |il| {
937 let ilid = il.id();
938 il.local_get(j)
939 .local_get(ln)
940 .binop(BinaryOp::I64GeS);
941 il.br_if(ibid);
942 il.local_get(h);
944 il.local_get(i)
945 .local_get(j)
946 .binop(BinaryOp::I64Add)
947 .i64_const(1)
948 .binop(BinaryOp::I64Add)
949 .i64_const(8)
950 .binop(BinaryOp::I64Mul);
951 il.call(get);
952 il.local_get(n);
953 il.local_get(j)
954 .i64_const(1)
955 .binop(BinaryOp::I64Add)
956 .i64_const(8)
957 .binop(BinaryOp::I64Mul);
958 il.call(get);
959 il.binop(BinaryOp::I64Ne);
960 il.if_else(
961 InstrSeqType::Simple(None),
962 |mm| {
963 mm.i64_const(0).local_set(m);
964 mm.br(ibid);
965 },
966 |_| {},
967 );
968 il.local_get(j)
969 .i64_const(1)
970 .binop(BinaryOp::I64Add)
971 .local_set(j);
972 il.br(ilid);
973 });
974 });
975 l.local_get(m).i64_const(1).binop(BinaryOp::I64Eq);
977 l.if_else(
978 InstrSeqType::Simple(None),
979 |fm| {
980 fm.i64_const(1).local_set(res);
981 fm.br(oid);
982 },
983 |_| {},
984 );
985 l.local_get(i)
986 .i64_const(1)
987 .binop(BinaryOp::I64Add)
988 .local_set(i);
989 l.br(lid);
990 });
991 });
992 },
993 |_| {},
994 );
995 },
996 );
997 bd.local_get(res);
998 }
999 fb.finish(vec![h, n], &mut wasm.funcs)
1000}
1001
1002fn build_str_index_of(wasm: &mut Module, get: FunctionId) -> FunctionId {
1006 let mut fb = FunctionBuilder::new(
1007 &mut wasm.types,
1008 &[ValType::I64, ValType::I64],
1009 &[ValType::I64],
1010 );
1011 let h = wasm.locals.add(ValType::I64);
1012 let n = wasm.locals.add(ValType::I64);
1013 let lh = wasm.locals.add(ValType::I64);
1014 let ln = wasm.locals.add(ValType::I64);
1015 let i = wasm.locals.add(ValType::I64);
1016 let j = wasm.locals.add(ValType::I64);
1017 let m = wasm.locals.add(ValType::I64);
1018 let res = wasm.locals.add(ValType::I64);
1019 {
1020 let mut bd = fb.func_body();
1021 bd.local_get(h).i64_const(0).call(get).local_set(lh);
1022 bd.local_get(n).i64_const(0).call(get).local_set(ln);
1023 bd.i64_const(-1).local_set(res); bd.local_get(ln).i64_const(0).binop(BinaryOp::I64Eq);
1025 bd.if_else(
1026 InstrSeqType::Simple(None),
1027 |t| {
1028 t.i64_const(0).local_set(res);
1030 },
1031 |e| {
1032 e.local_get(ln).local_get(lh).binop(BinaryOp::I64LeS);
1033 e.if_else(
1034 InstrSeqType::Simple(None),
1035 |s2| {
1036 s2.i64_const(0).local_set(i);
1037 s2.block(InstrSeqType::Simple(None), |o| {
1038 let oid = o.id();
1039 o.loop_(InstrSeqType::Simple(None), |l| {
1040 let lid = l.id();
1041 l.local_get(i)
1042 .local_get(ln)
1043 .binop(BinaryOp::I64Add)
1044 .local_get(lh)
1045 .binop(BinaryOp::I64GtS);
1046 l.br_if(oid);
1047 l.i64_const(1).local_set(m);
1048 l.i64_const(0).local_set(j);
1049 l.block(InstrSeqType::Simple(None), |ib| {
1050 let ibid = ib.id();
1051 ib.loop_(InstrSeqType::Simple(None), |il| {
1052 let ilid = il.id();
1053 il.local_get(j)
1054 .local_get(ln)
1055 .binop(BinaryOp::I64GeS);
1056 il.br_if(ibid);
1057 il.local_get(h);
1058 il.local_get(i)
1059 .local_get(j)
1060 .binop(BinaryOp::I64Add)
1061 .i64_const(1)
1062 .binop(BinaryOp::I64Add)
1063 .i64_const(8)
1064 .binop(BinaryOp::I64Mul);
1065 il.call(get);
1066 il.local_get(n);
1067 il.local_get(j)
1068 .i64_const(1)
1069 .binop(BinaryOp::I64Add)
1070 .i64_const(8)
1071 .binop(BinaryOp::I64Mul);
1072 il.call(get);
1073 il.binop(BinaryOp::I64Ne);
1074 il.if_else(
1075 InstrSeqType::Simple(None),
1076 |mm| {
1077 mm.i64_const(0).local_set(m);
1078 mm.br(ibid);
1079 },
1080 |_| {},
1081 );
1082 il.local_get(j)
1083 .i64_const(1)
1084 .binop(BinaryOp::I64Add)
1085 .local_set(j);
1086 il.br(ilid);
1087 });
1088 });
1089 l.local_get(m).i64_const(1).binop(BinaryOp::I64Eq);
1090 l.if_else(
1091 InstrSeqType::Simple(None),
1092 |fm| {
1093 fm.local_get(i).local_set(res);
1094 fm.br(oid);
1095 },
1096 |_| {},
1097 );
1098 l.local_get(i)
1099 .i64_const(1)
1100 .binop(BinaryOp::I64Add)
1101 .local_set(i);
1102 l.br(lid);
1103 });
1104 });
1105 },
1106 |_| {},
1107 );
1108 },
1109 );
1110 bd.local_get(res);
1111 }
1112 fb.finish(vec![h, n], &mut wasm.funcs)
1113}
1114
1115fn build_str_starts_with(wasm: &mut Module, get: FunctionId) -> FunctionId {
1119 let mut fb = FunctionBuilder::new(
1120 &mut wasm.types,
1121 &[ValType::I64, ValType::I64],
1122 &[ValType::I64],
1123 );
1124 let s = wasm.locals.add(ValType::I64);
1125 let p = wasm.locals.add(ValType::I64);
1126 let ls = wasm.locals.add(ValType::I64);
1127 let lp = wasm.locals.add(ValType::I64);
1128 let i = wasm.locals.add(ValType::I64);
1129 let res = wasm.locals.add(ValType::I64);
1130 {
1131 let mut bd = fb.func_body();
1132 bd.local_get(s).i64_const(0).call(get).local_set(ls);
1133 bd.local_get(p).i64_const(0).call(get).local_set(lp);
1134 bd.i64_const(0).local_set(i);
1135 bd.local_get(lp).local_get(ls).binop(BinaryOp::I64GtS);
1137 bd.if_else(
1138 InstrSeqType::Simple(None),
1139 |t| {
1140 t.i64_const(0).local_set(res);
1141 },
1142 |e| {
1143 e.i64_const(1).local_set(res);
1144 e.block(InstrSeqType::Simple(None), |o| {
1145 let oid = o.id();
1146 o.loop_(InstrSeqType::Simple(None), |l| {
1147 let lid = l.id();
1148 l.local_get(i).local_get(lp).binop(BinaryOp::I64GeS);
1149 l.br_if(oid);
1150 l.local_get(s);
1151 l.local_get(i)
1152 .i64_const(1)
1153 .binop(BinaryOp::I64Add)
1154 .i64_const(8)
1155 .binop(BinaryOp::I64Mul);
1156 l.call(get);
1157 l.local_get(p);
1158 l.local_get(i)
1159 .i64_const(1)
1160 .binop(BinaryOp::I64Add)
1161 .i64_const(8)
1162 .binop(BinaryOp::I64Mul);
1163 l.call(get);
1164 l.binop(BinaryOp::I64Ne);
1165 l.if_else(
1166 InstrSeqType::Simple(None),
1167 |t2| {
1168 t2.i64_const(0).local_set(res);
1169 t2.br(oid);
1170 },
1171 |_| {},
1172 );
1173 l.local_get(i)
1174 .i64_const(1)
1175 .binop(BinaryOp::I64Add)
1176 .local_set(i);
1177 l.br(lid);
1178 });
1179 });
1180 },
1181 );
1182 bd.local_get(res);
1183 }
1184 fb.finish(vec![s, p], &mut wasm.funcs)
1185}
1186
1187fn build_num_to_str(
1192 wasm: &mut Module,
1193 set: FunctionId,
1194 alloc: FunctionId,
1195) -> FunctionId {
1196 let mut fb =
1197 FunctionBuilder::new(&mut wasm.types, &[ValType::I64], &[ValType::I64]);
1198 let n = wasm.locals.add(ValType::I64);
1199 let neg = wasm.locals.add(ValType::I64);
1200 let m = wasm.locals.add(ValType::I64);
1201 let cnt = wasm.locals.add(ValType::I64);
1202 let len = wasm.locals.add(ValType::I64);
1203 let dst = wasm.locals.add(ValType::I64);
1204 let i = wasm.locals.add(ValType::I64);
1205 let q = wasm.locals.add(ValType::I64);
1206 {
1207 let mut bd = fb.func_body();
1208 bd.local_get(n).i64_const(0).binop(BinaryOp::I64LtS);
1211 bd.if_else(
1212 InstrSeqType::Simple(None),
1213 |t| {
1214 t.i64_const(1).local_set(neg);
1215 },
1216 |e| {
1217 e.i64_const(0).local_set(neg);
1218 },
1219 );
1220 bd.local_get(n).i64_const(0).binop(BinaryOp::I64GtS);
1225 bd.if_else(
1226 InstrSeqType::Simple(None),
1227 |t| {
1228 t.i64_const(0)
1229 .local_get(n)
1230 .binop(BinaryOp::I64Sub)
1231 .local_set(m);
1232 },
1233 |e| {
1234 e.local_get(n).local_set(m);
1235 },
1236 );
1237 bd.local_get(m).i64_const(0).binop(BinaryOp::I64Eq);
1238 bd.if_else(
1239 InstrSeqType::Simple(None),
1240 |t| {
1241 t.i64_const(16).call(alloc).local_set(dst);
1243 t.local_get(dst).i64_const(0).i64_const(1).call(set).drop();
1244 t.local_get(dst).i64_const(8).i64_const(48).call(set).drop();
1245 },
1246 |e| {
1247 e.i64_const(0).local_set(cnt);
1249 e.local_get(m).local_set(q);
1250 e.block(InstrSeqType::Simple(None), |o| {
1251 let oid = o.id();
1252 o.loop_(InstrSeqType::Simple(None), |l| {
1253 let lid = l.id();
1254 l.local_get(cnt)
1255 .i64_const(1)
1256 .binop(BinaryOp::I64Add)
1257 .local_set(cnt);
1258 l.local_get(q)
1259 .i64_const(10)
1260 .binop(BinaryOp::I64DivS)
1261 .local_set(q);
1262 l.local_get(q).i64_const(0).binop(BinaryOp::I64Eq);
1263 l.br_if(oid);
1264 l.br(lid);
1265 });
1266 });
1267 e.local_get(cnt)
1268 .local_get(neg)
1269 .binop(BinaryOp::I64Add)
1270 .local_set(len);
1271 e.local_get(len)
1272 .i64_const(1)
1273 .binop(BinaryOp::I64Add)
1274 .i64_const(8)
1275 .binop(BinaryOp::I64Mul)
1276 .call(alloc)
1277 .local_set(dst);
1278 e.local_get(dst).i64_const(0).local_get(len).call(set).drop();
1279 e.local_get(len)
1281 .i64_const(1)
1282 .binop(BinaryOp::I64Sub)
1283 .local_set(i);
1284 e.local_get(m).local_set(q);
1285 e.block(InstrSeqType::Simple(None), |o| {
1286 let oid = o.id();
1287 o.loop_(InstrSeqType::Simple(None), |l| {
1288 let lid = l.id();
1289 l.local_get(dst);
1290 l.local_get(i)
1291 .i64_const(1)
1292 .binop(BinaryOp::I64Add)
1293 .i64_const(8)
1294 .binop(BinaryOp::I64Mul);
1295 l.local_get(q)
1298 .i64_const(10)
1299 .binop(BinaryOp::I64RemS)
1300 .i64_const(-1)
1301 .binop(BinaryOp::I64Mul)
1302 .i64_const(48)
1303 .binop(BinaryOp::I64Add);
1304 l.call(set).drop();
1305 l.local_get(q)
1306 .i64_const(10)
1307 .binop(BinaryOp::I64DivS)
1308 .local_set(q);
1309 l.local_get(i)
1310 .i64_const(1)
1311 .binop(BinaryOp::I64Sub)
1312 .local_set(i);
1313 l.local_get(q).i64_const(0).binop(BinaryOp::I64Eq);
1314 l.br_if(oid);
1315 l.br(lid);
1316 });
1317 });
1318 e.local_get(neg).i64_const(0).binop(BinaryOp::I64Ne);
1320 e.if_else(
1321 InstrSeqType::Simple(None),
1322 |t| {
1323 t.local_get(dst)
1324 .i64_const(8)
1325 .i64_const(45)
1326 .call(set)
1327 .drop();
1328 },
1329 |_| {},
1330 );
1331 },
1332 );
1333 bd.local_get(dst);
1334 }
1335 fb.finish(vec![n], &mut wasm.funcs)
1336}
1337
1338fn build_str_to_num(wasm: &mut Module, get: FunctionId) -> FunctionId {
1341 let mut fb =
1342 FunctionBuilder::new(&mut wasm.types, &[ValType::I64], &[ValType::I64]);
1343 let s = wasm.locals.add(ValType::I64);
1344 let ls = wasm.locals.add(ValType::I64);
1345 let i = wasm.locals.add(ValType::I64);
1346 let neg = wasm.locals.add(ValType::I64);
1347 let acc = wasm.locals.add(ValType::I64);
1348 let ch = wasm.locals.add(ValType::I64);
1349 let res = wasm.locals.add(ValType::I64);
1350 {
1351 let mut bd = fb.func_body();
1352 bd.local_get(s).i64_const(0).call(get).local_set(ls);
1353 bd.i64_const(0).local_set(i);
1354 bd.i64_const(0).local_set(neg);
1355 bd.i64_const(0).local_set(acc);
1356 bd.local_get(ls).i64_const(0).binop(BinaryOp::I64GtS);
1358 bd.if_else(
1359 InstrSeqType::Simple(None),
1360 |t| {
1361 t.local_get(s).i64_const(8).call(get).i64_const(45);
1362 t.binop(BinaryOp::I64Eq);
1363 t.if_else(
1364 InstrSeqType::Simple(None),
1365 |t2| {
1366 t2.i64_const(1).local_set(neg);
1367 t2.i64_const(1).local_set(i);
1368 },
1369 |_| {},
1370 );
1371 },
1372 |_| {},
1373 );
1374 bd.block(InstrSeqType::Simple(None), |o| {
1375 let oid = o.id();
1376 o.loop_(InstrSeqType::Simple(None), |l| {
1377 let lid = l.id();
1378 l.local_get(i).local_get(ls).binop(BinaryOp::I64GeS);
1379 l.br_if(oid);
1380 l.local_get(s);
1381 l.local_get(i)
1382 .i64_const(1)
1383 .binop(BinaryOp::I64Add)
1384 .i64_const(8)
1385 .binop(BinaryOp::I64Mul);
1386 l.call(get).local_set(ch);
1387 l.local_get(ch).i64_const(48).binop(BinaryOp::I64LtS);
1388 l.local_get(ch).i64_const(57).binop(BinaryOp::I64GtS);
1389 l.binop(BinaryOp::I32Or); l.br_if(oid);
1391 l.local_get(acc).i64_const(10).binop(BinaryOp::I64Mul);
1392 l.local_get(ch).i64_const(48).binop(BinaryOp::I64Sub);
1393 l.binop(BinaryOp::I64Add).local_set(acc);
1394 l.local_get(i)
1395 .i64_const(1)
1396 .binop(BinaryOp::I64Add)
1397 .local_set(i);
1398 l.br(lid);
1399 });
1400 });
1401 bd.local_get(neg).i64_const(1).binop(BinaryOp::I64Eq);
1402 bd.if_else(
1403 InstrSeqType::Simple(None),
1404 |t| {
1405 t.i64_const(0)
1406 .local_get(acc)
1407 .binop(BinaryOp::I64Sub)
1408 .local_set(res);
1409 },
1410 |e| {
1411 e.local_get(acc).local_set(res);
1412 },
1413 );
1414 bd.local_get(res);
1415 }
1416 fb.finish(vec![s], &mut wasm.funcs)
1417}
1418
1419fn build_str_to_num_opt(
1424 wasm: &mut Module,
1425 get: FunctionId,
1426 set: FunctionId,
1427 alloc: FunctionId,
1428) -> FunctionId {
1429 let mut fb =
1430 FunctionBuilder::new(&mut wasm.types, &[ValType::I64], &[ValType::I64]);
1431 let s = wasm.locals.add(ValType::I64);
1432 let ls = wasm.locals.add(ValType::I64);
1433 let i = wasm.locals.add(ValType::I64);
1434 let neg = wasm.locals.add(ValType::I64);
1435 let acc = wasm.locals.add(ValType::I64);
1436 let ch = wasm.locals.add(ValType::I64);
1437 let ok = wasm.locals.add(ValType::I64);
1438 let dc = wasm.locals.add(ValType::I64);
1439 let res = wasm.locals.add(ValType::I64);
1440 let p = wasm.locals.add(ValType::I64);
1441 {
1442 let mut bd = fb.func_body();
1443 bd.local_get(s).i64_const(0).call(get).local_set(ls);
1444 bd.i64_const(0).local_set(i);
1445 bd.i64_const(0).local_set(neg);
1446 bd.i64_const(0).local_set(acc);
1447 bd.i64_const(1).local_set(ok);
1448 bd.i64_const(0).local_set(dc);
1449 bd.local_get(ls).i64_const(0).binop(BinaryOp::I64GtS);
1451 bd.if_else(
1452 InstrSeqType::Simple(None),
1453 |t| {
1454 t.local_get(s).i64_const(8).call(get).i64_const(45);
1455 t.binop(BinaryOp::I64Eq);
1456 t.if_else(
1457 InstrSeqType::Simple(None),
1458 |t2| {
1459 t2.i64_const(1).local_set(neg);
1460 t2.i64_const(1).local_set(i);
1461 },
1462 |_| {},
1463 );
1464 },
1465 |_| {},
1466 );
1467 bd.block(InstrSeqType::Simple(None), |o| {
1468 let oid = o.id();
1469 o.loop_(InstrSeqType::Simple(None), |l| {
1470 let lid = l.id();
1471 l.local_get(i).local_get(ls).binop(BinaryOp::I64GeS);
1472 l.br_if(oid);
1473 l.local_get(s);
1474 l.local_get(i)
1475 .i64_const(1)
1476 .binop(BinaryOp::I64Add)
1477 .i64_const(8)
1478 .binop(BinaryOp::I64Mul);
1479 l.call(get).local_set(ch);
1480 l.local_get(ch).i64_const(48).binop(BinaryOp::I64LtS);
1481 l.local_get(ch).i64_const(57).binop(BinaryOp::I64GtS);
1482 l.binop(BinaryOp::I32Or);
1483 l.if_else(
1484 InstrSeqType::Simple(None),
1485 |bad| {
1486 bad.i64_const(0).local_set(ok);
1488 bad.br(oid);
1489 },
1490 |_| {},
1491 );
1492 l.local_get(acc).i64_const(10).binop(BinaryOp::I64Mul);
1493 l.local_get(ch).i64_const(48).binop(BinaryOp::I64Sub);
1494 l.binop(BinaryOp::I64Add).local_set(acc);
1495 l.local_get(dc).i64_const(1).binop(BinaryOp::I64Add).local_set(dc);
1496 l.local_get(i)
1497 .i64_const(1)
1498 .binop(BinaryOp::I64Add)
1499 .local_set(i);
1500 l.br(lid);
1501 });
1502 });
1503 bd.local_get(dc).i64_const(0).binop(BinaryOp::I64Eq);
1505 bd.if_else(
1506 InstrSeqType::Simple(None),
1507 |z| {
1508 z.i64_const(0).local_set(ok);
1509 },
1510 |_| {},
1511 );
1512 bd.local_get(neg).i64_const(1).binop(BinaryOp::I64Eq);
1514 bd.if_else(
1515 InstrSeqType::Simple(None),
1516 |t| {
1517 t.i64_const(0)
1518 .local_get(acc)
1519 .binop(BinaryOp::I64Sub)
1520 .local_set(res);
1521 },
1522 |e| {
1523 e.local_get(acc).local_set(res);
1524 },
1525 );
1526 bd.i64_const(16).call(alloc).local_set(p);
1528 bd.local_get(ok).i64_const(0).binop(BinaryOp::I64Ne);
1529 bd.if_else(
1530 InstrSeqType::Simple(None),
1531 |t| {
1532 t.local_get(p).i64_const(0).i64_const(1).call(set).drop();
1533 t.local_get(p).i64_const(8).local_get(res).call(set).drop();
1534 },
1535 |e| {
1536 e.local_get(p).i64_const(0).i64_const(0).call(set).drop();
1537 },
1538 );
1539 bd.local_get(p);
1540 }
1541 fb.finish(vec![s], &mut wasm.funcs)
1542}
1543
1544fn build_list_cons(
1548 wasm: &mut Module,
1549 get: FunctionId,
1550 set: FunctionId,
1551 alloc: FunctionId,
1552) -> FunctionId {
1553 let mut fb = FunctionBuilder::new(
1554 &mut wasm.types,
1555 &[ValType::I64, ValType::I64],
1556 &[ValType::I64],
1557 );
1558 let head = wasm.locals.add(ValType::I64);
1559 let tail = wasm.locals.add(ValType::I64);
1560 let tl = wasm.locals.add(ValType::I64);
1561 let nl = wasm.locals.add(ValType::I64);
1562 let dst = wasm.locals.add(ValType::I64);
1563 let i = wasm.locals.add(ValType::I64);
1564 {
1565 let mut bd = fb.func_body();
1566 bd.local_get(tail).i64_const(0).call(get).local_set(tl);
1567 bd.local_get(tl).i64_const(1).binop(BinaryOp::I64Add).local_set(nl);
1568 bd.local_get(nl)
1569 .i64_const(1)
1570 .binop(BinaryOp::I64Add)
1571 .i64_const(8)
1572 .binop(BinaryOp::I64Mul)
1573 .call(alloc)
1574 .local_set(dst);
1575 bd.local_get(dst).i64_const(0).local_get(nl).call(set).drop();
1576 bd.local_get(dst).i64_const(8).local_get(head).call(set).drop();
1578 bd.i64_const(0).local_set(i);
1579 bd.block(InstrSeqType::Simple(None), |o| {
1580 let oid = o.id();
1581 o.loop_(InstrSeqType::Simple(None), |l| {
1582 let lid = l.id();
1583 l.local_get(i).local_get(tl).binop(BinaryOp::I64GeS);
1584 l.br_if(oid);
1585 l.local_get(dst);
1587 l.local_get(i)
1588 .i64_const(2)
1589 .binop(BinaryOp::I64Add)
1590 .i64_const(8)
1591 .binop(BinaryOp::I64Mul);
1592 l.local_get(tail);
1593 l.local_get(i)
1594 .i64_const(1)
1595 .binop(BinaryOp::I64Add)
1596 .i64_const(8)
1597 .binop(BinaryOp::I64Mul);
1598 l.call(get);
1599 l.call(set);
1600 l.drop();
1601 l.local_get(i)
1602 .i64_const(1)
1603 .binop(BinaryOp::I64Add)
1604 .local_set(i);
1605 l.br(lid);
1606 });
1607 });
1608 bd.local_get(dst);
1609 }
1610 fb.finish(vec![head, tail], &mut wasm.funcs)
1611}
1612
1613fn free_vars(
1618 store: &Store,
1619 hash: &NodeHash,
1620 bound: &HashSet<String>,
1621 acc: &mut Vec<String>,
1622) -> LowerResult<()> {
1623 let Some(node) = store.get(hash)? else {
1624 return Ok(());
1625 };
1626 match node {
1627 Node::Ref(n) => {
1628 if !bound.contains(&n) && !acc.contains(&n) {
1629 acc.push(n);
1630 }
1631 }
1632 Node::Lambda { params, body } => {
1633 let mut b2 = bound.clone();
1634 for p in ¶ms {
1635 b2.insert(p.name.clone());
1636 }
1637 free_vars(store, &body, &b2, acc)?;
1638 }
1639 Node::Match {
1640 scrutinee, arms, ..
1641 } => {
1642 free_vars(store, &scrutinee, bound, acc)?;
1643 for arm in &arms {
1644 let mut b2 = bound.clone();
1645 for x in &arm.bindings {
1646 b2.insert(x.clone());
1647 }
1648 free_vars(store, &arm.body, &b2, acc)?;
1649 }
1650 }
1651 other => {
1652 for ch in crate::check::child_hashes(&other) {
1653 free_vars(store, ch, bound, acc)?;
1654 }
1655 }
1656 }
1657 Ok(())
1658}
1659
1660struct LamInfo {
1663 hash: NodeHash,
1664 params: Vec<crate::node::Param>,
1665 body: NodeHash,
1666 captures: Vec<String>,
1667}
1668
1669fn collect_lambdas(
1673 store: &Store,
1674 hash: &NodeHash,
1675 seen: &mut HashSet<NodeHash>,
1676 out: &mut Vec<LamInfo>,
1677) -> LowerResult<()> {
1678 let Some(node) = store.get(hash)? else {
1679 return Ok(());
1680 };
1681 if let Node::Lambda { params, body } = &node {
1682 if seen.insert(hash.clone()) {
1683 let mut bound = HashSet::new();
1684 for p in params {
1685 bound.insert(p.name.clone());
1686 }
1687 let mut caps = Vec::new();
1688 free_vars(store, body, &bound, &mut caps)?;
1689 out.push(LamInfo {
1690 hash: hash.clone(),
1691 params: params.clone(),
1692 body: body.clone(),
1693 captures: caps,
1694 });
1695 }
1696 return collect_lambdas(store, body, seen, out);
1697 }
1698 for ch in crate::check::child_hashes(&node) {
1699 collect_lambdas(store, ch, seen, out)?;
1700 }
1701 Ok(())
1702}
1703
1704pub fn lower(store: &Store, module_hash: &NodeHash) -> LowerResult<Vec<u8>> {
1705 let Some(Node::Module {
1706 types, functions, ..
1707 }) = store.get(module_hash)?
1708 else {
1709 return Err(LowerError::NotAModule);
1710 };
1711
1712 let mut wasm = Module::default();
1713
1714 let mut recs: HashMap<String, Vec<String>> = HashMap::new();
1717 let mut vars: HashMap<String, Vec<(String, Vec<String>)>> = HashMap::new();
1718 for th in &types {
1719 match store.get(th)? {
1720 Some(Node::RecordDef { name, fields }) => {
1721 recs.insert(name, fields.into_iter().map(|(n, _)| n).collect());
1722 }
1723 Some(Node::VariantDef { name, cases }) => {
1724 vars.insert(
1725 name,
1726 cases
1727 .into_iter()
1728 .map(|(c, p)| (c, p.into_iter().map(|(n, _)| n).collect()))
1729 .collect(),
1730 );
1731 }
1732 _ => {}
1733 }
1734 }
1735
1736 let mem = wasm.memories.add_local(false, false, 1, None, None);
1738 let bump = wasm.globals.add_local(
1739 ValType::I64,
1740 true,
1741 false,
1742 ConstExpr::Value(Value::I64(16)),
1743 );
1744 let alloc = build_alloc(&mut wasm, bump, mem);
1745 let set = build_set(&mut wasm, mem);
1746 let get = build_get(&mut wasm, mem);
1747 let map_get = build_map_get(&mut wasm, get);
1748 let map_try_get = build_map_try_get(&mut wasm, get, set, alloc);
1749 let str_concat = build_str_concat(&mut wasm, get, set, alloc);
1750 let str_slice = build_str_slice(&mut wasm, get, set, alloc);
1751 let str_lower = build_str_lower(&mut wasm, get, set, alloc);
1752 let str_from_code = build_str_from_code(&mut wasm, set, alloc);
1753 let str_eq = build_str_eq(&mut wasm, get);
1754 let str_contains = build_str_contains(&mut wasm, get);
1755 let str_starts_with = build_str_starts_with(&mut wasm, get);
1756 let str_index_of = build_str_index_of(&mut wasm, get);
1757 let num_to_str = build_num_to_str(&mut wasm, set, alloc);
1758 let str_to_num = build_str_to_num(&mut wasm, get);
1759 let str_to_num_opt = build_str_to_num_opt(&mut wasm, get, set, alloc);
1760 let list_cons = build_list_cons(&mut wasm, get, set, alloc);
1761 let list_get = build_list_get(&mut wasm, get);
1762 let list_try_get = build_list_try_get(&mut wasm, get, set, alloc);
1763 wasm.exports.add("mem", mem);
1767 wasm.exports.add("__alloc", alloc);
1768
1769 let now_ty = wasm.types.add(&[], &[ValType::I64]);
1773 let (now, _) = wasm.add_import_func("host", "now", now_ty);
1774 let log_ty = wasm.types.add(&[ValType::I64], &[ValType::I64]);
1775 let (log, _) = wasm.add_import_func("host", "log", log_ty);
1776 let publish_ty = wasm.types.add(&[ValType::I64], &[ValType::I64]);
1777 let (publish, _) = wasm.add_import_func("host", "publish", publish_ty);
1778 let set_header_ty =
1779 wasm.types.add(&[ValType::I64, ValType::I64], &[ValType::I64]);
1780 let (set_header, _) =
1781 wasm.add_import_func("host", "set_header", set_header_ty);
1782 let rand_ty = wasm.types.add(&[], &[ValType::I64]);
1783 let (rand, _) = wasm.add_import_func("host", "rand", rand_ty);
1784 let dw_ty = wasm.types.add(&[ValType::I64, ValType::I64], &[ValType::I64]);
1785 let (disk_write, _) = wasm.add_import_func("host", "disk_write", dw_ty);
1786 let dr_ty = wasm.types.add(&[ValType::I64], &[ValType::I64]);
1787 let (disk_read, _) = wasm.add_import_func("host", "disk_read", dr_ty);
1788 let ng_ty = wasm.types.add(&[ValType::I64], &[ValType::I64]);
1789 let (net_get, _) = wasm.add_import_func("host", "net_get", ng_ty);
1790 let dq_ty = wasm.types.add(&[ValType::I64, ValType::I64], &[ValType::I64]);
1791 let (db_query, _) = wasm.add_import_func("host", "db_query", dq_ty);
1792
1793 let mut ftags: HashMap<String, i64> = HashMap::new();
1796 let mut fallible: HashSet<String> = HashSet::new();
1797 for fh in &functions {
1798 if let Some(Node::Function {
1799 name, on_failure, ..
1800 }) = store.get(fh)?
1801 {
1802 if !on_failure.is_empty() {
1803 fallible.insert(name.clone());
1804 }
1805 for f in on_failure {
1806 let next = (ftags.len() as i64) + 1;
1807 ftags.entry(f).or_insert(next);
1808 }
1809 }
1810 }
1811
1812 struct Pending {
1815 is_fallible: bool,
1816 body: Vec<NodeHash>,
1817 result: NodeHash,
1818 scope: HashMap<String, Slot>,
1820 id: FunctionId,
1821 }
1822
1823 let mut fn_ids: HashMap<String, FunctionId> = HashMap::new();
1824 let mut table_ids: Vec<FunctionId> = Vec::new();
1832 let mut fn_table_idx: HashMap<String, i64> = HashMap::new();
1833 let mut lam_table: HashMap<NodeHash, (i64, Vec<String>)> = HashMap::new();
1834 let mut pending: Vec<Pending> = Vec::with_capacity(functions.len());
1835 let mut top_level: Vec<(String, FunctionId, usize)> = Vec::new();
1837
1838 let mut lam_infos: Vec<LamInfo> = Vec::new();
1841 {
1842 let mut seen = HashSet::new();
1843 for fh in &functions {
1844 if let Some(Node::Function { body, result, .. }) = store.get(fh)? {
1845 for s in &body {
1846 collect_lambdas(store, s, &mut seen, &mut lam_infos)?;
1847 }
1848 collect_lambdas(store, &result, &mut seen, &mut lam_infos)?;
1849 }
1850 }
1851 }
1852
1853 for fh in &functions {
1868 let Some(Node::Function {
1869 name,
1870 params,
1871 on_failure,
1872 body,
1873 result,
1874 ..
1875 }) = store.get(fh)?
1876 else {
1877 continue;
1878 };
1879 let param_tys = vec![ValType::I64; params.len()];
1880 let fb = FunctionBuilder::new(&mut wasm.types, ¶m_tys, &[ValType::I64]);
1881
1882 let mut scope: HashMap<String, Slot> = HashMap::new();
1884 let mut param_locals = Vec::with_capacity(params.len());
1885 for p in ¶ms {
1886 let l = wasm.locals.add(ValType::I64);
1887 scope.insert(p.name.clone(), Slot::Local(l));
1888 param_locals.push(l);
1889 }
1890
1891 let id = fb.finish(param_locals, &mut wasm.funcs);
1892 wasm.exports.add(&name, id);
1893 top_level.push((name.clone(), id, params.len()));
1894 fn_ids.insert(name, id);
1895 pending.push(Pending {
1896 is_fallible: !on_failure.is_empty(),
1897 body,
1898 result,
1899 scope,
1900 id,
1901 });
1902 }
1903
1904 let mut max_arity = 0usize;
1908 for (name, real_id, arity) in &top_level {
1909 max_arity = max_arity.max(*arity);
1910 let sig: Vec<ValType> = vec![ValType::I64; arity + 1];
1911 let fb = FunctionBuilder::new(&mut wasm.types, &sig, &[ValType::I64]);
1912 let arg_locals: Vec<LocalId> =
1913 (0..arity + 1).map(|_| wasm.locals.add(ValType::I64)).collect();
1914 let tid = {
1915 let mut b = fb;
1916 {
1917 let mut body = b.func_body();
1918 for l in arg_locals.iter().take(*arity) {
1919 body.local_get(*l);
1920 }
1921 body.call(*real_id); }
1923 b.finish(arg_locals, &mut wasm.funcs)
1924 };
1925 let idx = table_ids.len() as i64;
1926 table_ids.push(tid);
1927 fn_table_idx.insert(name.clone(), idx);
1928 }
1929
1930 for li in &lam_infos {
1934 let arity = li.params.len();
1935 max_arity = max_arity.max(arity);
1936 let sig: Vec<ValType> = vec![ValType::I64; arity + 1];
1937 let fb = FunctionBuilder::new(&mut wasm.types, &sig, &[ValType::I64]);
1938 let mut scope: HashMap<String, Slot> = HashMap::new();
1939 let mut locals = Vec::with_capacity(arity + 1);
1940 for p in &li.params {
1941 let l = wasm.locals.add(ValType::I64);
1942 scope.insert(p.name.clone(), Slot::Local(l));
1943 locals.push(l);
1944 }
1945 let env = wasm.locals.add(ValType::I64);
1946 locals.push(env);
1947 for (j, cap) in li.captures.iter().enumerate() {
1948 scope.insert(
1949 cap.clone(),
1950 Slot::Member {
1951 base: env,
1952 off: (j as i64) * 8,
1953 },
1954 );
1955 }
1956 let id = fb.finish(locals, &mut wasm.funcs);
1957 let idx = table_ids.len() as i64;
1958 table_ids.push(id);
1959 lam_table.insert(li.hash.clone(), (idx, li.captures.clone()));
1960 pending.push(Pending {
1961 is_fallible: false,
1962 body: vec![],
1963 result: li.body.clone(),
1964 scope,
1965 id,
1966 });
1967 }
1968
1969 let tlen = table_ids.len() as u64;
1972 let func_table =
1973 wasm.tables
1974 .add_local(false, tlen, Some(tlen), RefType::Funcref);
1975 if !table_ids.is_empty() {
1976 wasm.elements.add(
1977 ElementKind::Active {
1978 table: func_table,
1979 offset: ConstExpr::Value(Value::I32(0)),
1980 },
1981 ElementItems::Functions(table_ids.clone()),
1982 );
1983 }
1984 let mut indirect_types: HashMap<usize, TypeId> = HashMap::new();
1985 for k in 0..=max_arity {
1986 let sig = vec![ValType::I64; k + 1];
1987 let ty = wasm.types.add(&sig, &[ValType::I64]);
1988 indirect_types.insert(k, ty);
1989 }
1990
1991 for mut p in pending {
1994 let locals_cell = RefCell::new(&mut wasm.locals);
1995 let ctx = Ctx {
1996 store,
1997 fns: &fn_ids,
1998 fn_table_idx: &fn_table_idx,
1999 lambdas: &lam_table,
2000 func_table,
2001 indirect_types: &indirect_types,
2002 recs: &recs,
2003 vars: &vars,
2004 ftags: &ftags,
2005 fallible: &fallible,
2006 locals: &locals_cell,
2007 alloc,
2008 set,
2009 get,
2010 map_get,
2011 map_try_get,
2012 now,
2013 log,
2014 publish,
2015 set_header,
2016 rand,
2017 disk_write,
2018 disk_read,
2019 net_get,
2020 db_query,
2021 str_concat,
2022 str_slice,
2023 str_lower,
2024 str_from_code,
2025 str_eq,
2026 str_contains,
2027 str_starts_with,
2028 str_index_of,
2029 num_to_str,
2030 str_to_num,
2031 str_to_num_opt,
2032 list_cons,
2033 list_get,
2034 list_try_get,
2035 };
2036 let lf = wasm.funcs.get_mut(p.id).kind.unwrap_local_mut();
2037 let mut seq = lf.builder_mut().func_body();
2038 for step_hash in &p.body {
2039 match store.get(step_hash)? {
2040 Some(Node::Step { binding, value }) => {
2041 lower_expr(&ctx, &p.scope, &mut seq, &value)?;
2042 let l = locals_cell.borrow_mut().add(ValType::I64);
2043 seq.local_set(l);
2044 p.scope.insert(binding, Slot::Local(l));
2045 }
2046 Some(Node::Hole { .. }) => return Err(LowerError::Hole),
2047 _ => return Err(LowerError::Unsupported("non-step in function body")),
2048 }
2049 }
2050 lower_expr(&ctx, &p.scope, &mut seq, &p.result)?;
2051 if p.is_fallible {
2052 let tmp = locals_cell.borrow_mut().add(ValType::I64);
2055 seq.local_set(tmp);
2056 seq.i64_const(16);
2057 seq.call(alloc); seq.i64_const(0);
2059 seq.i64_const(0);
2060 seq.call(set); seq.i64_const(8);
2062 seq.local_get(tmp);
2063 seq.call(set); }
2065 }
2066
2067 Ok(wasm.emit_wasm())
2068}
2069
2070fn lower_expr(
2071 c: &Ctx,
2072 scope: &HashMap<String, Slot>,
2073 seq: &mut InstrSeqBuilder,
2074 hash: &NodeHash,
2075) -> LowerResult<()> {
2076 match c.store.get(hash)? {
2077 Some(Node::Lit(v)) => {
2078 seq.i64_const(v);
2079 }
2080 Some(Node::FloatLit(bits)) => {
2081 seq.i64_const(bits as i64);
2083 }
2084 Some(Node::FloatOp { op, lhs, rhs }) => {
2085 use crate::node::BinOp::*;
2086 lower_expr(c, scope, seq, &lhs)?;
2087 seq.unop(UnaryOp::F64ReinterpretI64);
2088 lower_expr(c, scope, seq, &rhs)?;
2089 seq.unop(UnaryOp::F64ReinterpretI64);
2090 match op {
2091 Add => {
2092 seq.binop(BinaryOp::F64Add)
2093 .unop(UnaryOp::I64ReinterpretF64);
2094 }
2095 Sub => {
2096 seq.binop(BinaryOp::F64Sub)
2097 .unop(UnaryOp::I64ReinterpretF64);
2098 }
2099 Mul => {
2100 seq.binop(BinaryOp::F64Mul)
2101 .unop(UnaryOp::I64ReinterpretF64);
2102 }
2103 Div => {
2104 seq.binop(BinaryOp::F64Div)
2105 .unop(UnaryOp::I64ReinterpretF64);
2106 }
2107 Eq => {
2108 seq.binop(BinaryOp::F64Eq).unop(UnaryOp::I64ExtendUI32);
2109 }
2110 Lt => {
2111 seq.binop(BinaryOp::F64Lt).unop(UnaryOp::I64ExtendUI32);
2112 }
2113 Le => {
2114 seq.binop(BinaryOp::F64Le).unop(UnaryOp::I64ExtendUI32);
2115 }
2116 Gt => {
2117 seq.binop(BinaryOp::F64Gt).unop(UnaryOp::I64ExtendUI32);
2118 }
2119 Ge => {
2120 seq.binop(BinaryOp::F64Ge).unop(UnaryOp::I64ExtendUI32);
2121 }
2122 Mod | Neq | And | Or => {
2124 return Err(LowerError::Unsupported(
2125 "operator not defined on Float",
2126 ));
2127 }
2128 }
2129 }
2130 Some(Node::IntToFloat(a)) => {
2131 lower_expr(c, scope, seq, &a)?;
2132 seq.unop(UnaryOp::F64ConvertSI64)
2133 .unop(UnaryOp::I64ReinterpretF64);
2134 }
2135 Some(Node::FloatToInt(a)) => {
2136 lower_expr(c, scope, seq, &a)?;
2137 seq.unop(UnaryOp::F64ReinterpretI64)
2138 .unop(UnaryOp::I64TruncSF64);
2139 }
2140 Some(Node::DecimalLit(v)) => {
2141 seq.i64_const(v);
2143 }
2144 Some(Node::DecimalOp { op, lhs, rhs }) => {
2145 use crate::node::BinOp::*;
2146 match op {
2147 Mul => {
2149 lower_expr(c, scope, seq, &lhs)?;
2150 lower_expr(c, scope, seq, &rhs)?;
2151 seq.binop(BinaryOp::I64Mul)
2152 .i64_const(10_000)
2153 .binop(BinaryOp::I64DivS);
2154 }
2155 Div => {
2156 lower_expr(c, scope, seq, &lhs)?;
2157 seq.i64_const(10_000).binop(BinaryOp::I64Mul);
2158 lower_expr(c, scope, seq, &rhs)?;
2159 seq.binop(BinaryOp::I64DivS);
2160 }
2161 Add => {
2162 lower_expr(c, scope, seq, &lhs)?;
2163 lower_expr(c, scope, seq, &rhs)?;
2164 seq.binop(BinaryOp::I64Add);
2165 }
2166 Sub => {
2167 lower_expr(c, scope, seq, &lhs)?;
2168 lower_expr(c, scope, seq, &rhs)?;
2169 seq.binop(BinaryOp::I64Sub);
2170 }
2171 Eq | Neq | Lt | Le | Gt | Ge => {
2172 lower_expr(c, scope, seq, &lhs)?;
2173 lower_expr(c, scope, seq, &rhs)?;
2174 match op {
2175 Eq => seq.binop(BinaryOp::I64Eq),
2176 Neq => seq.binop(BinaryOp::I64Ne),
2177 Lt => seq.binop(BinaryOp::I64LtS),
2178 Le => seq.binop(BinaryOp::I64LeS),
2179 Gt => seq.binop(BinaryOp::I64GtS),
2180 Ge => seq.binop(BinaryOp::I64GeS),
2181 _ => unreachable!(),
2182 };
2183 seq.unop(UnaryOp::I64ExtendUI32);
2184 }
2185 Mod | And | Or => {
2186 return Err(LowerError::Unsupported(
2187 "operator not defined on Decimal",
2188 ));
2189 }
2190 }
2191 }
2192 Some(Node::IntToDecimal(a)) => {
2193 lower_expr(c, scope, seq, &a)?;
2194 seq.i64_const(10_000).binop(BinaryOp::I64Mul);
2195 }
2196 Some(Node::DecimalToInt(a)) => {
2197 lower_expr(c, scope, seq, &a)?;
2198 seq.i64_const(10_000).binop(BinaryOp::I64DivS);
2199 }
2200 Some(Node::DecimalRaw(a)) => {
2201 lower_expr(c, scope, seq, &a)?;
2203 }
2204 Some(Node::Bool(b)) => {
2205 seq.i64_const(if b { 1 } else { 0 });
2206 }
2207 Some(Node::Not(a)) => {
2208 lower_expr(c, scope, seq, &a)?;
2211 seq.unop(UnaryOp::I64Eqz).unop(UnaryOp::I64ExtendUI32);
2212 }
2213 Some(Node::Ref(name)) => {
2214 match scope
2215 .get(&name)
2216 .ok_or(LowerError::UnresolvedRef(name.clone()))?
2217 {
2218 Slot::Local(l) => {
2219 seq.local_get(*l);
2220 }
2221 Slot::Member { base, off } => {
2222 seq.local_get(*base);
2224 seq.i64_const(*off);
2225 seq.call(c.get);
2226 }
2227 }
2228 }
2229 Some(Node::Call { func, args }) => {
2230 for a in &args {
2231 lower_expr(c, scope, seq, a)?;
2232 }
2233 let id = *c
2234 .fns
2235 .get(&func)
2236 .ok_or(LowerError::UnknownCallee(func.clone()))?;
2237 seq.call(id);
2238 if c.fallible.contains(&func) {
2239 let r = c.locals.borrow_mut().add(ValType::I64);
2243 seq.local_set(r);
2244 seq.local_get(r);
2245 seq.i64_const(0);
2246 seq.call(c.get); seq.i64_const(0);
2248 seq.binop(BinaryOp::I64Ne); seq.if_else(
2250 InstrSeqType::Simple(None),
2251 |t| {
2252 t.local_get(r);
2253 t.return_();
2254 },
2255 |_| {},
2256 );
2257 seq.local_get(r);
2258 seq.i64_const(8);
2259 seq.call(c.get); }
2261 }
2262 Some(Node::FuncRef(name)) => {
2263 let idx = *c
2267 .fn_table_idx
2268 .get(&name)
2269 .ok_or(LowerError::UnknownCallee(name.clone()))?;
2270 let blk = c.locals.borrow_mut().add(ValType::I64);
2271 seq.i64_const(16);
2272 seq.call(c.alloc);
2273 seq.local_set(blk);
2274 seq.local_get(blk).i64_const(0).i64_const(idx).call(c.set);
2275 seq.drop();
2276 seq.local_get(blk).i64_const(8).i64_const(0).call(c.set);
2277 seq.drop();
2278 seq.local_get(blk);
2279 }
2280 Some(Node::Lambda { .. }) => {
2281 let (idx, captures) = c
2285 .lambdas
2286 .get(hash)
2287 .ok_or(LowerError::Unsupported("lambda not lifted"))?;
2288 let env = c.locals.borrow_mut().add(ValType::I64);
2289 if captures.is_empty() {
2290 seq.i64_const(0);
2291 seq.local_set(env);
2292 } else {
2293 seq.i64_const((8 * captures.len()) as i64);
2294 seq.call(c.alloc);
2295 seq.local_set(env);
2296 for (j, cap) in captures.iter().enumerate() {
2297 seq.local_get(env);
2298 seq.i64_const((j * 8) as i64);
2299 match scope
2300 .get(cap)
2301 .ok_or(LowerError::UnresolvedRef(cap.clone()))?
2302 {
2303 Slot::Local(l) => {
2304 seq.local_get(*l);
2305 }
2306 Slot::Member { base, off } => {
2307 seq.local_get(*base);
2308 seq.i64_const(*off);
2309 seq.call(c.get);
2310 }
2311 }
2312 seq.call(c.set);
2313 seq.drop();
2314 }
2315 }
2316 let idx = *idx;
2317 let blk = c.locals.borrow_mut().add(ValType::I64);
2318 seq.i64_const(16);
2319 seq.call(c.alloc);
2320 seq.local_set(blk);
2321 seq.local_get(blk).i64_const(0).i64_const(idx).call(c.set);
2322 seq.drop();
2323 seq.local_get(blk).i64_const(8).local_get(env).call(c.set);
2324 seq.drop();
2325 seq.local_get(blk);
2326 }
2327 Some(Node::CallValue { callee, args }) => {
2328 lower_expr(c, scope, seq, &callee)?;
2331 let p = c.locals.borrow_mut().add(ValType::I64);
2332 seq.local_set(p);
2333 for a in &args {
2334 lower_expr(c, scope, seq, a)?;
2335 }
2336 seq.local_get(p).i64_const(8).call(c.get); seq.local_get(p).i64_const(0).call(c.get); seq.unop(UnaryOp::I32WrapI64);
2339 let ty = *c.indirect_types.get(&args.len()).ok_or(
2340 LowerError::Unsupported("call_indirect arity not prepared"),
2341 )?;
2342 seq.call_indirect(ty, c.func_table);
2343 }
2344 Some(Node::Step { value, .. }) => {
2345 lower_expr(c, scope, seq, &value)?;
2346 }
2347 Some(Node::BinOp { op, lhs, rhs }) => {
2348 use crate::node::BinOp::*;
2349 match op {
2350 And | Or => {
2355 lower_expr(c, scope, seq, &lhs)?;
2356 seq.unop(UnaryOp::I32WrapI64);
2357 let is_and = matches!(op, And);
2358 let mut terr: Option<LowerError> = None;
2362 let mut eerr: Option<LowerError> = None;
2363 seq.if_else(
2364 InstrSeqType::Simple(Some(ValType::I64)),
2365 |t| {
2366 if is_and {
2367 if let Err(e) = lower_expr(c, scope, t, &rhs) {
2368 terr = Some(e);
2369 }
2370 } else {
2371 t.i64_const(1);
2372 }
2373 },
2374 |e| {
2375 if is_and {
2376 e.i64_const(0);
2377 } else if let Err(er) =
2378 lower_expr(c, scope, e, &rhs)
2379 {
2380 eerr = Some(er);
2381 }
2382 },
2383 );
2384 if let Some(e) = terr.or(eerr) {
2385 return Err(e);
2386 }
2387 }
2388 _ => {
2389 lower_expr(c, scope, seq, &lhs)?;
2390 lower_expr(c, scope, seq, &rhs)?;
2391 match op {
2392 Add => seq.binop(BinaryOp::I64Add),
2393 Sub => seq.binop(BinaryOp::I64Sub),
2394 Mul => seq.binop(BinaryOp::I64Mul),
2395 Div => seq.binop(BinaryOp::I64DivS),
2396 Mod => seq.binop(BinaryOp::I64RemS),
2397 Eq => seq
2400 .binop(BinaryOp::I64Eq)
2401 .unop(UnaryOp::I64ExtendUI32),
2402 Neq => seq
2403 .binop(BinaryOp::I64Ne)
2404 .unop(UnaryOp::I64ExtendUI32),
2405 Lt => seq
2406 .binop(BinaryOp::I64LtS)
2407 .unop(UnaryOp::I64ExtendUI32),
2408 Le => seq
2409 .binop(BinaryOp::I64LeS)
2410 .unop(UnaryOp::I64ExtendUI32),
2411 Gt => seq
2412 .binop(BinaryOp::I64GtS)
2413 .unop(UnaryOp::I64ExtendUI32),
2414 Ge => seq
2415 .binop(BinaryOp::I64GeS)
2416 .unop(UnaryOp::I64ExtendUI32),
2417 And | Or => unreachable!("handled above"),
2418 };
2419 }
2420 }
2421 }
2422 Some(Node::If {
2423 cond,
2424 then_branch,
2425 else_branch,
2426 }) => {
2427 lower_expr(c, scope, seq, &cond)?;
2429 seq.unop(UnaryOp::I32WrapI64);
2430 let mut terr: Option<LowerError> = None;
2433 let mut eerr: Option<LowerError> = None;
2434 seq.if_else(
2435 InstrSeqType::Simple(Some(ValType::I64)),
2436 |t| {
2437 if let Err(e) = lower_expr(c, scope, t, &then_branch) {
2438 terr = Some(e);
2439 }
2440 },
2441 |e| {
2442 if let Err(er) = lower_expr(c, scope, e, &else_branch) {
2443 eerr = Some(er);
2444 }
2445 },
2446 );
2447 if let Some(e) = terr {
2448 return Err(e);
2449 }
2450 if let Some(e) = eerr {
2451 return Err(e);
2452 }
2453 }
2454 Some(Node::Record { type_name, fields }) => {
2455 let order = c
2456 .recs
2457 .get(&type_name)
2458 .ok_or(LowerError::Unsupported("unknown record type in lowering"))?
2459 .clone();
2460 seq.i64_const((order.len() as i64) * 8);
2462 seq.call(c.alloc);
2463 for (i, fname) in order.iter().enumerate() {
2465 let fexpr = fields
2466 .iter()
2467 .find(|(n, _)| n == fname)
2468 .map(|(_, h)| h.clone())
2469 .ok_or(LowerError::Unsupported("missing record field in lowering"))?;
2470 seq.i64_const((i as i64) * 8);
2471 lower_expr(c, scope, seq, &fexpr)?;
2472 seq.call(c.set);
2473 }
2474 }
2476 Some(Node::Field {
2477 base,
2478 type_name,
2479 field,
2480 }) => {
2481 let order = c
2482 .recs
2483 .get(&type_name)
2484 .ok_or(LowerError::Unsupported("unknown record type in lowering"))?;
2485 let idx = order
2486 .iter()
2487 .position(|n| n == &field)
2488 .ok_or(LowerError::Unsupported("unknown field in lowering"))?;
2489 lower_expr(c, scope, seq, &base)?; seq.i64_const((idx as i64) * 8); seq.call(c.get); }
2493 Some(Node::Fail(name)) => {
2494 let tag = *c
2497 .ftags
2498 .get(&name)
2499 .ok_or(LowerError::Unsupported("unknown failure in lowering"))?;
2500 seq.i64_const(16);
2501 seq.call(c.alloc); seq.i64_const(0);
2503 seq.i64_const(tag);
2504 seq.call(c.set); seq.i64_const(8);
2506 seq.i64_const(0);
2507 seq.call(c.set); seq.return_(); }
2510 Some(Node::Handle { body, handlers }) => {
2511 let Some(Node::Call { func, args }) = c.store.get(&body)? else {
2515 return Err(LowerError::Unsupported(
2516 "Handle body must be a fallible call (v0.2)",
2517 ));
2518 };
2519 if !c.fallible.contains(&func) {
2520 return Err(LowerError::Unsupported(
2521 "Handle body must be a fallible call (v0.2)",
2522 ));
2523 }
2524 for a in &args {
2525 lower_expr(c, scope, seq, a)?;
2526 }
2527 let id = *c
2528 .fns
2529 .get(&func)
2530 .ok_or(LowerError::UnknownCallee(func.clone()))?;
2531 seq.call(id); let hb = c.locals.borrow_mut().add(ValType::I64);
2534 let tg = c.locals.borrow_mut().add(ValType::I64);
2535 let res = c.locals.borrow_mut().add(ValType::I64);
2536 let handled = c.locals.borrow_mut().add(ValType::I64);
2537 seq.local_set(hb);
2538 seq.local_get(hb);
2539 seq.i64_const(0);
2540 seq.call(c.get);
2541 seq.local_set(tg); seq.i64_const(0);
2543 seq.local_set(handled);
2544
2545 seq.local_get(tg);
2547 seq.i64_const(0);
2548 seq.binop(BinaryOp::I64Eq);
2549 seq.if_else(
2550 InstrSeqType::Simple(None),
2551 |t| {
2552 t.local_get(hb);
2553 t.i64_const(8);
2554 t.call(c.get);
2555 t.local_set(res);
2556 t.i64_const(1);
2557 t.local_set(handled);
2558 },
2559 |_| {},
2560 );
2561
2562 for (fname, recover) in &handlers {
2564 let tag = *c
2565 .ftags
2566 .get(fname)
2567 .ok_or(LowerError::Unsupported("unknown handled failure"))?;
2568 seq.local_get(tg);
2569 seq.i64_const(tag);
2570 seq.binop(BinaryOp::I64Eq);
2571 let mut herr: Option<LowerError> = None;
2572 seq.if_else(
2573 InstrSeqType::Simple(None),
2574 |t| match lower_expr(c, scope, t, recover) {
2575 Ok(()) => {
2576 t.local_set(res);
2577 t.i64_const(1);
2578 t.local_set(handled);
2579 }
2580 Err(e) => herr = Some(e),
2581 },
2582 |_| {},
2583 );
2584 if let Some(e) = herr {
2585 return Err(e);
2586 }
2587 }
2588
2589 seq.local_get(handled);
2591 seq.i64_const(0);
2592 seq.binop(BinaryOp::I64Eq);
2593 seq.if_else(
2594 InstrSeqType::Simple(None),
2595 |t| {
2596 t.local_get(hb);
2597 t.return_();
2598 },
2599 |_| {},
2600 );
2601
2602 seq.local_get(res);
2603 }
2604 Some(Node::Variant {
2605 type_name,
2606 case,
2607 fields,
2608 }) => {
2609 let cases = c
2610 .vars
2611 .get(&type_name)
2612 .ok_or(LowerError::Unsupported("unknown variant type in lowering"))?
2613 .clone();
2614 let k = cases
2615 .iter()
2616 .position(|(cn, _)| cn == &case)
2617 .ok_or(LowerError::Unsupported("unknown case in lowering"))?;
2618 let payload = cases[k].1.clone();
2619 seq.i64_const(((1 + payload.len()) as i64) * 8);
2621 seq.call(c.alloc); seq.i64_const(0);
2623 seq.i64_const(k as i64);
2624 seq.call(c.set); for (i, fname) in payload.iter().enumerate() {
2626 let fe = fields
2627 .iter()
2628 .find(|(n, _)| n == fname)
2629 .map(|(_, h)| h.clone())
2630 .ok_or(LowerError::Unsupported("missing variant field in lowering"))?;
2631 seq.i64_const(((1 + i) as i64) * 8);
2632 lower_expr(c, scope, seq, &fe)?;
2633 seq.call(c.set); }
2635 }
2637 Some(Node::Match {
2638 scrutinee,
2639 type_name,
2640 arms,
2641 }) => {
2642 let cases = c
2643 .vars
2644 .get(&type_name)
2645 .ok_or(LowerError::Unsupported("unknown variant type in lowering"))?
2646 .clone();
2647 lower_expr(c, scope, seq, &scrutinee)?; let sc = c.locals.borrow_mut().add(ValType::I64);
2649 seq.local_set(sc);
2650 seq.local_get(sc);
2652 seq.i64_const(0);
2653 seq.call(c.get);
2654 let tg = c.locals.borrow_mut().add(ValType::I64);
2655 seq.local_set(tg);
2656 let res = c.locals.borrow_mut().add(ValType::I64);
2657 for arm in &arms {
2658 let k = cases
2659 .iter()
2660 .position(|(cn, _)| cn == &arm.case)
2661 .ok_or(LowerError::Unsupported("unknown case in match lowering"))?;
2662 let mut s2 = scope.clone();
2663 for (i, b) in arm.bindings.iter().enumerate() {
2664 s2.insert(
2665 b.clone(),
2666 Slot::Member {
2667 base: sc,
2668 off: ((1 + i) as i64) * 8,
2669 },
2670 );
2671 }
2672 seq.local_get(tg);
2674 seq.i64_const(k as i64);
2675 seq.binop(BinaryOp::I64Eq); let mut berr: Option<LowerError> = None;
2677 seq.if_else(
2678 InstrSeqType::Simple(None),
2679 |t| match lower_expr(c, &s2, t, &arm.body) {
2680 Ok(()) => {
2681 t.local_set(res);
2682 }
2683 Err(e) => berr = Some(e),
2684 },
2685 |_| {},
2686 );
2687 if let Some(e) = berr {
2688 return Err(e);
2689 }
2690 }
2691 seq.local_get(res);
2692 }
2693 Some(Node::Str(s)) => {
2694 let bytes = s.as_bytes();
2697 seq.i64_const(((1 + bytes.len()) as i64) * 8);
2698 seq.call(c.alloc); seq.i64_const(0);
2700 seq.i64_const(bytes.len() as i64);
2701 seq.call(c.set); for (i, b) in bytes.iter().enumerate() {
2703 seq.i64_const(((1 + i) as i64) * 8);
2704 seq.i64_const(*b as i64);
2705 seq.call(c.set);
2706 }
2707 }
2709 Some(Node::StrLen(arg)) => {
2710 lower_expr(c, scope, seq, &arg)?; seq.i64_const(0);
2712 seq.call(c.get); }
2714 Some(Node::StrLower(arg)) => {
2715 lower_expr(c, scope, seq, &arg)?; seq.call(c.str_lower); }
2718 Some(Node::StrFromCode(arg)) => {
2719 lower_expr(c, scope, seq, &arg)?; seq.call(c.str_from_code); }
2722 Some(Node::StrConcat(a, b)) => {
2723 lower_expr(c, scope, seq, &a)?;
2724 lower_expr(c, scope, seq, &b)?;
2725 seq.call(c.str_concat);
2726 }
2727 Some(Node::StrSlice { s, start, len }) => {
2728 lower_expr(c, scope, seq, &s)?;
2729 lower_expr(c, scope, seq, &start)?;
2730 lower_expr(c, scope, seq, &len)?;
2731 seq.call(c.str_slice);
2732 }
2733 Some(Node::StrEq(a, b)) => {
2734 lower_expr(c, scope, seq, &a)?;
2735 lower_expr(c, scope, seq, &b)?;
2736 seq.call(c.str_eq); }
2738 Some(Node::StrContains { haystack, needle }) => {
2739 lower_expr(c, scope, seq, &haystack)?;
2740 lower_expr(c, scope, seq, &needle)?;
2741 seq.call(c.str_contains); }
2743 Some(Node::StrStartsWith { s, prefix }) => {
2744 lower_expr(c, scope, seq, &s)?;
2745 lower_expr(c, scope, seq, &prefix)?;
2746 seq.call(c.str_starts_with); }
2748 Some(Node::StrIndexOf { haystack, needle }) => {
2749 lower_expr(c, scope, seq, &haystack)?;
2750 lower_expr(c, scope, seq, &needle)?;
2751 seq.call(c.str_index_of); }
2753 Some(Node::NumberToStr(a)) => {
2754 lower_expr(c, scope, seq, &a)?;
2755 seq.call(c.num_to_str); }
2757 Some(Node::StrToNumberOpt(a)) => {
2758 lower_expr(c, scope, seq, &a)?;
2759 seq.call(c.str_to_num_opt); }
2761 Some(Node::StrToNumber(a)) => {
2762 lower_expr(c, scope, seq, &a)?;
2763 seq.call(c.str_to_num); }
2765 Some(Node::Now) => {
2766 seq.call(c.now); }
2768 Some(Node::List(elems)) => {
2769 seq.i64_const(((1 + elems.len()) as i64) * 8);
2771 seq.call(c.alloc); seq.i64_const(0);
2773 seq.i64_const(elems.len() as i64);
2774 seq.call(c.set); for (i, e) in elems.iter().enumerate() {
2776 seq.i64_const(((1 + i) as i64) * 8);
2777 lower_expr(c, scope, seq, e)?;
2778 seq.call(c.set);
2779 }
2780 }
2782 Some(Node::ListEmpty { .. }) => {
2783 seq.i64_const(8);
2785 seq.call(c.alloc); seq.i64_const(0);
2787 seq.i64_const(0);
2788 seq.call(c.set); }
2790 Some(Node::ListCons { head, tail }) => {
2791 lower_expr(c, scope, seq, &head)?;
2792 lower_expr(c, scope, seq, &tail)?;
2793 seq.call(c.list_cons); }
2795 Some(Node::OptionSome(v)) => {
2796 lower_expr(c, scope, seq, &v)?;
2798 let tmp = c.locals.borrow_mut().add(ValType::I64);
2799 seq.local_set(tmp);
2800 seq.i64_const(16);
2801 seq.call(c.alloc); seq.i64_const(0);
2803 seq.i64_const(1);
2804 seq.call(c.set); seq.i64_const(8);
2806 seq.local_get(tmp);
2807 seq.call(c.set); }
2809 Some(Node::OptionNone { .. }) => {
2810 seq.i64_const(16);
2812 seq.call(c.alloc); seq.i64_const(0);
2814 seq.i64_const(0);
2815 seq.call(c.set); }
2817 Some(Node::OptionElse { opt, default }) => {
2818 lower_expr(c, scope, seq, &opt)?; let pl = c.locals.borrow_mut().add(ValType::I64);
2820 seq.local_set(pl);
2821 seq.local_get(pl);
2822 seq.i64_const(0);
2823 seq.call(c.get); seq.i64_const(1);
2825 seq.binop(BinaryOp::I64Eq); let mut eerr: Option<LowerError> = None;
2827 seq.if_else(
2828 InstrSeqType::Simple(Some(ValType::I64)),
2829 |t| {
2830 t.local_get(pl).i64_const(8).call(c.get); },
2832 |e| {
2833 if let Err(er) = lower_expr(c, scope, e, &default) {
2834 eerr = Some(er);
2835 }
2836 },
2837 );
2838 if let Some(e) = eerr {
2839 return Err(e);
2840 }
2841 }
2842 Some(Node::OptionMatch {
2843 opt,
2844 some_bind,
2845 some_body,
2846 none_body,
2847 }) => {
2848 lower_expr(c, scope, seq, &opt)?; let pl = c.locals.borrow_mut().add(ValType::I64);
2850 seq.local_set(pl);
2851 seq.local_get(pl);
2852 seq.i64_const(0);
2853 seq.call(c.get); seq.i64_const(1);
2855 seq.binop(BinaryOp::I64Eq); let mut s2 = scope.clone();
2859 s2.insert(some_bind.clone(), Slot::Member { base: pl, off: 8 });
2860 let mut terr: Option<LowerError> = None;
2861 let mut eerr: Option<LowerError> = None;
2862 seq.if_else(
2863 InstrSeqType::Simple(Some(ValType::I64)),
2864 |t| {
2865 if let Err(e) = lower_expr(c, &s2, t, &some_body) {
2866 terr = Some(e);
2867 }
2868 },
2869 |e| {
2870 if let Err(er) = lower_expr(c, scope, e, &none_body) {
2871 eerr = Some(er);
2872 }
2873 },
2874 );
2875 if let Some(e) = terr.or(eerr) {
2876 return Err(e);
2877 }
2878 }
2879 Some(Node::ListTryGet { list, index }) => {
2880 lower_expr(c, scope, seq, &list)?;
2881 lower_expr(c, scope, seq, &index)?;
2882 seq.call(c.list_try_get); }
2884 Some(Node::ListLen(arg)) => {
2885 lower_expr(c, scope, seq, &arg)?; seq.i64_const(0);
2887 seq.call(c.get); }
2889 Some(Node::ListGet { list, index }) => {
2890 lower_expr(c, scope, seq, &list)?; lower_expr(c, scope, seq, &index)?; seq.call(c.list_get); }
2894 Some(Node::Map(pairs)) => {
2895 seq.i64_const(((1 + 2 * pairs.len()) as i64) * 8);
2897 seq.call(c.alloc); seq.i64_const(0);
2899 seq.i64_const(pairs.len() as i64);
2900 seq.call(c.set); for (i, (k, v)) in pairs.iter().enumerate() {
2902 seq.i64_const(((1 + 2 * i) as i64) * 8);
2903 lower_expr(c, scope, seq, k)?;
2904 seq.call(c.set);
2905 seq.i64_const(((2 + 2 * i) as i64) * 8);
2906 lower_expr(c, scope, seq, v)?;
2907 seq.call(c.set);
2908 }
2909 }
2911 Some(Node::MapGet { map, key }) => {
2912 lower_expr(c, scope, seq, &map)?; lower_expr(c, scope, seq, &key)?; seq.call(c.map_get); }
2916 Some(Node::MapTryGet { map, key }) => {
2917 lower_expr(c, scope, seq, &map)?; lower_expr(c, scope, seq, &key)?; seq.call(c.map_try_get); }
2921 Some(Node::MapLen(arg)) => {
2922 lower_expr(c, scope, seq, &arg)?; seq.i64_const(0);
2924 seq.call(c.get); }
2926 Some(Node::Log(arg)) => {
2927 lower_expr(c, scope, seq, &arg)?; seq.call(c.log); }
2930 Some(Node::Publish(arg)) => {
2931 lower_expr(c, scope, seq, &arg)?; seq.call(c.publish); }
2934 Some(Node::SetHeader { name, value }) => {
2935 lower_expr(c, scope, seq, &name)?; lower_expr(c, scope, seq, &value)?; seq.call(c.set_header); }
2939 Some(Node::Rand) => {
2940 seq.call(c.rand); }
2942 Some(Node::DiskWrite { path, content }) => {
2943 lower_expr(c, scope, seq, &path)?;
2944 lower_expr(c, scope, seq, &content)?;
2945 seq.call(c.disk_write); }
2947 Some(Node::DiskRead(path)) => {
2948 lower_expr(c, scope, seq, &path)?;
2949 seq.call(c.disk_read); }
2951 Some(Node::NetGet(url)) => {
2952 lower_expr(c, scope, seq, &url)?;
2953 seq.call(c.net_get); }
2955 Some(Node::DbQuery { sql, params }) => {
2956 lower_expr(c, scope, seq, &sql)?;
2957 lower_expr(c, scope, seq, ¶ms)?;
2958 seq.call(c.db_query); }
2960 Some(Node::MutNew(v)) => {
2961 seq.i64_const(8);
2963 seq.call(c.alloc); seq.i64_const(0);
2965 lower_expr(c, scope, seq, &v)?;
2966 seq.call(c.set); }
2968 Some(Node::MutGet(cell)) => {
2969 lower_expr(c, scope, seq, &cell)?; seq.i64_const(0);
2971 seq.call(c.get); }
2973 Some(Node::MutSet { cell, value }) => {
2974 lower_expr(c, scope, seq, &cell)?; seq.i64_const(0);
2976 lower_expr(c, scope, seq, &value)?; seq.call(c.set); seq.i64_const(0);
2980 seq.call(c.get); }
2982 Some(Node::RecordDef { .. }) | Some(Node::VariantDef { .. }) => {
2983 return Err(LowerError::Unsupported("type definition is not an expression"));
2984 }
2985 Some(Node::Hole { .. }) => return Err(LowerError::Hole),
2986 Some(Node::Function { .. }) | Some(Node::Module { .. }) => {
2987 return Err(LowerError::Unsupported("nested function or module"));
2988 }
2989 None => return Err(LowerError::Unsupported("missing node")),
2990 }
2991 Ok(())
2992}
2993
2994#[derive(Debug)]
2996pub enum RunError {
2997 Wasmtime(String),
2998 BadExport(String),
3000}
3001
3002impl std::fmt::Display for RunError {
3003 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3004 match self {
3005 RunError::Wasmtime(e) => write!(f, "wasmtime: {e}"),
3006 RunError::BadExport(n) => write!(f, "no callable i64 export `{n}`"),
3007 }
3008 }
3009}
3010
3011impl std::error::Error for RunError {}
3012
3013type WtStore = wasmtime::Store<()>;
3014
3015fn write_wasm_string(caller: &mut wasmtime::Caller<'_, ()>, s: &str) -> i64 {
3020 let bytes = s.as_bytes();
3021 let size = ((1 + bytes.len()) as i64) * 8;
3022 let Some(alloc) = caller
3023 .get_export("__alloc")
3024 .and_then(wasmtime::Extern::into_func)
3025 else {
3026 return -1;
3027 };
3028 let mut out = [wasmtime::Val::I64(0)];
3029 if alloc
3030 .call(&mut *caller, &[wasmtime::Val::I64(size)], &mut out)
3031 .is_err()
3032 {
3033 return -1;
3034 }
3035 let wasmtime::Val::I64(ptr) = out[0] else {
3036 return -1;
3037 };
3038 let ptr = ptr as usize;
3039 let Some(mem) = caller
3040 .get_export("mem")
3041 .and_then(wasmtime::Extern::into_memory)
3042 else {
3043 return -1;
3044 };
3045 let data = mem.data_mut(&mut *caller);
3046 let put = |data: &mut [u8], off: usize, v: i64| {
3047 data[off..off + 8].copy_from_slice(&v.to_le_bytes());
3048 };
3049 put(data, ptr, bytes.len() as i64);
3050 for (i, b) in bytes.iter().enumerate() {
3051 put(data, ptr + (1 + i) * 8, *b as i64);
3052 }
3053 ptr as i64
3054}
3055
3056fn read_wasm_string(data: &[u8], ptr: usize) -> String {
3059 let rd = |off: usize| -> i64 {
3060 let mut b = [0u8; 8];
3061 b.copy_from_slice(&data[off..off + 8]);
3062 i64::from_le_bytes(b)
3063 };
3064 let len = rd(ptr).max(0) as usize;
3065 let mut bytes = Vec::with_capacity(len);
3066 for i in 0..len {
3067 bytes.push((rd(ptr + (1 + i) * 8) & 0xff) as u8);
3068 }
3069 String::from_utf8_lossy(&bytes).into_owned()
3070}
3071
3072thread_local! {
3081 static PUBLISHED: std::cell::RefCell<Vec<String>> =
3082 const { std::cell::RefCell::new(Vec::new()) };
3083}
3084
3085pub fn drain_published() -> Vec<String> {
3088 PUBLISHED.with(|p| std::mem::take(&mut *p.borrow_mut()))
3089}
3090
3091fn record_published(topic: String) {
3092 PUBLISHED.with(|p| p.borrow_mut().push(topic));
3093}
3094
3095thread_local! {
3103 static RESP_HEADERS: std::cell::RefCell<Vec<(String, String)>> =
3104 const { std::cell::RefCell::new(Vec::new()) };
3105}
3106
3107pub fn drain_resp_headers() -> Vec<(String, String)> {
3110 RESP_HEADERS.with(|h| std::mem::take(&mut *h.borrow_mut()))
3111}
3112
3113fn record_resp_header(name: String, value: String) {
3114 RESP_HEADERS.with(|h| h.borrow_mut().push((name, value)));
3115}
3116
3117fn read_wasm_str_list(data: &[u8], ptr: usize) -> Vec<String> {
3120 let rd = |off: usize| -> i64 {
3121 let mut b = [0u8; 8];
3122 b.copy_from_slice(&data[off..off + 8]);
3123 i64::from_le_bytes(b)
3124 };
3125 let len = rd(ptr).max(0) as usize;
3126 (0..len)
3127 .map(|i| read_wasm_string(data, rd(ptr + (1 + i) * 8) as usize))
3128 .collect()
3129}
3130
3131fn run_sql(conn: &rusqlite::Connection, sql: &str, params: &[String]) -> String {
3146 use rusqlite::types::Value;
3147 fn esc(s: &str) -> String {
3151 let mut o = String::with_capacity(s.len());
3152 for ch in s.chars() {
3153 match ch {
3154 '\\' => o.push_str("\\\\"),
3155 '\t' => o.push_str("\\t"),
3156 '\n' => o.push_str("\\n"),
3157 c => o.push(c),
3158 }
3159 }
3160 o
3161 }
3162 fn cell(v: Value) -> String {
3163 match v {
3164 Value::Null => String::new(),
3165 Value::Integer(i) => i.to_string(),
3166 Value::Real(f) => f.to_string(),
3167 Value::Text(s) => esc(&s),
3168 Value::Blob(b) => esc(&String::from_utf8_lossy(&b)),
3169 }
3170 }
3171 let go = || -> rusqlite::Result<String> {
3172 let bound = rusqlite::params_from_iter(params.iter());
3173 let mut stmt = conn.prepare(sql)?;
3174 let ncol = stmt.column_count();
3175 if ncol == 0 {
3176 drop(stmt);
3177 conn.execute(sql, bound)?;
3178 return Ok(conn.last_insert_rowid().to_string());
3179 }
3180 let mut rows = stmt.query(bound)?;
3181 let mut out: Vec<String> = Vec::new();
3182 while let Some(row) = rows.next()? {
3183 let mut cols: Vec<String> = Vec::with_capacity(ncol);
3184 for c in 0..ncol {
3185 cols.push(cell(row.get::<_, Value>(c)?));
3186 }
3187 out.push(cols.join("\t"));
3188 }
3189 Ok(out.join("\n"))
3190 };
3191 match go() {
3192 Ok(s) => s,
3193 Err(e) => {
3194 eprintln!("cairn db_query error: {e}");
3195 String::new()
3196 }
3197 }
3198}
3199
3200fn instantiate(
3201 wasm: &[u8],
3202 now: Option<i64>,
3203 db: Option<&str>,
3204) -> std::result::Result<(WtStore, wasmtime::Instance), RunError> {
3205 use wasmtime::{Engine, Linker, Module as WtModule};
3206
3207 let engine = Engine::default();
3208 let module = WtModule::new(&engine, wasm).map_err(|e| RunError::Wasmtime(e.to_string()))?;
3209 let mut store = wasmtime::Store::new(&engine, ());
3210 let mut linker = Linker::new(&engine);
3211 linker
3214 .func_wrap("host", "now", move || -> i64 {
3215 match now {
3216 Some(v) => v,
3217 None => std::time::SystemTime::now()
3218 .duration_since(std::time::UNIX_EPOCH)
3219 .map(|d| d.as_millis() as i64)
3220 .unwrap_or(0),
3221 }
3222 })
3223 .map_err(|e| RunError::Wasmtime(e.to_string()))?;
3224 linker
3227 .func_wrap("host", "log", |v: i64| -> i64 {
3228 eprintln!("[cairn log] {v}");
3229 v
3230 })
3231 .map_err(|e| RunError::Wasmtime(e.to_string()))?;
3232 linker
3235 .func_wrap(
3236 "host",
3237 "publish",
3238 |mut caller: wasmtime::Caller<'_, ()>, topic_ptr: i64| -> i64 {
3239 let topic = {
3240 match caller
3241 .get_export("mem")
3242 .and_then(wasmtime::Extern::into_memory)
3243 {
3244 Some(mem) => read_wasm_string(
3245 mem.data(&caller),
3246 topic_ptr as usize,
3247 ),
3248 None => return 0,
3249 }
3250 };
3251 record_published(topic);
3252 0
3253 },
3254 )
3255 .map_err(|e| RunError::Wasmtime(e.to_string()))?;
3256 linker
3259 .func_wrap(
3260 "host",
3261 "set_header",
3262 |mut caller: wasmtime::Caller<'_, ()>,
3263 name_ptr: i64,
3264 value_ptr: i64|
3265 -> i64 {
3266 let (name, value) = {
3267 match caller
3268 .get_export("mem")
3269 .and_then(wasmtime::Extern::into_memory)
3270 {
3271 Some(mem) => {
3272 let data = mem.data(&caller);
3273 (
3274 read_wasm_string(data, name_ptr as usize),
3275 read_wasm_string(
3276 data,
3277 value_ptr as usize,
3278 ),
3279 )
3280 }
3281 None => return 0,
3282 }
3283 };
3284 record_resp_header(name, value);
3285 0
3286 },
3287 )
3288 .map_err(|e| RunError::Wasmtime(e.to_string()))?;
3289 linker
3291 .func_wrap("host", "rand", || -> i64 {
3292 let mut b = [0u8; 8];
3293 getrandom::getrandom(&mut b).expect("OS entropy");
3294 i64::from_le_bytes(b)
3295 })
3296 .map_err(|e| RunError::Wasmtime(e.to_string()))?;
3297 linker
3299 .func_wrap(
3300 "host",
3301 "disk_write",
3302 |mut caller: wasmtime::Caller<'_, ()>, p: i64, cptr: i64| -> i64 {
3303 let Some(mem) = caller
3304 .get_export("mem")
3305 .and_then(wasmtime::Extern::into_memory)
3306 else {
3307 return -1;
3308 };
3309 let data = mem.data(&caller);
3310 let path = read_wasm_string(data, p as usize);
3311 let content = read_wasm_string(data, cptr as usize);
3312 match std::fs::write(&path, content.as_bytes()) {
3313 Ok(()) => content.len() as i64,
3314 Err(_) => -1,
3315 }
3316 },
3317 )
3318 .map_err(|e| RunError::Wasmtime(e.to_string()))?;
3319 linker
3320 .func_wrap(
3321 "host",
3322 "disk_read",
3323 |mut caller: wasmtime::Caller<'_, ()>, p: i64| -> i64 {
3324 let path = {
3327 let Some(mem) = caller
3328 .get_export("mem")
3329 .and_then(wasmtime::Extern::into_memory)
3330 else {
3331 return -1;
3332 };
3333 read_wasm_string(mem.data(&caller), p as usize)
3334 };
3335 let contents = std::fs::read_to_string(&path).unwrap_or_default();
3336 write_wasm_string(&mut caller, &contents)
3337 },
3338 )
3339 .map_err(|e| RunError::Wasmtime(e.to_string()))?;
3340 linker
3343 .func_wrap(
3344 "host",
3345 "net_get",
3346 |mut caller: wasmtime::Caller<'_, ()>, u: i64| -> i64 {
3347 let url = {
3348 let Some(mem) = caller
3349 .get_export("mem")
3350 .and_then(wasmtime::Extern::into_memory)
3351 else {
3352 return -1;
3353 };
3354 read_wasm_string(mem.data(&caller), u as usize)
3355 };
3356 match ureq::get(&url).call() {
3357 Ok(resp) => resp.status() as i64,
3358 Err(ureq::Error::Status(code, _)) => code as i64,
3359 Err(_) => -1,
3360 }
3361 },
3362 )
3363 .map_err(|e| RunError::Wasmtime(e.to_string()))?;
3364 let conn = match db {
3370 Some(p) => rusqlite::Connection::open(p),
3371 None => rusqlite::Connection::open_in_memory(),
3372 }
3373 .map_err(|e| RunError::Wasmtime(format!("sqlite open: {e}")))?;
3374 let conn = std::sync::Mutex::new(conn);
3375 linker
3376 .func_wrap(
3377 "host",
3378 "db_query",
3379 move |mut caller: wasmtime::Caller<'_, ()>, q: i64, pp: i64| -> i64 {
3380 let (sql, params) = {
3381 let Some(mem) = caller
3382 .get_export("mem")
3383 .and_then(wasmtime::Extern::into_memory)
3384 else {
3385 return -1;
3386 };
3387 let data = mem.data(&caller);
3388 (
3389 read_wasm_string(data, q as usize),
3390 read_wasm_str_list(data, pp as usize),
3391 )
3392 };
3393 let result = {
3394 let conn = conn.lock().expect("db mutex");
3395 run_sql(&conn, &sql, ¶ms)
3396 };
3397 write_wasm_string(&mut caller, &result)
3398 },
3399 )
3400 .map_err(|e| RunError::Wasmtime(e.to_string()))?;
3401 let instance = linker
3402 .instantiate(&mut store, &module)
3403 .map_err(|e| RunError::Wasmtime(e.to_string()))?;
3404 Ok((store, instance))
3405}
3406
3407pub fn run_i64(wasm: &[u8], func: &str, args: &[i64]) -> std::result::Result<i64, RunError> {
3409 run_with(wasm, func, args, None)
3410}
3411
3412pub fn run_effectful_i64(
3417 wasm: &[u8],
3418 func: &str,
3419 args: &[i64],
3420 now_value: i64,
3421) -> std::result::Result<i64, RunError> {
3422 run_with(wasm, func, args, Some(now_value))
3423}
3424
3425fn run_with(
3426 wasm: &[u8],
3427 func: &str,
3428 args: &[i64],
3429 now: Option<i64>,
3430) -> std::result::Result<i64, RunError> {
3431 use wasmtime::Val;
3432 let (mut store, instance) = instantiate(wasm, now, None)?;
3433 let f = instance
3434 .get_func(&mut store, func)
3435 .ok_or_else(|| RunError::BadExport(func.to_string()))?;
3436 let params: Vec<Val> = args.iter().map(|a| Val::I64(*a)).collect();
3437 let mut results = [Val::I64(0)];
3438 f.call(&mut store, ¶ms, &mut results)
3439 .map_err(|e| RunError::Wasmtime(e.to_string()))?;
3440 match results[0] {
3441 Val::I64(v) => Ok(v),
3442 _ => Err(RunError::BadExport(func.to_string())),
3443 }
3444}
3445
3446pub fn run_fallible(
3449 wasm: &[u8],
3450 func: &str,
3451 args: &[i64],
3452) -> std::result::Result<std::result::Result<i64, i64>, RunError> {
3453 use wasmtime::Val;
3454 let (mut store, instance) = instantiate(wasm, None, None)?;
3455 let f = instance
3456 .get_func(&mut store, func)
3457 .ok_or_else(|| RunError::BadExport(func.to_string()))?;
3458 let memory = instance
3459 .get_memory(&mut store, "mem")
3460 .ok_or_else(|| RunError::BadExport("mem".to_string()))?;
3461
3462 let params: Vec<Val> = args.iter().map(|a| Val::I64(*a)).collect();
3463 let mut results = [Val::I64(0)];
3464 f.call(&mut store, ¶ms, &mut results)
3465 .map_err(|e| RunError::Wasmtime(e.to_string()))?;
3466
3467 let ptr = match results[0] {
3468 Val::I64(p) => p as usize,
3469 _ => return Err(RunError::BadExport(func.to_string())),
3470 };
3471 let data = memory.data(&store);
3472 let read = |off: usize| -> i64 {
3473 let mut b = [0u8; 8];
3474 b.copy_from_slice(&data[ptr + off..ptr + off + 8]);
3475 i64::from_le_bytes(b)
3476 };
3477 let tag = read(0);
3478 if tag == 0 {
3479 Ok(Ok(read(8)))
3480 } else {
3481 Ok(Err(tag))
3482 }
3483}
3484
3485fn write_string_via_export(
3489 store: &mut WtStore,
3490 instance: &wasmtime::Instance,
3491 s: &str,
3492) -> std::result::Result<i64, RunError> {
3493 use wasmtime::Val;
3494 let bytes = s.as_bytes();
3495 let size = ((1 + bytes.len()) as i64) * 8;
3496 let alloc = instance
3497 .get_func(&mut *store, "__alloc")
3498 .ok_or_else(|| RunError::BadExport("__alloc".to_string()))?;
3499 let mut out = [Val::I64(0)];
3500 alloc
3501 .call(&mut *store, &[Val::I64(size)], &mut out)
3502 .map_err(|e| RunError::Wasmtime(e.to_string()))?;
3503 let Val::I64(ptr) = out[0] else {
3504 return Err(RunError::BadExport("__alloc".to_string()));
3505 };
3506 let mem = instance
3507 .get_memory(&mut *store, "mem")
3508 .ok_or_else(|| RunError::BadExport("mem".to_string()))?;
3509 let data = mem.data_mut(&mut *store);
3510 let p = ptr as usize;
3511 data[p..p + 8].copy_from_slice(&(bytes.len() as i64).to_le_bytes());
3512 for (i, b) in bytes.iter().enumerate() {
3513 let off = p + (1 + i) * 8;
3514 data[off..off + 8].copy_from_slice(&(*b as i64).to_le_bytes());
3515 }
3516 Ok(ptr)
3517}
3518
3519pub fn serve_once(
3524 wasm: &[u8],
3525 handler: &str,
3526 request: &str,
3527) -> std::result::Result<String, RunError> {
3528 use wasmtime::Val;
3529 let (mut store, instance) = instantiate(wasm, None, None)?;
3530 let req_ptr = write_string_via_export(&mut store, &instance, request)?;
3531 let f = instance
3532 .get_func(&mut store, handler)
3533 .ok_or_else(|| RunError::BadExport(handler.to_string()))?;
3534 let mut out = [Val::I64(0)];
3535 f.call(&mut store, &[Val::I64(req_ptr)], &mut out)
3536 .map_err(|e| RunError::Wasmtime(e.to_string()))?;
3537 let Val::I64(resp_ptr) = out[0] else {
3538 return Err(RunError::BadExport(handler.to_string()));
3539 };
3540 let mem = instance
3541 .get_memory(&mut store, "mem")
3542 .ok_or_else(|| RunError::BadExport("mem".to_string()))?;
3543 Ok(read_wasm_string(mem.data(&store), resp_ptr as usize))
3544}
3545
3546fn write_record_via_export(
3552 store: &mut WtStore,
3553 instance: &wasmtime::Instance,
3554 slots: &[i64],
3555) -> std::result::Result<i64, RunError> {
3556 use wasmtime::Val;
3557 let size = (slots.len() as i64) * 8;
3558 let alloc = instance
3559 .get_func(&mut *store, "__alloc")
3560 .ok_or_else(|| RunError::BadExport("__alloc".to_string()))?;
3561 let mut out = [Val::I64(0)];
3562 alloc
3563 .call(&mut *store, &[Val::I64(size)], &mut out)
3564 .map_err(|e| RunError::Wasmtime(e.to_string()))?;
3565 let Val::I64(ptr) = out[0] else {
3566 return Err(RunError::BadExport("__alloc".to_string()));
3567 };
3568 let mem = instance
3569 .get_memory(&mut *store, "mem")
3570 .ok_or_else(|| RunError::BadExport("mem".to_string()))?;
3571 let data = mem.data_mut(&mut *store);
3572 for (i, v) in slots.iter().enumerate() {
3573 let off = ptr as usize + i * 8;
3574 data[off..off + 8].copy_from_slice(&v.to_le_bytes());
3575 }
3576 Ok(ptr)
3577}
3578
3579#[derive(Debug, Clone, PartialEq, Eq)]
3582pub struct HttpResponse {
3583 pub status: i64,
3584 pub body: String,
3585}
3586
3587pub fn serve_request(
3596 wasm: &[u8],
3597 handler: &str,
3598 method: &str,
3599 path: &str,
3600 body: &str,
3601) -> std::result::Result<HttpResponse, RunError> {
3602 serve_request_with(wasm, handler, method, path, body, "", None)
3603}
3604
3605pub fn serve_request_h(
3609 wasm: &[u8],
3610 handler: &str,
3611 method: &str,
3612 path: &str,
3613 body: &str,
3614 headers: &str,
3615) -> std::result::Result<HttpResponse, RunError> {
3616 serve_request_with(wasm, handler, method, path, body, headers, None)
3617}
3618
3619pub fn serve_request_db(
3625 wasm: &[u8],
3626 handler: &str,
3627 db_path: &str,
3628 method: &str,
3629 path: &str,
3630 body: &str,
3631) -> std::result::Result<HttpResponse, RunError> {
3632 serve_request_with(wasm, handler, method, path, body, "", Some(db_path))
3633}
3634
3635pub fn serve_request_db_h(
3639 wasm: &[u8],
3640 handler: &str,
3641 db_path: &str,
3642 method: &str,
3643 path: &str,
3644 body: &str,
3645 headers: &str,
3646) -> std::result::Result<HttpResponse, RunError> {
3647 serve_request_with(
3648 wasm,
3649 handler,
3650 method,
3651 path,
3652 body,
3653 headers,
3654 Some(db_path),
3655 )
3656}
3657
3658fn serve_request_with(
3659 wasm: &[u8],
3660 handler: &str,
3661 method: &str,
3662 path: &str,
3663 body: &str,
3664 headers: &str,
3665 db: Option<&str>,
3666) -> std::result::Result<HttpResponse, RunError> {
3667 use wasmtime::Val;
3668 let (mut store, instance) = instantiate(wasm, None, db)?;
3669 let m = write_string_via_export(&mut store, &instance, method)?;
3670 let p = write_string_via_export(&mut store, &instance, path)?;
3671 let b = write_string_via_export(&mut store, &instance, body)?;
3672 let hh = write_string_via_export(&mut store, &instance, headers)?;
3673 let req_ptr =
3674 write_record_via_export(&mut store, &instance, &[m, p, b, hh])?;
3675 let f = instance
3676 .get_func(&mut store, handler)
3677 .ok_or_else(|| RunError::BadExport(handler.to_string()))?;
3678 let mut out = [Val::I64(0)];
3679 f.call(&mut store, &[Val::I64(req_ptr)], &mut out)
3680 .map_err(|e| RunError::Wasmtime(e.to_string()))?;
3681 let Val::I64(resp_ptr) = out[0] else {
3682 return Err(RunError::BadExport(handler.to_string()));
3683 };
3684 let mem = instance
3685 .get_memory(&mut store, "mem")
3686 .ok_or_else(|| RunError::BadExport("mem".to_string()))?;
3687 let data = mem.data(&store);
3688 let slot = |i: usize| -> i64 {
3689 let off = resp_ptr as usize + i * 8;
3690 let mut x = [0u8; 8];
3691 x.copy_from_slice(&data[off..off + 8]);
3692 i64::from_le_bytes(x)
3693 };
3694 Ok(HttpResponse {
3695 status: slot(0),
3696 body: read_wasm_string(data, slot(1) as usize),
3697 })
3698}
3699
3700pub(crate) fn reason_phrase(status: i64) -> &'static str {
3704 match status {
3705 200 => "OK",
3706 201 => "Created",
3707 204 => "No Content",
3708 400 => "Bad Request",
3709 404 => "Not Found",
3710 500 => "Internal Server Error",
3711 s if (200..300).contains(&s) => "OK",
3712 s if (400..500).contains(&s) => "Client Error",
3713 _ => "Error",
3714 }
3715}
3716
3717pub fn serve_http(
3725 wasm: &[u8],
3726 handler: &str,
3727 addr: &str,
3728) -> std::result::Result<(), RunError> {
3729 serve_http_with(wasm, handler, addr, None)
3730}
3731
3732pub fn serve_http_db(
3735 wasm: &[u8],
3736 handler: &str,
3737 addr: &str,
3738 db_path: &str,
3739) -> std::result::Result<(), RunError> {
3740 serve_http_with(wasm, handler, addr, Some(db_path))
3741}
3742
3743fn serve_http_with(
3744 wasm: &[u8],
3745 handler: &str,
3746 addr: &str,
3747 db: Option<&str>,
3748) -> std::result::Result<(), RunError> {
3749 use std::io::{BufRead, BufReader, Read, Write};
3750 let listener = std::net::TcpListener::bind(addr)
3751 .map_err(|e| RunError::Wasmtime(e.to_string()))?;
3752 for stream in listener.incoming() {
3753 let Ok(mut stream) = stream else { continue };
3754 let mut reader = BufReader::new(&stream);
3755 let mut request_line = String::new();
3756 if reader.read_line(&mut request_line).is_err() {
3757 continue;
3758 }
3759 let mut parts = request_line.split_whitespace();
3760 let method = parts.next().unwrap_or("GET").to_string();
3761 let path = parts.next().unwrap_or("/").to_string();
3762 let mut content_length = 0usize;
3766 let mut header_lines: Vec<String> = Vec::new();
3767 loop {
3768 let mut h = String::new();
3769 if reader.read_line(&mut h).is_err() {
3770 break;
3771 }
3772 let h = h.trim_end();
3773 if h.is_empty() {
3774 break;
3775 }
3776 if let Some((name, value)) = h.split_once(':') {
3777 if name.eq_ignore_ascii_case("content-length") {
3778 content_length = value.trim().parse().unwrap_or(0);
3779 }
3780 }
3781 header_lines.push(h.to_string());
3782 }
3783 let headers = header_lines.join("\n");
3784 let mut req_body = String::new();
3785 if content_length > 0 {
3786 let mut buf = vec![0u8; content_length];
3787 if reader.read_exact(&mut buf).is_ok() {
3788 req_body = String::from_utf8_lossy(&buf).into_owned();
3789 }
3790 }
3791 drop(reader);
3792 let (status, body) = match serve_request_with(
3793 wasm, handler, &method, &path, &req_body, &headers, db,
3794 ) {
3795 Ok(r) => (r.status, r.body),
3796 Err(e) => (500, format!("cairn handler error: {e}")),
3797 };
3798 let extra: String = drain_resp_headers()
3801 .into_iter()
3802 .map(|(n, v)| format!("{n}: {v}\r\n"))
3803 .collect();
3804 let resp = format!(
3805 "HTTP/1.1 {status} {}\r\nContent-Length: {}\r\n\
3806 Content-Type: text/html; charset=utf-8\r\n{extra}\r\n{}",
3807 reason_phrase(status),
3808 body.len(),
3809 body
3810 );
3811 let _ = stream.write_all(resp.as_bytes());
3812 }
3813 Ok(())
3814}
3815
3816#[cfg(test)]
3817mod tests {
3818 use super::*;
3819 use crate::node::{Param, Produces};
3820 use crate::ty::{Confidence, Type};
3821 use std::collections::BTreeSet;
3822
3823 fn func(
3824 s: &Store,
3825 name: &str,
3826 params: &[&str],
3827 body: Vec<NodeHash>,
3828 result: NodeHash,
3829 ) -> NodeHash {
3830 s.put(&Node::Function {
3831 name: name.into(),
3832 type_params: vec![],
3833 params: params
3834 .iter()
3835 .map(|p| Param {
3836 name: (*p).into(),
3837 ty: Type::Number,
3838 min_confidence: Confidence::External,
3839 })
3840 .collect(),
3841 produces: Produces {
3842 ty: Type::Number,
3843 confidence: Confidence::Structural,
3844 },
3845 requires: BTreeSet::new(),
3846 on_failure: vec![],
3847 body,
3848 result,
3849 })
3850 .unwrap()
3851 }
3852
3853 fn module(s: &Store, fns: Vec<NodeHash>) -> NodeHash {
3854 s.put(&Node::Module {
3855 name: "m".into(),
3856 types: vec![],
3857 functions: fns,
3858 })
3859 .unwrap()
3860 }
3861
3862 #[test]
3863 fn a_constant_function_runs_correctly() {
3864 let s = Store::open_in_memory().unwrap();
3865 let lit = s.put(&Node::Lit(42)).unwrap();
3866 let answer = func(&s, "answer", &[], vec![], lit);
3867 let m = module(&s, vec![answer]);
3868 let wasm = lower(&s, &m).unwrap();
3869 assert_eq!(run_i64(&wasm, "answer", &[]).unwrap(), 42);
3870 }
3871
3872 #[test]
3873 fn publish_performs_the_live_effect_observably() {
3874 let s = Store::open_in_memory().unwrap();
3878 let topic = s.put(&Node::Str("items".into())).unwrap();
3879 let notify = func(
3880 &s,
3881 "notify",
3882 &[],
3883 vec![],
3884 s.put(&Node::Publish(topic)).unwrap(),
3885 );
3886 let m = module(&s, vec![notify]);
3887 let wasm = lower(&s, &m).unwrap();
3888 let _ = super::drain_published(); assert_eq!(run_i64(&wasm, "notify", &[]).unwrap(), 0);
3890 assert!(
3891 super::drain_published().contains(&"items".to_string()),
3892 "host::publish must record the topic"
3893 );
3894 }
3895
3896 #[test]
3897 fn num_to_str_handles_full_i64_range() {
3898 let s = Store::open_in_memory().unwrap();
3899 let rt = |name: &str, v: i64| {
3900 let lit = s.put(&Node::Lit(v)).unwrap();
3901 let n2s = s.put(&Node::NumberToStr(lit)).unwrap();
3902 func(&s, name, &[], vec![], s.put(&Node::StrToNumber(n2s)).unwrap())
3903 };
3904 let vals = [
3905 ("mn", i64::MIN),
3906 ("mx", i64::MAX),
3907 ("neg", -1),
3908 ("zero", 0),
3909 ("c", 100),
3910 ("near", i64::MIN + 7),
3911 ];
3912 let fns: Vec<_> = vals.iter().map(|(n, v)| rt(n, *v)).collect();
3913 let lit = s.put(&Node::Lit(i64::MIN)).unwrap();
3915 let n2s = s.put(&Node::NumberToStr(lit)).unwrap();
3916 let mut all = fns;
3917 all.push(func(&s, "mnlen", &[], vec![], s.put(&Node::StrLen(n2s)).unwrap()));
3918 let m = module(&s, all);
3919 let wasm = lower(&s, &m).unwrap();
3920 for (n, v) in vals {
3921 assert_eq!(run_i64(&wasm, n, &[]).unwrap(), v, "round-trip {n}");
3922 }
3923 assert_eq!(run_i64(&wasm, "mnlen", &[]).unwrap(), 20);
3924 }
3925
3926 #[test]
3927 fn boolean_and_comparison_operators_run() {
3928 let s = Store::open_in_memory().unwrap();
3929 let n = |v: i64| s.put(&Node::Lit(v)).unwrap();
3930 let b = |v: bool| s.put(&Node::Bool(v)).unwrap();
3931 let bin = |op, l: NodeHash, r: NodeHash| {
3932 s.put(&Node::BinOp {
3933 op,
3934 lhs: l,
3935 rhs: r,
3936 })
3937 .unwrap()
3938 };
3939 use crate::node::BinOp::*;
3940
3941 let rmod = func(&s, "rmod", &[], vec![], bin(Mod, n(7), n(3))); let le = func(&s, "le", &[], vec![], bin(Le, n(3), n(3))); let gt = func(&s, "gt", &[], vec![], bin(Gt, n(5), n(2))); let ge = func(&s, "ge", &[], vec![], bin(Ge, n(2), n(5))); let ne = func(&s, "ne", &[], vec![], bin(Neq, n(4), n(4))); let notf = func(
3947 &s,
3948 "notf",
3949 &[],
3950 vec![],
3951 s.put(&Node::Not(b(false))).unwrap(),
3952 ); let andt = func(
3954 &s,
3955 "andt",
3956 &[],
3957 vec![],
3958 bin(And, b(true), bin(Lt, n(1), n(2))),
3959 ); let sc_or =
3965 func(&s, "sc_or", &[], vec![], bin(Or, b(true), bin(Div, n(1), n(0))));
3966 let sc_and = func(
3967 &s,
3968 "sc_and",
3969 &[],
3970 vec![],
3971 bin(And, b(false), bin(Div, n(1), n(0))),
3972 );
3973
3974 let m = module(
3975 &s,
3976 vec![rmod, le, gt, ge, ne, notf, andt, sc_or, sc_and],
3977 );
3978 let wasm = lower(&s, &m).unwrap();
3979 assert_eq!(run_i64(&wasm, "rmod", &[]).unwrap(), 1);
3980 assert_eq!(run_i64(&wasm, "le", &[]).unwrap(), 1);
3981 assert_eq!(run_i64(&wasm, "gt", &[]).unwrap(), 1);
3982 assert_eq!(run_i64(&wasm, "ge", &[]).unwrap(), 0);
3983 assert_eq!(run_i64(&wasm, "ne", &[]).unwrap(), 0);
3984 assert_eq!(run_i64(&wasm, "notf", &[]).unwrap(), 1);
3985 assert_eq!(run_i64(&wasm, "andt", &[]).unwrap(), 1);
3986 assert_eq!(
3987 run_i64(&wasm, "sc_or", &[]).unwrap(),
3988 1,
3989 "Or must short-circuit past a trapping right operand"
3990 );
3991 assert_eq!(
3992 run_i64(&wasm, "sc_and", &[]).unwrap(),
3993 0,
3994 "And must short-circuit past a trapping right operand"
3995 );
3996 }
3997
3998 #[test]
3999 fn function_values_pass_and_call_indirect() {
4000 let s = Store::open_in_memory().unwrap();
4004 let n = |v: i64| s.put(&Node::Lit(v)).unwrap();
4005 let r = |name: &str| s.put(&Node::Ref(name.into())).unwrap();
4006
4007 let double = func(
4008 &s,
4009 "double",
4010 &["n"],
4011 vec![],
4012 s.put(&Node::BinOp {
4013 op: crate::node::BinOp::Mul,
4014 lhs: r("n"),
4015 rhs: n(2),
4016 })
4017 .unwrap(),
4018 );
4019 let apply = func(
4020 &s,
4021 "apply",
4022 &["f", "x"],
4023 vec![],
4024 s.put(&Node::CallValue {
4025 callee: r("f"),
4026 args: vec![r("x")],
4027 })
4028 .unwrap(),
4029 );
4030 let via_param = func(
4032 &s,
4033 "via_param",
4034 &[],
4035 vec![],
4036 s.put(&Node::Call {
4037 func: "apply".into(),
4038 args: vec![
4039 s.put(&Node::FuncRef("double".into())).unwrap(),
4040 n(21),
4041 ],
4042 })
4043 .unwrap(),
4044 );
4045 let direct = func(
4047 &s,
4048 "direct",
4049 &[],
4050 vec![],
4051 s.put(&Node::CallValue {
4052 callee: s.put(&Node::FuncRef("double".into())).unwrap(),
4053 args: vec![n(19)],
4054 })
4055 .unwrap(),
4056 );
4057 let m = module(&s, vec![double, apply, via_param, direct]);
4058 let wasm = lower(&s, &m).unwrap();
4059 assert_eq!(run_i64(&wasm, "via_param", &[]).unwrap(), 42);
4060 assert_eq!(run_i64(&wasm, "direct", &[]).unwrap(), 38);
4061 }
4062
4063 #[test]
4064 fn closures_capture_and_run() {
4065 let s = Store::open_in_memory().unwrap();
4068 let n = |v: i64| s.put(&Node::Lit(v)).unwrap();
4069 let r = |name: &str| s.put(&Node::Ref(name.into())).unwrap();
4070 let par = |name: &str| Param {
4071 name: name.into(),
4072 ty: Type::Number,
4073 min_confidence: Confidence::External,
4074 };
4075 let add = |a: NodeHash, b: NodeHash| {
4076 s.put(&Node::BinOp {
4077 op: crate::node::BinOp::Add,
4078 lhs: a,
4079 rhs: b,
4080 })
4081 .unwrap()
4082 };
4083
4084 let lam = s
4085 .put(&Node::Lambda {
4086 params: vec![par("x")],
4087 body: add(r("x"), r("k")),
4088 })
4089 .unwrap();
4090 let mk = func(&s, "mk", &["k"], vec![], lam);
4091 let apply = func(
4092 &s,
4093 "apply",
4094 &["f", "v"],
4095 vec![],
4096 s.put(&Node::CallValue {
4097 callee: r("f"),
4098 args: vec![r("v")],
4099 })
4100 .unwrap(),
4101 );
4102 let via_apply = func(
4103 &s,
4104 "via_apply",
4105 &[],
4106 vec![],
4107 s.put(&Node::Call {
4108 func: "apply".into(),
4109 args: vec![
4110 s.put(&Node::Call {
4111 func: "mk".into(),
4112 args: vec![n(10)],
4113 })
4114 .unwrap(),
4115 n(5),
4116 ],
4117 })
4118 .unwrap(),
4119 );
4120 let direct = func(
4121 &s,
4122 "direct",
4123 &[],
4124 vec![],
4125 s.put(&Node::CallValue {
4126 callee: s
4127 .put(&Node::Call {
4128 func: "mk".into(),
4129 args: vec![n(10)],
4130 })
4131 .unwrap(),
4132 args: vec![n(7)],
4133 })
4134 .unwrap(),
4135 );
4136
4137 let inner = s
4139 .put(&Node::Lambda {
4140 params: vec![par("y")],
4141 body: add(add(r("x"), r("y")), r("k")),
4142 })
4143 .unwrap();
4144 let outer = s
4145 .put(&Node::Lambda {
4146 params: vec![par("x")],
4147 body: inner,
4148 })
4149 .unwrap();
4150 let mk2 = func(&s, "mk2", &["k"], vec![], outer);
4151 let nested = func(
4152 &s,
4153 "nested",
4154 &[],
4155 vec![],
4156 s.put(&Node::CallValue {
4157 callee: s
4158 .put(&Node::CallValue {
4159 callee: s
4160 .put(&Node::Call {
4161 func: "mk2".into(),
4162 args: vec![n(100)],
4163 })
4164 .unwrap(),
4165 args: vec![n(20)],
4166 })
4167 .unwrap(),
4168 args: vec![n(3)],
4169 })
4170 .unwrap(),
4171 );
4172
4173 let m = module(
4174 &s,
4175 vec![mk, apply, via_apply, direct, mk2, nested],
4176 );
4177 let wasm = lower(&s, &m).unwrap();
4178 assert_eq!(run_i64(&wasm, "via_apply", &[]).unwrap(), 15);
4179 assert_eq!(run_i64(&wasm, "direct", &[]).unwrap(), 17);
4180 assert_eq!(
4181 run_i64(&wasm, "nested", &[]).unwrap(),
4182 123,
4183 "transitive capture: (x+y)+k = (20+3)+100"
4184 );
4185 }
4186
4187 #[test]
4188 fn option_match_drives_control_flow() {
4189 let s = Store::open_in_memory().unwrap();
4192 let n = |v: i64| s.put(&Node::Lit(v)).unwrap();
4193 let xs = s.put(&Node::List(vec![n(10), n(20), n(30)])).unwrap();
4194 let at = |i: i64| {
4195 s.put(&Node::OptionMatch {
4196 opt: s
4197 .put(&Node::ListTryGet {
4198 list: xs.clone(),
4199 index: n(i),
4200 })
4201 .unwrap(),
4202 some_bind: "v".into(),
4203 some_body: s.put(&Node::Ref("v".into())).unwrap(),
4204 none_body: n(-1),
4205 })
4206 .unwrap()
4207 };
4208 let hit = func(&s, "hit", &[], vec![], at(1)); let miss = func(&s, "miss", &[], vec![], at(9)); let m = module(&s, vec![hit, miss]);
4211 let wasm = lower(&s, &m).unwrap();
4212 assert_eq!(run_i64(&wasm, "hit", &[]).unwrap(), 20);
4213 assert_eq!(run_i64(&wasm, "miss", &[]).unwrap(), -1);
4214 }
4215
4216 #[test]
4217 fn map_try_get_yields_an_option() {
4218 let s = Store::open_in_memory().unwrap();
4220 let n = |v: i64| s.put(&Node::Lit(v)).unwrap();
4221 let m = s
4222 .put(&Node::Map(vec![(n(1), n(100)), (n(2), n(200))]))
4223 .unwrap();
4224 let look = |key: i64| {
4225 s.put(&Node::OptionMatch {
4226 opt: s
4227 .put(&Node::MapTryGet {
4228 map: m.clone(),
4229 key: n(key),
4230 })
4231 .unwrap(),
4232 some_bind: "v".into(),
4233 some_body: s.put(&Node::Ref("v".into())).unwrap(),
4234 none_body: n(-1),
4235 })
4236 .unwrap()
4237 };
4238 let hit = func(&s, "hit", &[], vec![], look(2)); let miss = func(&s, "miss", &[], vec![], look(9)); let md = module(&s, vec![hit, miss]);
4241 let wasm = lower(&s, &md).unwrap();
4242 assert_eq!(run_i64(&wasm, "hit", &[]).unwrap(), 200);
4243 assert_eq!(run_i64(&wasm, "miss", &[]).unwrap(), -1);
4244 }
4245
4246 #[test]
4247 fn float_arithmetic_is_real_ieee754() {
4248 use crate::node::BinOp;
4249 let s = Store::open_in_memory().unwrap();
4250 let f = |v: f64| s.put(&Node::FloatLit(v.to_bits())).unwrap();
4251 let op = |o, a: NodeHash, b: NodeHash| {
4252 s.put(&Node::FloatOp {
4253 op: o,
4254 lhs: a,
4255 rhs: b,
4256 })
4257 .unwrap()
4258 };
4259
4260 let prod = func(
4262 &s,
4263 "prod",
4264 &[],
4265 vec![],
4266 s.put(&Node::FloatToInt(op(BinOp::Mul, f(2.5), f(4.0)))).unwrap(),
4267 );
4268 let lt = func(&s, "lt", &[], vec![], op(BinOp::Lt, f(0.1), f(0.2)));
4270 let inexact = func(
4272 &s,
4273 "inexact",
4274 &[],
4275 vec![],
4276 op(BinOp::Eq, op(BinOp::Add, f(0.1), f(0.2)), f(0.3)),
4277 );
4278 let raw = func(&s, "raw", &[], vec![], f(3.5));
4280 let i2f2i = func(
4281 &s,
4282 "i2f2i",
4283 &[],
4284 vec![],
4285 s.put(&Node::FloatToInt(
4286 s.put(&Node::IntToFloat(s.put(&Node::Lit(7)).unwrap()))
4287 .unwrap(),
4288 ))
4289 .unwrap(),
4290 );
4291 let m = module(&s, vec![prod, lt, inexact, raw, i2f2i]);
4292 let wasm = lower(&s, &m).unwrap();
4293 assert_eq!(run_i64(&wasm, "prod", &[]).unwrap(), 10);
4294 assert_eq!(run_i64(&wasm, "lt", &[]).unwrap(), 1);
4295 assert_eq!(
4296 run_i64(&wasm, "inexact", &[]).unwrap(),
4297 0,
4298 "0.1+0.2 != 0.3 — genuine IEEE-754"
4299 );
4300 assert_eq!(
4301 run_i64(&wasm, "raw", &[]).unwrap(),
4302 3.5f64.to_bits() as i64,
4303 "Float travels as its f64 bit pattern in the i64 slot"
4304 );
4305 assert_eq!(run_i64(&wasm, "i2f2i", &[]).unwrap(), 7);
4306 }
4307
4308 #[test]
4309 fn decimal_is_exact() {
4310 use crate::node::BinOp;
4311 let s = Store::open_in_memory().unwrap();
4312 let d = |v: f64| {
4314 s.put(&Node::DecimalLit((v * 10_000.0).round() as i64))
4315 .unwrap()
4316 };
4317 let op = |o, a: NodeHash, b: NodeHash| {
4318 s.put(&Node::DecimalOp {
4319 op: o,
4320 lhs: a,
4321 rhs: b,
4322 })
4323 .unwrap()
4324 };
4325 let to_int = |x: NodeHash| s.put(&Node::DecimalToInt(x)).unwrap();
4326
4327 let prod = func(
4329 &s,
4330 "prod",
4331 &[],
4332 vec![],
4333 to_int(op(BinOp::Mul, d(1.25), d(4.0))),
4334 );
4335 let exact = func(
4337 &s,
4338 "exact",
4339 &[],
4340 vec![],
4341 op(BinOp::Eq, op(BinOp::Add, d(0.10), d(0.20)), d(0.30)),
4342 );
4343 let money = func(
4345 &s,
4346 "money",
4347 &[],
4348 vec![],
4349 to_int(op(BinOp::Add, d(19.99), d(0.01))),
4350 );
4351 let i2d2i = func(
4353 &s,
4354 "i2d2i",
4355 &[],
4356 vec![],
4357 to_int(s.put(&Node::IntToDecimal(s.put(&Node::Lit(7)).unwrap())).unwrap()),
4358 );
4359 let m = module(&s, vec![prod, exact, money, i2d2i]);
4360 let wasm = lower(&s, &m).unwrap();
4361 assert_eq!(run_i64(&wasm, "prod", &[]).unwrap(), 5);
4362 assert_eq!(
4363 run_i64(&wasm, "exact", &[]).unwrap(),
4364 1,
4365 "0.10+0.20 == 0.30 exactly — the point of Decimal"
4366 );
4367 assert_eq!(run_i64(&wasm, "money", &[]).unwrap(), 20);
4368 assert_eq!(run_i64(&wasm, "i2d2i", &[]).unwrap(), 7);
4369 }
4370
4371 #[test]
4372 fn out_of_bounds_list_get_and_str_slice_trap() {
4373 let s = Store::open_in_memory().unwrap();
4375 let n = |v: i64| s.put(&Node::Lit(v)).unwrap();
4376 let list = s.put(&Node::List(vec![n(10), n(20), n(30)])).unwrap();
4377
4378 let good = func(
4380 &s,
4381 "good",
4382 &[],
4383 vec![],
4384 s.put(&Node::ListGet {
4385 list: list.clone(),
4386 index: n(1),
4387 })
4388 .unwrap(),
4389 );
4390 let oob = func(
4392 &s,
4393 "oob",
4394 &[],
4395 vec![],
4396 s.put(&Node::ListGet {
4397 list,
4398 index: n(5),
4399 })
4400 .unwrap(),
4401 );
4402 let bad = func(
4404 &s,
4405 "bad",
4406 &[],
4407 vec![],
4408 s.put(&Node::StrLen(
4409 s.put(&Node::StrSlice {
4410 s: s.put(&Node::Str("ab".into())).unwrap(),
4411 start: n(0),
4412 len: n(5),
4413 })
4414 .unwrap(),
4415 ))
4416 .unwrap(),
4417 );
4418 let m = module(&s, vec![good, oob, bad]);
4419 let wasm = lower(&s, &m).unwrap();
4420 assert_eq!(run_i64(&wasm, "good", &[]).unwrap(), 20);
4421 assert!(run_i64(&wasm, "oob", &[]).is_err(), "OOB index must trap");
4422 assert!(
4423 run_i64(&wasm, "bad", &[]).is_err(),
4424 "OOB slice must trap"
4425 );
4426 }
4427
4428 #[test]
4429 fn str_to_number_opt_rejects_invalid_input() {
4430 let s = Store::open_in_memory().unwrap();
4434 let mk = |name: &str, input: &str| -> NodeHash {
4435 let body = s
4436 .put(&Node::OptionElse {
4437 opt: s
4438 .put(&Node::StrToNumberOpt(
4439 s.put(&Node::Str(input.into())).unwrap(),
4440 ))
4441 .unwrap(),
4442 default: s.put(&Node::Lit(-999)).unwrap(),
4443 })
4444 .unwrap();
4445 func(&s, name, &[], vec![], body)
4446 };
4447 let fns = vec![
4448 mk("a", "42"),
4449 mk("b", "-7"),
4450 mk("c", "0"),
4451 mk("d", "12x"),
4452 mk("e", ""),
4453 mk("f", "-"),
4454 mk("g", "3.5"),
4455 ];
4456 let m = module(&s, fns);
4457 let wasm = lower(&s, &m).unwrap();
4458 assert_eq!(run_i64(&wasm, "a", &[]).unwrap(), 42);
4459 assert_eq!(run_i64(&wasm, "b", &[]).unwrap(), -7);
4460 assert_eq!(run_i64(&wasm, "c", &[]).unwrap(), 0);
4461 assert_eq!(run_i64(&wasm, "d", &[]).unwrap(), -999); assert_eq!(run_i64(&wasm, "e", &[]).unwrap(), -999); assert_eq!(run_i64(&wasm, "f", &[]).unwrap(), -999); assert_eq!(run_i64(&wasm, "g", &[]).unwrap(), -999); }
4466
4467 #[test]
4468 fn option_recovers_from_out_of_bounds_without_trapping() {
4469 let s = Store::open_in_memory().unwrap();
4473 let n = |v: i64| s.put(&Node::Lit(v)).unwrap();
4474 let list = s.put(&Node::List(vec![n(10), n(20), n(30)])).unwrap();
4475 let body = s
4476 .put(&Node::OptionElse {
4477 opt: s
4478 .put(&Node::ListTryGet {
4479 list,
4480 index: s.put(&Node::Ref("i".into())).unwrap(),
4481 })
4482 .unwrap(),
4483 default: n(-1),
4484 })
4485 .unwrap();
4486 let at = func(&s, "at", &["i"], vec![], body);
4487 let some = func(
4489 &s,
4490 "som",
4491 &[],
4492 vec![],
4493 s.put(&Node::OptionElse {
4494 opt: s.put(&Node::OptionSome(n(7))).unwrap(),
4495 default: n(-1),
4496 })
4497 .unwrap(),
4498 );
4499 let none = func(
4500 &s,
4501 "non",
4502 &[],
4503 vec![],
4504 s.put(&Node::OptionElse {
4505 opt: s
4506 .put(&Node::OptionNone {
4507 elem: Type::Number,
4508 })
4509 .unwrap(),
4510 default: n(-1),
4511 })
4512 .unwrap(),
4513 );
4514 let m = module(&s, vec![at, some, none]);
4515 let wasm = lower(&s, &m).unwrap();
4516 assert_eq!(run_i64(&wasm, "at", &[0]).unwrap(), 10);
4517 assert_eq!(run_i64(&wasm, "at", &[2]).unwrap(), 30);
4518 assert_eq!(run_i64(&wasm, "at", &[5]).unwrap(), -1); assert_eq!(run_i64(&wasm, "at", &[-1]).unwrap(), -1); assert_eq!(run_i64(&wasm, "som", &[]).unwrap(), 7);
4521 assert_eq!(run_i64(&wasm, "non", &[]).unwrap(), -1);
4522 }
4523
4524 #[test]
4525 fn allocation_grows_past_the_initial_page() {
4526 let s = Store::open_in_memory().unwrap();
4531 let r = |n: &str| s.put(&Node::Ref(n.into())).unwrap();
4532 let num = |v: i64| s.put(&Node::Lit(v)).unwrap();
4533 let binop = |op, a: NodeHash, b: NodeHash| {
4534 s.put(&Node::BinOp { op, lhs: a, rhs: b }).unwrap()
4535 };
4536 let p = |name: &str, ty: Type| Param {
4537 name: name.into(),
4538 ty,
4539 min_confidence: Confidence::External,
4540 };
4541 let ext = |ty: Type| Produces {
4542 ty,
4543 confidence: Confidence::External,
4544 };
4545 let num_list = || Type::List(Box::new(Type::Number));
4546
4547 let mk = s
4548 .put(&Node::Function {
4549 name: "mk".into(),
4550 type_params: vec![],
4551 params: vec![p("n", Type::Number)],
4552 produces: ext(num_list()),
4553 requires: BTreeSet::new(),
4554 on_failure: vec![],
4555 body: vec![],
4556 result: s
4557 .put(&Node::If {
4558 cond: binop(crate::node::BinOp::Eq, r("n"), num(0)),
4559 then_branch: s
4560 .put(&Node::ListEmpty {
4561 elem: Type::Number,
4562 })
4563 .unwrap(),
4564 else_branch: s
4565 .put(&Node::ListCons {
4566 head: r("n"),
4567 tail: s
4568 .put(&Node::Call {
4569 func: "mk".into(),
4570 args: vec![binop(
4571 crate::node::BinOp::Sub,
4572 r("n"),
4573 num(1),
4574 )],
4575 })
4576 .unwrap(),
4577 })
4578 .unwrap(),
4579 })
4580 .unwrap(),
4581 })
4582 .unwrap();
4583 let sum = s
4584 .put(&Node::Function {
4585 name: "sum".into(),
4586 type_params: vec![],
4587 params: vec![p("xs", num_list()), p("i", Type::Number)],
4588 produces: ext(Type::Number),
4589 requires: BTreeSet::new(),
4590 on_failure: vec![],
4591 body: vec![s
4592 .put(&Node::Step {
4593 binding: "len".into(),
4594 value: s.put(&Node::ListLen(r("xs"))).unwrap(),
4595 })
4596 .unwrap()],
4597 result: s
4598 .put(&Node::If {
4599 cond: binop(crate::node::BinOp::Eq, r("i"), r("len")),
4600 then_branch: num(0),
4601 else_branch: binop(
4602 crate::node::BinOp::Add,
4603 s.put(&Node::ListGet {
4604 list: r("xs"),
4605 index: r("i"),
4606 })
4607 .unwrap(),
4608 s.put(&Node::Call {
4609 func: "sum".into(),
4610 args: vec![
4611 r("xs"),
4612 binop(crate::node::BinOp::Add, r("i"), num(1)),
4613 ],
4614 })
4615 .unwrap(),
4616 ),
4617 })
4618 .unwrap(),
4619 })
4620 .unwrap();
4621 let total = s
4622 .put(&Node::Function {
4623 name: "total".into(),
4624 type_params: vec![],
4625 params: vec![p("n", Type::Number)],
4626 produces: ext(Type::Number),
4627 requires: BTreeSet::new(),
4628 on_failure: vec![],
4629 body: vec![s
4630 .put(&Node::Step {
4631 binding: "xs".into(),
4632 value: s
4633 .put(&Node::Call {
4634 func: "mk".into(),
4635 args: vec![r("n")],
4636 })
4637 .unwrap(),
4638 })
4639 .unwrap()],
4640 result: s
4641 .put(&Node::Call {
4642 func: "sum".into(),
4643 args: vec![r("xs"), num(0)],
4644 })
4645 .unwrap(),
4646 })
4647 .unwrap();
4648 let m = module(&s, vec![total, mk, sum]);
4649 let wasm = lower(&s, &m).unwrap();
4650 assert_eq!(run_i64(&wasm, "total", &[1500]).unwrap(), 1_125_750);
4652 }
4653
4654 #[test]
4655 fn real_net_get_returns_the_http_status() {
4656 use std::io::{Read, Write};
4657 let listener =
4660 std::net::TcpListener::bind("127.0.0.1:0").unwrap();
4661 let port = listener.local_addr().unwrap().port();
4662 let server = std::thread::spawn(move || {
4663 if let Ok((mut stream, _)) = listener.accept() {
4664 let mut buf = [0u8; 256];
4665 let _ = stream.read(&mut buf); let _ = stream.write_all(
4667 b"HTTP/1.1 204 No Content\r\nContent-Length: 0\r\n\r\n",
4668 );
4669 }
4670 });
4671
4672 let s = Store::open_in_memory().unwrap();
4674 let url = s
4675 .put(&Node::Str(format!("http://127.0.0.1:{port}/")))
4676 .unwrap();
4677 let get = s.put(&Node::NetGet(url)).unwrap();
4678 let mut net = BTreeSet::new();
4679 net.insert(crate::ty::Effect::Net);
4680 let ping = s
4681 .put(&Node::Function {
4682 name: "ping".into(),
4683 type_params: vec![],
4684 params: vec![],
4685 produces: Produces {
4686 ty: Type::Number,
4687 confidence: Confidence::External,
4688 },
4689 requires: net,
4690 on_failure: vec![],
4691 body: vec![],
4692 result: get,
4693 })
4694 .unwrap();
4695 let m = module(&s, vec![ping]);
4696 let wasm = lower(&s, &m).unwrap();
4697 assert_eq!(run_i64(&wasm, "ping", &[]).unwrap(), 204);
4699 server.join().unwrap();
4700 }
4701
4702 #[test]
4703 fn a_function_returns_its_parameter() {
4704 let s = Store::open_in_memory().unwrap();
4705 let nref = s.put(&Node::Ref("n".into())).unwrap();
4706 let id = func(&s, "id", &["n"], vec![], nref);
4707 let m = module(&s, vec![id]);
4708 let wasm = lower(&s, &m).unwrap();
4709 assert_eq!(run_i64(&wasm, "id", &[7]).unwrap(), 7);
4710 }
4711
4712 #[test]
4713 fn a_call_through_a_binding_runs() {
4714 let s = Store::open_in_memory().unwrap();
4715 let nref = s.put(&Node::Ref("n".into())).unwrap();
4716 let id = func(&s, "id", &["n"], vec![], nref);
4717
4718 let seven = s.put(&Node::Lit(7)).unwrap();
4719 let call = s
4720 .put(&Node::Call {
4721 func: "id".into(),
4722 args: vec![seven],
4723 })
4724 .unwrap();
4725 let step = s
4726 .put(&Node::Step {
4727 binding: "x".into(),
4728 value: call,
4729 })
4730 .unwrap();
4731 let xref = s.put(&Node::Ref("x".into())).unwrap();
4732 let use_id = func(&s, "use_id", &[], vec![step], xref);
4733
4734 let m = module(&s, vec![id, use_id]);
4736 let wasm = lower(&s, &m).unwrap();
4737 assert_eq!(run_i64(&wasm, "use_id", &[]).unwrap(), 7);
4738 }
4739
4740 #[test]
4741 fn a_hole_cannot_be_lowered() {
4742 let s = Store::open_in_memory().unwrap();
4743 let hole = s
4744 .put(&Node::Hole {
4745 expects: "Number".into(),
4746 })
4747 .unwrap();
4748 let f = func(&s, "f", &[], vec![], hole);
4749 let m = module(&s, vec![f]);
4750 assert!(matches!(lower(&s, &m), Err(LowerError::Hole)));
4751 }
4752
4753 #[test]
4754 fn an_effectful_function_runs_via_a_host_import() {
4755 let s = Store::open_in_memory().unwrap();
4756 let now = s.put(&Node::Now).unwrap();
4758 let mut time = BTreeSet::new();
4759 time.insert(crate::ty::Effect::Time);
4760 let clock = s
4761 .put(&Node::Function {
4762 name: "clock".into(),
4763 type_params: vec![],
4764 params: vec![],
4765 produces: Produces {
4766 ty: Type::Number,
4767 confidence: Confidence::External,
4768 },
4769 requires: time,
4770 on_failure: vec![],
4771 body: vec![],
4772 result: now,
4773 })
4774 .unwrap();
4775 let m = module(&s, vec![clock]);
4777 let report = crate::check::Checker::new(&s).check(&m).unwrap();
4778 assert!(report.ok(), "unexpected: {:?}", report.violations);
4779
4780 let wasm = lower(&s, &m).unwrap();
4781 assert_eq!(run_effectful_i64(&wasm, "clock", &[], 42).unwrap(), 42);
4783 assert_eq!(run_effectful_i64(&wasm, "clock", &[], 7).unwrap(), 7);
4784 }
4785
4786 #[test]
4787 fn using_an_effect_without_declaring_it_is_a_principle_5_violation() {
4788 let s = Store::open_in_memory().unwrap();
4789 let now = s.put(&Node::Now).unwrap();
4790 let bad = s
4791 .put(&Node::Function {
4792 name: "bad".into(),
4793 type_params: vec![],
4794 params: vec![],
4795 produces: Produces {
4796 ty: Type::Number,
4797 confidence: Confidence::External,
4798 },
4799 requires: BTreeSet::new(), on_failure: vec![],
4801 body: vec![],
4802 result: now,
4803 })
4804 .unwrap();
4805 let report = crate::check::Checker::new(&s).check(&bad).unwrap();
4806 assert!(report.violations.iter().any(|v| v.principle == 5));
4807 }
4808
4809 #[test]
4810 fn records_construct_in_memory_and_fields_load() {
4811 let s = Store::open_in_memory().unwrap();
4812 let pd = s
4813 .put(&Node::RecordDef {
4814 name: "Point".into(),
4815 fields: vec![
4816 ("x".into(), Type::Number),
4817 ("y".into(), Type::Number),
4818 ],
4819 })
4820 .unwrap();
4821
4822 let field_fn = |s: &Store, name: &str, field: &str| -> NodeHash {
4825 let nref = s.put(&Node::Ref("n".into())).unwrap();
4826 let n2 = s.put(&Node::Ref("n".into())).unwrap();
4827 let one = s.put(&Node::Lit(1)).unwrap();
4828 let yexpr = s
4829 .put(&Node::BinOp {
4830 op: crate::node::BinOp::Add,
4831 lhs: n2,
4832 rhs: one,
4833 })
4834 .unwrap();
4835 let rec = s
4836 .put(&Node::Record {
4837 type_name: "Point".into(),
4838 fields: vec![("x".into(), nref), ("y".into(), yexpr)],
4839 })
4840 .unwrap();
4841 let step = s
4842 .put(&Node::Step {
4843 binding: "pt".into(),
4844 value: rec,
4845 })
4846 .unwrap();
4847 let ptref = s.put(&Node::Ref("pt".into())).unwrap();
4848 let fx = s
4849 .put(&Node::Field {
4850 base: ptref,
4851 type_name: "Point".into(),
4852 field: field.into(),
4853 })
4854 .unwrap();
4855 s.put(&Node::Function {
4856 name: name.into(),
4857 type_params: vec![],
4858 params: vec![Param {
4859 name: "n".into(),
4860 ty: Type::Number,
4861 min_confidence: Confidence::External,
4862 }],
4863 produces: Produces {
4864 ty: Type::Number,
4865 confidence: Confidence::External,
4866 },
4867 requires: BTreeSet::new(),
4868 on_failure: vec![],
4869 body: vec![step],
4870 result: fx,
4871 })
4872 .unwrap()
4873 };
4874
4875 let mk_x = field_fn(&s, "mk_x", "x");
4876 let mk_y = field_fn(&s, "mk_y", "y");
4877 let m = s
4878 .put(&Node::Module {
4879 name: "m".into(),
4880 types: vec![pd],
4881 functions: vec![mk_x, mk_y],
4882 })
4883 .unwrap();
4884 let wasm = lower(&s, &m).unwrap();
4885 assert_eq!(run_i64(&wasm, "mk_x", &[7]).unwrap(), 7);
4886 assert_eq!(run_i64(&wasm, "mk_y", &[7]).unwrap(), 8);
4887 }
4888
4889 #[test]
4890 fn variants_construct_and_match_dispatches() {
4891 let s = Store::open_in_memory().unwrap();
4892 let vd = s
4893 .put(&Node::VariantDef {
4894 name: "Status".into(),
4895 cases: vec![
4896 ("Active".into(), vec![]),
4897 ("Closed".into(), vec![("reason".into(), Type::Number)]),
4898 ],
4899 })
4900 .unwrap();
4901
4902 let n = s.put(&Node::Ref("n".into())).unwrap();
4905 let ctor = s
4906 .put(&Node::Variant {
4907 type_name: "Status".into(),
4908 case: "Closed".into(),
4909 fields: vec![("reason".into(), n)],
4910 })
4911 .unwrap();
4912 let step = s
4913 .put(&Node::Step {
4914 binding: "s".into(),
4915 value: ctor,
4916 })
4917 .unwrap();
4918 let sref = s.put(&Node::Ref("s".into())).unwrap();
4919 let zero = s.put(&Node::Lit(0)).unwrap();
4920 let rref = s.put(&Node::Ref("reason".into())).unwrap();
4921 let m_expr = s
4922 .put(&Node::Match {
4923 scrutinee: sref,
4924 type_name: "Status".into(),
4925 arms: vec![
4926 crate::node::MatchArm {
4927 case: "Active".into(),
4928 bindings: vec![],
4929 body: zero,
4930 },
4931 crate::node::MatchArm {
4932 case: "Closed".into(),
4933 bindings: vec!["reason".into()],
4934 body: rref,
4935 },
4936 ],
4937 })
4938 .unwrap();
4939 let closed = s
4940 .put(&Node::Function {
4941 name: "closed".into(),
4942 type_params: vec![],
4943 params: vec![Param {
4944 name: "n".into(),
4945 ty: Type::Number,
4946 min_confidence: Confidence::External,
4947 }],
4948 produces: Produces {
4949 ty: Type::Number,
4950 confidence: Confidence::External,
4951 },
4952 requires: BTreeSet::new(),
4953 on_failure: vec![],
4954 body: vec![step],
4955 result: m_expr,
4956 })
4957 .unwrap();
4958 let m = s
4959 .put(&Node::Module {
4960 name: "m".into(),
4961 types: vec![vd],
4962 functions: vec![closed],
4963 })
4964 .unwrap();
4965 let wasm = lower(&s, &m).unwrap();
4966 assert_eq!(run_i64(&wasm, "closed", &[7]).unwrap(), 7);
4968 assert_eq!(run_i64(&wasm, "closed", &[42]).unwrap(), 42);
4969 }
4970
4971 #[test]
4972 fn a_fallible_function_runs_and_fails() {
4973 let s = Store::open_in_memory().unwrap();
4974 let a = s.put(&Node::Ref("a".into())).unwrap();
4977 let b = s.put(&Node::Ref("b".into())).unwrap();
4978 let zero = s.put(&Node::Lit(0)).unwrap();
4979 let is_zero = s
4980 .put(&Node::BinOp {
4981 op: crate::node::BinOp::Eq,
4982 lhs: b.clone(),
4983 rhs: zero,
4984 })
4985 .unwrap();
4986 let boom = s.put(&Node::Fail("DivByZero".into())).unwrap();
4987 let div = s
4988 .put(&Node::BinOp {
4989 op: crate::node::BinOp::Div,
4990 lhs: a,
4991 rhs: b,
4992 })
4993 .unwrap();
4994 let iff = s
4995 .put(&Node::If {
4996 cond: is_zero,
4997 then_branch: boom,
4998 else_branch: div,
4999 })
5000 .unwrap();
5001 let f = s
5002 .put(&Node::Function {
5003 name: "safe_div".into(),
5004 type_params: vec![],
5005 params: vec![
5006 Param {
5007 name: "a".into(),
5008 ty: Type::Number,
5009 min_confidence: Confidence::External,
5010 },
5011 Param {
5012 name: "b".into(),
5013 ty: Type::Number,
5014 min_confidence: Confidence::External,
5015 },
5016 ],
5017 produces: Produces {
5018 ty: Type::Number,
5019 confidence: Confidence::External,
5020 },
5021 requires: BTreeSet::new(),
5022 on_failure: vec!["DivByZero".into()],
5023 body: vec![],
5024 result: iff,
5025 })
5026 .unwrap();
5027 let m = s
5028 .put(&Node::Module {
5029 name: "m".into(),
5030 types: vec![],
5031 functions: vec![f],
5032 })
5033 .unwrap();
5034 let wasm = lower(&s, &m).unwrap();
5035 assert_eq!(run_fallible(&wasm, "safe_div", &[6, 2]).unwrap(), Ok(3));
5036 assert_eq!(run_fallible(&wasm, "safe_div", &[1, 0]).unwrap(), Err(1));
5038 }
5039
5040 #[test]
5041 fn handle_recovers_a_failure_and_passes_ok_through() {
5042 let s = Store::open_in_memory().unwrap();
5043
5044 let mkfn = |s: &Store, name: &str, result: NodeHash| -> NodeHash {
5045 s.put(&Node::Function {
5046 name: name.into(),
5047 type_params: vec![],
5048 params: vec![],
5049 produces: Produces {
5050 ty: Type::Number,
5051 confidence: Confidence::Structural,
5052 },
5053 requires: BTreeSet::new(),
5054 on_failure: vec!["Boom".into()],
5055 body: vec![],
5056 result,
5057 })
5058 .unwrap()
5059 };
5060 let boom = s.put(&Node::Fail("Boom".into())).unwrap();
5061 let risky = mkfn(&s, "risky", boom); let seven = s.put(&Node::Lit(7)).unwrap();
5063 let okfn = mkfn(&s, "okfn", seven); let handled_caller = |s: &Store, name: &str, callee: &str| -> NodeHash {
5067 let call = s
5068 .put(&Node::Call {
5069 func: callee.into(),
5070 args: vec![],
5071 })
5072 .unwrap();
5073 let zero = s.put(&Node::Lit(0)).unwrap();
5074 let h = s
5075 .put(&Node::Handle {
5076 body: call,
5077 handlers: vec![("Boom".into(), zero)],
5078 })
5079 .unwrap();
5080 let step = s
5081 .put(&Node::Step {
5082 binding: "x".into(),
5083 value: h,
5084 })
5085 .unwrap();
5086 let xref = s.put(&Node::Ref("x".into())).unwrap();
5087 s.put(&Node::Function {
5088 name: name.into(),
5089 type_params: vec![],
5090 params: vec![],
5091 produces: Produces {
5092 ty: Type::Number,
5093 confidence: Confidence::Structural,
5094 },
5095 requires: BTreeSet::new(),
5096 on_failure: vec![], body: vec![step],
5098 result: xref,
5099 })
5100 .unwrap()
5101 };
5102 let recovered = handled_caller(&s, "recovered", "risky");
5103 let passed = handled_caller(&s, "passed", "okfn");
5104
5105 let m = s
5106 .put(&Node::Module {
5107 name: "m".into(),
5108 types: vec![],
5109 functions: vec![risky, okfn, recovered, passed],
5110 })
5111 .unwrap();
5112 let wasm = lower(&s, &m).unwrap();
5113 assert_eq!(run_i64(&wasm, "recovered", &[]).unwrap(), 0); assert_eq!(run_i64(&wasm, "passed", &[]).unwrap(), 7); }
5116
5117 #[test]
5118 fn a_generic_function_runs_at_a_scalar_and_a_pointer_type() {
5119 let s = Store::open_in_memory().unwrap();
5120 let pd = s
5121 .put(&Node::RecordDef {
5122 name: "Box".into(),
5123 fields: vec![("v".into(), Type::Number)],
5124 })
5125 .unwrap();
5126
5127 let x = s.put(&Node::Ref("x".into())).unwrap();
5129 let identity = s
5130 .put(&Node::Function {
5131 name: "identity".into(),
5132 type_params: vec!["T".into()],
5133 params: vec![Param {
5134 name: "x".into(),
5135 ty: Type::Var("T".into()),
5136 min_confidence: Confidence::Structural,
5137 }],
5138 produces: Produces {
5139 ty: Type::Var("T".into()),
5140 confidence: Confidence::Structural,
5141 },
5142 requires: BTreeSet::new(),
5143 on_failure: vec![],
5144 body: vec![],
5145 result: x,
5146 })
5147 .unwrap();
5148
5149 let seven = s.put(&Node::Lit(7)).unwrap();
5151 let cn = s
5152 .put(&Node::Call {
5153 func: "identity".into(),
5154 args: vec![seven],
5155 })
5156 .unwrap();
5157 let use_num = mono_fn(&s, "use_num", cn);
5158
5159 let five = s.put(&Node::Lit(5)).unwrap();
5161 let rec = s
5162 .put(&Node::Record {
5163 type_name: "Box".into(),
5164 fields: vec![("v".into(), five)],
5165 })
5166 .unwrap();
5167 let cb = s
5168 .put(&Node::Call {
5169 func: "identity".into(),
5170 args: vec![rec],
5171 })
5172 .unwrap();
5173 let step = s
5174 .put(&Node::Step {
5175 binding: "b".into(),
5176 value: cb,
5177 })
5178 .unwrap();
5179 let bref = s.put(&Node::Ref("b".into())).unwrap();
5180 let fv = s
5181 .put(&Node::Field {
5182 base: bref,
5183 type_name: "Box".into(),
5184 field: "v".into(),
5185 })
5186 .unwrap();
5187 let use_box = s
5188 .put(&Node::Function {
5189 name: "use_box".into(),
5190 type_params: vec![],
5191 params: vec![],
5192 produces: Produces {
5193 ty: Type::Number,
5194 confidence: Confidence::Structural,
5195 },
5196 requires: BTreeSet::new(),
5197 on_failure: vec![],
5198 body: vec![step],
5199 result: fv,
5200 })
5201 .unwrap();
5202
5203 let m = s
5204 .put(&Node::Module {
5205 name: "m".into(),
5206 types: vec![pd],
5207 functions: vec![identity, use_num, use_box],
5208 })
5209 .unwrap();
5210 let wasm = lower(&s, &m).unwrap();
5211 assert_eq!(run_i64(&wasm, "use_num", &[]).unwrap(), 7);
5214 assert_eq!(run_i64(&wasm, "use_box", &[]).unwrap(), 5);
5215 }
5216
5217 fn mono_fn(s: &Store, name: &str, result: NodeHash) -> NodeHash {
5219 s.put(&Node::Function {
5220 name: name.into(),
5221 type_params: vec![],
5222 params: vec![],
5223 produces: Produces {
5224 ty: Type::Number,
5225 confidence: Confidence::Structural,
5226 },
5227 requires: BTreeSet::new(),
5228 on_failure: vec![],
5229 body: vec![],
5230 result,
5231 })
5232 .unwrap()
5233 }
5234
5235 #[test]
5236 fn strings_lower_and_str_len_runs() {
5237 let s = Store::open_in_memory().unwrap();
5238 let hello = s.put(&Node::Str("hello".into())).unwrap();
5239 let sl = s.put(&Node::StrLen(hello)).unwrap();
5240 let size = mono_fn(&s, "size", sl);
5241
5242 let empty = s.put(&Node::Str(String::new())).unwrap();
5243 let sl0 = s.put(&Node::StrLen(empty)).unwrap();
5244 let zero = mono_fn(&s, "zero", sl0);
5245
5246 let m = s
5247 .put(&Node::Module {
5248 name: "m".into(),
5249 types: vec![],
5250 functions: vec![size, zero],
5251 })
5252 .unwrap();
5253 let wasm = lower(&s, &m).unwrap();
5254 assert_eq!(run_i64(&wasm, "size", &[]).unwrap(), 5);
5255 assert_eq!(run_i64(&wasm, "zero", &[]).unwrap(), 0);
5256 }
5257
5258 #[test]
5259 fn lists_construct_and_index_and_measure() {
5260 let s = Store::open_in_memory().unwrap();
5261 let list = |s: &Store| -> NodeHash {
5262 let a = s.put(&Node::Lit(10)).unwrap();
5263 let b = s.put(&Node::Lit(20)).unwrap();
5264 let cc = s.put(&Node::Lit(30)).unwrap();
5265 s.put(&Node::List(vec![a, b, cc])).unwrap()
5266 };
5267 let two = s.put(&Node::Lit(2)).unwrap();
5268 let third = s
5269 .put(&Node::ListGet {
5270 list: list(&s),
5271 index: two,
5272 })
5273 .unwrap();
5274 let get_third = mono_fn(&s, "third", third);
5275
5276 let len = s.put(&Node::ListLen(list(&s))).unwrap();
5277 let len3 = mono_fn(&s, "len3", len);
5278
5279 let m = s
5280 .put(&Node::Module {
5281 name: "m".into(),
5282 types: vec![],
5283 functions: vec![get_third, len3],
5284 })
5285 .unwrap();
5286 let wasm = lower(&s, &m).unwrap();
5287 assert_eq!(run_i64(&wasm, "third", &[]).unwrap(), 30);
5288 assert_eq!(run_i64(&wasm, "len3", &[]).unwrap(), 3);
5289 }
5290
5291 #[test]
5292 fn maps_construct_lookup_and_measure() {
5293 let s = Store::open_in_memory().unwrap();
5294 let map = |s: &Store| -> NodeHash {
5295 let k1 = s.put(&Node::Lit(1)).unwrap();
5296 let v1 = s.put(&Node::Lit(10)).unwrap();
5297 let k2 = s.put(&Node::Lit(2)).unwrap();
5298 let v2 = s.put(&Node::Lit(20)).unwrap();
5299 let k3 = s.put(&Node::Lit(3)).unwrap();
5300 let v3 = s.put(&Node::Lit(30)).unwrap();
5301 s.put(&Node::Map(vec![(k1, v1), (k2, v2), (k3, v3)]))
5302 .unwrap()
5303 };
5304 let two = s.put(&Node::Lit(2)).unwrap();
5305 let hit = s
5306 .put(&Node::MapGet {
5307 map: map(&s),
5308 key: two,
5309 })
5310 .unwrap();
5311 let lookup = mono_fn(&s, "lookup", hit);
5312
5313 let nine = s.put(&Node::Lit(9)).unwrap();
5314 let miss = s
5315 .put(&Node::MapGet {
5316 map: map(&s),
5317 key: nine,
5318 })
5319 .unwrap();
5320 let missing = mono_fn(&s, "missing", miss);
5321
5322 let sz = s.put(&Node::MapLen(map(&s))).unwrap();
5323 let size = mono_fn(&s, "size", sz);
5324
5325 let m = s
5326 .put(&Node::Module {
5327 name: "m".into(),
5328 types: vec![],
5329 functions: vec![lookup, missing, size],
5330 })
5331 .unwrap();
5332 let wasm = lower(&s, &m).unwrap();
5333 assert_eq!(run_i64(&wasm, "lookup", &[]).unwrap(), 20); assert_eq!(run_i64(&wasm, "missing", &[]).unwrap(), 0); assert_eq!(run_i64(&wasm, "size", &[]).unwrap(), 3);
5336 }
5337
5338 #[test]
5339 fn the_log_effect_runs_and_is_pass_through() {
5340 let s = Store::open_in_memory().unwrap();
5341 let n = s.put(&Node::Ref("n".into())).unwrap();
5342 let logged = s.put(&Node::Log(n)).unwrap();
5343 let mut log_eff = BTreeSet::new();
5344 log_eff.insert(crate::ty::Effect::Log);
5345 let f = s
5346 .put(&Node::Function {
5347 name: "trace".into(),
5348 type_params: vec![],
5349 params: vec![Param {
5350 name: "n".into(),
5351 ty: Type::Number,
5352 min_confidence: Confidence::External,
5353 }],
5354 produces: Produces {
5355 ty: Type::Number,
5356 confidence: Confidence::External,
5357 },
5358 requires: log_eff,
5359 on_failure: vec![],
5360 body: vec![],
5361 result: logged,
5362 })
5363 .unwrap();
5364 let m = module(&s, vec![f]);
5365 assert!(crate::check::Checker::new(&s).check(&m).unwrap().ok());
5367 let wasm = lower(&s, &m).unwrap();
5368 assert_eq!(run_i64(&wasm, "trace", &[99]).unwrap(), 99);
5370 }
5371
5372 #[test]
5373 fn logging_without_declaring_log_is_a_principle_5_violation() {
5374 let s = Store::open_in_memory().unwrap();
5375 let lit = s.put(&Node::Lit(1)).unwrap();
5376 let logged = s.put(&Node::Log(lit)).unwrap();
5377 let f = s
5378 .put(&Node::Function {
5379 name: "bad".into(),
5380 type_params: vec![],
5381 params: vec![],
5382 produces: Produces {
5383 ty: Type::Number,
5384 confidence: Confidence::Structural,
5385 },
5386 requires: BTreeSet::new(), on_failure: vec![],
5388 body: vec![],
5389 result: logged,
5390 })
5391 .unwrap();
5392 let r = crate::check::Checker::new(&s).check(&f).unwrap();
5393 assert!(r.violations.iter().any(|v| v.principle == 5));
5394 }
5395
5396 #[test]
5397 fn the_rand_effect_runs() {
5398 let s = Store::open_in_memory().unwrap();
5399 let r = s.put(&Node::Rand).unwrap();
5400 let mut rand_eff = BTreeSet::new();
5401 rand_eff.insert(crate::ty::Effect::Rand);
5402 let f = s
5403 .put(&Node::Function {
5404 name: "pick".into(),
5405 type_params: vec![],
5406 params: vec![],
5407 produces: Produces {
5408 ty: Type::Number,
5409 confidence: Confidence::External,
5410 },
5411 requires: rand_eff,
5412 on_failure: vec![],
5413 body: vec![],
5414 result: r,
5415 })
5416 .unwrap();
5417 let m = module(&s, vec![f]);
5418 assert!(crate::check::Checker::new(&s).check(&m).unwrap().ok());
5419 let wasm = lower(&s, &m).unwrap();
5420 let draws: std::collections::HashSet<i64> = (0..5)
5423 .map(|_| run_i64(&wasm, "pick", &[]).unwrap())
5424 .collect();
5425 assert!(draws.len() > 1, "rand returned a constant: {draws:?}");
5426 }
5427
5428 #[test]
5429 fn a_mutable_cell_creates_sets_and_reads() {
5430 let s = Store::open_in_memory().unwrap();
5431 let ten = s.put(&Node::Lit(10)).unwrap();
5434 let cell = s.put(&Node::MutNew(ten)).unwrap();
5435 let step_c = s
5436 .put(&Node::Step {
5437 binding: "c".into(),
5438 value: cell,
5439 })
5440 .unwrap();
5441 let cref1 = s.put(&Node::Ref("c".into())).unwrap();
5442 let twenty = s.put(&Node::Lit(20)).unwrap();
5443 let set = s
5444 .put(&Node::MutSet {
5445 cell: cref1,
5446 value: twenty,
5447 })
5448 .unwrap();
5449 let step_set = s
5450 .put(&Node::Step {
5451 binding: "_".into(),
5452 value: set,
5453 })
5454 .unwrap();
5455 let cref2 = s.put(&Node::Ref("c".into())).unwrap();
5456 let getc = s.put(&Node::MutGet(cref2)).unwrap();
5457 let mut mut_eff = BTreeSet::new();
5458 mut_eff.insert(crate::ty::Effect::Mut);
5459 let f = s
5460 .put(&Node::Function {
5461 name: "counter".into(),
5462 type_params: vec![],
5463 params: vec![],
5464 produces: Produces {
5465 ty: Type::Number,
5466 confidence: Confidence::Structural,
5467 },
5468 requires: mut_eff,
5469 on_failure: vec![],
5470 body: vec![step_c, step_set],
5471 result: getc,
5472 })
5473 .unwrap();
5474 let m = module(&s, vec![f]);
5475 assert!(
5476 crate::check::Checker::new(&s).check(&m).unwrap().ok(),
5477 "checker rejected the cell program"
5478 );
5479 let wasm = lower(&s, &m).unwrap();
5480 assert_eq!(run_i64(&wasm, "counter", &[]).unwrap(), 20);
5482 }
5483
5484 #[test]
5485 fn mutating_without_declaring_mut_is_a_principle_5_violation() {
5486 let s = Store::open_in_memory().unwrap();
5487 let one = s.put(&Node::Lit(1)).unwrap();
5488 let cell = s.put(&Node::MutNew(one)).unwrap();
5489 let f = s
5490 .put(&Node::Function {
5491 name: "bad".into(),
5492 type_params: vec![],
5493 params: vec![],
5494 produces: Produces {
5495 ty: Type::Cell(Box::new(Type::Number)),
5496 confidence: Confidence::Structural,
5497 },
5498 requires: BTreeSet::new(), on_failure: vec![],
5500 body: vec![],
5501 result: cell,
5502 })
5503 .unwrap();
5504 let r = crate::check::Checker::new(&s).check(&f).unwrap();
5505 assert!(r.violations.iter().any(|v| v.principle == 5));
5506 }
5507
5508 #[test]
5509 fn the_disk_effect_writes_and_reads_a_real_file() {
5510 let s = Store::open_in_memory().unwrap();
5511 let path = std::env::temp_dir()
5512 .join(format!("cairn_disk_{}.txt", std::process::id()))
5513 .to_string_lossy()
5514 .into_owned();
5515 let _ = std::fs::remove_file(&path);
5516
5517 let p1 = s.put(&Node::Str(path.clone())).unwrap();
5520 let content = s.put(&Node::Str("hello".into())).unwrap();
5521 let w = s
5522 .put(&Node::DiskWrite {
5523 path: p1,
5524 content,
5525 })
5526 .unwrap();
5527 let step = s
5528 .put(&Node::Step {
5529 binding: "_".into(),
5530 value: w,
5531 })
5532 .unwrap();
5533 let p2 = s.put(&Node::Str(path.clone())).unwrap();
5535 let rd = s.put(&Node::DiskRead(p2)).unwrap();
5536 let expect = s.put(&Node::Str("hello".into())).unwrap();
5537 let rd = s.put(&Node::StrEq(rd, expect)).unwrap();
5538 let mut disk = BTreeSet::new();
5539 disk.insert(crate::ty::Effect::Disk);
5540 let f = s
5541 .put(&Node::Function {
5542 name: "io".into(),
5543 type_params: vec![],
5544 params: vec![],
5545 produces: Produces {
5546 ty: Type::Bool, confidence: Confidence::External,
5548 },
5549 requires: disk,
5550 on_failure: vec![],
5551 body: vec![step],
5552 result: rd,
5553 })
5554 .unwrap();
5555 let m = module(&s, vec![f]);
5556 assert!(crate::check::Checker::new(&s).check(&m).unwrap().ok());
5557 let wasm = lower(&s, &m).unwrap();
5558 let got = run_i64(&wasm, "io", &[]).unwrap();
5561 let _ = std::fs::remove_file(&path);
5562 assert_eq!(got, 1);
5563 }
5564
5565 #[test]
5569 fn net_without_declaring_it_is_a_principle_5_violation() {
5570 let s = Store::open_in_memory().unwrap();
5571 let url = s.put(&Node::Str("u".into())).unwrap();
5572 let g = s.put(&Node::NetGet(url)).unwrap();
5573 let f = s
5574 .put(&Node::Function {
5575 name: "bad".into(),
5576 type_params: vec![],
5577 params: vec![],
5578 produces: Produces {
5579 ty: Type::Number,
5580 confidence: Confidence::External,
5581 },
5582 requires: BTreeSet::new(), on_failure: vec![],
5584 body: vec![],
5585 result: g,
5586 })
5587 .unwrap();
5588 let r = crate::check::Checker::new(&s).check(&f).unwrap();
5589 assert!(r.violations.iter().any(|v| v.principle == 5));
5590 }
5591
5592 #[test]
5593 fn the_db_effect_returns_a_string_result() {
5594 let s = Store::open_in_memory().unwrap();
5595 let sql = s.put(&Node::Str("SELECT 1".into())).unwrap();
5598 let q = s
5599 .put(&Node::DbQuery {
5600 sql,
5601 params: s.put(&Node::ListEmpty { elem: Type::String }).unwrap(),
5602 })
5603 .unwrap();
5604 let expect = s.put(&Node::Str("1".into())).unwrap();
5605 let eq = s.put(&Node::StrEq(q, expect)).unwrap();
5606 let mut db = BTreeSet::new();
5607 db.insert(crate::ty::Effect::Db);
5608 let f = s
5609 .put(&Node::Function {
5610 name: "query".into(),
5611 type_params: vec![],
5612 params: vec![],
5613 produces: Produces {
5614 ty: Type::Bool,
5615 confidence: Confidence::Structural,
5618 },
5619 requires: db,
5620 on_failure: vec![],
5621 body: vec![],
5622 result: eq,
5623 })
5624 .unwrap();
5625 let m = module(&s, vec![f]);
5626 assert!(crate::check::Checker::new(&s).check(&m).unwrap().ok());
5627 let wasm = lower(&s, &m).unwrap();
5628 assert_eq!(run_i64(&wasm, "query", &[]).unwrap(), 1);
5631 }
5632
5633 #[test]
5634 fn string_concat_slice_and_eq_run_correctly() {
5635 let s = Store::open_in_memory().unwrap();
5636 let lit = |s: &Store, t: &str| s.put(&Node::Str(t.into())).unwrap();
5637
5638 let cc = s
5640 .put(&Node::StrConcat(lit(&s, "ab"), lit(&s, "c")))
5641 .unwrap();
5642 let cc_eq = s.put(&Node::StrEq(cc, lit(&s, "abc"))).unwrap();
5643 let concat_ok = mono_fn(&s, "concat_ok", cc_eq);
5644
5645 let one = s.put(&Node::Lit(1)).unwrap();
5647 let three = s.put(&Node::Lit(3)).unwrap();
5648 let sl = s
5649 .put(&Node::StrSlice {
5650 s: lit(&s, "hello"),
5651 start: one,
5652 len: three,
5653 })
5654 .unwrap();
5655 let sl_eq = s.put(&Node::StrEq(sl, lit(&s, "ell"))).unwrap();
5656 let slice_ok = mono_fn(&s, "slice_ok", sl_eq);
5657
5658 let ne = s
5660 .put(&Node::StrEq(lit(&s, "abc"), lit(&s, "abd")))
5661 .unwrap();
5662 let neq = mono_fn(&s, "neq", ne);
5663
5664 let m = s
5665 .put(&Node::Module {
5666 name: "m".into(),
5667 types: vec![],
5668 functions: vec![concat_ok, slice_ok, neq],
5669 })
5670 .unwrap();
5671 let wasm = lower(&s, &m).unwrap();
5672 assert_eq!(run_i64(&wasm, "concat_ok", &[]).unwrap(), 1);
5673 assert_eq!(run_i64(&wasm, "slice_ok", &[]).unwrap(), 1);
5674 assert_eq!(run_i64(&wasm, "neq", &[]).unwrap(), 0);
5675 }
5676
5677 #[test]
5678 fn str_contains_runs() {
5679 let s = Store::open_in_memory().unwrap();
5680 let lit = |s: &Store, t: &str| s.put(&Node::Str(t.into())).unwrap();
5681 let mk = |s: &Store, name: &str, h: &str, n: &str| -> NodeHash {
5682 let node = s
5683 .put(&Node::StrContains {
5684 haystack: lit(s, h),
5685 needle: lit(s, n),
5686 })
5687 .unwrap();
5688 mono_fn(s, name, node)
5689 };
5690 let hit = mk(&s, "hit", "hello world", "o w"); let miss = mk(&s, "miss", "hello", "xyz"); let empty = mk(&s, "empty", "abc", ""); let edge = mk(&s, "edge", "abc", "abcd"); let m = s
5696 .put(&Node::Module {
5697 name: "m".into(),
5698 types: vec![],
5699 functions: vec![hit, miss, empty, edge],
5700 })
5701 .unwrap();
5702 let wasm = lower(&s, &m).unwrap();
5703 assert_eq!(run_i64(&wasm, "hit", &[]).unwrap(), 1);
5704 assert_eq!(run_i64(&wasm, "miss", &[]).unwrap(), 0);
5705 assert_eq!(run_i64(&wasm, "empty", &[]).unwrap(), 1);
5706 assert_eq!(run_i64(&wasm, "edge", &[]).unwrap(), 0);
5707 }
5708
5709 #[test]
5710 fn cairn_as_an_http_handler_round_trips_request_and_response() {
5711 let s = Store::open_in_memory().unwrap();
5712 let prefix = s.put(&Node::Str("echo:".into())).unwrap();
5715 let req = s.put(&Node::Ref("req".into())).unwrap();
5716 let body = s.put(&Node::StrConcat(prefix, req)).unwrap();
5717 let handle = s
5718 .put(&Node::Function {
5719 name: "handle".into(),
5720 type_params: vec![],
5721 params: vec![Param {
5722 name: "req".into(),
5723 ty: Type::String,
5724 min_confidence: Confidence::External,
5725 }],
5726 produces: Produces {
5727 ty: Type::String,
5728 confidence: Confidence::External,
5729 },
5730 requires: BTreeSet::new(),
5731 on_failure: vec![],
5732 body: vec![],
5733 result: body,
5734 })
5735 .unwrap();
5736 let m = module(&s, vec![handle]);
5737 assert!(crate::check::Checker::new(&s).check(&m).unwrap().ok());
5738 let wasm = lower(&s, &m).unwrap();
5739 assert_eq!(
5741 serve_once(&wasm, "handle", "/hello").unwrap(),
5742 "echo:/hello"
5743 );
5744 assert_eq!(serve_once(&wasm, "handle", "").unwrap(), "echo:");
5745 }
5746
5747 #[test]
5750 fn a_minimal_cairn_web_app_routes_and_renders_html() {
5751 let s = Store::open_in_memory().unwrap();
5752 let str_lit = |t: &str| -> NodeHash { s.put(&Node::Str(t.into())).unwrap() };
5753 let req_param = || Param {
5754 name: "req".into(),
5755 ty: Type::String,
5756 min_confidence: Confidence::External,
5757 };
5758
5759 let home_body = str_lit("<h1>Cairn</h1>");
5761 let home = s
5762 .put(&Node::Function {
5763 name: "home".into(),
5764 type_params: vec![],
5765 params: vec![req_param()],
5766 produces: Produces {
5767 ty: Type::String,
5768 confidence: Confidence::Structural,
5769 },
5770 requires: BTreeSet::new(),
5771 on_failure: vec![],
5772 body: vec![],
5773 result: home_body,
5774 })
5775 .unwrap();
5776
5777 let q = s
5781 .put(&Node::DbQuery {
5782 sql: str_lit("SELECT 'Acme'"),
5783 params: s.put(&Node::ListEmpty { elem: Type::String }).unwrap(),
5784 })
5785 .unwrap();
5786 let inner = s.put(&Node::StrConcat(q, str_lit("</ul>"))).unwrap();
5787 let cust_body = s
5788 .put(&Node::StrConcat(str_lit("<ul>"), inner))
5789 .unwrap();
5790 let mut db = BTreeSet::new();
5791 db.insert(crate::ty::Effect::Db);
5792 let customers = s
5793 .put(&Node::Function {
5794 name: "customers".into(),
5795 type_params: vec![],
5796 params: vec![req_param()],
5797 produces: Produces {
5798 ty: Type::String,
5799 confidence: Confidence::Structural,
5800 },
5801 requires: db.clone(),
5802 on_failure: vec![],
5803 body: vec![],
5804 result: cust_body,
5805 })
5806 .unwrap();
5807
5808 let is_root = s
5813 .put(&Node::StrEq(
5814 s.put(&Node::Ref("req".into())).unwrap(),
5815 str_lit("/"),
5816 ))
5817 .unwrap();
5818 let call_home = s
5819 .put(&Node::Call {
5820 func: "home".into(),
5821 args: vec![s.put(&Node::Ref("req".into())).unwrap()],
5822 })
5823 .unwrap();
5824 let has_cust = s
5825 .put(&Node::StrContains {
5826 haystack: s.put(&Node::Ref("req".into())).unwrap(),
5827 needle: str_lit("/customers"),
5828 })
5829 .unwrap();
5830 let call_cust = s
5831 .put(&Node::Call {
5832 func: "customers".into(),
5833 args: vec![s.put(&Node::Ref("req".into())).unwrap()],
5834 })
5835 .unwrap();
5836 let inner_if = s
5837 .put(&Node::If {
5838 cond: has_cust,
5839 then_branch: call_cust,
5840 else_branch: str_lit("<h1>404</h1>"),
5841 })
5842 .unwrap();
5843 let route = s
5844 .put(&Node::If {
5845 cond: is_root,
5846 then_branch: call_home,
5847 else_branch: inner_if,
5848 })
5849 .unwrap();
5850 let router = s
5851 .put(&Node::Function {
5852 name: "router".into(),
5853 type_params: vec![],
5854 params: vec![req_param()],
5855 produces: Produces {
5856 ty: Type::String,
5857 confidence: Confidence::External,
5858 },
5859 requires: db,
5860 on_failure: vec![],
5861 body: vec![],
5862 result: route,
5863 })
5864 .unwrap();
5865
5866 let m = s
5867 .put(&Node::Module {
5868 name: "app".into(),
5869 types: vec![],
5870 functions: vec![home, customers, router],
5871 })
5872 .unwrap();
5873 assert!(
5875 crate::check::Checker::new(&s).check(&m).unwrap().ok(),
5876 "the web app did not type-check"
5877 );
5878 let wasm = lower(&s, &m).unwrap();
5879 assert_eq!(
5880 serve_once(&wasm, "router", "/").unwrap(),
5881 "<h1>Cairn</h1>"
5882 );
5883 assert_eq!(
5884 serve_once(&wasm, "router", "/customers").unwrap(),
5885 "<ul>Acme</ul>"
5886 );
5887 assert_eq!(
5888 serve_once(&wasm, "router", "/nope").unwrap(),
5889 "<h1>404</h1>"
5890 );
5891 }
5892
5893 #[test]
5894 fn a_function_can_call_itself_recursively() {
5895 let s = Store::open_in_memory().unwrap();
5899 let nref = s.put(&Node::Ref("n".into())).unwrap();
5900 let zero = s.put(&Node::Lit(0)).unwrap();
5901 let cond = s
5902 .put(&Node::BinOp {
5903 op: crate::node::BinOp::Eq,
5904 lhs: nref.clone(),
5905 rhs: zero.clone(),
5906 })
5907 .unwrap();
5908 let one = s.put(&Node::Lit(1)).unwrap();
5909 let nm1 = s
5910 .put(&Node::BinOp {
5911 op: crate::node::BinOp::Sub,
5912 lhs: nref.clone(),
5913 rhs: one,
5914 })
5915 .unwrap();
5916 let rec = s
5917 .put(&Node::Call {
5918 func: "sum".into(),
5919 args: vec![nm1],
5920 })
5921 .unwrap();
5922 let n_plus_rec = s
5923 .put(&Node::BinOp {
5924 op: crate::node::BinOp::Add,
5925 lhs: nref,
5926 rhs: rec,
5927 })
5928 .unwrap();
5929 let body = s
5930 .put(&Node::If {
5931 cond,
5932 then_branch: zero,
5933 else_branch: n_plus_rec,
5934 })
5935 .unwrap();
5936 let sum = func(&s, "sum", &["n"], vec![], body);
5937 let m = module(&s, vec![sum]);
5938 let wasm = lower(&s, &m).unwrap();
5939 assert_eq!(run_i64(&wasm, "sum", &[5]).unwrap(), 15); assert_eq!(run_i64(&wasm, "sum", &[0]).unwrap(), 0);
5941 }
5942
5943 #[test]
5944 fn mutually_recursive_functions_lower_regardless_of_order() {
5945 let s = Store::open_in_memory().unwrap();
5948 let mk = |name: &str, other: &str, base: i64| -> NodeHash {
5949 let nref = s.put(&Node::Ref("n".into())).unwrap();
5950 let zero = s.put(&Node::Lit(0)).unwrap();
5951 let cond = s
5952 .put(&Node::BinOp {
5953 op: crate::node::BinOp::Eq,
5954 lhs: nref.clone(),
5955 rhs: zero,
5956 })
5957 .unwrap();
5958 let base_lit = s.put(&Node::Lit(base)).unwrap();
5959 let one = s.put(&Node::Lit(1)).unwrap();
5960 let nm1 = s
5961 .put(&Node::BinOp {
5962 op: crate::node::BinOp::Sub,
5963 lhs: nref,
5964 rhs: one,
5965 })
5966 .unwrap();
5967 let rec = s
5968 .put(&Node::Call {
5969 func: other.into(),
5970 args: vec![nm1],
5971 })
5972 .unwrap();
5973 let body = s
5974 .put(&Node::If {
5975 cond,
5976 then_branch: base_lit,
5977 else_branch: rec,
5978 })
5979 .unwrap();
5980 func(&s, name, &["n"], vec![], body)
5981 };
5982 let is_even = mk("is_even", "is_odd", 1);
5983 let is_odd = mk("is_odd", "is_even", 0);
5984 let m = module(&s, vec![is_even, is_odd]);
5987 let wasm = lower(&s, &m).unwrap();
5988 assert_eq!(run_i64(&wasm, "is_even", &[10]).unwrap(), 1);
5989 assert_eq!(run_i64(&wasm, "is_even", &[7]).unwrap(), 0);
5990 assert_eq!(run_i64(&wasm, "is_odd", &[7]).unwrap(), 1);
5991 }
5992
5993 #[test]
5994 fn a_typed_element_tree_renders_to_html() {
5995 let s = Store::open_in_memory().unwrap();
6000 let str_lit = |t: &str| -> NodeHash { s.put(&Node::Str(t.into())).unwrap() };
6001 let cat = |a: NodeHash, b: NodeHash| -> NodeHash {
6002 s.put(&Node::StrConcat(a, b)).unwrap()
6003 };
6004 let elem_ty = Type::Named("Element".into());
6005 let str_out = || Produces {
6006 ty: Type::String,
6007 confidence: Confidence::External,
6008 };
6009 let param = |name: &str, ty: Type| Param {
6010 name: name.into(),
6011 ty,
6012 min_confidence: Confidence::External,
6013 };
6014
6015 let element_def = s
6020 .put(&Node::VariantDef {
6021 name: "Element".into(),
6022 cases: vec![
6023 ("Text".into(), vec![("content".into(), Type::String)]),
6024 (
6025 "El".into(),
6026 vec![
6027 ("tag".into(), Type::String),
6028 ("kids".into(), Type::List(Box::new(elem_ty.clone()))),
6029 ],
6030 ),
6031 ],
6032 })
6033 .unwrap();
6034
6035 let kids_ref = s.put(&Node::Ref("kids".into())).unwrap();
6039 let i_ref = s.put(&Node::Ref("i".into())).unwrap();
6040 let len_step = s
6041 .put(&Node::Step {
6042 binding: "n".into(),
6043 value: s.put(&Node::ListLen(kids_ref.clone())).unwrap(),
6044 })
6045 .unwrap();
6046 let at_end = s
6047 .put(&Node::BinOp {
6048 op: crate::node::BinOp::Eq,
6049 lhs: i_ref.clone(),
6050 rhs: s.put(&Node::Ref("n".into())).unwrap(),
6051 })
6052 .unwrap();
6053 let head = s
6054 .put(&Node::Call {
6055 func: "render_html".into(),
6056 args: vec![s
6057 .put(&Node::ListGet {
6058 list: kids_ref.clone(),
6059 index: i_ref.clone(),
6060 })
6061 .unwrap()],
6062 })
6063 .unwrap();
6064 let tail = s
6065 .put(&Node::Call {
6066 func: "render_kids".into(),
6067 args: vec![
6068 kids_ref,
6069 s.put(&Node::BinOp {
6070 op: crate::node::BinOp::Add,
6071 lhs: i_ref,
6072 rhs: s.put(&Node::Lit(1)).unwrap(),
6073 })
6074 .unwrap(),
6075 ],
6076 })
6077 .unwrap();
6078 let kids_body = s
6079 .put(&Node::If {
6080 cond: at_end,
6081 then_branch: str_lit(""),
6082 else_branch: cat(head, tail),
6083 })
6084 .unwrap();
6085 let render_kids = s
6086 .put(&Node::Function {
6087 name: "render_kids".into(),
6088 type_params: vec![],
6089 params: vec![
6090 param("kids", Type::List(Box::new(elem_ty.clone()))),
6091 param("i", Type::Number),
6092 ],
6093 produces: str_out(),
6094 requires: BTreeSet::new(),
6095 on_failure: vec![],
6096 body: vec![len_step],
6097 result: kids_body,
6098 })
6099 .unwrap();
6100
6101 let tag_ref = s.put(&Node::Ref("tag".into())).unwrap();
6109 let open = cat(cat(str_lit("<"), tag_ref.clone()), str_lit(">"));
6110 let close = cat(cat(str_lit("</"), tag_ref), str_lit(">"));
6111 let inner = s
6112 .put(&Node::Call {
6113 func: "render_kids".into(),
6114 args: vec![
6115 s.put(&Node::Ref("kids".into())).unwrap(),
6116 s.put(&Node::Lit(0)).unwrap(),
6117 ],
6118 })
6119 .unwrap();
6120 let el_body = cat(open, cat(inner, close));
6121 let html_match = s
6122 .put(&Node::Match {
6123 scrutinee: s.put(&Node::Ref("e".into())).unwrap(),
6124 type_name: "Element".into(),
6125 arms: vec![
6126 crate::node::MatchArm {
6127 case: "Text".into(),
6128 bindings: vec!["content".into()],
6129 body: s.put(&Node::Ref("content".into())).unwrap(),
6130 },
6131 crate::node::MatchArm {
6132 case: "El".into(),
6133 bindings: vec!["tag".into(), "kids".into()],
6134 body: el_body,
6135 },
6136 ],
6137 })
6138 .unwrap();
6139 let render_html = s
6140 .put(&Node::Function {
6141 name: "render_html".into(),
6142 type_params: vec![],
6143 params: vec![param("e", elem_ty.clone())],
6144 produces: str_out(),
6145 requires: BTreeSet::new(),
6146 on_failure: vec![],
6147 body: vec![],
6148 result: html_match,
6149 })
6150 .unwrap();
6151
6152 let li = |text: &str| -> NodeHash {
6155 let t = s
6156 .put(&Node::Variant {
6157 type_name: "Element".into(),
6158 case: "Text".into(),
6159 fields: vec![("content".into(), str_lit(text))],
6160 })
6161 .unwrap();
6162 s.put(&Node::Variant {
6163 type_name: "Element".into(),
6164 case: "El".into(),
6165 fields: vec![
6166 ("tag".into(), str_lit("li")),
6167 ("kids".into(), s.put(&Node::List(vec![t])).unwrap()),
6168 ],
6169 })
6170 .unwrap()
6171 };
6172 let ul = s
6173 .put(&Node::Variant {
6174 type_name: "Element".into(),
6175 case: "El".into(),
6176 fields: vec![
6177 ("tag".into(), str_lit("ul")),
6178 (
6179 "kids".into(),
6180 s.put(&Node::List(vec![li("Hello"), li("World")]))
6181 .unwrap(),
6182 ),
6183 ],
6184 })
6185 .unwrap();
6186 let home = s
6187 .put(&Node::Function {
6188 name: "home".into(),
6189 type_params: vec![],
6190 params: vec![param("req", Type::String)],
6191 produces: str_out(),
6192 requires: BTreeSet::new(),
6193 on_failure: vec![],
6194 body: vec![],
6195 result: s
6196 .put(&Node::Call {
6197 func: "render_html".into(),
6198 args: vec![ul],
6199 })
6200 .unwrap(),
6201 })
6202 .unwrap();
6203
6204 let m = s
6207 .put(&Node::Module {
6208 name: "view".into(),
6209 types: vec![element_def],
6210 functions: vec![home, render_html, render_kids],
6211 })
6212 .unwrap();
6213 let report = crate::check::Checker::new(&s).check(&m).unwrap();
6214 assert!(
6215 report.ok(),
6216 "typed view model did not type-check: {:?}",
6217 report.violations
6218 );
6219 let wasm = lower(&s, &m).unwrap();
6220 assert_eq!(
6221 serve_once(&wasm, "home", "/").unwrap(),
6222 "<ul><li>Hello</li><li>World</li></ul>"
6223 );
6224 }
6225
6226 #[test]
6227 fn a_structured_request_routes_to_a_typed_response() {
6228 let s = Store::open_in_memory().unwrap();
6233 let str_lit = |t: &str| -> NodeHash { s.put(&Node::Str(t.into())).unwrap() };
6234 let request_def = s
6236 .put(&Node::RecordDef {
6237 name: "Request".into(),
6238 fields: vec![
6239 ("method".into(), Type::String),
6240 ("path".into(), Type::String),
6241 ("body".into(), Type::String),
6242 ],
6243 })
6244 .unwrap();
6245 let response_def = s
6246 .put(&Node::RecordDef {
6247 name: "Response".into(),
6248 fields: vec![
6249 ("status".into(), Type::Number),
6250 ("body".into(), Type::String),
6251 ],
6252 })
6253 .unwrap();
6254 let resp = |status: i64, body: &str| -> NodeHash {
6255 s.put(&Node::Record {
6256 type_name: "Response".into(),
6257 fields: vec![
6258 ("status".into(), s.put(&Node::Lit(status)).unwrap()),
6259 ("body".into(), str_lit(body)),
6260 ],
6261 })
6262 .unwrap()
6263 };
6264 let field = |binding: &str, f: &str| -> NodeHash {
6265 s.put(&Node::Step {
6266 binding: binding.into(),
6267 value: s
6268 .put(&Node::Field {
6269 base: s.put(&Node::Ref("req".into())).unwrap(),
6270 type_name: "Request".into(),
6271 field: f.into(),
6272 })
6273 .unwrap(),
6274 })
6275 .unwrap()
6276 };
6277 let path_is = |p: &str| -> NodeHash {
6278 s.put(&Node::StrEq(
6279 s.put(&Node::Ref("path".into())).unwrap(),
6280 str_lit(p),
6281 ))
6282 .unwrap()
6283 };
6284 let is_post = s
6285 .put(&Node::StrEq(
6286 s.put(&Node::Ref("method".into())).unwrap(),
6287 str_lit("POST"),
6288 ))
6289 .unwrap();
6290 let customers = s
6292 .put(&Node::If {
6293 cond: is_post,
6294 then_branch: resp(201, "created"),
6295 else_branch: resp(200, "list"),
6296 })
6297 .unwrap();
6298 let body = s
6299 .put(&Node::If {
6300 cond: path_is("/"),
6301 then_branch: resp(200, "home"),
6302 else_branch: s
6303 .put(&Node::If {
6304 cond: path_is("/customers"),
6305 then_branch: customers,
6306 else_branch: resp(404, "not found"),
6307 })
6308 .unwrap(),
6309 })
6310 .unwrap();
6311 let route = s
6312 .put(&Node::Function {
6313 name: "route".into(),
6314 type_params: vec![],
6315 params: vec![Param {
6316 name: "req".into(),
6317 ty: Type::Named("Request".into()),
6318 min_confidence: Confidence::External,
6319 }],
6320 produces: Produces {
6321 ty: Type::Named("Response".into()),
6322 confidence: Confidence::External,
6323 },
6324 requires: BTreeSet::new(),
6325 on_failure: vec![],
6326 body: vec![field("method", "method"), field("path", "path")],
6327 result: body,
6328 })
6329 .unwrap();
6330 let m = s
6331 .put(&Node::Module {
6332 name: "app".into(),
6333 types: vec![request_def, response_def],
6334 functions: vec![route],
6335 })
6336 .unwrap();
6337 let report = crate::check::Checker::new(&s).check(&m).unwrap();
6338 assert!(
6339 report.ok(),
6340 "structured router did not type-check: {:?}",
6341 report.violations
6342 );
6343 let wasm = lower(&s, &m).unwrap();
6344 assert_eq!(
6345 serve_request(&wasm, "route", "GET", "/", "").unwrap(),
6346 HttpResponse {
6347 status: 200,
6348 body: "home".into()
6349 }
6350 );
6351 assert_eq!(
6352 serve_request(&wasm, "route", "GET", "/customers", "").unwrap(),
6353 HttpResponse {
6354 status: 200,
6355 body: "list".into()
6356 }
6357 );
6358 assert_eq!(
6359 serve_request(&wasm, "route", "POST", "/customers", "").unwrap(),
6360 HttpResponse {
6361 status: 201,
6362 body: "created".into()
6363 }
6364 );
6365 assert_eq!(
6366 serve_request(&wasm, "route", "GET", "/nope", "").unwrap(),
6367 HttpResponse {
6368 status: 404,
6369 body: "not found".into()
6370 }
6371 );
6372 }
6373
6374 #[test]
6375 fn a_deckhand_customers_page_end_to_end() {
6376 let s = Store::open_in_memory().unwrap();
6382 let str_lit = |t: &str| -> NodeHash { s.put(&Node::Str(t.into())).unwrap() };
6383 let cat = |a: NodeHash, b: NodeHash| -> NodeHash {
6384 s.put(&Node::StrConcat(a, b)).unwrap()
6385 };
6386 let elem_ty = Type::Named("Element".into());
6387 let str_out = || Produces {
6388 ty: Type::String,
6389 confidence: Confidence::External,
6390 };
6391 let param = |name: &str, ty: Type| Param {
6392 name: name.into(),
6393 ty,
6394 min_confidence: Confidence::External,
6395 };
6396
6397 let element_def = s
6399 .put(&Node::VariantDef {
6400 name: "Element".into(),
6401 cases: vec![
6402 ("Text".into(), vec![("content".into(), Type::String)]),
6403 (
6404 "El".into(),
6405 vec![
6406 ("tag".into(), Type::String),
6407 ("kids".into(), Type::List(Box::new(elem_ty.clone()))),
6408 ],
6409 ),
6410 ],
6411 })
6412 .unwrap();
6413 let kids_ref = s.put(&Node::Ref("kids".into())).unwrap();
6414 let i_ref = s.put(&Node::Ref("i".into())).unwrap();
6415 let len_step = s
6416 .put(&Node::Step {
6417 binding: "n".into(),
6418 value: s.put(&Node::ListLen(kids_ref.clone())).unwrap(),
6419 })
6420 .unwrap();
6421 let at_end = s
6422 .put(&Node::BinOp {
6423 op: crate::node::BinOp::Eq,
6424 lhs: i_ref.clone(),
6425 rhs: s.put(&Node::Ref("n".into())).unwrap(),
6426 })
6427 .unwrap();
6428 let head = s
6429 .put(&Node::Call {
6430 func: "render_html".into(),
6431 args: vec![s
6432 .put(&Node::ListGet {
6433 list: kids_ref.clone(),
6434 index: i_ref.clone(),
6435 })
6436 .unwrap()],
6437 })
6438 .unwrap();
6439 let tail = s
6440 .put(&Node::Call {
6441 func: "render_kids".into(),
6442 args: vec![
6443 kids_ref,
6444 s.put(&Node::BinOp {
6445 op: crate::node::BinOp::Add,
6446 lhs: i_ref,
6447 rhs: s.put(&Node::Lit(1)).unwrap(),
6448 })
6449 .unwrap(),
6450 ],
6451 })
6452 .unwrap();
6453 let render_kids = s
6454 .put(&Node::Function {
6455 name: "render_kids".into(),
6456 type_params: vec![],
6457 params: vec![
6458 param("kids", Type::List(Box::new(elem_ty.clone()))),
6459 param("i", Type::Number),
6460 ],
6461 produces: str_out(),
6462 requires: BTreeSet::new(),
6463 on_failure: vec![],
6464 body: vec![len_step],
6465 result: s
6466 .put(&Node::If {
6467 cond: at_end,
6468 then_branch: str_lit(""),
6469 else_branch: cat(head, tail),
6470 })
6471 .unwrap(),
6472 })
6473 .unwrap();
6474 let tag_ref = s.put(&Node::Ref("tag".into())).unwrap();
6475 let open = cat(cat(str_lit("<"), tag_ref.clone()), str_lit(">"));
6476 let close = cat(cat(str_lit("</"), tag_ref), str_lit(">"));
6477 let inner = s
6478 .put(&Node::Call {
6479 func: "render_kids".into(),
6480 args: vec![
6481 s.put(&Node::Ref("kids".into())).unwrap(),
6482 s.put(&Node::Lit(0)).unwrap(),
6483 ],
6484 })
6485 .unwrap();
6486 let render_html = s
6487 .put(&Node::Function {
6488 name: "render_html".into(),
6489 type_params: vec![],
6490 params: vec![param("e", elem_ty.clone())],
6491 produces: str_out(),
6492 requires: BTreeSet::new(),
6493 on_failure: vec![],
6494 body: vec![],
6495 result: s
6496 .put(&Node::Match {
6497 scrutinee: s.put(&Node::Ref("e".into())).unwrap(),
6498 type_name: "Element".into(),
6499 arms: vec![
6500 crate::node::MatchArm {
6501 case: "Text".into(),
6502 bindings: vec!["content".into()],
6503 body: s.put(&Node::Ref("content".into())).unwrap(),
6504 },
6505 crate::node::MatchArm {
6506 case: "El".into(),
6507 bindings: vec!["tag".into(), "kids".into()],
6508 body: cat(open, cat(inner, close)),
6509 },
6510 ],
6511 })
6512 .unwrap(),
6513 })
6514 .unwrap();
6515
6516 let request_def = s
6518 .put(&Node::RecordDef {
6519 name: "Request".into(),
6520 fields: vec![
6521 ("method".into(), Type::String),
6522 ("path".into(), Type::String),
6523 ("body".into(), Type::String),
6524 ],
6525 })
6526 .unwrap();
6527 let response_def = s
6528 .put(&Node::RecordDef {
6529 name: "Response".into(),
6530 fields: vec![
6531 ("status".into(), Type::Number),
6532 ("body".into(), Type::String),
6533 ],
6534 })
6535 .unwrap();
6536
6537 let el = |tag: &str, kids: Vec<NodeHash>| -> NodeHash {
6539 s.put(&Node::Variant {
6540 type_name: "Element".into(),
6541 case: "El".into(),
6542 fields: vec![
6543 ("tag".into(), str_lit(tag)),
6544 ("kids".into(), s.put(&Node::List(kids)).unwrap()),
6545 ],
6546 })
6547 .unwrap()
6548 };
6549 let rows = s
6550 .put(&Node::DbQuery {
6551 sql: str_lit("SELECT 'Acme'"),
6552 params: s.put(&Node::ListEmpty { elem: Type::String }).unwrap(),
6553 })
6554 .unwrap();
6555 let row_text = s
6556 .put(&Node::Variant {
6557 type_name: "Element".into(),
6558 case: "Text".into(),
6559 fields: vec![("content".into(), rows)],
6560 })
6561 .unwrap();
6562 let page = el("ul", vec![el("li", vec![row_text])]);
6563 let customers = s
6564 .put(&Node::Record {
6565 type_name: "Response".into(),
6566 fields: vec![
6567 ("status".into(), s.put(&Node::Lit(200)).unwrap()),
6568 (
6569 "body".into(),
6570 s.put(&Node::Call {
6571 func: "render_html".into(),
6572 args: vec![page],
6573 })
6574 .unwrap(),
6575 ),
6576 ],
6577 })
6578 .unwrap();
6579 let not_found = s
6580 .put(&Node::Record {
6581 type_name: "Response".into(),
6582 fields: vec![
6583 ("status".into(), s.put(&Node::Lit(404)).unwrap()),
6584 ("body".into(), str_lit("not found")),
6585 ],
6586 })
6587 .unwrap();
6588 let route = s
6589 .put(&Node::Function {
6590 name: "route".into(),
6591 type_params: vec![],
6592 params: vec![param("req", Type::Named("Request".into()))],
6593 produces: Produces {
6594 ty: Type::Named("Response".into()),
6595 confidence: Confidence::External,
6596 },
6597 requires: {
6598 let mut e = BTreeSet::new();
6599 e.insert(crate::ty::Effect::Db);
6600 e
6601 },
6602 on_failure: vec![],
6603 body: vec![s
6604 .put(&Node::Step {
6605 binding: "path".into(),
6606 value: s
6607 .put(&Node::Field {
6608 base: s.put(&Node::Ref("req".into())).unwrap(),
6609 type_name: "Request".into(),
6610 field: "path".into(),
6611 })
6612 .unwrap(),
6613 })
6614 .unwrap()],
6615 result: s
6616 .put(&Node::If {
6617 cond: s
6618 .put(&Node::StrEq(
6619 s.put(&Node::Ref("path".into())).unwrap(),
6620 str_lit("/customers"),
6621 ))
6622 .unwrap(),
6623 then_branch: customers,
6624 else_branch: not_found,
6625 })
6626 .unwrap(),
6627 })
6628 .unwrap();
6629
6630 let m = s
6633 .put(&Node::Module {
6634 name: "deckhand".into(),
6635 types: vec![element_def, request_def, response_def],
6636 functions: vec![route, render_html, render_kids],
6637 })
6638 .unwrap();
6639 let report = crate::check::Checker::new(&s).check(&m).unwrap();
6640 assert!(
6641 report.ok(),
6642 "the Deckhand page did not type-check: {:?}",
6643 report.violations
6644 );
6645 let wasm = lower(&s, &m).unwrap();
6646 assert_eq!(
6647 serve_request(&wasm, "route", "GET", "/customers", "").unwrap(),
6648 HttpResponse {
6649 status: 200,
6650 body: "<ul><li>Acme</li></ul>".into(),
6651 }
6652 );
6653 assert_eq!(
6654 serve_request(&wasm, "route", "GET", "/", "").unwrap(),
6655 HttpResponse {
6656 status: 404,
6657 body: "not found".into(),
6658 }
6659 );
6660 }
6661
6662 #[test]
6663 fn reason_phrase_covers_framework_codes_and_falls_back_by_class() {
6664 assert_eq!(reason_phrase(200), "OK");
6665 assert_eq!(reason_phrase(201), "Created");
6666 assert_eq!(reason_phrase(404), "Not Found");
6667 assert_eq!(reason_phrase(500), "Internal Server Error");
6668 assert_eq!(reason_phrase(299), "OK");
6670 assert_eq!(reason_phrase(418), "Client Error");
6671 assert_eq!(reason_phrase(0), "Error");
6672 }
6673
6674 #[test]
6675 fn prefix_routing_extracts_a_path_param() {
6676 let s = Store::open_in_memory().unwrap();
6679 let str_lit = |t: &str| -> NodeHash { s.put(&Node::Str(t.into())).unwrap() };
6680 let request_def = s
6681 .put(&Node::RecordDef {
6682 name: "Request".into(),
6683 fields: vec![
6684 ("method".into(), Type::String),
6685 ("path".into(), Type::String),
6686 ("body".into(), Type::String),
6687 ],
6688 })
6689 .unwrap();
6690 let response_def = s
6691 .put(&Node::RecordDef {
6692 name: "Response".into(),
6693 fields: vec![
6694 ("status".into(), Type::Number),
6695 ("body".into(), Type::String),
6696 ],
6697 })
6698 .unwrap();
6699 let resp = |status: i64, body: NodeHash| -> NodeHash {
6700 s.put(&Node::Record {
6701 type_name: "Response".into(),
6702 fields: vec![
6703 ("status".into(), s.put(&Node::Lit(status)).unwrap()),
6704 ("body".into(), body),
6705 ],
6706 })
6707 .unwrap()
6708 };
6709 let path_ref = || s.put(&Node::Ref("path".into())).unwrap();
6710 let plen_ref = || s.put(&Node::Ref("plen".into())).unwrap();
6711 let id = s
6713 .put(&Node::StrSlice {
6714 s: path_ref(),
6715 start: plen_ref(),
6716 len: s
6717 .put(&Node::BinOp {
6718 op: crate::node::BinOp::Sub,
6719 lhs: s.put(&Node::StrLen(path_ref())).unwrap(),
6720 rhs: plen_ref(),
6721 })
6722 .unwrap(),
6723 })
6724 .unwrap();
6725 let route = s
6726 .put(&Node::Function {
6727 name: "route".into(),
6728 type_params: vec![],
6729 params: vec![Param {
6730 name: "req".into(),
6731 ty: Type::Named("Request".into()),
6732 min_confidence: Confidence::External,
6733 }],
6734 produces: Produces {
6735 ty: Type::Named("Response".into()),
6736 confidence: Confidence::External,
6737 },
6738 requires: BTreeSet::new(),
6739 on_failure: vec![],
6740 body: vec![
6741 s.put(&Node::Step {
6742 binding: "path".into(),
6743 value: s
6744 .put(&Node::Field {
6745 base: s.put(&Node::Ref("req".into())).unwrap(),
6746 type_name: "Request".into(),
6747 field: "path".into(),
6748 })
6749 .unwrap(),
6750 })
6751 .unwrap(),
6752 s.put(&Node::Step {
6753 binding: "plen".into(),
6754 value: s.put(&Node::StrLen(str_lit("/customers/"))).unwrap(),
6755 })
6756 .unwrap(),
6757 ],
6758 result: s
6759 .put(&Node::If {
6760 cond: s
6761 .put(&Node::StrStartsWith {
6762 s: path_ref(),
6763 prefix: str_lit("/customers/"),
6764 })
6765 .unwrap(),
6766 then_branch: resp(
6767 200,
6768 s.put(&Node::StrConcat(str_lit("customer "), id))
6769 .unwrap(),
6770 ),
6771 else_branch: resp(404, str_lit("not found")),
6772 })
6773 .unwrap(),
6774 })
6775 .unwrap();
6776 assert!(crate::render::render(&s, &route)
6778 .unwrap()
6779 .contains("str_starts_with("));
6780 let m = s
6781 .put(&Node::Module {
6782 name: "app".into(),
6783 types: vec![request_def, response_def],
6784 functions: vec![route],
6785 })
6786 .unwrap();
6787 let report = crate::check::Checker::new(&s).check(&m).unwrap();
6788 assert!(
6789 report.ok(),
6790 "prefix router did not type-check: {:?}",
6791 report.violations
6792 );
6793 let wasm = lower(&s, &m).unwrap();
6794 assert_eq!(
6795 serve_request(&wasm, "route", "GET", "/customers/42", "").unwrap(),
6796 HttpResponse {
6797 status: 200,
6798 body: "customer 42".into(),
6799 }
6800 );
6801 assert_eq!(
6802 serve_request(&wasm, "route", "GET", "/about", "").unwrap(),
6803 HttpResponse {
6804 status: 404,
6805 body: "not found".into(),
6806 }
6807 );
6808 }
6809
6810 #[test]
6811 fn number_and_string_conversions_round_trip() {
6812 let s = Store::open_in_memory().unwrap();
6814 let req = s.put(&Node::Ref("req".into())).unwrap();
6815 let parsed = s.put(&Node::StrToNumber(req)).unwrap();
6816 let back = s.put(&Node::NumberToStr(parsed)).unwrap();
6817 let conv = s
6818 .put(&Node::Function {
6819 name: "conv".into(),
6820 type_params: vec![],
6821 params: vec![Param {
6822 name: "req".into(),
6823 ty: Type::String,
6824 min_confidence: Confidence::External,
6825 }],
6826 produces: Produces {
6827 ty: Type::String,
6828 confidence: Confidence::External,
6829 },
6830 requires: BTreeSet::new(),
6831 on_failure: vec![],
6832 body: vec![],
6833 result: back,
6834 })
6835 .unwrap();
6836 let rendered = crate::render::render(&s, &conv).unwrap();
6838 assert!(rendered.contains("number_to_str("));
6839 assert!(rendered.contains("str_to_number("));
6840 let m = s
6841 .put(&Node::Module {
6842 name: "m".into(),
6843 types: vec![],
6844 functions: vec![conv],
6845 })
6846 .unwrap();
6847 let report = crate::check::Checker::new(&s).check(&m).unwrap();
6848 assert!(
6849 report.ok(),
6850 "conversions did not type-check: {:?}",
6851 report.violations
6852 );
6853 let wasm = lower(&s, &m).unwrap();
6854 for (input, expected) in [
6855 ("42", "42"),
6856 ("-7", "-7"),
6857 ("0", "0"),
6858 ("-1000000", "-1000000"),
6859 ("12ab", "12"), ("x", "0"), ("", "0"), ] {
6863 assert_eq!(
6864 serve_once(&wasm, "conv", input).unwrap(),
6865 expected,
6866 "conv({input:?})"
6867 );
6868 }
6869 }
6870
6871 #[test]
6872 fn numeric_path_param_routes() {
6873 let s = Store::open_in_memory().unwrap();
6876 let str_lit = |t: &str| -> NodeHash { s.put(&Node::Str(t.into())).unwrap() };
6877 let request_def = s
6878 .put(&Node::RecordDef {
6879 name: "Request".into(),
6880 fields: vec![
6881 ("method".into(), Type::String),
6882 ("path".into(), Type::String),
6883 ("body".into(), Type::String),
6884 ],
6885 })
6886 .unwrap();
6887 let response_def = s
6888 .put(&Node::RecordDef {
6889 name: "Response".into(),
6890 fields: vec![
6891 ("status".into(), Type::Number),
6892 ("body".into(), Type::String),
6893 ],
6894 })
6895 .unwrap();
6896 let resp = |status: i64, body: NodeHash| -> NodeHash {
6897 s.put(&Node::Record {
6898 type_name: "Response".into(),
6899 fields: vec![
6900 ("status".into(), s.put(&Node::Lit(status)).unwrap()),
6901 ("body".into(), body),
6902 ],
6903 })
6904 .unwrap()
6905 };
6906 let path_ref = || s.put(&Node::Ref("path".into())).unwrap();
6907 let plen_ref = || s.put(&Node::Ref("plen".into())).unwrap();
6908 let id_str = s
6909 .put(&Node::StrSlice {
6910 s: path_ref(),
6911 start: plen_ref(),
6912 len: s
6913 .put(&Node::BinOp {
6914 op: crate::node::BinOp::Sub,
6915 lhs: s.put(&Node::StrLen(path_ref())).unwrap(),
6916 rhs: plen_ref(),
6917 })
6918 .unwrap(),
6919 })
6920 .unwrap();
6921 let echoed = s
6923 .put(&Node::StrConcat(
6924 str_lit("id="),
6925 s.put(&Node::NumberToStr(
6926 s.put(&Node::StrToNumber(id_str)).unwrap(),
6927 ))
6928 .unwrap(),
6929 ))
6930 .unwrap();
6931 let route = s
6932 .put(&Node::Function {
6933 name: "route".into(),
6934 type_params: vec![],
6935 params: vec![Param {
6936 name: "req".into(),
6937 ty: Type::Named("Request".into()),
6938 min_confidence: Confidence::External,
6939 }],
6940 produces: Produces {
6941 ty: Type::Named("Response".into()),
6942 confidence: Confidence::External,
6943 },
6944 requires: BTreeSet::new(),
6945 on_failure: vec![],
6946 body: vec![
6947 s.put(&Node::Step {
6948 binding: "path".into(),
6949 value: s
6950 .put(&Node::Field {
6951 base: s.put(&Node::Ref("req".into())).unwrap(),
6952 type_name: "Request".into(),
6953 field: "path".into(),
6954 })
6955 .unwrap(),
6956 })
6957 .unwrap(),
6958 s.put(&Node::Step {
6959 binding: "plen".into(),
6960 value: s.put(&Node::StrLen(str_lit("/customers/"))).unwrap(),
6961 })
6962 .unwrap(),
6963 ],
6964 result: s
6965 .put(&Node::If {
6966 cond: s
6967 .put(&Node::StrStartsWith {
6968 s: path_ref(),
6969 prefix: str_lit("/customers/"),
6970 })
6971 .unwrap(),
6972 then_branch: resp(200, echoed),
6973 else_branch: resp(404, str_lit("not found")),
6974 })
6975 .unwrap(),
6976 })
6977 .unwrap();
6978 let m = s
6979 .put(&Node::Module {
6980 name: "app".into(),
6981 types: vec![request_def, response_def],
6982 functions: vec![route],
6983 })
6984 .unwrap();
6985 let report = crate::check::Checker::new(&s).check(&m).unwrap();
6986 assert!(
6987 report.ok(),
6988 "numeric router did not type-check: {:?}",
6989 report.violations
6990 );
6991 let wasm = lower(&s, &m).unwrap();
6992 assert_eq!(
6993 serve_request(&wasm, "route", "GET", "/customers/42", "").unwrap(),
6994 HttpResponse {
6995 status: 200,
6996 body: "id=42".into(),
6997 }
6998 );
6999 assert_eq!(
7000 serve_request(&wasm, "route", "GET", "/customers/abc", "").unwrap(),
7001 HttpResponse {
7002 status: 200,
7003 body: "id=0".into(),
7004 }
7005 );
7006 assert_eq!(
7007 serve_request(&wasm, "route", "GET", "/x", "").unwrap(),
7008 HttpResponse {
7009 status: 404,
7010 body: "not found".into(),
7011 }
7012 );
7013 }
7014
7015 #[test]
7016 fn a_rails_style_blog_app() {
7017 let s = Store::open_in_memory().unwrap();
7026 let lit = |t: &str| -> NodeHash { s.put(&Node::Str(t.into())).unwrap() };
7027 let num = |n: i64| -> NodeHash { s.put(&Node::Lit(n)).unwrap() };
7028 let r = |name: &str| -> NodeHash { s.put(&Node::Ref(name.into())).unwrap() };
7029 let cats = |parts: &[NodeHash]| -> NodeHash {
7030 let mut it = parts.iter().cloned();
7031 let first = it.next().unwrap();
7032 it.fold(first, |acc, p| s.put(&Node::StrConcat(acc, p)).unwrap())
7033 };
7034 let param = |name: &str, ty: Type| Param {
7035 name: name.into(),
7036 ty,
7037 min_confidence: Confidence::External,
7038 };
7039 let str_out = || Produces {
7040 ty: Type::String,
7041 confidence: Confidence::External,
7042 };
7043 let post_ty = Type::Named("Post".into());
7044
7045 let post_def = s
7046 .put(&Node::RecordDef {
7047 name: "Post".into(),
7048 fields: vec![
7049 ("id".into(), Type::Number),
7050 ("title".into(), Type::String),
7051 ("body".into(), Type::String),
7052 ],
7053 })
7054 .unwrap();
7055 let request_def = s
7056 .put(&Node::RecordDef {
7057 name: "Request".into(),
7058 fields: vec![
7059 ("method".into(), Type::String),
7060 ("path".into(), Type::String),
7061 ("body".into(), Type::String),
7062 ],
7063 })
7064 .unwrap();
7065 let response_def = s
7066 .put(&Node::RecordDef {
7067 name: "Response".into(),
7068 fields: vec![
7069 ("status".into(), Type::Number),
7070 ("body".into(), Type::String),
7071 ],
7072 })
7073 .unwrap();
7074
7075 let post = |id: i64, title: &str, body: &str| -> NodeHash {
7077 s.put(&Node::Record {
7078 type_name: "Post".into(),
7079 fields: vec![
7080 ("id".into(), num(id)),
7081 ("title".into(), lit(title)),
7082 ("body".into(), lit(body)),
7083 ],
7084 })
7085 .unwrap()
7086 };
7087 let posts = s
7088 .put(&Node::Function {
7089 name: "posts".into(),
7090 type_params: vec![],
7091 params: vec![],
7092 produces: Produces {
7093 ty: Type::List(Box::new(post_ty.clone())),
7094 confidence: Confidence::External,
7095 },
7096 requires: BTreeSet::new(),
7097 on_failure: vec![],
7098 body: vec![],
7099 result: s
7100 .put(&Node::List(vec![
7101 post(1, "Hello World", "The first post."),
7102 post(2, "On Cairn", "Programs as reasoning chains."),
7103 ]))
7104 .unwrap(),
7105 })
7106 .unwrap();
7107
7108 let field = |f: &str| -> NodeHash {
7110 s.put(&Node::Field {
7111 base: s
7112 .put(&Node::ListGet {
7113 list: r("ps"),
7114 index: r("i"),
7115 })
7116 .unwrap(),
7117 type_name: "Post".into(),
7118 field: f.into(),
7119 })
7120 .unwrap()
7121 };
7122 let len_step = || {
7123 s.put(&Node::Step {
7124 binding: "n".into(),
7125 value: s.put(&Node::ListLen(r("ps"))).unwrap(),
7126 })
7127 .unwrap()
7128 };
7129 let at_end = || {
7130 s.put(&Node::BinOp {
7131 op: crate::node::BinOp::Eq,
7132 lhs: r("i"),
7133 rhs: r("n"),
7134 })
7135 .unwrap()
7136 };
7137 let next_i = || {
7138 s.put(&Node::BinOp {
7139 op: crate::node::BinOp::Add,
7140 lhs: r("i"),
7141 rhs: num(1),
7142 })
7143 .unwrap()
7144 };
7145
7146 let posts_html = s
7148 .put(&Node::Function {
7149 name: "posts_html".into(),
7150 type_params: vec![],
7151 params: vec![
7152 param("ps", Type::List(Box::new(post_ty.clone()))),
7153 param("i", Type::Number),
7154 ],
7155 produces: str_out(),
7156 requires: BTreeSet::new(),
7157 on_failure: vec![],
7158 body: vec![len_step()],
7159 result: s
7160 .put(&Node::If {
7161 cond: at_end(),
7162 then_branch: lit(""),
7163 else_branch: cats(&[
7164 lit("<li>"),
7165 s.put(&Node::NumberToStr(field("id"))).unwrap(),
7166 lit(": "),
7167 field("title"),
7168 lit("</li>"),
7169 s.put(&Node::Call {
7170 func: "posts_html".into(),
7171 args: vec![r("ps"), next_i()],
7172 })
7173 .unwrap(),
7174 ]),
7175 })
7176 .unwrap(),
7177 })
7178 .unwrap();
7179
7180 let show_html = s
7182 .put(&Node::Function {
7183 name: "show_html".into(),
7184 type_params: vec![],
7185 params: vec![
7186 param("ps", Type::List(Box::new(post_ty.clone()))),
7187 param("id", Type::Number),
7188 param("i", Type::Number),
7189 ],
7190 produces: str_out(),
7191 requires: BTreeSet::new(),
7192 on_failure: vec![],
7193 body: vec![len_step()],
7194 result: s
7195 .put(&Node::If {
7196 cond: at_end(),
7197 then_branch: lit("<p>not found</p>"),
7198 else_branch: s
7199 .put(&Node::If {
7200 cond: s
7201 .put(&Node::BinOp {
7202 op: crate::node::BinOp::Eq,
7203 lhs: field("id"),
7204 rhs: r("id"),
7205 })
7206 .unwrap(),
7207 then_branch: cats(&[
7208 lit("<h1>"),
7209 field("title"),
7210 lit("</h1><p>"),
7211 field("body"),
7212 lit("</p>"),
7213 ]),
7214 else_branch: s
7215 .put(&Node::Call {
7216 func: "show_html".into(),
7217 args: vec![r("ps"), r("id"), next_i()],
7218 })
7219 .unwrap(),
7220 })
7221 .unwrap(),
7222 })
7223 .unwrap(),
7224 })
7225 .unwrap();
7226
7227 let resp = |status: i64, body: NodeHash| -> NodeHash {
7229 s.put(&Node::Record {
7230 type_name: "Response".into(),
7231 fields: vec![("status".into(), num(status)), ("body".into(), body)],
7232 })
7233 .unwrap()
7234 };
7235 let id_from_path = s
7236 .put(&Node::StrToNumber(
7237 s.put(&Node::StrSlice {
7238 s: r("path"),
7239 start: r("plen"),
7240 len: s
7241 .put(&Node::BinOp {
7242 op: crate::node::BinOp::Sub,
7243 lhs: s.put(&Node::StrLen(r("path"))).unwrap(),
7244 rhs: r("plen"),
7245 })
7246 .unwrap(),
7247 })
7248 .unwrap(),
7249 ))
7250 .unwrap();
7251 let route = s
7252 .put(&Node::Function {
7253 name: "route".into(),
7254 type_params: vec![],
7255 params: vec![param("req", Type::Named("Request".into()))],
7256 produces: Produces {
7257 ty: Type::Named("Response".into()),
7258 confidence: Confidence::External,
7259 },
7260 requires: BTreeSet::new(),
7261 on_failure: vec![],
7262 body: vec![
7263 s.put(&Node::Step {
7264 binding: "path".into(),
7265 value: s
7266 .put(&Node::Field {
7267 base: r("req"),
7268 type_name: "Request".into(),
7269 field: "path".into(),
7270 })
7271 .unwrap(),
7272 })
7273 .unwrap(),
7274 s.put(&Node::Step {
7275 binding: "ps".into(),
7276 value: s
7277 .put(&Node::Call {
7278 func: "posts".into(),
7279 args: vec![],
7280 })
7281 .unwrap(),
7282 })
7283 .unwrap(),
7284 s.put(&Node::Step {
7285 binding: "plen".into(),
7286 value: s.put(&Node::StrLen(lit("/posts/"))).unwrap(),
7287 })
7288 .unwrap(),
7289 ],
7290 result: s
7291 .put(&Node::If {
7292 cond: s
7293 .put(&Node::StrEq(r("path"), lit("/posts")))
7294 .unwrap(),
7295 then_branch: resp(
7296 200,
7297 cats(&[
7298 lit("<ul>"),
7299 s.put(&Node::Call {
7300 func: "posts_html".into(),
7301 args: vec![r("ps"), num(0)],
7302 })
7303 .unwrap(),
7304 lit("</ul>"),
7305 ]),
7306 ),
7307 else_branch: s
7308 .put(&Node::If {
7309 cond: s
7310 .put(&Node::StrStartsWith {
7311 s: r("path"),
7312 prefix: lit("/posts/"),
7313 })
7314 .unwrap(),
7315 then_branch: resp(
7316 200,
7317 s.put(&Node::Call {
7318 func: "show_html".into(),
7319 args: vec![
7320 r("ps"),
7321 id_from_path,
7322 num(0),
7323 ],
7324 })
7325 .unwrap(),
7326 ),
7327 else_branch: resp(404, lit("not found")),
7328 })
7329 .unwrap(),
7330 })
7331 .unwrap(),
7332 })
7333 .unwrap();
7334
7335 let m = s
7336 .put(&Node::Module {
7337 name: "blog".into(),
7338 types: vec![post_def, request_def, response_def],
7339 functions: vec![route, posts_html, show_html, posts],
7340 })
7341 .unwrap();
7342 let report = crate::check::Checker::new(&s).check(&m).unwrap();
7343 assert!(
7344 report.ok(),
7345 "the blog app did not type-check: {:?}",
7346 report.violations
7347 );
7348 let wasm = lower(&s, &m).unwrap();
7349 assert_eq!(
7350 serve_request(&wasm, "route", "GET", "/posts", "").unwrap(),
7351 HttpResponse {
7352 status: 200,
7353 body: "<ul><li>1: Hello World</li><li>2: On Cairn</li></ul>"
7354 .into(),
7355 }
7356 );
7357 assert_eq!(
7358 serve_request(&wasm, "route", "GET", "/posts/2", "").unwrap(),
7359 HttpResponse {
7360 status: 200,
7361 body: "<h1>On Cairn</h1><p>Programs as reasoning chains.</p>"
7362 .into(),
7363 }
7364 );
7365 assert_eq!(
7366 serve_request(&wasm, "route", "GET", "/posts/9", "").unwrap(),
7367 HttpResponse {
7368 status: 200,
7369 body: "<p>not found</p>".into(),
7370 }
7371 );
7372 assert_eq!(
7373 serve_request(&wasm, "route", "GET", "/", "").unwrap(),
7374 HttpResponse {
7375 status: 404,
7376 body: "not found".into(),
7377 }
7378 );
7379 }
7380
7381 #[test]
7382 fn runtime_lists_via_cons() {
7383 let s = Store::open_in_memory().unwrap();
7387 let r = |name: &str| -> NodeHash { s.put(&Node::Ref(name.into())).unwrap() };
7388 let num = |n: i64| -> NodeHash { s.put(&Node::Lit(n)).unwrap() };
7389 let bin = |op, a: NodeHash, b: NodeHash| -> NodeHash {
7390 s.put(&Node::BinOp { op, lhs: a, rhs: b }).unwrap()
7391 };
7392 let param = |name: &str, ty: Type| Param {
7393 name: name.into(),
7394 ty,
7395 min_confidence: Confidence::External,
7396 };
7397 let num_list = || Type::List(Box::new(Type::Number));
7398 let ext = |ty: Type| Produces {
7399 ty,
7400 confidence: Confidence::External,
7401 };
7402
7403 let mk = s
7404 .put(&Node::Function {
7405 name: "mk".into(),
7406 type_params: vec![],
7407 params: vec![param("n", Type::Number), param("i", Type::Number)],
7408 produces: ext(num_list()),
7409 requires: BTreeSet::new(),
7410 on_failure: vec![],
7411 body: vec![],
7412 result: s
7413 .put(&Node::If {
7414 cond: bin(crate::node::BinOp::Eq, r("i"), r("n")),
7415 then_branch: s
7416 .put(&Node::ListEmpty {
7417 elem: Type::Number,
7418 })
7419 .unwrap(),
7420 else_branch: s
7421 .put(&Node::ListCons {
7422 head: r("i"),
7423 tail: s
7424 .put(&Node::Call {
7425 func: "mk".into(),
7426 args: vec![
7427 r("n"),
7428 bin(
7429 crate::node::BinOp::Add,
7430 r("i"),
7431 num(1),
7432 ),
7433 ],
7434 })
7435 .unwrap(),
7436 })
7437 .unwrap(),
7438 })
7439 .unwrap(),
7440 })
7441 .unwrap();
7442
7443 let sum = s
7444 .put(&Node::Function {
7445 name: "sum".into(),
7446 type_params: vec![],
7447 params: vec![param("xs", num_list()), param("i", Type::Number)],
7448 produces: ext(Type::Number),
7449 requires: BTreeSet::new(),
7450 on_failure: vec![],
7451 body: vec![s
7452 .put(&Node::Step {
7453 binding: "len".into(),
7454 value: s.put(&Node::ListLen(r("xs"))).unwrap(),
7455 })
7456 .unwrap()],
7457 result: s
7458 .put(&Node::If {
7459 cond: bin(crate::node::BinOp::Eq, r("i"), r("len")),
7460 then_branch: num(0),
7461 else_branch: bin(
7462 crate::node::BinOp::Add,
7463 s.put(&Node::ListGet {
7464 list: r("xs"),
7465 index: r("i"),
7466 })
7467 .unwrap(),
7468 s.put(&Node::Call {
7469 func: "sum".into(),
7470 args: vec![
7471 r("xs"),
7472 bin(crate::node::BinOp::Add, r("i"), num(1)),
7473 ],
7474 })
7475 .unwrap(),
7476 ),
7477 })
7478 .unwrap(),
7479 })
7480 .unwrap();
7481
7482 let total = s
7483 .put(&Node::Function {
7484 name: "total".into(),
7485 type_params: vec![],
7486 params: vec![param("n", Type::Number)],
7487 produces: ext(Type::Number),
7488 requires: BTreeSet::new(),
7489 on_failure: vec![],
7490 body: vec![s
7491 .put(&Node::Step {
7492 binding: "xs".into(),
7493 value: s
7494 .put(&Node::Call {
7495 func: "mk".into(),
7496 args: vec![r("n"), num(0)],
7497 })
7498 .unwrap(),
7499 })
7500 .unwrap()],
7501 result: s
7502 .put(&Node::Call {
7503 func: "sum".into(),
7504 args: vec![r("xs"), num(0)],
7505 })
7506 .unwrap(),
7507 })
7508 .unwrap();
7509
7510 let rendered = crate::render::render(&s, &mk).unwrap();
7512 assert!(rendered.contains("list_empty<Number>()"), "{rendered}");
7513 assert!(rendered.contains("cons("), "{rendered}");
7514
7515 let m = s
7516 .put(&Node::Module {
7517 name: "m".into(),
7518 types: vec![],
7519 functions: vec![total, mk, sum],
7520 })
7521 .unwrap();
7522 let report = crate::check::Checker::new(&s).check(&m).unwrap();
7523 assert!(
7524 report.ok(),
7525 "runtime lists did not type-check: {:?}",
7526 report.violations
7527 );
7528 let wasm = lower(&s, &m).unwrap();
7529 assert_eq!(run_i64(&wasm, "total", &[5]).unwrap(), 10); assert_eq!(run_i64(&wasm, "total", &[0]).unwrap(), 0); assert_eq!(run_i64(&wasm, "total", &[1]).unwrap(), 0);
7532 assert_eq!(run_i64(&wasm, "total", &[4]).unwrap(), 6); }
7534
7535 #[test]
7536 fn str_index_of_finds_substring() {
7537 let s = Store::open_in_memory().unwrap();
7538 let strf = |name: &str, needle: &str| -> NodeHash {
7539 let call = s
7540 .put(&Node::StrIndexOf {
7541 haystack: s.put(&Node::Ref("req".into())).unwrap(),
7542 needle: s.put(&Node::Str(needle.into())).unwrap(),
7543 })
7544 .unwrap();
7545 s.put(&Node::Function {
7546 name: name.into(),
7547 type_params: vec![],
7548 params: vec![Param {
7549 name: "req".into(),
7550 ty: Type::String,
7551 min_confidence: Confidence::External,
7552 }],
7553 produces: Produces {
7554 ty: Type::String,
7555 confidence: Confidence::External,
7556 },
7557 requires: BTreeSet::new(),
7558 on_failure: vec![],
7559 body: vec![],
7560 result: s.put(&Node::NumberToStr(call)).unwrap(),
7561 })
7562 .unwrap()
7563 };
7564 let idx = strf("idx", "::");
7565 let idx0 = strf("idx0", "");
7566 assert!(crate::render::render(&s, &idx)
7567 .unwrap()
7568 .contains("str_index_of("));
7569 let m = s
7570 .put(&Node::Module {
7571 name: "m".into(),
7572 types: vec![],
7573 functions: vec![idx, idx0],
7574 })
7575 .unwrap();
7576 assert!(crate::check::Checker::new(&s).check(&m).unwrap().ok());
7577 let wasm = lower(&s, &m).unwrap();
7578 assert_eq!(serve_once(&wasm, "idx", "ab::cd").unwrap(), "2");
7579 assert_eq!(serve_once(&wasm, "idx", "::x").unwrap(), "0");
7580 assert_eq!(serve_once(&wasm, "idx", "abc").unwrap(), "-1");
7581 assert_eq!(serve_once(&wasm, "idx", "").unwrap(), "-1");
7582 assert_eq!(serve_once(&wasm, "idx0", "anything").unwrap(), "0");
7583 }
7584
7585 #[test]
7586 fn a_db_backed_blog_index() {
7587 let s = Store::open_in_memory().unwrap();
7591 let lit = |t: &str| -> NodeHash { s.put(&Node::Str(t.into())).unwrap() };
7592 let num = |n: i64| -> NodeHash { s.put(&Node::Lit(n)).unwrap() };
7593 let r = |name: &str| -> NodeHash { s.put(&Node::Ref(name.into())).unwrap() };
7594 let bin = |op, a: NodeHash, b: NodeHash| -> NodeHash {
7595 s.put(&Node::BinOp { op, lhs: a, rhs: b }).unwrap()
7596 };
7597 let step = |binding: &str, value: NodeHash| -> NodeHash {
7598 s.put(&Node::Step {
7599 binding: binding.into(),
7600 value,
7601 })
7602 .unwrap()
7603 };
7604 let slice = |str_: NodeHash, start: NodeHash, len: NodeHash| -> NodeHash {
7605 s.put(&Node::StrSlice {
7606 s: str_,
7607 start,
7608 len,
7609 })
7610 .unwrap()
7611 };
7612 let call = |f: &str, args: Vec<NodeHash>| -> NodeHash {
7613 s.put(&Node::Call {
7614 func: f.into(),
7615 args,
7616 })
7617 .unwrap()
7618 };
7619 let cats = |parts: &[NodeHash]| -> NodeHash {
7620 let mut it = parts.iter().cloned();
7621 let first = it.next().unwrap();
7622 it.fold(first, |acc, p| s.put(&Node::StrConcat(acc, p)).unwrap())
7623 };
7624 let param = |name: &str, ty: Type| Param {
7625 name: name.into(),
7626 ty,
7627 min_confidence: Confidence::External,
7628 };
7629 let ext = |ty: Type| Produces {
7630 ty,
7631 confidence: Confidence::External,
7632 };
7633 let post_ty = Type::Named("Post".into());
7634 let post_list = || Type::List(Box::new(Type::Named("Post".into())));
7635 let idx = |hay: NodeHash, needle: &str| -> NodeHash {
7636 s.put(&Node::StrIndexOf {
7637 haystack: hay,
7638 needle: lit(needle),
7639 })
7640 .unwrap()
7641 };
7642
7643 let post_def = s
7644 .put(&Node::RecordDef {
7645 name: "Post".into(),
7646 fields: vec![
7647 ("id".into(), Type::Number),
7648 ("title".into(), Type::String),
7649 ("body".into(), Type::String),
7650 ],
7651 })
7652 .unwrap();
7653 let request_def = s
7654 .put(&Node::RecordDef {
7655 name: "Request".into(),
7656 fields: vec![
7657 ("method".into(), Type::String),
7658 ("path".into(), Type::String),
7659 ("body".into(), Type::String),
7660 ],
7661 })
7662 .unwrap();
7663 let response_def = s
7664 .put(&Node::RecordDef {
7665 name: "Response".into(),
7666 fields: vec![
7667 ("status".into(), Type::Number),
7668 ("body".into(), Type::String),
7669 ],
7670 })
7671 .unwrap();
7672
7673 let t1p1 = bin(crate::node::BinOp::Add, r("t1"), num(1));
7675 let t2p1 = bin(crate::node::BinOp::Add, r("t2"), num(1));
7676 let parse_row = s
7677 .put(&Node::Function {
7678 name: "parse_row".into(),
7679 type_params: vec![],
7680 params: vec![param("row", Type::String)],
7681 produces: ext(post_ty.clone()),
7682 requires: BTreeSet::new(),
7683 on_failure: vec![],
7684 body: vec![
7685 step("rlen", s.put(&Node::StrLen(r("row"))).unwrap()),
7686 step("t1", idx(r("row"), "\t")),
7687 step("ids", slice(r("row"), num(0), r("t1"))),
7688 step(
7689 "after1",
7690 slice(
7691 r("row"),
7692 t1p1.clone(),
7693 bin(crate::node::BinOp::Sub, r("rlen"), t1p1),
7694 ),
7695 ),
7696 step("alen", s.put(&Node::StrLen(r("after1"))).unwrap()),
7697 step("t2", idx(r("after1"), "\t")),
7698 step("title", slice(r("after1"), num(0), r("t2"))),
7699 step(
7700 "pbody",
7701 slice(
7702 r("after1"),
7703 t2p1.clone(),
7704 bin(crate::node::BinOp::Sub, r("alen"), t2p1),
7705 ),
7706 ),
7707 ],
7708 result: s
7709 .put(&Node::Record {
7710 type_name: "Post".into(),
7711 fields: vec![
7712 (
7713 "id".into(),
7714 s.put(&Node::StrToNumber(r("ids"))).unwrap(),
7715 ),
7716 ("title".into(), r("title")),
7717 ("body".into(), r("pbody")),
7718 ],
7719 })
7720 .unwrap(),
7721 })
7722 .unwrap();
7723
7724 let nlp1 = bin(crate::node::BinOp::Add, r("nl"), num(1));
7726 let parse_posts = s
7727 .put(&Node::Function {
7728 name: "parse_posts".into(),
7729 type_params: vec![],
7730 params: vec![param("rows", Type::String)],
7731 produces: ext(post_list()),
7732 requires: BTreeSet::new(),
7733 on_failure: vec![],
7734 body: vec![
7735 step("rl", s.put(&Node::StrLen(r("rows"))).unwrap()),
7736 step("nl", idx(r("rows"), "\n")),
7737 ],
7738 result: s
7739 .put(&Node::If {
7740 cond: bin(crate::node::BinOp::Eq, r("rl"), num(0)),
7741 then_branch: s
7742 .put(&Node::ListEmpty {
7743 elem: post_ty.clone(),
7744 })
7745 .unwrap(),
7746 else_branch: s
7747 .put(&Node::If {
7748 cond: bin(
7749 crate::node::BinOp::Eq,
7750 r("nl"),
7751 num(-1),
7752 ),
7753 then_branch: s
7754 .put(&Node::ListCons {
7755 head: call(
7756 "parse_row",
7757 vec![r("rows")],
7758 ),
7759 tail: s
7760 .put(&Node::ListEmpty {
7761 elem: post_ty.clone(),
7762 })
7763 .unwrap(),
7764 })
7765 .unwrap(),
7766 else_branch: s
7767 .put(&Node::ListCons {
7768 head: call(
7769 "parse_row",
7770 vec![slice(
7771 r("rows"),
7772 num(0),
7773 r("nl"),
7774 )],
7775 ),
7776 tail: call(
7777 "parse_posts",
7778 vec![slice(
7779 r("rows"),
7780 nlp1.clone(),
7781 bin(
7782 crate::node::BinOp::Sub,
7783 r("rl"),
7784 nlp1,
7785 ),
7786 )],
7787 ),
7788 })
7789 .unwrap(),
7790 })
7791 .unwrap(),
7792 })
7793 .unwrap(),
7794 })
7795 .unwrap();
7796
7797 let pf = |f: &str| -> NodeHash {
7799 s.put(&Node::Field {
7800 base: s
7801 .put(&Node::ListGet {
7802 list: r("ps"),
7803 index: r("i"),
7804 })
7805 .unwrap(),
7806 type_name: "Post".into(),
7807 field: f.into(),
7808 })
7809 .unwrap()
7810 };
7811 let posts_html = s
7812 .put(&Node::Function {
7813 name: "posts_html".into(),
7814 type_params: vec![],
7815 params: vec![param("ps", post_list()), param("i", Type::Number)],
7816 produces: ext(Type::String),
7817 requires: BTreeSet::new(),
7818 on_failure: vec![],
7819 body: vec![step("n", s.put(&Node::ListLen(r("ps"))).unwrap())],
7820 result: s
7821 .put(&Node::If {
7822 cond: bin(crate::node::BinOp::Eq, r("i"), r("n")),
7823 then_branch: lit(""),
7824 else_branch: cats(&[
7825 lit("<li>"),
7826 s.put(&Node::NumberToStr(pf("id"))).unwrap(),
7827 lit(": "),
7828 pf("title"),
7829 lit("</li>"),
7830 call(
7831 "posts_html",
7832 vec![
7833 r("ps"),
7834 bin(crate::node::BinOp::Add, r("i"), num(1)),
7835 ],
7836 ),
7837 ]),
7838 })
7839 .unwrap(),
7840 })
7841 .unwrap();
7842
7843 let mut db = BTreeSet::new();
7845 db.insert(crate::ty::Effect::Db);
7846 let route = s
7847 .put(&Node::Function {
7848 name: "route".into(),
7849 type_params: vec![],
7850 params: vec![param("req", Type::Named("Request".into()))],
7851 produces: ext(Type::Named("Response".into())),
7852 requires: db,
7853 on_failure: vec![],
7854 body: {
7855 let none = || {
7856 s.put(&Node::ListEmpty { elem: Type::String }).unwrap()
7857 };
7858 let dbq = |sql: &str| {
7859 s.put(&Node::DbQuery {
7860 sql: lit(sql),
7861 params: none(),
7862 })
7863 .unwrap()
7864 };
7865 vec![
7866 step(
7870 "_c",
7871 dbq(
7872 "CREATE TABLE IF NOT EXISTS posts \
7873 (id INTEGER PRIMARY KEY, title TEXT, body TEXT)",
7874 ),
7875 ),
7876 step(
7877 "_i1",
7878 dbq(
7879 "INSERT INTO posts (title, body) \
7880 VALUES ('Hello World', 'The first post.')",
7881 ),
7882 ),
7883 step(
7884 "_i2",
7885 dbq(
7886 "INSERT INTO posts (title, body) VALUES \
7887 ('On Cairn', 'Programs as reasoning chains.')",
7888 ),
7889 ),
7890 step(
7891 "rows",
7892 dbq("SELECT id, title, body FROM posts ORDER BY id"),
7893 ),
7894 step("ps", call("parse_posts", vec![r("rows")])),
7895 ]
7896 },
7897 result: s
7898 .put(&Node::Record {
7899 type_name: "Response".into(),
7900 fields: vec![
7901 ("status".into(), num(200)),
7902 (
7903 "body".into(),
7904 cats(&[
7905 lit("<ul>"),
7906 call("posts_html", vec![r("ps"), num(0)]),
7907 lit("</ul>"),
7908 ]),
7909 ),
7910 ],
7911 })
7912 .unwrap(),
7913 })
7914 .unwrap();
7915
7916 let m = s
7917 .put(&Node::Module {
7918 name: "blog".into(),
7919 types: vec![post_def, request_def, response_def],
7920 functions: vec![route, parse_posts, parse_row, posts_html],
7921 })
7922 .unwrap();
7923 let report = crate::check::Checker::new(&s).check(&m).unwrap();
7924 assert!(
7925 report.ok(),
7926 "db-backed blog did not type-check: {:?}",
7927 report.violations
7928 );
7929 let wasm = lower(&s, &m).unwrap();
7930 assert_eq!(
7931 serve_request(&wasm, "route", "GET", "/posts", "").unwrap(),
7932 HttpResponse {
7933 status: 200,
7934 body: "<ul><li>1: Hello World</li><li>2: On Cairn</li></ul>"
7935 .into(),
7936 }
7937 );
7938 }
7939
7940 #[test]
7941 fn a_persistent_sqlite_blog() {
7942 let nanos = std::time::SystemTime::now()
7947 .duration_since(std::time::UNIX_EPOCH)
7948 .unwrap()
7949 .as_nanos();
7950 let db = std::env::temp_dir()
7951 .join(format!("cairn_sqlite_blog_{}_{nanos}.db", std::process::id()));
7952 let dbs = db.to_str().unwrap().to_string();
7953 let _ = std::fs::remove_file(&db);
7954
7955 let s = Store::open_in_memory().unwrap();
7956 let lit = |t: &str| -> NodeHash { s.put(&Node::Str(t.into())).unwrap() };
7957 let num = |n: i64| -> NodeHash { s.put(&Node::Lit(n)).unwrap() };
7958 let r = |name: &str| -> NodeHash { s.put(&Node::Ref(name.into())).unwrap() };
7959 let bin = |op, a: NodeHash, b: NodeHash| -> NodeHash {
7960 s.put(&Node::BinOp { op, lhs: a, rhs: b }).unwrap()
7961 };
7962 let step = |binding: &str, value: NodeHash| -> NodeHash {
7963 s.put(&Node::Step {
7964 binding: binding.into(),
7965 value,
7966 })
7967 .unwrap()
7968 };
7969 let slice = |str_: NodeHash, start: NodeHash, len: NodeHash| -> NodeHash {
7970 s.put(&Node::StrSlice {
7971 s: str_,
7972 start,
7973 len,
7974 })
7975 .unwrap()
7976 };
7977 let call = |f: &str, args: Vec<NodeHash>| -> NodeHash {
7978 s.put(&Node::Call {
7979 func: f.into(),
7980 args,
7981 })
7982 .unwrap()
7983 };
7984 let cats = |parts: &[NodeHash]| -> NodeHash {
7985 let mut it = parts.iter().cloned();
7986 let first = it.next().unwrap();
7987 it.fold(first, |acc, p| s.put(&Node::StrConcat(acc, p)).unwrap())
7988 };
7989 let param = |name: &str, ty: Type| Param {
7990 name: name.into(),
7991 ty,
7992 min_confidence: Confidence::External,
7993 };
7994 let ext = |ty: Type| Produces {
7995 ty,
7996 confidence: Confidence::External,
7997 };
7998 let post_ty = Type::Named("Post".into());
7999 let post_list = || Type::List(Box::new(Type::Named("Post".into())));
8000 let idx = |hay: NodeHash, needle: &str| -> NodeHash {
8001 s.put(&Node::StrIndexOf {
8002 haystack: hay,
8003 needle: lit(needle),
8004 })
8005 .unwrap()
8006 };
8007 let dbq = |sql: &str| -> NodeHash {
8008 s.put(&Node::DbQuery {
8009 sql: lit(sql),
8010 params: s.put(&Node::ListEmpty { elem: Type::String }).unwrap(),
8011 })
8012 .unwrap()
8013 };
8014
8015 let post_def = s
8016 .put(&Node::RecordDef {
8017 name: "Post".into(),
8018 fields: vec![
8019 ("id".into(), Type::Number),
8020 ("title".into(), Type::String),
8021 ("body".into(), Type::String),
8022 ],
8023 })
8024 .unwrap();
8025 let request_def = s
8026 .put(&Node::RecordDef {
8027 name: "Request".into(),
8028 fields: vec![
8029 ("method".into(), Type::String),
8030 ("path".into(), Type::String),
8031 ("body".into(), Type::String),
8032 ],
8033 })
8034 .unwrap();
8035 let response_def = s
8036 .put(&Node::RecordDef {
8037 name: "Response".into(),
8038 fields: vec![
8039 ("status".into(), Type::Number),
8040 ("body".into(), Type::String),
8041 ],
8042 })
8043 .unwrap();
8044
8045 let t1p1 = bin(crate::node::BinOp::Add, r("t1"), num(1));
8048 let t2p1 = bin(crate::node::BinOp::Add, r("t2"), num(1));
8049 let parse_row = s
8050 .put(&Node::Function {
8051 name: "parse_row".into(),
8052 type_params: vec![],
8053 params: vec![param("row", Type::String)],
8054 produces: ext(post_ty.clone()),
8055 requires: BTreeSet::new(),
8056 on_failure: vec![],
8057 body: vec![
8058 step("rlen", s.put(&Node::StrLen(r("row"))).unwrap()),
8059 step("t1", idx(r("row"), "\t")),
8060 step("ids", slice(r("row"), num(0), r("t1"))),
8061 step(
8062 "after1",
8063 slice(
8064 r("row"),
8065 t1p1.clone(),
8066 bin(crate::node::BinOp::Sub, r("rlen"), t1p1),
8067 ),
8068 ),
8069 step("alen", s.put(&Node::StrLen(r("after1"))).unwrap()),
8070 step("t2", idx(r("after1"), "\t")),
8071 step("title", slice(r("after1"), num(0), r("t2"))),
8072 step(
8073 "pbody",
8074 slice(
8075 r("after1"),
8076 t2p1.clone(),
8077 bin(crate::node::BinOp::Sub, r("alen"), t2p1),
8078 ),
8079 ),
8080 ],
8081 result: s
8082 .put(&Node::Record {
8083 type_name: "Post".into(),
8084 fields: vec![
8085 (
8086 "id".into(),
8087 s.put(&Node::StrToNumber(r("ids"))).unwrap(),
8088 ),
8089 ("title".into(), r("title")),
8090 ("body".into(), r("pbody")),
8091 ],
8092 })
8093 .unwrap(),
8094 })
8095 .unwrap();
8096 let nlp1 = bin(crate::node::BinOp::Add, r("nl"), num(1));
8097 let parse_posts = s
8098 .put(&Node::Function {
8099 name: "parse_posts".into(),
8100 type_params: vec![],
8101 params: vec![param("rows", Type::String)],
8102 produces: ext(post_list()),
8103 requires: BTreeSet::new(),
8104 on_failure: vec![],
8105 body: vec![
8106 step("rl", s.put(&Node::StrLen(r("rows"))).unwrap()),
8107 step("nl", idx(r("rows"), "\n")),
8108 ],
8109 result: s
8110 .put(&Node::If {
8111 cond: bin(crate::node::BinOp::Eq, r("rl"), num(0)),
8112 then_branch: s
8113 .put(&Node::ListEmpty {
8114 elem: post_ty.clone(),
8115 })
8116 .unwrap(),
8117 else_branch: s
8118 .put(&Node::If {
8119 cond: bin(
8120 crate::node::BinOp::Eq,
8121 r("nl"),
8122 num(-1),
8123 ),
8124 then_branch: s
8125 .put(&Node::ListCons {
8126 head: call(
8127 "parse_row",
8128 vec![r("rows")],
8129 ),
8130 tail: s
8131 .put(&Node::ListEmpty {
8132 elem: post_ty.clone(),
8133 })
8134 .unwrap(),
8135 })
8136 .unwrap(),
8137 else_branch: s
8138 .put(&Node::ListCons {
8139 head: call(
8140 "parse_row",
8141 vec![slice(
8142 r("rows"),
8143 num(0),
8144 r("nl"),
8145 )],
8146 ),
8147 tail: call(
8148 "parse_posts",
8149 vec![slice(
8150 r("rows"),
8151 nlp1.clone(),
8152 bin(
8153 crate::node::BinOp::Sub,
8154 r("rl"),
8155 nlp1,
8156 ),
8157 )],
8158 ),
8159 })
8160 .unwrap(),
8161 })
8162 .unwrap(),
8163 })
8164 .unwrap(),
8165 })
8166 .unwrap();
8167 let pf = |f: &str| -> NodeHash {
8168 s.put(&Node::Field {
8169 base: s
8170 .put(&Node::ListGet {
8171 list: r("ps"),
8172 index: r("i"),
8173 })
8174 .unwrap(),
8175 type_name: "Post".into(),
8176 field: f.into(),
8177 })
8178 .unwrap()
8179 };
8180 let posts_html = s
8181 .put(&Node::Function {
8182 name: "posts_html".into(),
8183 type_params: vec![],
8184 params: vec![param("ps", post_list()), param("i", Type::Number)],
8185 produces: ext(Type::String),
8186 requires: BTreeSet::new(),
8187 on_failure: vec![],
8188 body: vec![step("n", s.put(&Node::ListLen(r("ps"))).unwrap())],
8189 result: s
8190 .put(&Node::If {
8191 cond: bin(crate::node::BinOp::Eq, r("i"), r("n")),
8192 then_branch: lit(""),
8193 else_branch: cats(&[
8194 lit("<li>"),
8195 s.put(&Node::NumberToStr(pf("id"))).unwrap(),
8196 lit(": "),
8197 pf("title"),
8198 lit("</li>"),
8199 call(
8200 "posts_html",
8201 vec![
8202 r("ps"),
8203 bin(crate::node::BinOp::Add, r("i"), num(1)),
8204 ],
8205 ),
8206 ]),
8207 })
8208 .unwrap(),
8209 })
8210 .unwrap();
8211
8212 let resp = |status: i64, body: NodeHash| -> NodeHash {
8215 s.put(&Node::Record {
8216 type_name: "Response".into(),
8217 fields: vec![("status".into(), num(status)), ("body".into(), body)],
8218 })
8219 .unwrap()
8220 };
8221 let mut dbset = BTreeSet::new();
8222 dbset.insert(crate::ty::Effect::Db);
8223 let route = s
8224 .put(&Node::Function {
8225 name: "route".into(),
8226 type_params: vec![],
8227 params: vec![param("req", Type::Named("Request".into()))],
8228 produces: ext(Type::Named("Response".into())),
8229 requires: dbset,
8230 on_failure: vec![],
8231 body: vec![step(
8232 "path",
8233 s.put(&Node::Field {
8234 base: r("req"),
8235 type_name: "Request".into(),
8236 field: "path".into(),
8237 })
8238 .unwrap(),
8239 )],
8240 result: s
8241 .put(&Node::If {
8242 cond: s
8243 .put(&Node::StrEq(r("path"), lit("/seed")))
8244 .unwrap(),
8245 then_branch: resp(
8246 200,
8247 cats(&[
8248 dbq("CREATE TABLE IF NOT EXISTS posts \
8249 (id INTEGER PRIMARY KEY, title TEXT, body TEXT)"),
8250 dbq("INSERT INTO posts (title, body) \
8251 VALUES ('Hello World', 'The first post.')"),
8252 dbq("INSERT INTO posts (title, body) VALUES \
8253 ('On Cairn', 'Programs as reasoning chains.')"),
8254 ]),
8255 ),
8256 else_branch: s
8257 .put(&Node::If {
8258 cond: s
8259 .put(&Node::StrEq(r("path"), lit("/posts")))
8260 .unwrap(),
8261 then_branch: resp(
8262 200,
8263 cats(&[
8264 lit("<ul>"),
8265 call(
8266 "posts_html",
8267 vec![
8268 call(
8269 "parse_posts",
8270 vec![dbq(
8271 "SELECT id, title, body \
8272 FROM posts ORDER BY id",
8273 )],
8274 ),
8275 num(0),
8276 ],
8277 ),
8278 lit("</ul>"),
8279 ]),
8280 ),
8281 else_branch: resp(404, lit("not found")),
8282 })
8283 .unwrap(),
8284 })
8285 .unwrap(),
8286 })
8287 .unwrap();
8288
8289 let m = s
8290 .put(&Node::Module {
8291 name: "blog".into(),
8292 types: vec![post_def, request_def, response_def],
8293 functions: vec![route, parse_posts, parse_row, posts_html],
8294 })
8295 .unwrap();
8296 let report = crate::check::Checker::new(&s).check(&m).unwrap();
8297 assert!(
8298 report.ok(),
8299 "persistent blog did not type-check: {:?}",
8300 report.violations
8301 );
8302 let wasm = lower(&s, &m).unwrap();
8303
8304 assert_eq!(
8306 serve_request_db(&wasm, "route", &dbs, "POST", "/seed", "").unwrap(),
8307 HttpResponse {
8308 status: 200,
8309 body: "012".into(),
8310 }
8311 );
8312 assert_eq!(
8315 serve_request_db(&wasm, "route", &dbs, "GET", "/posts", "").unwrap(),
8316 HttpResponse {
8317 status: 200,
8318 body: "<ul><li>1: Hello World</li><li>2: On Cairn</li></ul>"
8319 .into(),
8320 }
8321 );
8322 assert!(db.exists(), "the SQLite file should persist on disk");
8323
8324 let _ = std::fs::remove_file(&db);
8325 let _ = std::fs::remove_file(format!("{dbs}-journal"));
8326 let _ = std::fs::remove_file(format!("{dbs}-wal"));
8327 let _ = std::fs::remove_file(format!("{dbs}-shm"));
8328 }
8329}