Skip to main content

tla_syntax/
print.rs

1//! Render an expression back to TLA+ source.
2//!
3//! Written so the parser can read the result: parentheses are placed by
4//! precedence rather than preserved from the input, and where TLA+ offers
5//! several spellings of an operator the ASCII one is used. A round-trip
6//! through `parse_expression` gives back the same tree.
7//!
8//! Parenthesisation is conservative. A prefix operator is bracketed wherever
9//! an infix operator of the same precedence could otherwise steal its operand,
10//! which puts a few brackets in that a person would not — `-a * b` genuinely
11//! needs them, and `UNCHANGED v` is bracketed by the same rule. Being correct
12//! matters more here than being pretty: this output is what a counterexample
13//! quotes.
14
15use std::fmt;
16
17use crate::ast::{Bound, Decl, Def, ExceptPath, Expr, LetInstance, Module, Param, QuantKind, Unit};
18use crate::token::Op;
19
20/// The binding power of the constructs that run to the end of the expression.
21/// They are parenthesized inside any operator, which is what makes
22/// `(\A x \in S : P) /\ Q` come back as itself.
23const TRAILING: u8 = 0;
24const ATOM: u8 = u8::MAX;
25
26impl fmt::Display for Expr {
27    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
28        write(self, TRAILING, f)
29    }
30}
31
32impl fmt::Display for Bound {
33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34        if self.destructure {
35            write!(f, "<<{}>>", self.names.join(", "))?;
36        } else {
37            f.write_str(&self.names.join(", "))?;
38        }
39        match &self.domain {
40            Some(domain) => write!(f, " \\in {domain}"),
41            None => Ok(()),
42        }
43    }
44}
45
46fn precedence(e: &Expr) -> u8 {
47    match e {
48        Expr::Binary(op, ..) => op.infix_prec().unwrap_or(TRAILING),
49        Expr::Unary(op, _) if op.is_postfix() => ATOM,
50        Expr::Unary(op, _) => op.prefix_prec().saturating_sub(1),
51        Expr::Quant { .. }
52        | Expr::Choose { .. }
53        | Expr::Let { .. }
54        | Expr::If { .. }
55        | Expr::Case { .. }
56        | Expr::Lambda { .. } => TRAILING,
57        _ => ATOM,
58    }
59}
60
61fn write(e: &Expr, min: u8, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62    if precedence(e) < min {
63        write!(f, "(")?;
64        write(e, TRAILING, f)?;
65        return write!(f, ")");
66    }
67    bare(e, f)
68}
69
70#[expect(
71    clippy::too_many_lines,
72    reason = "one arm per syntactic form; splitting it would only scatter the grammar"
73)]
74fn bare(e: &Expr, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75    match e {
76        Expr::Num(n) => write!(f, "{n}"),
77        Expr::Decimal(text) => f.write_str(text),
78        Expr::Str(s) => write!(f, "\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\"")),
79        Expr::Bool(b) => f.write_str(if *b { "TRUE" } else { "FALSE" }),
80        Expr::Ident(name) => f.write_str(name),
81        Expr::At => f.write_str("@"),
82        Expr::Prime(inner) => {
83            write(inner, ATOM, f)?;
84            f.write_str("'")
85        }
86        Expr::Apply(head, args) => {
87            write(head, ATOM, f)?;
88            write!(f, "({})", list(args))
89        }
90        Expr::FnApply(head, args) => {
91            write(head, ATOM, f)?;
92            write!(f, "[{}]", list(args))
93        }
94        Expr::Field(inner, name) => {
95            write(inner, ATOM, f)?;
96            write!(f, ".{name}")
97        }
98        Expr::Qualified {
99            instance,
100            name,
101            args,
102        } => {
103            write!(f, "{instance}!{name}")?;
104            if args.is_empty() {
105                Ok(())
106            } else {
107                write!(f, "({})", list(args))
108            }
109        }
110        Expr::Unary(op, operand) if op.is_postfix() => {
111            write(operand, ATOM, f)?;
112            f.write_str(op.symbol())
113        }
114        Expr::Unary(op, operand) => {
115            f.write_str(op.symbol())?;
116            if op.is_word() {
117                f.write_str(" ")?;
118            }
119            write(operand, op.prefix_prec(), f)
120        }
121        Expr::Binary(op, lhs, rhs) => {
122            let prec = op.infix_prec().unwrap_or(TRAILING);
123            let (left, right) = if op.is_right_assoc() {
124                (prec + 1, prec)
125            } else {
126                (prec, prec + 1)
127            };
128            write(lhs, left, f)?;
129            // `1..n` is how a range is written; everything else reads better
130            // with room around it.
131            if *op == Op::DotDot {
132                f.write_str(op.symbol())?;
133            } else {
134                write!(f, " {} ", op.symbol())?;
135            }
136            write(rhs, right, f)
137        }
138        Expr::Tuple(items) => write!(f, "<<{}>>", list(items)),
139        Expr::SetEnum(items) => write!(f, "{{{}}}", list(items)),
140        Expr::SetFilter { bound, pred } => write!(f, "{{{bound} : {pred}}}"),
141        Expr::SetMap { expr, bounds } => write!(f, "{{{expr} : {}}}", bounds_list(bounds)),
142        Expr::Record(fields) => write!(f, "[{}]", fields_list(fields, "|->")),
143        Expr::RecordSet(fields) => write!(f, "[{}]", fields_list(fields, ":")),
144        Expr::FnDef { bounds, body } => {
145            write!(f, "[{} |-> {body}]", bounds_list(bounds))
146        }
147        Expr::FnSet { domain, range } => write!(f, "[{domain} -> {range}]"),
148        Expr::Except { base, updates } => {
149            write!(f, "[{base} EXCEPT ")?;
150            for (i, (path, value)) in updates.iter().enumerate() {
151                if i > 0 {
152                    f.write_str(", ")?;
153                }
154                f.write_str("!")?;
155                for step in path {
156                    match step {
157                        ExceptPath::Index(index) => write!(f, "[{index}]")?,
158                        ExceptPath::Field(name) => write!(f, ".{name}")?,
159                    }
160                }
161                write!(f, " = {value}")?;
162            }
163            f.write_str("]")
164        }
165        Expr::Quant { kind, bounds, body } => {
166            let symbol = match kind {
167                QuantKind::Forall => Op::Forall,
168                QuantKind::Exists => Op::Exists,
169                QuantKind::TemporalForall => Op::TemporalForall,
170                QuantKind::TemporalExists => Op::TemporalExists,
171            };
172            write!(f, "{} {} : {body}", symbol.symbol(), bounds_list(bounds))
173        }
174        Expr::Choose { bound, body } => write!(f, "CHOOSE {bound} : {body}"),
175        Expr::Let {
176            defs,
177            instances,
178            body,
179        } => {
180            f.write_str("LET ")?;
181            for (i, def) in defs.iter().enumerate() {
182                if i > 0 {
183                    f.write_str(" ")?;
184                }
185                write!(f, "{def}")?;
186            }
187            for instance in instances {
188                if !defs.is_empty() {
189                    f.write_str(" ")?;
190                }
191                write!(f, "{instance}")?;
192            }
193            write!(f, " IN {body}")
194        }
195        Expr::If {
196            cond,
197            then,
198            otherwise,
199        } => write!(f, "IF {cond} THEN {then} ELSE {otherwise}"),
200        Expr::Case { arms, other } => {
201            f.write_str("CASE ")?;
202            for (i, (guard, result)) in arms.iter().enumerate() {
203                if i > 0 {
204                    f.write_str(" [] ")?;
205                }
206                write!(f, "{guard} -> {result}")?;
207            }
208            match other {
209                Some(value) => write!(f, " [] OTHER -> {value}"),
210                None => Ok(()),
211            }
212        }
213        Expr::Lambda { params, body } => write!(f, "LAMBDA {} : {body}", params_list(params)),
214        Expr::ActionBox { action, subscript } => write!(f, "[{action}]_{subscript}"),
215        Expr::ActionAngle { action, subscript } => write!(f, "<<{action}>>_{subscript}"),
216        Expr::Fairness {
217            strong,
218            subscript,
219            action,
220        } => {
221            let kind = if *strong { "SF" } else { "WF" };
222            write!(f, "{kind}_{subscript}({action})")
223        }
224    }
225}
226
227impl fmt::Display for Def {
228    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
229        f.write_str(&self.name)?;
230        if !self.params.is_empty() {
231            write!(f, "({})", params_list(&self.params))?;
232        }
233        write!(f, " == {}", self.body)
234    }
235}
236
237impl fmt::Display for Param {
238    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239        f.write_str(&self.name)?;
240        if self.arity > 0 {
241            let holes = vec!["_"; self.arity].join(", ");
242            write!(f, "({holes})")?;
243        }
244        Ok(())
245    }
246}
247
248fn params_list(params: &[Param]) -> String {
249    params
250        .iter()
251        .map(ToString::to_string)
252        .collect::<Vec<_>>()
253        .join(", ")
254}
255
256fn list(items: &[Expr]) -> String {
257    items
258        .iter()
259        .map(ToString::to_string)
260        .collect::<Vec<_>>()
261        .join(", ")
262}
263
264fn bounds_list(bounds: &[Bound]) -> String {
265    bounds
266        .iter()
267        .map(ToString::to_string)
268        .collect::<Vec<_>>()
269        .join(", ")
270}
271
272fn fields_list(fields: &[(String, Expr)], separator: &str) -> String {
273    fields
274        .iter()
275        .map(|(name, value)| format!("{name} {separator} {value}"))
276        .collect::<Vec<_>>()
277        .join(", ")
278}
279
280/// A module, written back out in a single canonical form.
281///
282/// Layout is not preserved — bulleted lists become infix operators and
283/// alignment is lost — so this is a normal form rather than a formatter that
284/// respects the author's hand. Two files that mean the same thing print the
285/// same way, which is what makes it useful for comparing them.
286impl fmt::Display for Module {
287    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
288        writeln!(f, "---- MODULE {} ----", self.name)?;
289        if !self.extends.is_empty() {
290            writeln!(f, "EXTENDS {}", self.extends.join(", "))?;
291        }
292        for unit in &self.units {
293            write!(f, "{unit}")?;
294        }
295        writeln!(f, "====")
296    }
297}
298
299impl fmt::Display for Unit {
300    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301        match self {
302            Unit::Constants(decls) => writeln!(f, "CONSTANTS {}", decls_list(decls)),
303            Unit::Variables(names) => writeln!(f, "VARIABLES {}", names.join(", ")),
304            Unit::Recursive(decls) => writeln!(f, "RECURSIVE {}", decls_list(decls)),
305            Unit::Def(def) => {
306                if def.local {
307                    write!(f, "LOCAL ")?;
308                }
309                writeln!(f, "{def}")
310            }
311            Unit::Instance { name, module, subs } => {
312                match name {
313                    Some(name) => write!(f, "{name} == INSTANCE {module}")?,
314                    None => write!(f, "INSTANCE {module}")?,
315                }
316                if !subs.is_empty() {
317                    let with: Vec<String> = subs
318                        .iter()
319                        .map(|(name, value)| format!("{name} <- {value}"))
320                        .collect();
321                    write!(f, " WITH {}", with.join(", "))?;
322                }
323                writeln!(f)
324            }
325            Unit::Assume(e) => writeln!(f, "ASSUME {e}"),
326            Unit::Theorem(e) => writeln!(f, "THEOREM {e}"),
327            Unit::Inner(module) => write!(f, "{module}"),
328            Unit::Opaque => Ok(()),
329        }
330    }
331}
332
333impl fmt::Display for LetInstance {
334    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
335        if let Some(name) = &self.name {
336            write!(f, "{name} == ")?;
337        }
338        write!(f, "INSTANCE {}", self.module)?;
339        if !self.subs.is_empty() {
340            let with: Vec<String> = self
341                .subs
342                .iter()
343                .map(|(name, value)| format!("{name} <- {value}"))
344                .collect();
345            write!(f, " WITH {}", with.join(", "))?;
346        }
347        Ok(())
348    }
349}
350
351impl fmt::Display for Decl {
352    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
353        f.write_str(&self.name)?;
354        if self.arity > 0 {
355            write!(f, "({})", vec!["_"; self.arity].join(", "))?;
356        }
357        Ok(())
358    }
359}
360
361fn decls_list(decls: &[Decl]) -> String {
362    decls
363        .iter()
364        .map(ToString::to_string)
365        .collect::<Vec<_>>()
366        .join(", ")
367}