Skip to main content

shape_ast/error/
context.rs

1//! Error context and helper utilities
2//!
3//! This module provides utilities for adding context to errors,
4//! converting spans to locations, and building error chains.
5
6use super::types::{ShapeError, SourceLocation};
7
8/// Result type alias for Shape operations
9pub type Result<T> = std::result::Result<T, ShapeError>;
10
11/// Error context builder for adding context to errors
12pub struct ErrorContext<T> {
13    result: std::result::Result<T, ShapeError>,
14}
15
16impl<T> ErrorContext<T> {
17    pub fn new(result: std::result::Result<T, ShapeError>) -> Self {
18        Self { result }
19    }
20
21    /// Add location information to the error
22    pub fn with_location(self, location: SourceLocation) -> std::result::Result<T, ShapeError> {
23        self.result.map_err(|mut e| {
24            match &mut e {
25                ShapeError::ParseError { location: loc, .. }
26                | ShapeError::LexError { location: loc, .. }
27                | ShapeError::SemanticError { location: loc, .. }
28                | ShapeError::RuntimeError { location: loc, .. } => {
29                    *loc = Some(location);
30                }
31                _ => {}
32            }
33            e
34        })
35    }
36
37    /// Add a custom context message
38    pub fn context(self, msg: impl Into<String>) -> std::result::Result<T, ShapeError> {
39        self.result
40            .map_err(|e| ShapeError::Custom(format!("{}: {}", msg.into(), e)))
41    }
42}
43
44/// Extension trait for Result types to add error context
45pub trait ResultExt<T> {
46    fn with_location(self, location: SourceLocation) -> Result<T>;
47    fn with_context(self, msg: impl Into<String>) -> Result<T>;
48}
49
50impl<T> ResultExt<T> for Result<T> {
51    fn with_location(self, location: SourceLocation) -> Result<T> {
52        ErrorContext::new(self).with_location(location)
53    }
54
55    fn with_context(self, msg: impl Into<String>) -> Result<T> {
56        ErrorContext::new(self).context(msg)
57    }
58}
59
60/// Convert a byte-offset Span to line/column SourceLocation
61///
62/// This function takes the source text and a Span (containing byte offsets)
63/// and converts it to a SourceLocation with line numbers and column positions.
64pub fn span_to_location(
65    source: &str,
66    span: crate::ast::Span,
67    file: Option<String>,
68) -> SourceLocation {
69    if span.is_dummy() {
70        // Return a placeholder for dummy spans
71        let mut loc = SourceLocation::new(1, 1);
72        loc.is_synthetic = true;
73        return loc;
74    }
75
76    let mut line = 1usize;
77    let mut col = 1usize;
78    let mut last_newline_pos = 0usize;
79
80    // Find line and column by counting newlines up to span.start
81    for (i, ch) in source.char_indices() {
82        if i >= span.start {
83            break;
84        }
85        if ch == '\n' {
86            line += 1;
87            col = 1;
88            last_newline_pos = i + 1;
89        } else {
90            col += 1;
91        }
92    }
93
94    // Extract the source line containing the span
95    let line_start = last_newline_pos;
96    let line_end = source[span.start.min(source.len())..]
97        .find('\n')
98        .map(|i| span.start.min(source.len()) + i)
99        .unwrap_or(source.len());
100    let source_line = source.get(line_start..line_end).unwrap_or("").to_string();
101
102    let mut loc = SourceLocation::new(line, col)
103        .with_length(span.len())
104        .with_source_line(source_line);
105
106    if let Some(f) = file {
107        loc = loc.with_file(f);
108    }
109
110    loc
111}