1use crate::ast::*;
14use crate::lexer::{Trivia, TriviaKind};
15
16pub fn render_from_ast(source: &str, module: &Module, trivia: &[Trivia]) -> Result<String, String> {
26 check_nesting(module)?;
27 tile(source, module, trivia)
28}
29
30pub fn check_nesting(module: &Module) -> Result<(), String> {
34 let mut spans: Vec<Span> = Vec::new();
35 collect_module(module, &mut spans);
36 spans.retain(|s| !s.is_empty());
37 spans.sort_by(|a, b| a.start.cmp(&b.start).then(b.end.cmp(&a.end)));
39
40 let mut stack: Vec<Span> = Vec::new();
41 for span in spans {
42 while stack.last().is_some_and(|top| top.end <= span.start) {
44 stack.pop();
45 }
46 if let Some(parent) = stack.last() {
50 if !parent.contains(&span) {
51 return Err(format!(
52 "span [{}, {}) overlaps [{}, {}) without nesting",
53 span.start, span.end, parent.start, parent.end
54 ));
55 }
56 }
57 stack.push(span);
58 }
59 Ok(())
60}
61
62fn tile(source: &str, module: &Module, trivia: &[Trivia]) -> Result<String, String> {
68 let mut content: Vec<Span> = Vec::new();
69 collect_module(module, &mut content);
70 let container = module.span;
71 content.retain(|s| !(s.is_empty() || (s.start == container.start && s.end == container.end)));
72
73 let mut items: Vec<(usize, usize)> = content.iter().map(|s| (s.start, s.end)).collect();
74 items.extend(
77 trivia
78 .iter()
79 .filter(|t| !matches!(t.kind, TriviaKind::BlankLines(_)))
80 .map(|t| (t.start, t.end)),
81 );
82 items.sort_unstable_by(|a, b| a.0.cmp(&b.0).then(b.1.cmp(&a.1)));
86
87 let mut out = String::with_capacity(source.len());
88 let mut prev = 0usize;
89 for (start, end) in items {
90 if start < prev {
91 if end <= prev {
95 continue;
96 }
97 return Err(format!(
98 "span/trivia interval [{}, {}) overlaps previous tile ending at {}",
99 start, end, prev
100 ));
101 }
102 let gap = &source[prev..start];
103 if !gap.chars().all(char::is_whitespace) {
104 return Err(format!(
105 "bytes {}..{} not covered by any node or trivia span: {:?}",
106 prev, start, gap
107 ));
108 }
109 out.push_str(gap);
110 out.push_str(&source[start..end]);
111 prev = end;
112 }
113 let tail = &source[prev..];
114 if !tail.chars().all(char::is_whitespace) {
115 return Err(format!("bytes {}.. lost at EOF: {:?}", prev, tail));
116 }
117 out.push_str(tail);
118
119 if out != source {
120 return Err(format!(
121 "reconstruction differs from source ({} vs {} bytes)",
122 out.len(),
123 source.len()
124 ));
125 }
126 Ok(out)
127}
128
129fn collect_module(m: &Module, out: &mut Vec<Span>) {
132 out.push(m.span);
133 if !m.header.is_empty() {
134 out.push(m.header);
135 }
136 for imp in &m.imports {
137 out.push(imp.span);
138 }
139 for d in &m.decls {
140 collect_decl(d, out);
141 }
142}
143
144fn collect_decl(d: &Decl, out: &mut Vec<Span>) {
145 match d {
146 Decl::Template(t) => {
147 out.push(t.span);
148 for f in &t.fields {
149 out.push(f.span);
150 }
151 for b in &t.body {
152 collect_tbody(b, out);
153 }
154 }
155 Decl::Interface(i) => {
156 out.push(i.span);
157 for m in &i.methods {
158 out.push(m.span);
159 }
160 for c in &i.choices {
161 collect_choice(c, out);
162 }
163 }
164 Decl::Function(f) => {
165 out.push(f.span);
168 for eq in &f.equations {
169 collect_eq(eq, out);
170 }
171 if let Some(sig) = f.sig_span {
172 out.push(sig);
173 }
174 }
175 Decl::TypeDef { span, .. } | Decl::Unknown { span, .. } => out.push(*span),
176 }
177}
178
179fn collect_tbody(b: &TemplateBodyDecl, out: &mut Vec<Span>) {
180 match b {
181 TemplateBodyDecl::Signatory { parties, span, .. }
182 | TemplateBodyDecl::Observer { parties, span, .. } => {
183 out.push(*span);
184 for e in parties {
185 collect_expr(e, out);
186 }
187 }
188 TemplateBodyDecl::Ensure { expr, span, .. }
189 | TemplateBodyDecl::Maintainer { expr, span, .. }
190 | TemplateBodyDecl::Key { expr, span, .. } => {
191 out.push(*span);
192 collect_expr(expr, out);
193 }
194 TemplateBodyDecl::Choice(c) => collect_choice(c, out),
195 TemplateBodyDecl::InterfaceInstance(ii) => {
196 out.push(ii.span);
197 for m in &ii.methods {
198 collect_binding(m, out);
199 }
200 }
201 TemplateBodyDecl::Other { span, .. } => out.push(*span),
202 }
203}
204
205fn collect_choice(c: &ChoiceDecl, out: &mut Vec<Span>) {
206 out.push(c.span);
207 for p in &c.params {
208 out.push(p.span);
209 }
210 for e in &c.controllers {
211 collect_expr(e, out);
212 }
213 for e in &c.observers {
214 collect_expr(e, out);
215 }
216 if let Some(b) = &c.body {
217 collect_expr(b, out);
218 }
219}
220
221fn collect_eq(eq: &Equation, out: &mut Vec<Span>) {
222 out.push(eq.span);
223 for p in &eq.params {
224 collect_pat(p, out);
225 }
226 collect_expr(&eq.body, out);
227 for (g, b) in &eq.guards {
228 collect_expr(g, out);
229 collect_expr(b, out);
230 }
231 for wb in &eq.where_bindings {
232 collect_binding(wb, out);
233 }
234}
235
236fn collect_binding(b: &Binding, out: &mut Vec<Span>) {
237 out.push(b.span);
238 collect_pat(&b.pat, out);
239 for p in &b.params {
240 collect_pat(p, out);
241 }
242 collect_expr(&b.expr, out);
243}
244
245fn collect_pat(p: &Pat, out: &mut Vec<Span>) {
246 out.push(p.span());
247 match p {
248 Pat::Con { args, .. } => {
249 for a in args {
250 collect_pat(a, out);
251 }
252 }
253 Pat::Tuple { items, .. } | Pat::List { items, .. } => {
254 for it in items {
255 collect_pat(it, out);
256 }
257 }
258 Pat::As { pat, .. } => collect_pat(pat, out),
259 _ => {}
260 }
261}
262
263fn collect_expr(e: &Expr, out: &mut Vec<Span>) {
264 out.push(e.span());
265 match e {
266 Expr::App { func, args, .. } => {
267 collect_expr(func, out);
268 for a in args {
269 collect_expr(a, out);
270 }
271 }
272 Expr::BinOp { lhs, rhs, .. } => {
273 collect_expr(lhs, out);
274 collect_expr(rhs, out);
275 }
276 Expr::Neg { expr, .. } => collect_expr(expr, out),
277 Expr::Lambda { params, body, .. } => {
278 for p in params {
279 collect_pat(p, out);
280 }
281 collect_expr(body, out);
282 }
283 Expr::If {
284 cond,
285 then_branch,
286 else_branch,
287 ..
288 } => {
289 collect_expr(cond, out);
290 collect_expr(then_branch, out);
291 collect_expr(else_branch, out);
292 }
293 Expr::Case {
294 scrutinee, alts, ..
295 } => {
296 collect_expr(scrutinee, out);
297 for a in alts {
298 collect_alt(a, out);
299 }
300 }
301 Expr::Do { stmts, .. } => {
302 for s in stmts {
303 collect_dostmt(s, out);
304 }
305 }
306 Expr::LetIn { bindings, body, .. } => {
307 for b in bindings {
308 collect_binding(b, out);
309 }
310 collect_expr(body, out);
311 }
312 Expr::Record { base, fields, .. } => {
313 collect_expr(base, out);
314 for f in fields {
315 collect_field_assign(f, out);
316 }
317 }
318 Expr::Tuple { items, .. } | Expr::List { items, .. } => {
319 for it in items {
320 collect_expr(it, out);
321 }
322 }
323 Expr::Try { body, handlers, .. } => {
324 collect_expr(body, out);
325 for h in handlers {
326 collect_alt(h, out);
327 }
328 }
329 Expr::Section {
330 operand: Some(o), ..
331 } => collect_expr(o, out),
332 _ => {}
333 }
334}
335
336fn collect_alt(a: &Alt, out: &mut Vec<Span>) {
337 out.push(a.span);
338 collect_pat(&a.pat, out);
339 collect_expr(&a.body, out);
340}
341
342fn collect_field_assign(f: &FieldAssign, out: &mut Vec<Span>) {
343 out.push(f.span);
344 if let Some(v) = &f.value {
345 collect_expr(v, out);
346 }
347}
348
349fn collect_dostmt(s: &DoStmt, out: &mut Vec<Span>) {
350 match s {
351 DoStmt::Bind {
352 pat, expr, span, ..
353 } => {
354 out.push(*span);
355 collect_pat(pat, out);
356 collect_expr(expr, out);
357 }
358 DoStmt::Let { bindings, span, .. } => {
359 out.push(*span);
360 for b in bindings {
361 collect_binding(b, out);
362 }
363 }
364 DoStmt::Expr { expr, span, .. } => {
365 out.push(*span);
366 collect_expr(expr, out);
367 }
368 }
369}
370
371#[cfg(test)]
372mod tests {
373 use super::*;
374 use crate::lexer::{Pos, Trivia};
375
376 #[test]
377 fn tile_reports_overlapping_intervals() {
378 let source = "module M where\n-- comment\n";
379 let module = Module {
380 name: "M".to_string(),
381 pos: Pos { line: 1, column: 1 },
382 span: Span::new(0, source.len()),
383 header: Span::new(0, "module M where".len()),
384 imports: Vec::new(),
385 decls: Vec::new(),
386 };
387 let trivia = vec![Trivia {
388 kind: TriviaKind::LineComment,
389 text: "-- comment".to_string(),
390 pos: Pos { line: 2, column: 1 },
391 start: "module M wher".len(),
392 end: "module M where\n-- comment".len(),
393 }];
394
395 let err = tile(source, &module, &trivia).unwrap_err();
396 assert!(
397 err.contains("overlaps previous tile"),
398 "overlap should fail loudly, got: {err}"
399 );
400 }
401}