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 for span in &spans {
37 if !span.is_valid() {
38 return Err(format!("invalid span [{}, {})", span.start, span.end));
39 }
40 }
41 spans.retain(|s| !s.is_empty());
42 spans.sort_by(|a, b| a.start.cmp(&b.start).then(b.end.cmp(&a.end)));
44
45 let mut stack: Vec<Span> = Vec::new();
46 for span in spans {
47 while stack.last().is_some_and(|top| top.end <= span.start) {
49 stack.pop();
50 }
51 if let Some(parent) = stack.last() {
55 if !parent.contains(&span) {
56 return Err(format!(
57 "span [{}, {}) overlaps [{}, {}) without nesting",
58 span.start, span.end, parent.start, parent.end
59 ));
60 }
61 }
62 stack.push(span);
63 }
64 Ok(())
65}
66
67fn tile(source: &str, module: &Module, trivia: &[Trivia]) -> Result<String, String> {
73 let mut content: Vec<Span> = Vec::new();
74 collect_module(module, &mut content);
75 let container = module.span;
76 content.retain(|s| !(s.is_empty() || (s.start == container.start && s.end == container.end)));
77
78 let mut items: Vec<(usize, usize)> = content.iter().map(|s| (s.start, s.end)).collect();
79 items.extend(
82 trivia
83 .iter()
84 .filter(|t| !matches!(t.kind, TriviaKind::BlankLines(_)))
85 .map(|t| (t.start, t.end)),
86 );
87 items.sort_unstable_by(|a, b| a.0.cmp(&b.0).then(b.1.cmp(&a.1)));
91
92 let mut out = String::with_capacity(source.len());
93 let mut prev = 0usize;
94 for (start, end) in items {
95 validate_interval(source, start, end)?;
96 if start < prev {
97 if end <= prev {
101 continue;
102 }
103 return Err(format!(
104 "span/trivia interval [{start}, {end}) overlaps previous tile ending at {prev}"
105 ));
106 }
107 let gap = &source[prev..start];
108 if !gap.chars().all(char::is_whitespace) {
109 return Err(format!(
110 "bytes {prev}..{start} not covered by any node or trivia span: {gap:?}"
111 ));
112 }
113 out.push_str(gap);
114 out.push_str(&source[start..end]);
115 prev = end;
116 }
117 let tail = &source[prev..];
118 if !tail.chars().all(char::is_whitespace) {
119 return Err(format!("bytes {prev}.. lost at EOF: {tail:?}"));
120 }
121 out.push_str(tail);
122
123 if out != source {
124 return Err(format!(
125 "reconstruction differs from source ({} vs {} bytes)",
126 out.len(),
127 source.len()
128 ));
129 }
130 Ok(out)
131}
132
133fn validate_interval(source: &str, start: usize, end: usize) -> Result<(), String> {
134 if start > end {
135 return Err(format!(
136 "span/trivia interval [{start}, {end}) has start after end"
137 ));
138 }
139 if end > source.len() {
140 return Err(format!(
141 "span/trivia interval [{start}, {end}) exceeds source length {}",
142 source.len()
143 ));
144 }
145 if !source.is_char_boundary(start) || !source.is_char_boundary(end) {
146 return Err(format!(
147 "span/trivia interval [{start}, {end}) does not align with UTF-8 boundaries"
148 ));
149 }
150 Ok(())
151}
152
153fn collect_module(module: &Module, spans: &mut Vec<Span>) {
156 spans.push(module.span);
157 if !module.header.is_empty() {
158 spans.push(module.header);
159 }
160 for import in &module.imports {
161 spans.push(import.span);
162 }
163 for decl in &module.decls {
164 collect_decl(decl, spans);
165 }
166}
167
168fn collect_decl(decl: &Decl, spans: &mut Vec<Span>) {
169 match decl {
170 Decl::Template(template) => {
171 spans.push(template.span);
172 for field in &template.fields {
173 spans.push(field.span);
174 }
175 for body_decl in &template.body {
176 collect_tbody(body_decl, spans);
177 }
178 }
179 Decl::Interface(interface) => {
180 spans.push(interface.span);
181 for method in &interface.methods {
182 spans.push(method.span);
183 }
184 for choice in &interface.choices {
185 collect_choice(choice, spans);
186 }
187 }
188 Decl::Function(function) => {
189 spans.push(function.span);
192 for equation in &function.equations {
193 collect_eq(equation, spans);
194 }
195 if let Some(sig) = function.sig_span {
196 spans.push(sig);
197 }
198 }
199 Decl::TypeDef { span, .. } | Decl::Unknown { span, .. } => spans.push(*span),
200 }
201}
202
203fn collect_tbody(template_body_decl: &TemplateBodyDecl, spans: &mut Vec<Span>) {
204 match template_body_decl {
205 TemplateBodyDecl::Signatory { parties, span, .. }
206 | TemplateBodyDecl::Observer { parties, span, .. } => {
207 spans.push(*span);
208 for party in parties {
209 collect_expr(party, spans);
210 }
211 }
212 TemplateBodyDecl::Ensure { expr, span, .. }
213 | TemplateBodyDecl::Maintainer { expr, span, .. }
214 | TemplateBodyDecl::Key { expr, span, .. } => {
215 spans.push(*span);
216 collect_expr(expr, spans);
217 }
218 TemplateBodyDecl::Choice(choice) => collect_choice(choice, spans),
219 TemplateBodyDecl::InterfaceInstance(interface_instance) => {
220 spans.push(interface_instance.span);
221 for method in &interface_instance.methods {
222 collect_binding(method, spans);
223 }
224 }
225 TemplateBodyDecl::Other { span, .. } => spans.push(*span),
226 }
227}
228
229fn collect_choice(choice: &ChoiceDecl, spans: &mut Vec<Span>) {
230 spans.push(choice.span);
231 for param in &choice.params {
232 spans.push(param.span);
233 }
234 for controller in &choice.controllers {
235 collect_expr(controller, spans);
236 }
237 for observer in &choice.observers {
238 collect_expr(observer, spans);
239 }
240 if let Some(body) = &choice.body {
241 collect_expr(body, spans);
242 }
243}
244
245fn collect_eq(equation: &Equation, spans: &mut Vec<Span>) {
246 spans.push(equation.span);
247 for param in &equation.params {
248 collect_pat(param, spans);
249 }
250 collect_expr(&equation.body, spans);
251 for (guard, body) in &equation.guards {
252 collect_expr(guard, spans);
253 collect_expr(body, spans);
254 }
255 for where_binding in &equation.where_bindings {
256 collect_binding(where_binding, spans);
257 }
258}
259
260fn collect_binding(binding: &Binding, spans: &mut Vec<Span>) {
261 spans.push(binding.span);
262 collect_pat(&binding.pat, spans);
263 for param in &binding.params {
264 collect_pat(param, spans);
265 }
266 collect_expr(&binding.expr, spans);
267}
268
269fn collect_pat(pattern: &Pat, spans: &mut Vec<Span>) {
270 spans.push(pattern.span());
271 match pattern {
272 Pat::Con { args, .. } => {
273 for arg in args {
274 collect_pat(arg, spans);
275 }
276 }
277 Pat::Tuple { items, .. } | Pat::List { items, .. } => {
278 for item in items {
279 collect_pat(item, spans);
280 }
281 }
282 Pat::As { pat, .. } => collect_pat(pat, spans),
283 Pat::Var { .. } | Pat::Wild { .. } | Pat::Lit { .. } | Pat::Other { .. } => {}
284 }
285}
286
287fn collect_expr(expr: &Expr, spans: &mut Vec<Span>) {
288 spans.push(expr.span());
289 match expr {
290 Expr::App { func, args, .. } => {
291 collect_expr(func, spans);
292 for arg in args {
293 collect_expr(arg, spans);
294 }
295 }
296 Expr::BinOp { lhs, rhs, .. } => {
297 collect_expr(lhs, spans);
298 collect_expr(rhs, spans);
299 }
300 Expr::Neg { expr, .. } => collect_expr(expr, spans),
301 Expr::Lambda { params, body, .. } => {
302 for param in params {
303 collect_pat(param, spans);
304 }
305 collect_expr(body, spans);
306 }
307 Expr::If {
308 cond,
309 then_branch,
310 else_branch,
311 ..
312 } => {
313 collect_expr(cond, spans);
314 collect_expr(then_branch, spans);
315 collect_expr(else_branch, spans);
316 }
317 Expr::Case {
318 scrutinee, alts, ..
319 } => {
320 collect_expr(scrutinee, spans);
321 for alt in alts {
322 collect_alt(alt, spans);
323 }
324 }
325 Expr::Do { stmts, .. } => {
326 for stmt in stmts {
327 collect_dostmt(stmt, spans);
328 }
329 }
330 Expr::LetIn { bindings, body, .. } => {
331 for binding in bindings {
332 collect_binding(binding, spans);
333 }
334 collect_expr(body, spans);
335 }
336 Expr::Record { base, fields, .. } => {
337 collect_expr(base, spans);
338 for field in fields {
339 collect_field_assign(field, spans);
340 }
341 }
342 Expr::Tuple { items, .. } | Expr::List { items, .. } => {
343 for item in items {
344 collect_expr(item, spans);
345 }
346 }
347 Expr::Try { body, handlers, .. } => {
348 collect_expr(body, spans);
349 for handler in handlers {
350 collect_alt(handler, spans);
351 }
352 }
353 Expr::Section {
354 operand: Some(operand),
355 ..
356 } => collect_expr(operand, spans),
357 Expr::Var { .. }
358 | Expr::Con { .. }
359 | Expr::Lit { .. }
360 | Expr::Section { operand: None, .. }
361 | Expr::Error { .. } => {}
362 }
363}
364
365fn collect_alt(alt: &Alt, spans: &mut Vec<Span>) {
366 spans.push(alt.span);
367 collect_pat(&alt.pat, spans);
368 collect_expr(&alt.body, spans);
369}
370
371fn collect_field_assign(field_assign: &FieldAssign, spans: &mut Vec<Span>) {
372 spans.push(field_assign.span);
373 if let Some(value) = &field_assign.value {
374 collect_expr(value, spans);
375 }
376}
377
378fn collect_dostmt(do_stmt: &DoStmt, spans: &mut Vec<Span>) {
379 match do_stmt {
380 DoStmt::Bind {
381 pat, expr, span, ..
382 } => {
383 spans.push(*span);
384 collect_pat(pat, spans);
385 collect_expr(expr, spans);
386 }
387 DoStmt::Let { bindings, span, .. } => {
388 spans.push(*span);
389 for binding in bindings {
390 collect_binding(binding, spans);
391 }
392 }
393 DoStmt::Expr { expr, span, .. } => {
394 spans.push(*span);
395 collect_expr(expr, spans);
396 }
397 }
398}
399
400#[cfg(test)]
401mod tests {
402 use super::*;
403 use crate::lexer::{Pos, Trivia};
404
405 #[test]
406 fn tile_reports_overlapping_intervals() {
407 let source = "module M where\n-- comment\n";
408 let module = Module {
409 name: "M".to_string(),
410 pos: Pos { line: 1, column: 1 },
411 span: Span::new(0, source.len()),
412 header: Span::new(0, "module M where".len()),
413 imports: Vec::new(),
414 decls: Vec::new(),
415 };
416 let trivia = vec![Trivia {
417 kind: TriviaKind::LineComment,
418 text: "-- comment".to_string(),
419 pos: Pos { line: 2, column: 1 },
420 start: "module M wher".len(),
421 end: "module M where\n-- comment".len(),
422 }];
423
424 let err = tile(source, &module, &trivia).unwrap_err();
425 assert!(
426 err.contains("overlaps previous tile"),
427 "overlap should fail loudly, got: {err}"
428 );
429 }
430}