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