1use sim_codec::encode_string_literal;
4use sim_kernel::{Error, Expr, Result, Symbol};
5use sim_shape::{
6 GrammarDialect, GrammarGraph, GrammarPosition, GrammarRenderer, Production, TerminalAtom,
7};
8
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
11pub struct LispGrammarRenderer {
12 dialect: GrammarDialect,
13}
14
15impl LispGrammarRenderer {
16 pub fn new(dialect: GrammarDialect) -> Self {
18 Self { dialect }
19 }
20
21 pub fn sexpr() -> Self {
23 Self::new(GrammarDialect::SExpr)
24 }
25
26 pub fn gbnf() -> Self {
28 Self::new(GrammarDialect::Gbnf)
29 }
30}
31
32impl GrammarRenderer for LispGrammarRenderer {
33 fn codec_symbol(&self) -> Symbol {
34 Symbol::qualified("codec", "lisp")
35 }
36
37 fn dialect(&self) -> GrammarDialect {
38 self.dialect
39 }
40
41 fn render(&self, graph: &GrammarGraph, position: GrammarPosition) -> Result<String> {
42 match self.dialect {
43 GrammarDialect::SExpr => render_lisp_sexpr_graph(graph, position),
44 GrammarDialect::Gbnf => render_lisp_gbnf_graph(graph, position),
45 unsupported => Err(grammar_error(format!(
46 "codec/lisp does not support {unsupported:?} grammar dialect"
47 ))),
48 }
49 }
50}
51
52fn render_lisp_sexpr_graph(graph: &GrammarGraph, position: GrammarPosition) -> Result<String> {
53 let mut forms = vec![
54 format!("(codec {})", Symbol::qualified("codec", "lisp")),
55 format!("(position {})", position_name(position)),
56 format!("(decode-target {})", lisp_decode_target(position)),
57 format!("(root {})", render_lisp_sexpr(&graph.root)?),
58 ];
59 for (name, production) in &graph.defs {
60 forms.push(format!("(def {} {})", name, render_lisp_sexpr(production)?));
61 }
62 Ok(format!("(grammar {})", forms.join(" ")))
63}
64
65fn render_lisp_sexpr(production: &Production) -> Result<String> {
66 match production {
67 Production::Terminal(atom) => render_lisp_terminal(atom),
68 Production::Seq(items) => render_wrapped("seq", items.iter().map(render_lisp_sexpr)),
69 Production::Alt(choices) => render_wrapped("alt", choices.iter().map(render_lisp_sexpr)),
70 Production::Repeat { inner, at_least } => Ok(format!(
71 "(repeat {} {})",
72 at_least,
73 render_lisp_sexpr(inner)?
74 )),
75 Production::Call { head, args } => {
76 let mut rendered = Vec::with_capacity(args.len() + 1);
77 rendered.push(render_lisp_sexpr(head)?);
78 for arg in args {
79 rendered.push(render_lisp_sexpr(arg)?);
80 }
81 Ok(format!("({})", rendered.join(" ")))
82 }
83 Production::Ref(name) => Ok(format!("(ref {})", name)),
84 }
85}
86
87fn render_lisp_terminal(atom: &TerminalAtom) -> Result<String> {
88 Ok(match atom {
89 TerminalAtom::Any => "_".to_owned(),
90 TerminalAtom::Nil => "nil".to_owned(),
91 TerminalAtom::Bool => "Bool".to_owned(),
92 TerminalAtom::Number => "Number".to_owned(),
93 TerminalAtom::String => "String".to_owned(),
94 TerminalAtom::List => "List".to_owned(),
95 TerminalAtom::Map => "Map".to_owned(),
96 TerminalAtom::Symbol => "Symbol".to_owned(),
97 TerminalAtom::Exact(expr) => render_exact_lisp(expr)?,
98 })
99}
100
101fn render_lisp_gbnf_graph(graph: &GrammarGraph, position: GrammarPosition) -> Result<String> {
102 let mut lines = vec![
103 format!(
104 "# codec/lisp position={} target={}",
105 position_name(position),
106 lisp_decode_target(position)
107 ),
108 format!("root ::= {}", render_lisp_gbnf(&graph.root)?),
109 ];
110 for (name, production) in &graph.defs {
111 lines.push(format!(
112 "{} ::= {}",
113 rule_name(name),
114 render_lisp_gbnf(production)?
115 ));
116 }
117 Ok(lines.join("\n"))
118}
119
120fn render_lisp_gbnf(production: &Production) -> Result<String> {
121 match production {
122 Production::Terminal(atom) => render_lisp_gbnf_terminal(atom),
123 Production::Seq(items) => {
124 let rendered = items
125 .iter()
126 .map(render_lisp_gbnf)
127 .collect::<Result<Vec<_>>>()?;
128 Ok(format!("({})", rendered.join(" ")))
129 }
130 Production::Alt(choices) => {
131 let rendered = choices
132 .iter()
133 .map(render_lisp_gbnf)
134 .collect::<Result<Vec<_>>>()?;
135 Ok(format!("({})", rendered.join(" | ")))
136 }
137 Production::Repeat { inner, .. } => Ok(format!("({})*", render_lisp_gbnf(inner)?)),
138 Production::Call { head, args } => {
139 let mut rendered = Vec::with_capacity(args.len() + 1);
140 rendered.push(render_lisp_gbnf(head)?);
141 for arg in args {
142 rendered.push(render_lisp_gbnf(arg)?);
143 }
144 Ok(format!("\"(\" {} \")\"", rendered.join(" ")))
145 }
146 Production::Ref(name) => Ok(rule_name(name)),
147 }
148}
149
150fn render_lisp_gbnf_terminal(atom: &TerminalAtom) -> Result<String> {
151 Ok(match atom {
152 TerminalAtom::Any => "sexpr".to_owned(),
153 TerminalAtom::Nil => "\"nil\"".to_owned(),
154 TerminalAtom::Bool => "(\"true\" | \"false\")".to_owned(),
155 TerminalAtom::Number => "number".to_owned(),
156 TerminalAtom::String => "string".to_owned(),
157 TerminalAtom::List => "list".to_owned(),
158 TerminalAtom::Map => "map".to_owned(),
159 TerminalAtom::Symbol => "symbol".to_owned(),
160 TerminalAtom::Exact(expr) => gbnf_literal(&render_exact_lisp(expr)?),
161 })
162}
163
164fn render_wrapped(head: &str, values: impl Iterator<Item = Result<String>>) -> Result<String> {
165 let rendered = values.collect::<Result<Vec<_>>>()?;
166 Ok(format!("({} {})", head, rendered.join(" ")))
167}
168
169fn render_exact_lisp(expr: &Expr) -> Result<String> {
170 Ok(match expr {
171 Expr::Nil => "nil".to_owned(),
172 Expr::Bool(true) => "true".to_owned(),
173 Expr::Bool(false) => "false".to_owned(),
174 Expr::Number(number) => number.canonical.clone(),
175 Expr::String(text) => encode_string_literal(text),
176 Expr::Symbol(symbol) => symbol.to_string(),
177 Expr::List(items) | Expr::Vector(items) => {
178 let items = items
179 .iter()
180 .map(render_exact_lisp)
181 .collect::<Result<Vec<_>>>()?;
182 format!("({})", items.join(" "))
183 }
184 Expr::Map(entries) => {
185 let entries = entries
186 .iter()
187 .map(|(key, value)| {
188 Ok(format!(
189 "({} {})",
190 render_exact_lisp(key)?,
191 render_exact_lisp(value)?
192 ))
193 })
194 .collect::<Result<Vec<_>>>()?;
195 format!("(map {})", entries.join(" "))
196 }
197 _ => {
198 return Err(grammar_error(
199 "exact Lisp grammar terminals support data-like expressions",
200 ));
201 }
202 })
203}
204
205fn gbnf_literal(text: &str) -> String {
206 format!("\"{}\"", text.replace('\\', "\\\\").replace('"', "\\\""))
207}
208
209fn rule_name(symbol: &Symbol) -> String {
210 let mut out = String::new();
211 for ch in symbol.to_string().chars() {
212 if ch.is_ascii_alphanumeric() || ch == '-' {
213 out.push(ch);
214 } else {
215 out.push('-');
216 }
217 }
218 if out
219 .chars()
220 .next()
221 .is_none_or(|ch| !ch.is_ascii_alphabetic())
222 {
223 out.insert_str(0, "r-");
224 }
225 out
226}
227
228fn position_name(position: GrammarPosition) -> &'static str {
229 match position {
230 GrammarPosition::Eval => "eval",
231 GrammarPosition::Quote => "quote",
232 GrammarPosition::Data => "data",
233 GrammarPosition::Pattern => "pattern",
234 GrammarPosition::Surface => "surface",
235 }
236}
237
238fn lisp_decode_target(position: GrammarPosition) -> &'static str {
239 match position {
240 GrammarPosition::Eval => "term",
241 GrammarPosition::Quote
242 | GrammarPosition::Data
243 | GrammarPosition::Pattern
244 | GrammarPosition::Surface => "datum",
245 }
246}
247
248fn grammar_error(message: impl Into<String>) -> Error {
249 Error::Eval(format!("codec/lisp grammar renderer: {}", message.into()))
250}
251
252#[cfg(test)]
253mod tests {
254 use std::sync::Arc;
255
256 use sim_kernel::Symbol;
257 use sim_shape::{
258 ExprKind, ExprKindShape, FieldShape, FieldSpec, GrammarDialect, GrammarPosition,
259 GrammarTarget, OneOfShape, Shape, ShapeDefRef, ShapeDefs, shape_grammar,
260 };
261
262 use super::LispGrammarRenderer;
263
264 #[test]
265 fn lisp_sexpr_renders_calls_and_refs() {
266 let grammar = shape_grammar(
267 recursive_node_shape().as_ref(),
268 GrammarTarget {
269 codec: Symbol::qualified("codec", "lisp"),
270 dialect: GrammarDialect::SExpr,
271 position: GrammarPosition::Eval,
272 },
273 &LispGrammarRenderer::sexpr(),
274 )
275 .unwrap();
276
277 assert!(grammar.text.contains("(decode-target term)"));
278 assert!(grammar.text.contains("(shape/fields"));
279 assert!(grammar.text.contains("(ref Node)"));
280 assert!(grammar.text.contains("name"));
281 assert!(grammar.text.contains("next"));
282 }
283
284 #[test]
285 fn lisp_gbnf_uses_named_rules_for_refs() {
286 let grammar = shape_grammar(
287 recursive_node_shape().as_ref(),
288 GrammarTarget {
289 codec: Symbol::qualified("codec", "lisp"),
290 dialect: GrammarDialect::Gbnf,
291 position: GrammarPosition::Quote,
292 },
293 &LispGrammarRenderer::gbnf(),
294 )
295 .unwrap();
296
297 assert!(grammar.text.contains("target=datum"));
298 assert!(grammar.text.contains("Node ::="));
299 assert!(grammar.text.contains("Node"));
300 }
301
302 #[test]
303 fn lisp_renderer_rejects_unsupported_dialect() {
304 let err = shape_grammar(
305 recursive_node_shape().as_ref(),
306 GrammarTarget {
307 codec: Symbol::qualified("codec", "lisp"),
308 dialect: GrammarDialect::JsonSchema,
309 position: GrammarPosition::Data,
310 },
311 &LispGrammarRenderer::new(GrammarDialect::JsonSchema),
312 )
313 .unwrap_err();
314
315 assert!(err.to_string().contains("does not support JsonSchema"));
316 }
317
318 fn recursive_node_shape() -> Arc<dyn Shape> {
319 let node = Symbol::new("Node");
320 Arc::new(ShapeDefs::new(
321 Arc::new(ShapeDefRef::new(node.clone())),
322 vec![(
323 node.clone(),
324 Arc::new(FieldShape::anonymous(vec![
325 FieldSpec::required(
326 Symbol::new("name"),
327 Arc::new(ExprKindShape::new(ExprKind::String)),
328 ),
329 FieldSpec::required(
330 Symbol::new("next"),
331 Arc::new(OneOfShape::new(vec![
332 Arc::new(ExprKindShape::new(ExprKind::Nil)),
333 Arc::new(ShapeDefRef::new(node)),
334 ])),
335 ),
336 ])),
337 )],
338 ))
339 }
340}