1use sim_kernel::{Expr, Symbol};
10
11pub const ARTICLE_CLASS: &str = "doc/Article";
13
14pub const BLOCK_KEY: &str = "block";
16
17fn key(name: &str) -> Expr {
18 Expr::Symbol(Symbol::new(name))
19}
20
21fn map(entries: Vec<(&str, Expr)>) -> Expr {
22 Expr::Map(entries.into_iter().map(|(k, v)| (key(k), v)).collect())
23}
24
25pub fn field<'a>(value: &'a Expr, name: &str) -> Option<&'a Expr> {
27 let Expr::Map(entries) = value else {
28 return None;
29 };
30 entries.iter().find_map(|(entry_key, entry_value)| {
31 matches!(entry_key, Expr::Symbol(symbol) if &*symbol.name == name && symbol.namespace.is_none())
32 .then_some(entry_value)
33 })
34}
35
36pub fn article(title: &str, blocks: Vec<Expr>) -> Expr {
38 map(vec![
39 ("class", Expr::Symbol(Symbol::qualified("doc", "Article"))),
40 ("title", Expr::String(title.to_owned())),
41 ("blocks", Expr::List(blocks)),
42 ])
43}
44
45pub fn title(doc: &Expr) -> Option<String> {
47 match field(doc, "title") {
48 Some(Expr::String(text)) => Some(text.clone()),
49 _ => None,
50 }
51}
52
53pub fn blocks(doc: &Expr) -> Vec<Expr> {
55 match field(doc, "blocks") {
56 Some(Expr::List(items)) => items.clone(),
57 _ => Vec::new(),
58 }
59}
60
61pub fn block_kind(block: &Expr) -> Option<Symbol> {
63 match field(block, BLOCK_KEY) {
64 Some(Expr::Symbol(symbol)) => Some(symbol.clone()),
65 _ => None,
66 }
67}
68
69fn block(kind: &str, mut entries: Vec<(&str, Expr)>) -> Expr {
70 let mut pairs = vec![(BLOCK_KEY, Expr::Symbol(Symbol::new(kind)))];
71 pairs.append(&mut entries);
72 map(pairs)
73}
74
75pub fn section(heading: &str) -> Expr {
77 block("section", vec![("title", Expr::String(heading.to_owned()))])
78}
79
80pub fn prose(text: &str) -> Expr {
82 block("prose", vec![("text", Expr::String(text.to_owned()))])
83}
84
85pub fn equation(source: &str) -> Expr {
87 block("equation", vec![("tex", Expr::String(source.to_owned()))])
88}
89
90pub fn figure(caption: &str, src: &str) -> Expr {
92 block(
93 "figure",
94 vec![
95 ("caption", Expr::String(caption.to_owned())),
96 ("src", Expr::String(src.to_owned())),
97 ],
98 )
99}
100
101pub fn table(rows: Vec<Vec<Expr>>) -> Expr {
103 let rows = rows.into_iter().map(Expr::List).collect();
104 block("table", vec![("rows", Expr::List(rows))])
105}
106
107pub fn citation(cite_key: &str, text: &str) -> Expr {
109 block(
110 "citation",
111 vec![
112 ("key", Expr::Symbol(Symbol::new(cite_key))),
113 ("text", Expr::String(text.to_owned())),
114 ],
115 )
116}
117
118pub fn embed_block(value: Expr, lens: &str) -> Expr {
121 block(
122 "embed",
123 vec![("value", value), ("lens", Expr::Symbol(Symbol::new(lens)))],
124 )
125}