1use std::collections::BTreeMap;
4
5use sim_kernel::{Error, Expr, Result};
6use sim_value::build::{entry, list, map, sym, text, uint};
7
8mod decode;
9mod expr;
10mod model;
11
12pub use decode::decode_markup_doc;
13use expr::*;
14pub use model::{
15 BackendId, Inline, MarkupBlock, MarkupDoc, MathSource, SourceDoc, Span, SpanState,
16};
17
18impl MarkupDoc {
19 pub fn as_expr(&self) -> Expr {
21 let mut entries = vec![
22 entry("kind", sym("markup-doc")),
23 entry(
24 "blocks",
25 list(self.blocks.iter().map(MarkupBlock::as_expr).collect()),
26 ),
27 entry("attrs", attrs_expr(&self.attrs)),
28 ];
29 if let Some(title) = &self.title {
30 entries.push(entry("title", text(title)));
31 }
32 if let Some(source) = &self.source {
33 entries.push(entry("source", source.as_expr()));
34 }
35 Expr::Map(entries)
36 }
37
38 pub fn from_expr(expr: &Expr) -> Result<Self> {
40 let entries = map_entries(expr, "markup document")?;
41 require_kind(entries, "markup-doc", "markup document")?;
42 let title = optional_string(entries, "title")?.map(str::to_owned);
43 let blocks = required_list(entries, "blocks", "markup document")?
44 .iter()
45 .map(MarkupBlock::from_expr)
46 .collect::<Result<Vec<_>>>()?;
47 let attrs = match field(entries, "attrs") {
48 Some(Expr::Map(attrs)) => attrs_from_entries(attrs.as_slice())?,
49 Some(_) => return Err(Error::Eval("markup attrs must be a map".to_owned())),
50 None => BTreeMap::new(),
51 };
52 let source = match field(entries, "source") {
53 Some(expr) => Some(SourceDoc::from_expr(expr)?),
54 None => None,
55 };
56 Ok(Self {
57 title,
58 blocks,
59 attrs,
60 source,
61 })
62 }
63
64 pub fn to_source_text(&self) -> String {
69 if let Some(source) = &self.source {
70 return source.text.clone();
71 }
72 let mut out = String::new();
73 for (index, block) in self.blocks.iter().enumerate() {
74 if index > 0 {
75 out.push_str("\n\n");
76 }
77 block.write_source(&mut out);
78 }
79 out
80 }
81}
82
83impl SourceDoc {
84 fn as_expr(&self) -> Expr {
85 map(vec![
86 ("backend", text(&self.backend.0)),
87 ("text", text(&self.text)),
88 ])
89 }
90
91 fn from_expr(expr: &Expr) -> Result<Self> {
92 let entries = map_entries(expr, "markup source")?;
93 Ok(Self {
94 backend: BackendId(required_string(entries, "backend", "markup source")?.to_owned()),
95 text: required_string(entries, "text", "markup source")?.to_owned(),
96 })
97 }
98}
99
100impl Span {
101 fn as_expr(&self) -> Expr {
102 map(vec![
103 ("start", uint(self.start as u64)),
104 ("end", uint(self.end as u64)),
105 ("state", sym(self.state.as_str())),
106 ])
107 }
108
109 fn from_expr(expr: &Expr) -> Result<Self> {
110 let entries = map_entries(expr, "span")?;
111 Ok(Self {
112 start: required_usize(entries, "start", "span")?,
113 end: required_usize(entries, "end", "span")?,
114 state: match field(entries, "state") {
115 Some(value) => SpanState::from_expr(value)?,
116 None => SpanState::Preserved,
117 },
118 })
119 }
120}
121
122impl SpanState {
123 fn as_str(&self) -> &'static str {
124 match self {
125 Self::Preserved => "preserved",
126 Self::Dirty => "dirty",
127 }
128 }
129
130 fn from_expr(expr: &Expr) -> Result<Self> {
131 match expr {
132 Expr::Symbol(symbol) if symbol.namespace.is_none() => match symbol.name.as_ref() {
133 "preserved" => Ok(Self::Preserved),
134 "dirty" => Ok(Self::Dirty),
135 other => Err(Error::Eval(format!("unknown span state {other}"))),
136 },
137 Expr::String(value) => match value.as_str() {
138 "preserved" => Ok(Self::Preserved),
139 "dirty" => Ok(Self::Dirty),
140 other => Err(Error::Eval(format!("unknown span state {other}"))),
141 },
142 _ => Err(Error::Eval("span state must be a symbol".to_owned())),
143 }
144 }
145}
146
147impl MathSource {
148 fn as_expr(&self) -> Expr {
149 map(vec![
150 ("notation", text(&self.notation)),
151 ("text", text(&self.text)),
152 ])
153 }
154
155 fn from_expr(expr: &Expr) -> Result<Self> {
156 let entries = map_entries(expr, "math source")?;
157 Ok(Self {
158 notation: required_string(entries, "notation", "math source")?.to_owned(),
159 text: required_string(entries, "text", "math source")?.to_owned(),
160 })
161 }
162}
163
164impl MarkupBlock {
165 pub fn as_expr(&self) -> Expr {
167 match self {
168 Self::Heading {
169 level,
170 text: heading,
171 id,
172 span,
173 } => {
174 let mut entries = vec![
175 entry("kind", sym("heading")),
176 entry("level", uint(u64::from(*level))),
177 entry("text", inline_list(heading)),
178 ];
179 push_optional_string(&mut entries, "id", id);
180 push_optional_span(&mut entries, span);
181 Expr::Map(entries)
182 }
183 Self::Paragraph { content, span } => {
184 let mut entries = vec![
185 entry("kind", sym("paragraph")),
186 entry("content", inline_list(content)),
187 ];
188 push_optional_span(&mut entries, span);
189 Expr::Map(entries)
190 }
191 Self::CodeBlock { lang, code, span } => {
192 let mut entries = vec![entry("kind", sym("code-block")), entry("code", text(code))];
193 push_optional_string(&mut entries, "lang", lang);
194 push_optional_span(&mut entries, span);
195 Expr::Map(entries)
196 }
197 Self::MathBlock { source, span } => {
198 let mut entries = vec![
199 entry("kind", sym("math-block")),
200 entry("source", source.as_expr()),
201 ];
202 push_optional_span(&mut entries, span);
203 Expr::Map(entries)
204 }
205 Self::Quote { blocks, span } => {
206 let mut entries = vec![
207 entry("kind", sym("quote")),
208 entry("blocks", block_list(blocks)),
209 ];
210 push_optional_span(&mut entries, span);
211 Expr::Map(entries)
212 }
213 Self::List {
214 ordered,
215 items,
216 span,
217 } => {
218 let mut entries = vec![
219 entry("kind", sym("list")),
220 entry("ordered", Expr::Bool(*ordered)),
221 entry(
222 "items",
223 list(items.iter().map(|item| block_list(item)).collect()),
224 ),
225 ];
226 push_optional_span(&mut entries, span);
227 Expr::Map(entries)
228 }
229 Self::Table { header, rows, span } => {
230 let mut entries = vec![
231 entry("kind", sym("table")),
232 entry(
233 "header",
234 list(header.iter().map(|cell| inline_list(cell)).collect()),
235 ),
236 entry(
237 "rows",
238 list(
239 rows.iter()
240 .map(|row| list(row.iter().map(|cell| inline_list(cell)).collect()))
241 .collect(),
242 ),
243 ),
244 ];
245 push_optional_span(&mut entries, span);
246 Expr::Map(entries)
247 }
248 Self::Figure { src, caption, span } => {
249 let mut entries = vec![
250 entry("kind", sym("figure")),
251 entry("src", text(src)),
252 entry("caption", inline_list(caption)),
253 ];
254 push_optional_span(&mut entries, span);
255 Expr::Map(entries)
256 }
257 Self::Raw {
258 backend,
259 text: raw,
260 span,
261 } => {
262 let mut entries = vec![
263 entry("kind", sym("raw")),
264 entry("backend", text(&backend.0)),
265 entry("text", text(raw)),
266 ];
267 push_optional_span(&mut entries, span);
268 Expr::Map(entries)
269 }
270 }
271 }
272
273 pub fn from_expr(expr: &Expr) -> Result<Self> {
275 let entries = map_entries(expr, "markup block")?;
276 match required_kind(entries, "markup block")?.as_str() {
277 "heading" => Ok(Self::Heading {
278 level: required_u8(entries, "level", "heading")?,
279 text: inline_vec(required_list(entries, "text", "heading")?)?,
280 id: optional_string(entries, "id")?.map(str::to_owned),
281 span: optional_span(entries)?,
282 }),
283 "paragraph" => Ok(Self::Paragraph {
284 content: inline_vec(required_list(entries, "content", "paragraph")?)?,
285 span: optional_span(entries)?,
286 }),
287 "code-block" => Ok(Self::CodeBlock {
288 lang: optional_string(entries, "lang")?.map(str::to_owned),
289 code: required_string(entries, "code", "code block")?.to_owned(),
290 span: optional_span(entries)?,
291 }),
292 "math-block" => Ok(Self::MathBlock {
293 source: MathSource::from_expr(required_field(entries, "source", "math block")?)?,
294 span: optional_span(entries)?,
295 }),
296 "quote" => Ok(Self::Quote {
297 blocks: block_vec(required_list(entries, "blocks", "quote")?)?,
298 span: optional_span(entries)?,
299 }),
300 "list" => {
301 let items = required_list(entries, "items", "list")?
302 .iter()
303 .map(|item| block_vec(as_list(item, "list item")?))
304 .collect::<Result<Vec<_>>>()?;
305 Ok(Self::List {
306 ordered: required_bool(entries, "ordered", "list")?,
307 items,
308 span: optional_span(entries)?,
309 })
310 }
311 "table" => {
312 let header = required_list(entries, "header", "table")?
313 .iter()
314 .map(|cell| inline_vec(as_list(cell, "table header cell")?))
315 .collect::<Result<Vec<_>>>()?;
316 let rows = required_list(entries, "rows", "table")?
317 .iter()
318 .map(|row| {
319 as_list(row, "table row")?
320 .iter()
321 .map(|cell| inline_vec(as_list(cell, "table cell")?))
322 .collect::<Result<Vec<_>>>()
323 })
324 .collect::<Result<Vec<_>>>()?;
325 Ok(Self::Table {
326 header,
327 rows,
328 span: optional_span(entries)?,
329 })
330 }
331 "figure" => Ok(Self::Figure {
332 src: required_string(entries, "src", "figure")?.to_owned(),
333 caption: inline_vec(required_list(entries, "caption", "figure")?)?,
334 span: optional_span(entries)?,
335 }),
336 "raw" => Ok(Self::Raw {
337 backend: BackendId(required_string(entries, "backend", "raw block")?.to_owned()),
338 text: required_string(entries, "text", "raw block")?.to_owned(),
339 span: optional_span(entries)?,
340 }),
341 other => Err(Error::Eval(format!("unknown markup block kind {other}"))),
342 }
343 }
344
345 fn write_source(&self, out: &mut String) {
346 match self {
347 Self::Heading { level, text, .. } => {
348 out.push_str(&"#".repeat(usize::from(*level).max(1)));
349 out.push(' ');
350 write_inlines(out, text);
351 }
352 Self::Paragraph { content, .. } => write_inlines(out, content),
353 Self::CodeBlock { lang, code, .. } => {
354 out.push_str("```");
355 if let Some(lang) = lang {
356 out.push_str(lang);
357 }
358 out.push('\n');
359 out.push_str(code);
360 if !code.ends_with('\n') {
361 out.push('\n');
362 }
363 out.push_str("```");
364 }
365 Self::MathBlock { source, .. } => {
366 out.push_str("$$\n");
367 out.push_str(&source.text);
368 out.push_str("\n$$");
369 }
370 Self::Quote { blocks, .. } => {
371 let text = blocks_to_source(blocks);
372 for (index, line) in text.lines().enumerate() {
373 if index > 0 {
374 out.push('\n');
375 }
376 out.push_str("> ");
377 out.push_str(line);
378 }
379 }
380 Self::List { ordered, items, .. } => {
381 for (index, item) in items.iter().enumerate() {
382 if index > 0 {
383 out.push('\n');
384 }
385 if *ordered {
386 out.push_str(&format!("{}. ", index + 1));
387 } else {
388 out.push_str("- ");
389 }
390 out.push_str(&blocks_to_source(item).replace('\n', "\n "));
391 }
392 }
393 Self::Table { header, rows, .. } => {
394 write_table_row(out, header);
395 out.push('\n');
396 write_table_separator(out, header.len());
397 for row in rows {
398 out.push('\n');
399 write_table_row(out, row);
400 }
401 }
402 Self::Figure { src, caption, .. } => {
403 out.push_str(";
406 out.push_str(src);
407 out.push(')');
408 }
409 Self::Raw { text, .. } => out.push_str(text),
410 }
411 }
412}
413
414impl Inline {
415 fn as_expr(&self) -> Expr {
416 match self {
417 Self::Text(value) => map(vec![("kind", sym("text")), ("text", text(value))]),
418 Self::Emph(items) => map(vec![("kind", sym("emph")), ("content", inline_list(items))]),
419 Self::Strong(items) => map(vec![
420 ("kind", sym("strong")),
421 ("content", inline_list(items)),
422 ]),
423 Self::Code(value) => map(vec![("kind", sym("code")), ("text", text(value))]),
424 Self::Link { label, target } => map(vec![
425 ("kind", sym("link")),
426 ("label", inline_list(label)),
427 ("target", text(target)),
428 ]),
429 Self::Math(source) => map(vec![("kind", sym("math")), ("source", source.as_expr())]),
430 Self::Raw { backend, text: raw } => map(vec![
431 ("kind", sym("raw")),
432 ("backend", text(&backend.0)),
433 ("text", text(raw)),
434 ]),
435 }
436 }
437
438 fn from_expr(expr: &Expr) -> Result<Self> {
439 if let Expr::String(value) = expr {
440 return Ok(Self::Text(value.clone()));
441 }
442 let entries = map_entries(expr, "inline")?;
443 match required_kind(entries, "inline")?.as_str() {
444 "text" => Ok(Self::Text(
445 required_string(entries, "text", "inline")?.to_owned(),
446 )),
447 "emph" => Ok(Self::Emph(inline_vec(required_list(
448 entries, "content", "inline",
449 )?)?)),
450 "strong" => Ok(Self::Strong(inline_vec(required_list(
451 entries, "content", "inline",
452 )?)?)),
453 "code" => Ok(Self::Code(
454 required_string(entries, "text", "inline")?.to_owned(),
455 )),
456 "link" => Ok(Self::Link {
457 label: inline_vec(required_list(entries, "label", "inline")?)?,
458 target: required_string(entries, "target", "inline")?.to_owned(),
459 }),
460 "math" => Ok(Self::Math(MathSource::from_expr(required_field(
461 entries, "source", "inline",
462 )?)?)),
463 "raw" => Ok(Self::Raw {
464 backend: BackendId(required_string(entries, "backend", "inline")?.to_owned()),
465 text: required_string(entries, "text", "inline")?.to_owned(),
466 }),
467 other => Err(Error::Eval(format!("unknown inline kind {other}"))),
468 }
469 }
470}