1use sim_kernel::{Expr, Symbol};
9use sim_lib_scene::{node, sym};
10use sim_lib_view::{Mode, render_value, universal_scene};
11
12use crate::doc::{block_kind, blocks, field, title};
13
14pub const ARTICLE_FORMATTED_LENS: &str = "view:doc-article";
16
17pub const ARTICLE_SOURCE_LENS: &str = "view:doc-source";
19
20pub fn with_cache(block: Expr, scene: Expr) -> Expr {
22 let Expr::Map(mut entries) = block else {
23 return block;
24 };
25 entries.push((Expr::Symbol(Symbol::new("cache")), scene));
26 Expr::Map(entries)
27}
28
29pub fn article_formatted(doc: &Expr) -> Expr {
31 let canvas = blocks(doc).iter().map(block_formatted).collect();
32 node(
33 "stack",
34 vec![
35 ("role", sym("article")),
36 ("dir", sym("row")),
37 (
38 "children",
39 Expr::List(vec![
40 article_outline(doc),
41 node(
42 "stack",
43 vec![
44 ("role", sym("canvas")),
45 ("dir", sym("column")),
46 ("children", Expr::List(canvas)),
47 ],
48 ),
49 ]),
50 ),
51 ],
52 )
53}
54
55pub fn article_source(doc: &Expr) -> Expr {
57 let mut children = vec![text(format!(
58 "# {}",
59 title(doc).unwrap_or_else(|| "untitled".to_owned())
60 ))];
61 for block in blocks(doc) {
62 children.push(text(render_value(&block)));
63 }
64 node(
65 "stack",
66 vec![
67 ("role", sym("source")),
68 ("dir", sym("column")),
69 ("children", Expr::List(children)),
70 ],
71 )
72}
73
74pub fn article_outline(doc: &Expr) -> Expr {
76 let sections = blocks(doc)
77 .iter()
78 .filter(|block| block_kind(block).as_ref().map(|k| k.name.as_ref()) == Some("section"))
79 .map(|block| {
80 let heading = match field(block, "title") {
81 Some(Expr::String(text)) => text.clone(),
82 _ => "section".to_owned(),
83 };
84 text(heading)
85 })
86 .collect();
87 node(
88 "tree",
89 vec![
90 ("label", Expr::String("outline".to_owned())),
91 ("nodes", Expr::List(sections)),
92 ],
93 )
94}
95
96fn block_formatted(block: &Expr) -> Expr {
97 match block_kind(block)
98 .as_ref()
99 .map(|k| k.name.to_string())
100 .as_deref()
101 {
102 Some("section") => node(
103 "text",
104 vec![
105 ("role", sym("heading")),
106 ("text", string_field(block, "title")),
107 ],
108 ),
109 Some("prose") => node("text", vec![("text", string_field(block, "text"))]),
110 Some("equation") => node(
111 "box",
112 vec![
113 ("role", sym("equation")),
114 (
115 "children",
116 Expr::List(vec![node(
117 "text",
118 vec![("text", string_field(block, "tex"))],
119 )]),
120 ),
121 ],
122 ),
123 Some("figure") => node(
124 "box",
125 vec![
126 ("role", sym("figure")),
127 (
128 "children",
129 Expr::List(vec![node(
130 "text",
131 vec![("text", string_field(block, "caption"))],
132 )]),
133 ),
134 ],
135 ),
136 Some("table") => table_scene(block),
137 Some("citation") => node(
138 "text",
139 vec![
140 ("role", sym("citation")),
141 ("text", string_field(block, "text")),
142 ],
143 ),
144 Some("embed") => embed_scene(block),
145 _ => node(
146 "text",
147 vec![("text", Expr::String("[unknown block]".to_owned()))],
148 ),
149 }
150}
151
152fn embed_scene(block: &Expr) -> Expr {
153 let lens = match field(block, "lens") {
154 Some(Expr::Symbol(symbol)) => symbol.clone(),
155 _ => Symbol::new("view:default"),
156 };
157 let inner = match field(block, "cache") {
159 Some(cached) => cached.clone(),
160 None => universal_scene(field(block, "value").unwrap_or(&Expr::Nil), Mode::Builder),
161 };
162 node(
163 "embed",
164 vec![("lens", Expr::Symbol(lens)), ("scene", inner)],
165 )
166}
167
168fn table_scene(block: &Expr) -> Expr {
169 let rows = match field(block, "rows") {
170 Some(Expr::List(rows)) => rows
171 .iter()
172 .map(|row| match row {
173 Expr::List(cells) => Expr::List(
174 cells
175 .iter()
176 .map(|cell| Expr::String(render_value(cell)))
177 .collect(),
178 ),
179 other => Expr::List(vec![Expr::String(render_value(other))]),
180 })
181 .collect(),
182 _ => Vec::new(),
183 };
184 node("table", vec![("rows", Expr::List(rows))])
185}
186
187pub fn export_intermediate(doc: &Expr) -> Expr {
190 crate::doc::article(
191 &title(doc).unwrap_or_else(|| "untitled".to_owned()),
192 blocks(doc),
193 )
194}
195
196pub fn export_markdown(doc: &Expr) -> String {
198 let mut out = format!(
199 "# {}\n\n",
200 title(doc).unwrap_or_else(|| "untitled".to_owned())
201 );
202 for block in blocks(doc) {
203 let line = match block_kind(&block)
204 .as_ref()
205 .map(|k| k.name.to_string())
206 .as_deref()
207 {
208 Some("section") => format!("## {}\n\n", string_text(&block, "title")),
209 Some("prose") => format!("{}\n\n", string_text(&block, "text")),
210 Some("equation") => format!("$$\n{}\n$$\n\n", string_text(&block, "tex")),
211 Some("citation") => format!("> {}\n\n", string_text(&block, "text")),
212 Some("embed") => "[embedded runtime value]\n\n".to_owned(),
213 _ => String::new(),
214 };
215 out.push_str(&line);
216 }
217 out
218}
219
220fn string_field(block: &Expr, name: &str) -> Expr {
221 Expr::String(string_text(block, name))
222}
223
224fn string_text(block: &Expr, name: &str) -> String {
225 match field(block, name) {
226 Some(Expr::String(text)) => text.clone(),
227 Some(other) => render_value(other),
228 None => String::new(),
229 }
230}
231
232fn text(content: String) -> Expr {
233 node("text", vec![("text", Expr::String(content))])
234}