1use crate::ast::*;
14use crate::lexer::{Trivia, TriviaKind};
15
16#[derive(Debug, Clone, PartialEq, Eq)]
17#[non_exhaustive]
18pub enum AstSpanError {
19 InvalidSpan {
20 start: usize,
21 end: usize,
22 },
23 OverlappingSpans {
24 start: usize,
25 end: usize,
26 parent_start: usize,
27 parent_end: usize,
28 },
29 OverlappingTile {
30 start: usize,
31 end: usize,
32 previous_end: usize,
33 },
34 UncoveredBytes {
35 start: usize,
36 end: usize,
37 text: String,
38 },
39 UncoveredTail {
40 start: usize,
41 text: String,
42 },
43 ReconstructionMismatch {
44 reconstructed_len: usize,
45 source_len: usize,
46 },
47 IntervalStartAfterEnd {
48 start: usize,
49 end: usize,
50 },
51 IntervalExceedsSource {
52 start: usize,
53 end: usize,
54 source_len: usize,
55 },
56 IntervalNotUtf8Boundary {
57 start: usize,
58 end: usize,
59 },
60}
61
62impl std::fmt::Display for AstSpanError {
63 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
64 match self {
65 Self::InvalidSpan { start, end } => write!(f, "invalid span [{start}, {end})"),
66 Self::OverlappingSpans {
67 start,
68 end,
69 parent_start,
70 parent_end,
71 } => write!(
72 f,
73 "span [{start}, {end}) overlaps [{parent_start}, {parent_end}) without nesting"
74 ),
75 Self::OverlappingTile {
76 start,
77 end,
78 previous_end,
79 } => write!(
80 f,
81 "span/trivia interval [{start}, {end}) overlaps previous tile ending at {previous_end}"
82 ),
83 Self::UncoveredBytes { start, end, text } => write!(
84 f,
85 "bytes {start}..{end} not covered by any node or trivia span: {text:?}"
86 ),
87 Self::UncoveredTail { start, text } => {
88 write!(f, "bytes {start}.. lost at EOF: {text:?}")
89 }
90 Self::ReconstructionMismatch {
91 reconstructed_len,
92 source_len,
93 } => write!(
94 f,
95 "reconstruction differs from source ({reconstructed_len} vs {source_len} bytes)"
96 ),
97 Self::IntervalStartAfterEnd { start, end } => {
98 write!(f, "span/trivia interval [{start}, {end}) has start after end")
99 }
100 Self::IntervalExceedsSource {
101 start,
102 end,
103 source_len,
104 } => write!(
105 f,
106 "span/trivia interval [{start}, {end}) exceeds source length {source_len}"
107 ),
108 Self::IntervalNotUtf8Boundary { start, end } => write!(
109 f,
110 "span/trivia interval [{start}, {end}) does not align with UTF-8 boundaries"
111 ),
112 }
113 }
114}
115
116impl std::error::Error for AstSpanError {}
117
118pub fn render_from_ast(
128 source: &str,
129 module: &Module,
130 trivia: &[Trivia],
131) -> Result<String, AstSpanError> {
132 check_nesting(module)?;
133 tile(source, module, trivia)
134}
135
136fn check_nesting(module: &Module) -> Result<(), AstSpanError> {
140 let mut spans: Vec<Span> = Vec::new();
141 collect_module(module, &mut spans);
142 for span in &spans {
143 if !span.is_valid() {
144 return Err(AstSpanError::InvalidSpan {
145 start: span.start,
146 end: span.end,
147 });
148 }
149 }
150 spans.retain(|s| !s.is_empty());
151 spans.sort_by(|a, b| a.start.cmp(&b.start).then(b.end.cmp(&a.end)));
153
154 let mut stack: Vec<Span> = Vec::new();
155 for span in spans {
156 while stack.last().is_some_and(|top| top.end <= span.start) {
158 stack.pop();
159 }
160 if let Some(parent) = stack.last() {
164 if !parent.contains(&span) {
165 return Err(AstSpanError::OverlappingSpans {
166 start: span.start,
167 end: span.end,
168 parent_start: parent.start,
169 parent_end: parent.end,
170 });
171 }
172 }
173 stack.push(span);
174 }
175 Ok(())
176}
177
178fn tile(source: &str, module: &Module, trivia: &[Trivia]) -> Result<String, AstSpanError> {
184 let mut content: Vec<Span> = Vec::new();
185 collect_module(module, &mut content);
186 let container = module.span;
187 content.retain(|s| !(s.is_empty() || (s.start == container.start && s.end == container.end)));
188
189 let mut items: Vec<(usize, usize)> = content.iter().map(|s| (s.start, s.end)).collect();
190 items.extend(
193 trivia
194 .iter()
195 .filter(|t| !matches!(t.kind, TriviaKind::BlankLines(_)))
196 .map(|t| (t.start, t.end)),
197 );
198 items.sort_unstable_by(|a, b| a.0.cmp(&b.0).then(b.1.cmp(&a.1)));
202
203 let mut out = String::with_capacity(source.len());
204 let mut prev = 0usize;
205 for (start, end) in items {
206 validate_interval(source, start, end)?;
207 if start < prev {
208 if end <= prev {
212 continue;
213 }
214 return Err(AstSpanError::OverlappingTile {
215 start,
216 end,
217 previous_end: prev,
218 });
219 }
220 let gap = &source[prev..start];
221 if !gap.chars().all(char::is_whitespace) {
222 return Err(AstSpanError::UncoveredBytes {
223 start: prev,
224 end: start,
225 text: gap.to_string(),
226 });
227 }
228 out.push_str(gap);
229 out.push_str(&source[start..end]);
230 prev = end;
231 }
232 let tail = &source[prev..];
233 if !tail.chars().all(char::is_whitespace) {
234 return Err(AstSpanError::UncoveredTail {
235 start: prev,
236 text: tail.to_string(),
237 });
238 }
239 out.push_str(tail);
240
241 if out != source {
242 return Err(AstSpanError::ReconstructionMismatch {
243 reconstructed_len: out.len(),
244 source_len: source.len(),
245 });
246 }
247 Ok(out)
248}
249
250const fn validate_interval(source: &str, start: usize, end: usize) -> Result<(), AstSpanError> {
251 if start > end {
252 return Err(AstSpanError::IntervalStartAfterEnd { start, end });
253 }
254 if end > source.len() {
255 return Err(AstSpanError::IntervalExceedsSource {
256 start,
257 end,
258 source_len: source.len(),
259 });
260 }
261 if !source.is_char_boundary(start) || !source.is_char_boundary(end) {
262 return Err(AstSpanError::IntervalNotUtf8Boundary { start, end });
263 }
264 Ok(())
265}
266
267fn collect_module(module: &Module, spans: &mut Vec<Span>) {
270 spans.push(module.span);
271 if !module.header.is_empty() {
272 spans.push(module.header);
273 }
274 for import in &module.imports {
275 spans.push(import.span);
276 }
277 for decl in &module.decls {
278 collect_decl(decl, spans);
279 }
280}
281
282fn collect_decl(decl: &Decl, spans: &mut Vec<Span>) {
283 match decl {
284 Decl::Template(template) => {
285 spans.push(template.span);
286 for field in &template.fields {
287 spans.push(field.span);
288 }
289 for body_decl in &template.body {
290 collect_tbody(body_decl, spans);
291 }
292 }
293 Decl::Interface(interface) => {
294 spans.push(interface.span);
295 for method in &interface.methods {
296 spans.push(method.span);
297 }
298 for choice in &interface.choices {
299 collect_choice(choice, spans);
300 }
301 }
302 Decl::Function(function) => {
303 spans.push(function.span);
306 for equation in &function.equations {
307 collect_eq(equation, spans);
308 }
309 if let Some(sig) = function.sig_span {
310 spans.push(sig);
311 }
312 }
313 Decl::TypeDef { span, .. } | Decl::Unknown { span, .. } => spans.push(*span),
314 }
315}
316
317fn collect_tbody(template_body_decl: &TemplateBodyDecl, spans: &mut Vec<Span>) {
318 match template_body_decl {
319 TemplateBodyDecl::Signatory { parties, span, .. }
320 | TemplateBodyDecl::Observer { parties, span, .. } => {
321 spans.push(*span);
322 for party in parties {
323 collect_expr(party, spans);
324 }
325 }
326 TemplateBodyDecl::Ensure { expr, span, .. }
327 | TemplateBodyDecl::Maintainer { expr, span, .. }
328 | TemplateBodyDecl::Key { expr, span, .. } => {
329 spans.push(*span);
330 collect_expr(expr, spans);
331 }
332 TemplateBodyDecl::Choice(choice) => collect_choice(choice, spans),
333 TemplateBodyDecl::InterfaceInstance(interface_instance) => {
334 spans.push(interface_instance.span);
335 for method in &interface_instance.methods {
336 collect_binding(method, spans);
337 }
338 }
339 TemplateBodyDecl::Other { span, .. } => spans.push(*span),
340 }
341}
342
343fn collect_choice(choice: &ChoiceDecl, spans: &mut Vec<Span>) {
344 spans.push(choice.span);
345 for param in &choice.params {
346 spans.push(param.span);
347 }
348 for controller in &choice.controllers {
349 collect_expr(controller, spans);
350 }
351 for observer in &choice.observers {
352 collect_expr(observer, spans);
353 }
354 if let Some(body) = &choice.body {
355 collect_expr(body, spans);
356 }
357}
358
359fn collect_eq(equation: &Equation, spans: &mut Vec<Span>) {
360 spans.push(equation.span);
361 for param in &equation.params {
362 collect_pat(param, spans);
363 }
364 collect_expr(&equation.body, spans);
365 for (guard, body) in &equation.guards {
366 collect_expr(guard, spans);
367 collect_expr(body, spans);
368 }
369 for where_binding in &equation.where_bindings {
370 collect_binding(where_binding, spans);
371 }
372}
373
374fn collect_binding(binding: &Binding, spans: &mut Vec<Span>) {
375 spans.push(binding.span);
376 collect_pat(&binding.pat, spans);
377 for param in &binding.params {
378 collect_pat(param, spans);
379 }
380 collect_expr(&binding.expr, spans);
381}
382
383fn collect_pat(pattern: &Pat, spans: &mut Vec<Span>) {
384 spans.push(pattern.span());
385 match pattern {
386 Pat::Con { args, .. } => {
387 for arg in args {
388 collect_pat(arg, spans);
389 }
390 }
391 Pat::Tuple { items, .. } | Pat::List { items, .. } => {
392 for item in items {
393 collect_pat(item, spans);
394 }
395 }
396 Pat::As { pat, .. } => collect_pat(pat, spans),
397 Pat::Var { .. } | Pat::Wild { .. } | Pat::Lit { .. } | Pat::Other { .. } => {}
398 }
399}
400
401fn collect_expr(expr: &Expr, spans: &mut Vec<Span>) {
402 spans.push(expr.span());
403 match expr {
404 Expr::App { func, args, .. } => {
405 collect_expr(func, spans);
406 for arg in args {
407 collect_expr(arg, spans);
408 }
409 }
410 Expr::BinOp { lhs, rhs, .. } => {
411 collect_expr(lhs, spans);
412 collect_expr(rhs, spans);
413 }
414 Expr::Neg { expr, .. } => collect_expr(expr, spans),
415 Expr::Lambda { params, body, .. } => {
416 for param in params {
417 collect_pat(param, spans);
418 }
419 collect_expr(body, spans);
420 }
421 Expr::If {
422 cond,
423 then_branch,
424 else_branch,
425 ..
426 } => {
427 collect_expr(cond, spans);
428 collect_expr(then_branch, spans);
429 collect_expr(else_branch, spans);
430 }
431 Expr::Case {
432 scrutinee, alts, ..
433 } => {
434 collect_expr(scrutinee, spans);
435 for alt in alts {
436 collect_alt(alt, spans);
437 }
438 }
439 Expr::Do { stmts, .. } => {
440 for stmt in stmts {
441 collect_dostmt(stmt, spans);
442 }
443 }
444 Expr::LetIn { bindings, body, .. } => {
445 for binding in bindings {
446 collect_binding(binding, spans);
447 }
448 collect_expr(body, spans);
449 }
450 Expr::Record { base, fields, .. } => {
451 collect_expr(base, spans);
452 for field in fields {
453 collect_field_assign(field, spans);
454 }
455 }
456 Expr::Tuple { items, .. } | Expr::List { items, .. } => {
457 for item in items {
458 collect_expr(item, spans);
459 }
460 }
461 Expr::Try { body, handlers, .. } => {
462 collect_expr(body, spans);
463 for handler in handlers {
464 collect_alt(handler, spans);
465 }
466 }
467 Expr::Section {
468 operand: Some(operand),
469 ..
470 } => collect_expr(operand, spans),
471 Expr::Var { .. }
472 | Expr::Con { .. }
473 | Expr::Lit { .. }
474 | Expr::Section { operand: None, .. }
475 | Expr::Error { .. } => {}
476 }
477}
478
479fn collect_alt(alt: &Alt, spans: &mut Vec<Span>) {
480 spans.push(alt.span);
481 collect_pat(&alt.pat, spans);
482 collect_expr(&alt.body, spans);
483}
484
485fn collect_field_assign(field_assign: &FieldAssign, spans: &mut Vec<Span>) {
486 spans.push(field_assign.span);
487 if let Some(value) = &field_assign.value {
488 collect_expr(value, spans);
489 }
490}
491
492fn collect_dostmt(do_stmt: &DoStmt, spans: &mut Vec<Span>) {
493 match do_stmt {
494 DoStmt::Bind {
495 pat, expr, span, ..
496 } => {
497 spans.push(*span);
498 collect_pat(pat, spans);
499 collect_expr(expr, spans);
500 }
501 DoStmt::Let { bindings, span, .. } => {
502 spans.push(*span);
503 for binding in bindings {
504 collect_binding(binding, spans);
505 }
506 }
507 DoStmt::Expr { expr, span, .. } => {
508 spans.push(*span);
509 collect_expr(expr, spans);
510 }
511 }
512}
513
514#[cfg(test)]
515mod tests {
516 use super::*;
517 use crate::lexer::{Pos, Trivia};
518
519 #[test]
520 fn tile_reports_overlapping_intervals() {
521 let source = "module M where\n-- comment\n";
522 let module = Module {
523 name: "M".to_string(),
524 pos: Pos { line: 1, column: 1 },
525 span: Span::new(0, source.len()),
526 header: Span::new(0, "module M where".len()),
527 imports: Vec::new(),
528 decls: Vec::new(),
529 };
530 let trivia = vec![Trivia {
531 kind: TriviaKind::LineComment,
532 text: "-- comment".to_string(),
533 pos: Pos { line: 2, column: 1 },
534 start: "module M wher".len(),
535 end: "module M where\n-- comment".len(),
536 }];
537
538 let err = tile(source, &module, &trivia).unwrap_err();
539 assert!(
540 err.to_string().contains("overlaps previous tile"),
541 "overlap should fail loudly, got: {err}"
542 );
543 }
544
545 #[test]
546 fn validate_interval_reports_malformed_bounds() {
547 assert_eq!(
548 validate_interval("abc", 2, 1),
549 Err(AstSpanError::IntervalStartAfterEnd { start: 2, end: 1 })
550 );
551 assert_eq!(
552 validate_interval("abc", 0, 4),
553 Err(AstSpanError::IntervalExceedsSource {
554 start: 0,
555 end: 4,
556 source_len: 3
557 })
558 );
559 assert_eq!(
560 validate_interval("é", 1, 2),
561 Err(AstSpanError::IntervalNotUtf8Boundary { start: 1, end: 2 })
562 );
563 }
564
565 #[test]
566 fn tile_reports_uncovered_non_whitespace_bytes() {
567 let source = "module M where\nmissing\n";
568 let module = Module {
569 name: "M".to_string(),
570 pos: Pos { line: 1, column: 1 },
571 span: Span::new(0, source.len()),
572 header: Span::new(0, "module M where".len()),
573 imports: Vec::new(),
574 decls: Vec::new(),
575 };
576
577 assert_eq!(
578 tile(source, &module, &[]),
579 Err(AstSpanError::UncoveredTail {
580 start: "module M where".len(),
581 text: "\nmissing\n".to_string(),
582 })
583 );
584 }
585
586 #[test]
587 fn render_from_ast_roundtrips_header_only_module() {
588 let source = "module M where\n";
589 let module = Module {
590 name: "M".to_string(),
591 pos: Pos { line: 1, column: 1 },
592 span: Span::new(0, source.len()),
593 header: Span::new(0, "module M where".len()),
594 imports: Vec::new(),
595 decls: Vec::new(),
596 };
597
598 assert_eq!(render_from_ast(source, &module, &[]).as_deref(), Ok(source));
599 }
600}