sim_codec_bridge/
frame_render.rs1use sim_kernel::{Error, Expr, Result, Symbol};
2
3use crate::{
4 BridgeFramePayload, FrameHoleKind, FrameHoleSpec, FrameSpec, frame_book::validate_frame_payload,
5};
6
7pub fn render_frame(spec: &FrameSpec, payload: &Expr) -> Result<String> {
11 render_frame_with_prose(spec, payload, |name, _| {
12 Err(Error::Eval(format!(
13 "BRIDGE prose hole {} requires fenced rendering",
14 name.as_qualified_str()
15 )))
16 })
17}
18
19pub fn render_frame_with_prose<F>(
21 spec: &FrameSpec,
22 payload: &Expr,
23 mut render_prose: F,
24) -> Result<String>
25where
26 F: FnMut(&Symbol, &Expr) -> Result<String>,
27{
28 let payload = BridgeFramePayload::from_expr(payload)?;
29 if payload.frame != spec.id {
30 return Err(Error::Eval(format!(
31 "BRIDGE frame payload {} does not match spec {}",
32 payload.frame, spec.id
33 )));
34 }
35 validate_frame_payload(spec, &payload)?;
36 let mut body = spec.template.to_owned();
37 for hole in &spec.holes {
38 let value = payload.slots.get(&hole.name).ok_or_else(|| {
39 Error::Eval(format!(
40 "BRIDGE frame {} missing hole {}",
41 spec.id, hole.name
42 ))
43 })?;
44 let rendered = render_hole(hole, value, &mut render_prose)?;
45 let marker = format!("{{{}}}", hole.name.name);
46 if !body.contains(&marker) {
47 return Err(Error::Eval(format!(
48 "BRIDGE frame {} template is missing marker {marker}",
49 spec.id
50 )));
51 }
52 body = body.replace(&marker, &rendered);
53 }
54 Ok(format!("{}{}", spec.prefix, body))
55}
56
57fn render_hole<F>(hole: &FrameHoleSpec, value: &Expr, render_prose: &mut F) -> Result<String>
58where
59 F: FnMut(&Symbol, &Expr) -> Result<String>,
60{
61 match hole.kind {
62 FrameHoleKind::Ref | FrameHoleKind::Term | FrameHoleKind::Choice => token_value(value),
63 FrameHoleKind::Path => path_value(value),
64 FrameHoleKind::Number => match value {
65 Expr::Number(number) => Ok(number.canonical.clone()),
66 _ => unreachable!("validated number hole"),
67 },
68 FrameHoleKind::Prose => render_prose(&hole.name, value),
69 }
70}
71
72fn token_value(value: &Expr) -> Result<String> {
73 match value {
74 Expr::Symbol(symbol) => Ok(symbol.as_qualified_str().to_owned()),
75 Expr::String(text) => Ok(text.clone()),
76 _ => unreachable!("validated token hole"),
77 }
78}
79
80fn path_value(value: &Expr) -> Result<String> {
81 let items = match value {
82 Expr::Vector(items) | Expr::List(items) => items,
83 _ => unreachable!("validated path hole"),
84 };
85 items
86 .iter()
87 .map(token_value)
88 .collect::<Result<Vec<_>>>()
89 .map(|parts| parts.join("."))
90}