1use crate::node::{Node, NodeHash};
19use crate::store::{Result, Store};
20use crate::ty::{Confidence, Effect, Type};
21use serde::Serialize;
22use std::cell::Cell;
23use std::collections::HashMap;
24
25const LB: char = '\u{1}'; const SEP: char = '\u{2}'; const RB: char = '\u{3}'; thread_local! {
39 static MARK: Cell<bool> = const { Cell::new(false) };
40}
41
42struct MarkGuard;
46impl MarkGuard {
47 fn on() -> Self {
48 MARK.with(|m| m.set(true));
49 MarkGuard
50 }
51}
52impl Drop for MarkGuard {
53 fn drop(&mut self) {
54 MARK.with(|m| m.set(false));
55 }
56}
57
58fn wrap(hash: &NodeHash, s: String) -> String {
61 if MARK.with(Cell::get) {
62 format!("{LB}{hash}{SEP}{s}{RB}")
63 } else {
64 s
65 }
66}
67
68#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
71pub struct Span {
72 pub hash: NodeHash,
73 pub start: usize,
74 pub end: usize,
75}
76
77#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
80pub struct Addressed {
81 pub text: String,
82 pub spans: Vec<Span>,
83}
84
85pub fn render_addressed(store: &Store, root: &NodeHash) -> Result<Addressed> {
89 let marked = {
90 let _g = MarkGuard::on();
91 render(store, root)?
92 };
93 let mut text = String::with_capacity(marked.len());
94 let mut stack: Vec<(NodeHash, usize)> = Vec::new();
95 let mut spans: Vec<Span> = Vec::new();
96 let mut chars = marked.chars();
97 while let Some(c) = chars.next() {
98 match c {
99 LB => {
100 let mut h = String::new();
101 for hc in chars.by_ref() {
102 if hc == SEP {
103 break;
104 }
105 h.push(hc);
106 }
107 stack.push((NodeHash::parse(&h), text.len()));
108 }
109 RB => {
110 if let Some((hash, start)) = stack.pop() {
111 spans.push(Span {
112 hash,
113 start,
114 end: text.len(),
115 });
116 }
117 }
118 _ => text.push(c),
119 }
120 }
121 Ok(Addressed { text, spans })
122}
123
124pub fn node_at(a: &Addressed, offset: usize) -> Option<NodeHash> {
127 a.spans
128 .iter()
129 .filter(|s| offset >= s.start && offset < s.end)
130 .min_by_key(|s| s.end - s.start)
131 .map(|s| s.hash.clone())
132}
133
134pub fn render(store: &Store, root: &NodeHash) -> Result<String> {
136 let params = param_names(store, root)?;
137 let Some(node) = store.get(root)? else {
138 return Ok("<missing>".to_string());
139 };
140 Ok(match node {
141 Node::Module {
142 name,
143 types,
144 functions,
145 } => {
146 let mut parts = vec![format!("module {name}"), String::new()];
147 for th in &types {
148 parts.push(render_typedef(store, th)?);
149 parts.push(String::new());
150 }
151 for fh in &functions {
152 parts.push(render_function(store, fh, ¶ms)?);
153 parts.push(String::new());
154 }
155 parts.push("end".to_string());
156 wrap(root, parts.join("\n"))
157 }
158 Node::Function { .. } => render_function(store, root, ¶ms)?,
159 Node::RecordDef { .. } | Node::VariantDef { .. } => render_typedef(store, root)?,
160 _ => expr(store, root, ¶ms),
161 })
162}
163
164fn render_typedef(store: &Store, hash: &NodeHash) -> Result<String> {
167 match store.get(hash)? {
168 Some(Node::RecordDef { name, fields }) => {
169 let mut out = vec![format!("type {name} = record")];
170 for (fname, fty) in &fields {
171 out.push(format!(" {fname}: {}", ty(fty)));
172 }
173 out.push("end".to_string());
174 Ok(wrap(hash, out.join("\n")))
175 }
176 Some(Node::VariantDef { name, cases }) => {
177 let mut out = vec![format!("type {name} = variant")];
178 for (cname, payload) in &cases {
179 if payload.is_empty() {
180 out.push(format!(" {cname}"));
181 } else {
182 let fs: Vec<String> = payload
183 .iter()
184 .map(|(fn_, ft)| format!("{fn_}: {}", ty(ft)))
185 .collect();
186 out.push(format!(" {cname}({})", fs.join(", ")));
187 }
188 }
189 out.push("end".to_string());
190 Ok(wrap(hash, out.join("\n")))
191 }
192 _ => Ok(wrap(hash, "<missing>".to_string())),
193 }
194}
195
196fn param_names(store: &Store, root: &NodeHash) -> Result<HashMap<String, Vec<String>>> {
200 let mut map = HashMap::new();
201 let mut add = |n: &Node| {
202 if let Node::Function { name, params, .. } = n {
203 map.insert(
204 name.clone(),
205 params.iter().map(|p| p.name.clone()).collect(),
206 );
207 }
208 };
209 match store.get(root)? {
210 Some(Node::Module { functions, .. }) => {
211 for fh in &functions {
212 if let Some(f) = store.get(fh)? {
213 add(&f);
214 }
215 }
216 }
217 Some(f @ Node::Function { .. }) => add(&f),
218 _ => {}
219 }
220 Ok(map)
221}
222
223fn render_function(
224 store: &Store,
225 fh: &NodeHash,
226 pmap: &HashMap<String, Vec<String>>,
227) -> Result<String> {
228 let Some(Node::Function {
229 name,
230 type_params,
231 params,
232 produces,
233 requires,
234 on_failure,
235 body,
236 result,
237 }) = store.get(fh)?
238 else {
239 return Ok(wrap(fh, "<missing>".to_string()));
240 };
241
242 let header = if type_params.is_empty() {
243 format!("function {name}")
244 } else {
245 format!("function {name}<{}>", type_params.join(", "))
246 };
247 let mut out: Vec<String> = vec![header];
248
249 if !params.is_empty() {
250 out.push(" given".to_string());
251 for p in ¶ms {
252 out.push(format!(
253 " {}: {}{}",
254 p.name,
255 ty(&p.ty),
256 conf_suffix(p.min_confidence)
257 ));
258 }
259 }
260
261 out.push(format!(
262 " produces {}{}",
263 ty(&produces.ty),
264 conf_suffix(produces.confidence)
265 ));
266
267 if requires.is_empty() {
268 out.push(" pure".to_string());
269 } else {
270 let names: Vec<String> = requires.iter().map(|e| effect(*e).to_string()).collect();
271 out.push(format!(" requires {}", names.join(", ")));
272 }
273
274 if !on_failure.is_empty() {
275 out.push(" on_failure".to_string());
276 for f in &on_failure {
277 out.push(format!(" {f}"));
278 }
279 }
280
281 out.push("do".to_string());
282 for step_hash in &body {
283 match store.get(step_hash)? {
284 Some(Node::Step { binding, value }) => match store.get(&value)? {
285 Some(Node::Handle { body, handlers }) => {
288 out.push(format!(
289 " step {} = {}",
290 binding,
291 expr(store, &body, pmap)
292 ));
293 for (v, r) in &handlers {
294 out.push(format!(" on {} -> {}", v, expr(store, r, pmap)));
295 }
296 }
297 _ => {
298 out.push(format!(
299 " step {} = {}",
300 binding,
301 expr(store, &value, pmap)
302 ));
303 }
304 },
305 Some(Node::Hole { expects }) => {
306 out.push(format!(" <hole: {expects}>"));
307 }
308 _ => out.push(" <missing>".to_string()),
309 }
310 }
311 out.push(format!(" yield {}", expr(store, &result, pmap)));
312 out.push("end".to_string());
313 Ok(wrap(fh, out.join("\n")))
314}
315
316fn expr(store: &Store, hash: &NodeHash, pmap: &HashMap<String, Vec<String>>) -> String {
320 wrap(hash, expr_body(store, hash, pmap))
321}
322
323fn expr_body(store: &Store, hash: &NodeHash, pmap: &HashMap<String, Vec<String>>) -> String {
324 match store.get(hash) {
325 Ok(Some(Node::Lit(v))) => v.to_string(),
326 Ok(Some(Node::FloatLit(bits))) => format!("{}f", f64::from_bits(bits)),
327 Ok(Some(Node::FloatOp { op, lhs, rhs })) => format!(
328 "{} {} {}",
329 operand(store, &lhs, pmap),
330 op.symbol(),
331 operand(store, &rhs, pmap)
332 ),
333 Ok(Some(Node::IntToFloat(a))) => {
334 format!("to_float({})", expr(store, &a, pmap))
335 }
336 Ok(Some(Node::FloatToInt(a))) => {
337 format!("to_int({})", expr(store, &a, pmap))
338 }
339 Ok(Some(Node::DecimalLit(v))) => {
340 format!("{}.{:04}d", v / 10000, (v % 10000).abs())
341 }
342 Ok(Some(Node::DecimalOp { op, lhs, rhs })) => format!(
343 "{} {} {}",
344 operand(store, &lhs, pmap),
345 op.symbol(),
346 operand(store, &rhs, pmap)
347 ),
348 Ok(Some(Node::IntToDecimal(a))) => {
349 format!("to_decimal({})", expr(store, &a, pmap))
350 }
351 Ok(Some(Node::DecimalToInt(a))) => {
352 format!("decimal_to_int({})", expr(store, &a, pmap))
353 }
354 Ok(Some(Node::DecimalRaw(a))) => {
355 format!("decimal_raw({})", expr(store, &a, pmap))
356 }
357 Ok(Some(Node::Bool(b))) => b.to_string(),
358 Ok(Some(Node::Not(a))) => format!("!{}", operand(store, &a, pmap)),
359 Ok(Some(Node::Str(s))) => format!("{s:?}"),
360 Ok(Some(Node::Now)) => "now()".to_string(),
361 Ok(Some(Node::List(es))) => {
362 let parts: Vec<String> =
363 es.iter().map(|e| expr(store, e, pmap)).collect();
364 format!("[{}]", parts.join(", "))
365 }
366 Ok(Some(Node::ListEmpty { elem })) => {
367 format!("list_empty<{}>()", ty(&elem))
368 }
369 Ok(Some(Node::ListCons { head, tail })) => format!(
370 "cons({}, {})",
371 expr(store, &head, pmap),
372 expr(store, &tail, pmap)
373 ),
374 Ok(Some(Node::OptionSome(v))) => {
375 format!("some({})", expr(store, &v, pmap))
376 }
377 Ok(Some(Node::OptionNone { elem })) => {
378 format!("none<{}>()", ty(&elem))
379 }
380 Ok(Some(Node::OptionElse { opt, default })) => format!(
381 "{} else {}",
382 expr(store, &opt, pmap),
383 expr(store, &default, pmap)
384 ),
385 Ok(Some(Node::OptionMatch {
386 opt,
387 some_bind,
388 some_body,
389 none_body,
390 })) => format!(
391 "match {} {{ Some({some_bind}) -> {}, None -> {} }}",
392 expr(store, &opt, pmap),
393 expr(store, &some_body, pmap),
394 expr(store, &none_body, pmap)
395 ),
396 Ok(Some(Node::ListTryGet { list, index })) => format!(
397 "list_try_get({}, {})",
398 expr(store, &list, pmap),
399 expr(store, &index, pmap)
400 ),
401 Ok(Some(Node::ListLen(a))) => {
402 format!("list_len({})", expr(store, &a, pmap))
403 }
404 Ok(Some(Node::ListGet { list, index })) => format!(
405 "list_get({}, {})",
406 expr(store, &list, pmap),
407 expr(store, &index, pmap)
408 ),
409 Ok(Some(Node::Map(pairs))) => {
410 let parts: Vec<String> = pairs
411 .iter()
412 .map(|(k, v)| {
413 format!("{}: {}", expr(store, k, pmap), expr(store, v, pmap))
414 })
415 .collect();
416 format!("{{{}}}", parts.join(", "))
417 }
418 Ok(Some(Node::MapGet { map, key })) => format!(
419 "map_get({}, {})",
420 expr(store, &map, pmap),
421 expr(store, &key, pmap)
422 ),
423 Ok(Some(Node::MapTryGet { map, key })) => format!(
424 "map_try_get({}, {})",
425 expr(store, &map, pmap),
426 expr(store, &key, pmap)
427 ),
428 Ok(Some(Node::MapLen(a))) => format!("map_len({})", expr(store, &a, pmap)),
429 Ok(Some(Node::Log(a))) => format!("log({})", expr(store, &a, pmap)),
430 Ok(Some(Node::Publish(a))) => {
431 format!("publish({})", expr(store, &a, pmap))
432 }
433 Ok(Some(Node::SetHeader { name, value })) => format!(
434 "set_header({}, {})",
435 expr(store, &name, pmap),
436 expr(store, &value, pmap)
437 ),
438 Ok(Some(Node::Rand)) => "rand()".to_string(),
439 Ok(Some(Node::MutNew(v))) => format!("cell({})", expr(store, &v, pmap)),
440 Ok(Some(Node::MutGet(cl))) => {
441 format!("cell_get({})", expr(store, &cl, pmap))
442 }
443 Ok(Some(Node::MutSet { cell, value })) => format!(
444 "cell_set({}, {})",
445 expr(store, &cell, pmap),
446 expr(store, &value, pmap)
447 ),
448 Ok(Some(Node::DiskWrite { path, content })) => format!(
449 "disk_write({}, {})",
450 expr(store, &path, pmap),
451 expr(store, &content, pmap)
452 ),
453 Ok(Some(Node::DiskRead(p))) => {
454 format!("disk_read({})", expr(store, &p, pmap))
455 }
456 Ok(Some(Node::NetGet(u))) => {
457 format!("net_get({})", expr(store, &u, pmap))
458 }
459 Ok(Some(Node::DbQuery { sql, params })) => format!(
460 "db_query({}, {})",
461 expr(store, &sql, pmap),
462 expr(store, ¶ms, pmap)
463 ),
464 Ok(Some(Node::StrLen(a))) => {
465 format!("str_len({})", expr(store, &a, pmap))
466 }
467 Ok(Some(Node::StrLower(a))) => {
468 format!("str_lower({})", expr(store, &a, pmap))
469 }
470 Ok(Some(Node::StrFromCode(a))) => {
471 format!("str_from_code({})", expr(store, &a, pmap))
472 }
473 Ok(Some(Node::NumberToStr(a))) => {
474 format!("number_to_str({})", expr(store, &a, pmap))
475 }
476 Ok(Some(Node::StrToNumber(a))) => {
477 format!("str_to_number({})", expr(store, &a, pmap))
478 }
479 Ok(Some(Node::StrToNumberOpt(a))) => {
480 format!("str_to_number_opt({})", expr(store, &a, pmap))
481 }
482 Ok(Some(Node::StrConcat(a, b))) => format!(
483 "str_concat({}, {})",
484 expr(store, &a, pmap),
485 expr(store, &b, pmap)
486 ),
487 Ok(Some(Node::StrSlice { s, start, len })) => format!(
488 "str_slice({}, {}, {})",
489 expr(store, &s, pmap),
490 expr(store, &start, pmap),
491 expr(store, &len, pmap)
492 ),
493 Ok(Some(Node::StrEq(a, b))) => format!(
494 "str_eq({}, {})",
495 expr(store, &a, pmap),
496 expr(store, &b, pmap)
497 ),
498 Ok(Some(Node::StrContains { haystack, needle })) => format!(
499 "str_contains({}, {})",
500 expr(store, &haystack, pmap),
501 expr(store, &needle, pmap)
502 ),
503 Ok(Some(Node::StrStartsWith { s, prefix })) => format!(
504 "str_starts_with({}, {})",
505 expr(store, &s, pmap),
506 expr(store, &prefix, pmap)
507 ),
508 Ok(Some(Node::StrIndexOf { haystack, needle })) => format!(
509 "str_index_of({}, {})",
510 expr(store, &haystack, pmap),
511 expr(store, &needle, pmap)
512 ),
513 Ok(Some(Node::Ref(name))) => name,
514 Ok(Some(Node::Hole { expects })) => format!("<hole: {expects}>"),
515 Ok(Some(Node::Step { value, .. })) => expr(store, &value, pmap),
516 Ok(Some(Node::BinOp { op, lhs, rhs })) => {
517 format!(
518 "{} {} {}",
519 operand(store, &lhs, pmap),
520 op.symbol(),
521 operand(store, &rhs, pmap)
522 )
523 }
524 Ok(Some(Node::If {
525 cond,
526 then_branch,
527 else_branch,
528 })) => format!(
529 "if {} then {} else {} end",
530 expr(store, &cond, pmap),
531 expr(store, &then_branch, pmap),
532 expr(store, &else_branch, pmap)
533 ),
534 Ok(Some(Node::Fail(v))) => format!("fail {v}"),
535 Ok(Some(Node::Handle { body, handlers })) => {
536 let mut s = expr(store, &body, pmap);
537 for (v, r) in &handlers {
538 s.push_str(&format!(" on {v} -> {}", expr(store, r, pmap)));
539 }
540 s
541 }
542 Ok(Some(Node::Call { func, args })) => {
543 let rendered: Vec<String> =
544 args.iter().map(|a| expr(store, a, pmap)).collect();
545 match pmap.get(&func) {
546 Some(names) if names.len() == rendered.len() => {
547 let pairs: Vec<String> = names
548 .iter()
549 .zip(&rendered)
550 .map(|(n, a)| format!("{n}: {a}"))
551 .collect();
552 format!("{func}({})", pairs.join(", "))
553 }
554 _ => format!("{func}({})", rendered.join(", ")),
555 }
556 }
557 Ok(Some(Node::Lambda { params, body })) => {
558 let ps: Vec<String> = params.iter().map(|p| p.name.clone()).collect();
559 format!("|{}| {}", ps.join(", "), expr(store, &body, pmap))
560 }
561 Ok(Some(Node::FuncRef(name))) => format!("&{name}"),
562 Ok(Some(Node::CallValue { callee, args })) => {
563 let rendered: Vec<String> =
564 args.iter().map(|a| expr(store, a, pmap)).collect();
565 format!(
566 "{}({})",
567 operand(store, &callee, pmap),
568 rendered.join(", ")
569 )
570 }
571 Ok(Some(Node::Record { type_name, fields })) => {
572 let parts: Vec<String> = fields
573 .iter()
574 .map(|(n, h)| format!("{n}: {}", expr(store, h, pmap)))
575 .collect();
576 format!("{type_name} {{ {} }}", parts.join(", "))
577 }
578 Ok(Some(Node::Field { base, field, .. })) => {
579 format!("{}.{field}", operand(store, &base, pmap))
580 }
581 Ok(Some(Node::Variant {
582 type_name,
583 case,
584 fields,
585 })) => {
586 if fields.is_empty() {
587 format!("{type_name}.{case}")
588 } else {
589 let parts: Vec<String> = fields
590 .iter()
591 .map(|(n, h)| format!("{n}: {}", expr(store, h, pmap)))
592 .collect();
593 format!("{type_name}.{case} {{ {} }}", parts.join(", "))
594 }
595 }
596 Ok(Some(Node::Match {
597 scrutinee, arms, ..
598 })) => {
599 let parts: Vec<String> = arms
600 .iter()
601 .map(|a| {
602 let binds = if a.bindings.is_empty() {
603 String::new()
604 } else {
605 format!("({})", a.bindings.join(", "))
606 };
607 format!(
608 "{}{} -> {}",
609 a.case,
610 binds,
611 expr(store, &a.body, pmap)
612 )
613 })
614 .collect();
615 format!(
616 "match {} {{ {} }}",
617 expr(store, &scrutinee, pmap),
618 parts.join(", ")
619 )
620 }
621 Ok(Some(Node::Function { .. }))
622 | Ok(Some(Node::Module { .. }))
623 | Ok(Some(Node::RecordDef { .. }))
624 | Ok(Some(Node::VariantDef { .. })) => "<nested>".to_string(),
625 Ok(None) => "<missing>".to_string(),
626 Err(_) => "<error>".to_string(),
627 }
628}
629
630fn operand(store: &Store, hash: &NodeHash, pmap: &HashMap<String, Vec<String>>) -> String {
633 let inner = expr(store, hash, pmap);
634 if matches!(
635 store.get(hash),
636 Ok(Some(Node::BinOp { .. }))
637 | Ok(Some(Node::FloatOp { .. }))
638 | Ok(Some(Node::DecimalOp { .. }))
639 | Ok(Some(Node::Not(_)))
640 | Ok(Some(Node::FuncRef(_)))
644 | Ok(Some(Node::Lambda { .. }))
645 | Ok(Some(Node::CallValue { .. }))
646 ) {
647 format!("({inner})")
648 } else {
649 inner
650 }
651}
652
653fn ty(t: &Type) -> String {
654 match t {
655 Type::Number => "Number".into(),
656 Type::Float => "Float".into(),
657 Type::Decimal => "Decimal".into(),
658 Type::String => "String".into(),
659 Type::Bool => "Bool".into(),
660 Type::Bytes => "Bytes".into(),
661 Type::List(e) => format!("List<{}>", ty(e)),
662 Type::Map(k, v) => format!("Map<{}, {}>", ty(k), ty(v)),
663 Type::Option(e) => format!("Option<{}>", ty(e)),
664 Type::Result(o, e) => format!("Result<{}, {}>", ty(o), ty(e)),
665 Type::Named(n) => n.clone(),
666 Type::Cell(e) => format!("Cell<{}>", ty(e)),
667 Type::Fn { params, ret, .. } => {
668 let ps: Vec<String> = params.iter().map(ty).collect();
669 format!("Fn({}) -> {}", ps.join(", "), ty(ret))
670 }
671 Type::Var(v) => v.clone(),
672 Type::Never => "Never".into(),
673 }
674}
675
676fn conf_suffix(c: Confidence) -> &'static str {
678 match c {
679 Confidence::Structural => "",
680 Confidence::External => " @ external",
681 Confidence::Validated => " @ validated",
682 Confidence::Persisted => " @ persisted",
683 }
684}
685
686fn effect(e: Effect) -> &'static str {
687 match e {
688 Effect::Net => "Net",
689 Effect::Disk => "Disk",
690 Effect::Db => "Db",
691 Effect::Mut => "Mut",
692 Effect::Time => "Time",
693 Effect::Rand => "Rand",
694 Effect::Log => "Log",
695 Effect::Live => "Live",
696 Effect::Resp => "Resp",
697 }
698}
699
700#[cfg(test)]
701mod tests {
702 use super::*;
703 use crate::node::{Param, Produces};
704 use std::collections::BTreeSet;
705
706 #[allow(clippy::too_many_arguments)]
709 fn f(
710 s: &Store,
711 name: &str,
712 params: Vec<Param>,
713 prod: Produces,
714 requires: BTreeSet<Effect>,
715 on_failure: Vec<&str>,
716 body: Vec<NodeHash>,
717 result: NodeHash,
718 ) -> NodeHash {
719 s.put(&Node::Function {
720 name: name.into(),
721 type_params: vec![],
722 params,
723 produces: prod,
724 requires,
725 on_failure: on_failure.into_iter().map(String::from).collect(),
726 body,
727 result,
728 })
729 .unwrap()
730 }
731
732 fn p(name: &str, ty: Type, c: Confidence) -> Param {
733 Param {
734 name: name.into(),
735 ty,
736 min_confidence: c,
737 }
738 }
739
740 #[test]
741 fn pure_constant_function() {
742 let s = Store::open_in_memory().unwrap();
743 let lit = s.put(&Node::Lit(42)).unwrap();
744 let answer = f(
745 &s,
746 "answer",
747 vec![],
748 Produces {
749 ty: Type::Number,
750 confidence: Confidence::Structural,
751 },
752 BTreeSet::new(),
753 vec![],
754 vec![],
755 lit,
756 );
757 let expected = "\
758function answer
759 produces Number
760 pure
761do
762 yield 42
763end";
764 assert_eq!(render(&s, &answer).unwrap(), expected);
765 }
766
767 #[test]
768 fn param_with_non_baseline_confidence_renders_at_suffix() {
769 let s = Store::open_in_memory().unwrap();
770 let nref = s.put(&Node::Ref("n".into())).unwrap();
771 let id = f(
772 &s,
773 "id",
774 vec![p("n", Type::Number, Confidence::External)],
775 Produces {
776 ty: Type::Number,
777 confidence: Confidence::Structural,
778 },
779 BTreeSet::new(),
780 vec![],
781 vec![],
782 nref,
783 );
784 let expected = "\
785function id
786 given
787 n: Number @ external
788 produces Number
789 pure
790do
791 yield n
792end";
793 assert_eq!(render(&s, &id).unwrap(), expected);
794 }
795
796 #[test]
797 fn effects_and_failures_render_in_the_header() {
798 let s = Store::open_in_memory().unwrap();
799 let zero = s.put(&Node::Lit(0)).unwrap();
800 let mut req = BTreeSet::new();
801 req.insert(Effect::Db);
802 req.insert(Effect::Net);
803 let risky = f(
804 &s,
805 "risky",
806 vec![],
807 Produces {
808 ty: Type::Number,
809 confidence: Confidence::Structural,
810 },
811 req,
812 vec!["Boom"],
813 vec![],
814 zero,
815 );
816 let expected = "\
818function risky
819 produces Number
820 requires Net, Db
821 on_failure
822 Boom
823do
824 yield 0
825end";
826 assert_eq!(render(&s, &risky).unwrap(), expected);
827 }
828
829 #[test]
830 fn calls_render_with_named_arguments_from_the_module() {
831 let s = Store::open_in_memory().unwrap();
832 let nref = s.put(&Node::Ref("n".into())).unwrap();
833 let id = f(
834 &s,
835 "id",
836 vec![p("n", Type::Number, Confidence::Structural)],
837 Produces {
838 ty: Type::Number,
839 confidence: Confidence::Structural,
840 },
841 BTreeSet::new(),
842 vec![],
843 vec![],
844 nref,
845 );
846 let seven = s.put(&Node::Lit(7)).unwrap();
847 let call = s
848 .put(&Node::Call {
849 func: "id".into(),
850 args: vec![seven],
851 })
852 .unwrap();
853 let step = s
854 .put(&Node::Step {
855 binding: "x".into(),
856 value: call,
857 })
858 .unwrap();
859 let xref = s.put(&Node::Ref("x".into())).unwrap();
860 let use_id = f(
861 &s,
862 "use_id",
863 vec![],
864 Produces {
865 ty: Type::Number,
866 confidence: Confidence::Structural,
867 },
868 BTreeSet::new(),
869 vec![],
870 vec![step],
871 xref,
872 );
873 let m = s
874 .put(&Node::Module {
875 name: "m".into(),
876 types: vec![],
877 functions: vec![id, use_id],
878 })
879 .unwrap();
880 let out = render(&s, &m).unwrap();
881 assert!(out.contains("module m"));
882 assert!(out.contains("step x = id(n: 7)"), "got:\n{out}");
883 assert!(out.trim_end().ends_with("end"));
884 }
885
886 #[test]
887 fn a_hole_renders_as_a_hole_marker() {
888 let s = Store::open_in_memory().unwrap();
889 let hole = s
890 .put(&Node::Hole {
891 expects: "Number".into(),
892 })
893 .unwrap();
894 let g = f(
895 &s,
896 "g",
897 vec![],
898 Produces {
899 ty: Type::Number,
900 confidence: Confidence::Structural,
901 },
902 BTreeSet::new(),
903 vec![],
904 vec![],
905 hole,
906 );
907 assert!(render(&s, &g).unwrap().contains("yield <hole: Number>"));
908 }
909
910 #[test]
911 fn a_record_def_and_field_access_render() {
912 let s = Store::open_in_memory().unwrap();
913 let pd = s
914 .put(&Node::RecordDef {
915 name: "Point".into(),
916 fields: vec![("x".into(), Type::Number)],
917 })
918 .unwrap();
919 let pref = s.put(&Node::Ref("pt".into())).unwrap();
920 let fx = s
921 .put(&Node::Field {
922 base: pref,
923 type_name: "Point".into(),
924 field: "x".into(),
925 })
926 .unwrap();
927 let get = f(
928 &s,
929 "get",
930 vec![p("pt", Type::Named("Point".into()), Confidence::Structural)],
931 Produces {
932 ty: Type::Number,
933 confidence: Confidence::Structural,
934 },
935 BTreeSet::new(),
936 vec![],
937 vec![],
938 fx,
939 );
940 let m = s
941 .put(&Node::Module {
942 name: "m".into(),
943 types: vec![pd],
944 functions: vec![get],
945 })
946 .unwrap();
947 let out = render(&s, &m).unwrap();
948 assert!(out.contains("type Point = record"), "got:\n{out}");
949 assert!(out.contains(" x: Number"));
950 assert!(out.contains("yield pt.x"));
951 }
952
953 #[test]
954 fn a_generic_function_header_renders() {
955 let s = Store::open_in_memory().unwrap();
956 let x = s.put(&Node::Ref("x".into())).unwrap();
957 let id = s
958 .put(&Node::Function {
959 name: "identity".into(),
960 type_params: vec!["T".into()],
961 params: vec![Param {
962 name: "x".into(),
963 ty: Type::Var("T".into()),
964 min_confidence: Confidence::Structural,
965 }],
966 produces: Produces {
967 ty: Type::Var("T".into()),
968 confidence: Confidence::Structural,
969 },
970 requires: BTreeSet::new(),
971 on_failure: vec![],
972 body: vec![],
973 result: x,
974 })
975 .unwrap();
976 let out = render(&s, &id).unwrap();
977 assert!(out.contains("function identity<T>"), "got:\n{out}");
978 assert!(out.contains("x: T"));
979 }
980}
981
982#[cfg(test)]
983mod v04_review {
984 use super::*;
985 use crate::node::{BinOp, Node, Param, Produces};
986 use crate::store::Store;
987 use crate::ty::{Confidence, Type};
988
989 #[test]
995 fn v04_callee_projection_is_unambiguous() {
996 let s = Store::open_in_memory().unwrap();
997 let p = |v: i64| s.put(&Node::Lit(v)).unwrap();
998 let r = |n: &str| s.put(&Node::Ref(n.into())).unwrap();
999 let par = |n: &str, t: Type| Param { name: n.into(), ty: t, min_confidence: Confidence::External };
1000
1001 let dbl = s.put(&Node::Lambda {
1003 params: vec![par("x", Type::Number)],
1004 body: s.put(&Node::BinOp { op: BinOp::Mul, lhs: r("x"), rhs: p(2) }).unwrap(),
1005 }).unwrap();
1006 let g = s.put(&Node::FuncRef("helper".into())).unwrap();
1008 let applied = s.put(&Node::CallValue { callee: g.clone(), args: vec![r("n")] }).unwrap();
1010 let inline = s.put(&Node::CallValue { callee: dbl.clone(), args: vec![r("n")] }).unwrap();
1012 let opt = s.put(&Node::OptionSome(r("n"))).unwrap();
1014 let picked = s.put(&Node::OptionMatch {
1015 opt,
1016 some_bind: "v".into(),
1017 some_body: s.put(&Node::BinOp { op: BinOp::Add, lhs: r("v"), rhs: p(1) }).unwrap(),
1018 none_body: p(0),
1019 }).unwrap();
1020 let money = s.put(&Node::DecimalOp {
1022 op: BinOp::Add,
1023 lhs: s.put(&Node::DecimalLit(199900)).unwrap(),
1024 rhs: s.put(&Node::DecimalLit(100)).unwrap(),
1025 }).unwrap();
1026 let ratio = s.put(&Node::FloatOp {
1028 op: BinOp::Mul,
1029 lhs: s.put(&Node::FloatLit(1.5f64.to_bits())).unwrap(),
1030 rhs: s.put(&Node::IntToFloat(r("n"))).unwrap(),
1031 }).unwrap();
1032 let logic = s.put(&Node::BinOp {
1034 op: BinOp::Or,
1035 lhs: s.put(&Node::BinOp {
1036 op: BinOp::And,
1037 lhs: s.put(&Node::BinOp { op: BinOp::Eq, lhs: r("n"), rhs: p(0) }).unwrap(),
1038 rhs: s.put(&Node::BinOp { op: BinOp::Lt, lhs: r("n"), rhs: p(0) }).unwrap(),
1039 }).unwrap(),
1040 rhs: s.put(&Node::BinOp { op: BinOp::Ge, lhs: r("n"), rhs: p(1) }).unwrap(),
1041 }).unwrap();
1042 let notlogic = s.put(&Node::Not(logic)).unwrap();
1043 let body = vec![
1044 s.put(&Node::Step { binding: "applied".into(), value: applied }).unwrap(),
1045 s.put(&Node::Step { binding: "inline".into(), value: inline }).unwrap(),
1046 s.put(&Node::Step { binding: "picked".into(), value: picked }).unwrap(),
1047 s.put(&Node::Step { binding: "money".into(), value: money }).unwrap(),
1048 s.put(&Node::Step { binding: "ratio".into(), value: ratio }).unwrap(),
1049 ];
1050 let yield_ = s.put(&Node::BinOp {
1051 op: BinOp::Add,
1052 lhs: r("applied"),
1053 rhs: s.put(&Node::BinOp {
1054 op: BinOp::Add,
1055 lhs: r("picked"),
1056 rhs: s.put(&Node::DecimalToInt(r("money"))).unwrap(),
1057 }).unwrap(),
1058 }).unwrap();
1059 let f = s.put(&Node::Function {
1060 name: "demo".into(),
1061 type_params: vec![],
1062 params: vec![par("n", Type::Number)],
1063 produces: Produces { ty: Type::Number, confidence: Confidence::External },
1064 requires: Default::default(),
1065 on_failure: vec![],
1066 body,
1067 result: s.put(&Node::BinOp { op: BinOp::Add, lhs: yield_, rhs: notlogic }).unwrap(),
1068 }).unwrap();
1069 let out = render(&s, &f).unwrap();
1070 assert!(
1073 out.contains("step applied = (&helper)(n)"),
1074 "FuncRef callee must render as `(&helper)(n)`, not `&helper(n)`:\n{out}"
1075 );
1076 assert!(
1077 out.contains("step inline = (|x| x * 2)(n)"),
1078 "Lambda callee must render parenthesized so the body is \
1079 delimited, not `|x| x * 2(n)`:\n{out}"
1080 );
1081 assert!(!out.contains("&helper(n)") || out.contains("(&helper)(n)"));
1083 assert!(!out.contains("step inline = |x|"));
1084 assert!(out.contains("match some(n) { Some(v) -> v + 1, None -> 0 }"));
1086 assert!(out.contains("19.9900d + 0.0100d") && out.contains("1.5f"));
1088 }
1089
1090 #[test]
1094 fn render_addressed_is_byte_identical_and_addressable() {
1095 let s = Store::open_in_memory().unwrap();
1096 let two = s.put(&Node::Lit(2)).unwrap();
1097 let three = s.put(&Node::Lit(3)).unwrap();
1098 let add = s
1099 .put(&Node::BinOp {
1100 op: BinOp::Add,
1101 lhs: two.clone(),
1102 rhs: three.clone(),
1103 })
1104 .unwrap();
1105 let g = s
1106 .put(&Node::Function {
1107 name: "g".into(),
1108 type_params: vec![],
1109 params: vec![],
1110 produces: Produces {
1111 ty: Type::Number,
1112 confidence: Confidence::Structural,
1113 },
1114 requires: std::collections::BTreeSet::new(),
1115 on_failure: vec![],
1116 body: vec![],
1117 result: add.clone(),
1118 })
1119 .unwrap();
1120
1121 let canon = render(&s, &g).unwrap();
1122 let a = render_addressed(&s, &g).unwrap();
1123
1124 assert_eq!(a.text, canon);
1127 assert!(!a.text.contains(['\u{1}', '\u{2}', '\u{3}']));
1128
1129 let root = a.spans.iter().find(|x| x.hash == g).unwrap();
1131 assert_eq!((root.start, root.end), (0, a.text.len()));
1132
1133 for sp in &a.spans {
1135 assert!(sp.start <= sp.end && sp.end <= a.text.len());
1136 assert!(root.start <= sp.start && sp.end <= root.end);
1137 }
1138
1139 let i2 = a.text.find("2 + 3").unwrap();
1142 let ip = a.text.find('+').unwrap();
1143 let i3 = a.text.find("3\n").unwrap(); assert_eq!(node_at(&a, i2), Some(two.clone()));
1145 assert_eq!(node_at(&a, i3), Some(three.clone()));
1146 assert_eq!(node_at(&a, ip), Some(add.clone()));
1147
1148 assert_eq!(node_at(&a, 0), Some(g.clone()));
1151 assert_eq!(node_at(&a, a.text.len()), None);
1152
1153 let sp = |h: &NodeHash| a.spans.iter().find(|x| &x.hash == h).unwrap();
1155 let (s2, sa) = (sp(&two), sp(&add));
1156 assert!(sa.start <= s2.start && s2.end <= sa.end);
1157 assert!(sa.end - sa.start > s2.end - s2.start);
1158 assert!(root.start <= sa.start && sa.end <= root.end);
1159 }
1160}