1use intent_parser::ast;
6
7use crate::format_type;
8
9pub fn format(file: &ast::File) -> String {
11 let mut out = String::new();
12 out.push_str(&format!("module {}\n", file.module.name));
13
14 if let Some(doc) = &file.doc {
15 out.push('\n');
16 for line in &doc.lines {
17 out.push_str(&format!("--- {}\n", line.trim()));
18 }
19 }
20
21 for item in &file.items {
22 out.push('\n');
23 match item {
24 ast::TopLevelItem::Entity(e) => fmt_entity(&mut out, e),
25 ast::TopLevelItem::Action(a) => fmt_action(&mut out, a),
26 ast::TopLevelItem::Invariant(i) => fmt_invariant(&mut out, i),
27 ast::TopLevelItem::EdgeCases(ec) => fmt_edge_cases(&mut out, ec),
28 }
29 }
30
31 out
32}
33
34fn fmt_doc(out: &mut String, doc: &Option<ast::DocBlock>) {
35 if let Some(doc) = doc {
36 for line in &doc.lines {
37 out.push_str(&format!(" --- {}\n", line.trim()));
38 }
39 out.push('\n');
40 }
41}
42
43fn fmt_entity(out: &mut String, entity: &ast::EntityDecl) {
44 out.push_str(&format!("entity {} {{\n", entity.name));
45 fmt_doc(out, &entity.doc);
46 for field in &entity.fields {
47 out.push_str(&format!(" {}: {}\n", field.name, format_type(&field.ty)));
48 }
49 out.push_str("}\n");
50}
51
52fn fmt_action(out: &mut String, action: &ast::ActionDecl) {
53 out.push_str(&format!("action {} {{\n", action.name));
54 fmt_doc(out, &action.doc);
55
56 for param in &action.params {
57 out.push_str(&format!(" {}: {}\n", param.name, format_type(¶m.ty)));
58 }
59
60 if let Some(req) = &action.requires {
61 out.push_str("\n requires {\n");
62 for cond in &req.conditions {
63 out.push_str(&format!(" {}\n", fmt_expr(cond)));
64 }
65 out.push_str(" }\n");
66 }
67
68 if let Some(ens) = &action.ensures {
69 out.push_str("\n ensures {\n");
70 for item in &ens.items {
71 match item {
72 ast::EnsuresItem::Expr(expr) => {
73 out.push_str(&format!(" {}\n", fmt_expr(expr)));
74 }
75 ast::EnsuresItem::When(w) => {
76 out.push_str(&format!(
77 " when {} => {}\n",
78 fmt_expr(&w.condition),
79 fmt_expr(&w.consequence)
80 ));
81 }
82 }
83 }
84 out.push_str(" }\n");
85 }
86
87 if let Some(props) = &action.properties {
88 out.push_str("\n properties {\n");
89 for entry in &props.entries {
90 out.push_str(&format!(
91 " {}: {}\n",
92 entry.key,
93 fmt_prop_value(&entry.value)
94 ));
95 }
96 out.push_str(" }\n");
97 }
98
99 out.push_str("}\n");
100}
101
102fn fmt_invariant(out: &mut String, inv: &ast::InvariantDecl) {
103 out.push_str(&format!("invariant {} {{\n", inv.name));
104 fmt_doc(out, &inv.doc);
105 out.push_str(&format!(" {}\n", fmt_expr(&inv.body)));
106 out.push_str("}\n");
107}
108
109fn fmt_edge_cases(out: &mut String, ec: &ast::EdgeCasesDecl) {
110 out.push_str("edge_cases {\n");
111 for rule in &ec.rules {
112 out.push_str(&format!(
113 " when {} => {}({})\n",
114 fmt_expr(&rule.condition),
115 rule.action.name,
116 fmt_call_args(&rule.action.args),
117 ));
118 }
119 out.push_str("}\n");
120}
121
122fn fmt_expr(expr: &ast::Expr) -> String {
123 match &expr.kind {
124 ast::ExprKind::Implies(l, r) => format!("{} => {}", fmt_expr(l), fmt_expr(r)),
125 ast::ExprKind::Or(l, r) => format!("{} || {}", fmt_expr(l), fmt_expr(r)),
126 ast::ExprKind::And(l, r) => format!("{} && {}", fmt_expr(l), fmt_expr(r)),
127 ast::ExprKind::Not(e) => format!("!{}", fmt_expr(e)),
128 ast::ExprKind::Compare { left, op, right } => {
129 let op_str = match op {
130 ast::CmpOp::Eq => "==",
131 ast::CmpOp::Ne => "!=",
132 ast::CmpOp::Lt => "<",
133 ast::CmpOp::Gt => ">",
134 ast::CmpOp::Le => "<=",
135 ast::CmpOp::Ge => ">=",
136 };
137 format!("{} {} {}", fmt_expr(left), op_str, fmt_expr(right))
138 }
139 ast::ExprKind::Arithmetic { left, op, right } => {
140 let op_str = match op {
141 ast::ArithOp::Add => "+",
142 ast::ArithOp::Sub => "-",
143 };
144 format!("{} {} {}", fmt_expr(left), op_str, fmt_expr(right))
145 }
146 ast::ExprKind::Old(e) => format!("old({})", fmt_expr(e)),
147 ast::ExprKind::Quantifier {
148 kind,
149 binding,
150 ty,
151 body,
152 } => {
153 let kw = match kind {
154 ast::QuantifierKind::Forall => "forall",
155 ast::QuantifierKind::Exists => "exists",
156 };
157 format!("{} {}: {} => {}", kw, binding, ty, fmt_expr(body))
158 }
159 ast::ExprKind::Call { name, args } => {
160 format!("{}({})", name, fmt_call_args(args))
161 }
162 ast::ExprKind::FieldAccess { root, fields } => {
163 format!("{}.{}", fmt_expr(root), fields.join("."))
164 }
165 ast::ExprKind::List(items) => {
166 let inner: Vec<_> = items.iter().map(fmt_expr).collect();
167 format!("[{}]", inner.join(", "))
168 }
169 ast::ExprKind::Ident(name) => name.clone(),
170 ast::ExprKind::Literal(lit) => fmt_literal(lit),
171 }
172}
173
174fn fmt_literal(lit: &ast::Literal) -> String {
175 match lit {
176 ast::Literal::Null => "null".to_string(),
177 ast::Literal::Bool(b) => b.to_string(),
178 ast::Literal::Int(n) => n.to_string(),
179 ast::Literal::Decimal(s) => s.clone(),
180 ast::Literal::String(s) => format!("\"{}\"", s),
181 }
182}
183
184fn fmt_call_args(args: &[ast::CallArg]) -> String {
185 args.iter()
186 .map(|a| match a {
187 ast::CallArg::Named { key, value, .. } => {
188 format!("{}: {}", key, fmt_expr(value))
189 }
190 ast::CallArg::Positional(e) => fmt_expr(e),
191 })
192 .collect::<Vec<_>>()
193 .join(", ")
194}
195
196fn fmt_prop_value(val: &ast::PropValue) -> String {
197 match val {
198 ast::PropValue::Literal(lit) => fmt_literal(lit),
199 ast::PropValue::Ident(s) => s.clone(),
200 ast::PropValue::List(items) => {
201 let inner: Vec<_> = items.iter().map(fmt_prop_value).collect();
202 format!("[{}]", inner.join(", "))
203 }
204 ast::PropValue::Object(fields) => {
205 let inner: Vec<_> = fields
206 .iter()
207 .map(|(k, v)| format!("{}: {}", k, fmt_prop_value(v)))
208 .collect();
209 format!("{{ {} }}", inner.join(", "))
210 }
211 }
212}