1use crate::ast::{
14 AssignmentStatement, BinOpKind, BlockStatement, CallExpression, ComponentExpression,
15 ConstructorCall, ElseBranch, Expression, Ident, IfStatement, ImportItem, ReturnStatement,
16 Statement, TableFieldValue, UnaryOpKind,
17};
18use crate::token::Unit;
19
20const INDENT_STEP: usize = 4;
21
22pub fn unparse_program(stmts: &[Statement]) -> String {
23 let mut out = String::new();
24 let mut p = Printer {
25 out: &mut out,
26 indent: 0,
27 };
28 for (i, s) in stmts.iter().enumerate() {
29 if i > 0 {
30 p.out.push('\n');
31 }
32 p.write_indent();
33 p.statement(s);
34 p.out.push('\n');
35 }
36 out
37}
38
39pub fn unparse_expression(e: &Expression) -> String {
40 let mut out = String::new();
41 let mut p = Printer {
42 out: &mut out,
43 indent: 0,
44 };
45 p.expression(e);
46 out
47}
48
49pub fn unparse_component(ce: &ComponentExpression) -> String {
50 let mut out = String::new();
51 let mut p = Printer {
52 out: &mut out,
53 indent: 0,
54 };
55 p.component(ce);
56 out
57}
58
59struct Printer<'a> {
60 out: &'a mut String,
61 indent: usize,
62}
63
64impl<'a> Printer<'a> {
65 fn write_indent(&mut self) {
66 for _ in 0..self.indent {
67 self.out.push(' ');
68 }
69 }
70
71 fn statement(&mut self, s: &Statement) {
74 match s {
75 Statement::Assignment(a) => self.assignment(a),
76 Statement::Reassign { target, value } => {
77 self.expression(target);
78 self.out.push_str(" = ");
79 self.expression(value);
80 }
81 Statement::Return(ReturnStatement { value }) => {
82 self.out.push_str("return");
83 if let Some(v) = value {
84 self.out.push(' ');
85 self.expression(v);
86 }
87 }
88 Statement::If(i) => self.if_stmt(i),
89 Statement::Block(b) => self.block(b),
90 Statement::Expression(e) => self.expression(e),
91 Statement::ForIn {
92 binding,
93 iterable,
94 body,
95 } => {
96 self.out.push_str("for ");
97 self.out.push_str(&binding.0);
98 self.out.push_str(" in ");
99 self.expression(iterable);
100 self.out.push(' ');
101 self.block(body);
102 }
103 Statement::While { condition, body } => {
104 self.out.push_str("while ");
105 self.expression(condition);
106 self.out.push(' ');
107 self.block(body);
108 }
109 Statement::Break => self.out.push_str("break"),
110 Statement::Continue => self.out.push_str("continue"),
111 Statement::Import { items, path } => {
112 self.out.push_str("import { ");
113 for (i, item) in items.iter().enumerate() {
114 if i > 0 {
115 self.out.push_str(", ");
116 }
117 self.import_item(item);
118 }
119 self.out.push_str(" } from ");
120 self.string_literal(path);
121 }
122 }
123 }
124
125 fn assignment(&mut self, a: &AssignmentStatement) {
126 if a.exported {
127 self.out.push_str("export ");
128 }
129 if let Expression::Function { params, body } = &a.value {
131 self.out.push_str("fn ");
132 self.out.push_str(&a.name.0);
133 self.params_and_body(params, body);
134 return;
135 }
136 self.out.push_str("let ");
137 self.out.push_str(&a.name.0);
138 self.out.push_str(" = ");
139 self.expression(&a.value);
140 }
141
142 fn if_stmt(&mut self, i: &IfStatement) {
143 self.out.push_str("if ");
144 self.expression(&i.condition);
145 self.out.push(' ');
146 self.block(&i.then_branch);
147 if let Some(e) = &i.else_branch {
148 self.out.push_str(" else ");
149 self.else_branch(e);
150 }
151 }
152
153 fn else_branch(&mut self, e: &ElseBranch) {
154 match e {
155 ElseBranch::Block(block) => self.block(block),
156 ElseBranch::If(next_if) => self.if_stmt(next_if),
157 }
158 }
159
160 fn import_item(&mut self, item: &ImportItem) {
161 match item {
162 ImportItem::Named(n) => self.out.push_str(&n.0),
163 ImportItem::NamedAlias { name, alias } => {
164 self.out.push_str(&name.0);
165 self.out.push_str(" as ");
166 self.out.push_str(&alias.0);
167 }
168 ImportItem::PositionalAlias { index, alias } => {
169 self.out.push_str(&index.to_string());
170 self.out.push_str(" as ");
171 self.out.push_str(&alias.0);
172 }
173 }
174 }
175
176 fn block(&mut self, b: &BlockStatement) {
177 if b.statements.is_empty() {
178 self.out.push_str("{}");
179 return;
180 }
181 self.out.push('{');
182 self.out.push('\n');
183 self.indent += INDENT_STEP;
184 for s in &b.statements {
185 self.write_indent();
186 self.statement(s);
187 self.out.push('\n');
188 }
189 self.indent -= INDENT_STEP;
190 self.write_indent();
191 self.out.push('}');
192 }
193
194 fn params_and_body(&mut self, params: &[Ident], body: &BlockStatement) {
195 self.out.push('(');
196 for (i, p) in params.iter().enumerate() {
197 if i > 0 {
198 self.out.push_str(", ");
199 }
200 self.out.push_str(&p.0);
201 }
202 self.out.push_str(") ");
203 self.block(body);
204 }
205
206 fn expression(&mut self, e: &Expression) {
209 match e {
210 Expression::String(s) => self.string_literal(s),
211 Expression::Number(n) => self.number(*n),
212 Expression::Dimension(n, unit) => {
213 self.number(*n);
214 self.out.push_str(unit_suffix(*unit));
215 }
216 Expression::Bool(b) => self.out.push_str(if *b { "true" } else { "false" }),
217 Expression::Null => self.out.push_str("null"),
218 Expression::Identifier(Ident(name)) => self.out.push_str(name),
219 Expression::Array(items) => {
220 self.out.push('[');
221 for (i, item) in items.iter().enumerate() {
222 if i > 0 {
223 self.out.push_str(", ");
224 }
225 self.expression(item);
226 }
227 self.out.push(']');
228 }
229 Expression::Table(fields) => self.table(fields),
230 Expression::Index { base, index } => {
231 self.atom(base);
232 self.out.push('[');
233 self.expression(index);
234 self.out.push(']');
235 }
236 Expression::Call(c) => self.call(c),
237 Expression::Component(c) => self.component(c),
238 Expression::BinaryOp { op, lhs, rhs } => self.binop(op, lhs, rhs),
239 Expression::UnaryOp { op, operand } => {
240 let sym = match op {
241 UnaryOpKind::Neg => "-",
242 UnaryOpKind::Not => "!",
243 };
244 self.out.push_str(sym);
245 self.atom(operand);
246 }
247 Expression::Function { params, body } => {
248 self.out.push_str("fn");
249 self.params_and_body(params, body);
250 }
251 }
252 }
253
254 fn atom(&mut self, e: &Expression) {
257 match e {
258 Expression::BinaryOp { .. }
259 | Expression::UnaryOp { .. }
260 | Expression::Index { .. }
261 | Expression::Function { .. } => {
262 self.out.push('(');
263 self.expression(e);
264 self.out.push(')');
265 }
266 _ => self.expression(e),
267 }
268 }
269
270 fn table(&mut self, fields: &[TableFieldValue]) {
271 if fields.is_empty() {
272 self.out.push_str("{}");
273 return;
274 }
275 self.out.push('{');
276 self.out.push('\n');
277 self.indent += INDENT_STEP;
278 for field in fields {
279 self.write_indent();
280 self.out.push_str(&field.name.0);
281 self.out.push_str(" = ");
282 self.expression(&field.value);
283 self.out.push('\n');
284 }
285 self.indent -= INDENT_STEP;
286 self.write_indent();
287 self.out.push('}');
288 }
289
290 fn call(&mut self, c: &CallExpression) {
291 if let Expression::BinaryOp {
293 op: BinOpKind::Dot,
294 lhs,
295 rhs,
296 } = &*c.callee
297 {
298 if let Expression::Identifier(Ident(method)) = &**rhs {
299 self.atom(lhs);
300 self.out.push('.');
301 self.out.push_str(method);
302 self.args(&c.args);
303 return;
304 }
305 }
306 self.atom(&c.callee);
307 self.args(&c.args);
308 }
309
310 fn args(&mut self, args: &[Expression]) {
311 self.out.push('(');
312 for (i, a) in args.iter().enumerate() {
313 if i > 0 {
314 self.out.push_str(", ");
315 }
316 self.expression(a);
317 }
318 self.out.push(')');
319 }
320
321 fn binop(&mut self, op: &BinOpKind, lhs: &Expression, rhs: &Expression) {
322 if matches!(op, BinOpKind::Dot) {
323 self.atom(lhs);
324 self.out.push('.');
325 self.atom(rhs);
326 return;
327 }
328 let sym = binop_sym(op);
329 self.atom(lhs);
330 self.out.push(' ');
331 self.out.push_str(sym);
332 self.out.push(' ');
333 self.atom(rhs);
334 }
335
336 fn component(&mut self, ce: &ComponentExpression) {
337 self.out.push_str(&ce.component_type.0);
338 for ctor in &ce.constructors {
339 self.constructor(ctor);
340 }
341 if ce.body.statements.is_empty() {
346 if ce.constructors.is_empty() {
347 self.out.push_str(" {}");
348 }
349 return;
350 }
351 self.out.push(' ');
352 self.block(&ce.body);
353 }
354
355 fn constructor(&mut self, c: &ConstructorCall) {
356 self.out.push('.');
357 self.out.push_str(&c.method.0);
358 self.args(&c.args);
359 }
360
361 fn number(&mut self, n: f64) {
364 if !n.is_finite() {
365 self.out.push_str(&format!("{n}"));
368 return;
369 }
370 let s = format!("{n}");
371 if !s.contains('.') && !s.contains('e') && !s.contains('E') {
374 self.out.push_str(&s);
375 self.out.push_str(".0");
376 } else {
377 self.out.push_str(&s);
378 }
379 }
380
381 fn string_literal(&mut self, s: &str) {
382 self.out.push('"');
383 for ch in s.chars() {
384 match ch {
385 '\\' => self.out.push_str("\\\\"),
386 '"' => self.out.push_str("\\\""),
387 '\n' => self.out.push_str("\\n"),
388 '\r' => self.out.push_str("\\r"),
389 '\t' => self.out.push_str("\\t"),
390 c => self.out.push(c),
391 }
392 }
393 self.out.push('"');
394 }
395}
396
397fn binop_sym(op: &BinOpKind) -> &'static str {
398 match op {
399 BinOpKind::Add => "+",
400 BinOpKind::Sub => "-",
401 BinOpKind::Mul => "*",
402 BinOpKind::Div => "/",
403 BinOpKind::Rem => "%",
404 BinOpKind::Eq => "==",
405 BinOpKind::NotEq => "!=",
406 BinOpKind::Lt => "<",
407 BinOpKind::Gt => ">",
408 BinOpKind::LtEq => "<=",
409 BinOpKind::GtEq => ">=",
410 BinOpKind::And => "&&",
411 BinOpKind::Or => "||",
412 BinOpKind::Pipe => "|>",
413 BinOpKind::Query => "->",
414 BinOpKind::Dot => ".",
415 }
416}
417
418fn unit_suffix(u: Unit) -> &'static str {
419 match u {
420 Unit::Percent => "%",
421 Unit::GlyphUnits => "gu",
422 Unit::WorldUnits => "wu",
423 Unit::Degrees => "deg",
424 Unit::Radians => "rad",
425 }
426}
427
428#[cfg(test)]
433mod tests {
434 use super::*;
435 use crate::parser::MeowMeowParser;
436 use crate::tokenizer::MeowMeowTokenizer;
437
438 fn parse(src: &str) -> Vec<Statement> {
439 let tokens = MeowMeowTokenizer::new(src)
440 .tokenize()
441 .expect("tokenize failed");
442 MeowMeowParser::new(tokens)
443 .parse_program()
444 .expect("parse failed")
445 }
446
447 fn round_trip(src: &str) {
448 let ast1 = parse(src);
449 let text = unparse_program(&ast1);
450 let ast2 = match std::panic::catch_unwind(|| parse(&text)) {
451 Ok(a) => a,
452 Err(_) => panic!("re-parse panicked. Unparsed text was:\n{text}"),
453 };
454 assert_eq!(
455 ast1, ast2,
456 "AST changed after round trip.\n--- original ---\n{src}\n--- unparsed ---\n{text}\n"
457 );
458 }
459
460 #[test]
461 fn round_trip_cat_mms() {
462 let src = include_str!("../tests/fixtures/cat.mms");
463 round_trip(src);
464 }
465
466 #[test]
467 fn round_trip_minimal_component() {
468 round_trip("T {}");
469 }
470
471 #[test]
472 fn round_trip_builder_chain() {
473 round_trip("T.position(0.0, 1.0, -2.5).scale(0.5, 0.5, 0.5) { R.cube() }");
474 }
475
476 #[test]
477 fn round_trip_let_and_arith() {
478 round_trip("let x = 1.0 + 2.0 * 3.0\nlet y = -x");
479 }
480
481 #[test]
482 fn round_trip_function() {
483 round_trip("fn double(n) { return n * 2.0 }\nlet four = double(2.0)");
484 }
485
486 #[test]
487 fn round_trip_for_loop() {
488 round_trip("let sum = 0.0\nfor i in [1.0, 2.0, 3.0] { sum = sum + i }");
489 }
490
491 #[test]
492 fn round_trip_if_else() {
493 round_trip("if true { let a = 1.0 } else { let b = 2.0 }");
494 }
495
496 #[test]
497 fn round_trip_import() {
498 round_trip("import { foo, bar as baz, 0 as cat } from \"cat.mms\"");
499 }
500
501 #[test]
502 fn round_trip_dimensions() {
503 round_trip("let a = 50%\nlet b = 20gu\nlet c = 30deg\nlet d = 0.5rad");
504 }
505
506 #[test]
507 fn round_trip_strings_with_escapes() {
508 round_trip("let s = \"hello \\\"world\\\"\\nnext line\"");
509 }
510
511 #[test]
512 fn negative_number_idempotent() {
513 round_trip("T.position(-0.22, 1.38, 0.52) {}");
515 }
516}