egglog_ast/
span.rs

1use std::fmt::{self, Debug, Display};
2use std::sync::Arc;
3
4#[derive(Clone, PartialEq, Eq, Hash)]
5pub enum Span {
6    Panic,
7    Egglog(Arc<EgglogSpan>),
8    Rust(Arc<RustSpan>),
9}
10
11#[derive(Debug, Clone, PartialEq, Eq, Hash)]
12pub struct EgglogSpan {
13    pub file: Arc<SrcFile>,
14    pub i: usize,
15    pub j: usize,
16}
17
18#[derive(Debug, Clone, PartialEq, Eq, Hash)]
19pub struct RustSpan {
20    pub file: &'static str,
21    pub line: u32,
22    pub column: u32,
23}
24
25#[derive(Debug, PartialEq, Eq, Hash, Clone)]
26pub struct SrcFile {
27    pub name: Option<String>,
28    pub contents: String,
29}
30
31impl SrcFile {
32    pub fn get_location(&self, offset: usize) -> (usize, usize) {
33        let mut line = 1;
34        let mut col = 1;
35        for (i, c) in self.contents.char_indices() {
36            if i == offset {
37                break;
38            }
39            if c == '\n' {
40                line += 1;
41                col = 1;
42            } else {
43                col += 1;
44            }
45        }
46        (line, col)
47    }
48}
49
50impl Span {
51    pub fn string(&self) -> &str {
52        match self {
53            Span::Panic => panic!("Span::Panic in Span::string"),
54            Span::Rust(_) => panic!("Span::Rust cannot track end position"),
55            Span::Egglog(span) => &span.file.contents[span.i..span.j],
56        }
57    }
58}
59
60impl Debug for Span {
61    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
62        write!(f, "{}", self)
63    }
64}
65
66impl Display for Span {
67    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68        match self {
69            Span::Panic => panic!("Span::Panic in impl Display"),
70            Span::Rust(span) => write!(f, "At {}:{} of {}", span.line, span.column, span.file),
71            Span::Egglog(span) => {
72                let (start_line, start_col) = span.file.get_location(span.i);
73                let (end_line, end_col) = span
74                    .file
75                    .get_location((span.j.saturating_sub(1)).max(span.i));
76                let quote = self.string();
77                // Use just the file name, not the full path, for cross-platform consistency in snapshots
78                let display_name = span.file.name.as_ref().map(|path| {
79                    std::path::Path::new(path)
80                        .file_name()
81                        .and_then(|n| n.to_str())
82                        .unwrap_or(path)
83                });
84                match (&display_name, start_line == end_line) {
85                    (Some(filename), true) => write!(
86                        f,
87                        "In {}:{}-{} of {filename}: {quote}",
88                        start_line, start_col, end_col
89                    ),
90                    (Some(filename), false) => write!(
91                        f,
92                        "In {}:{}-{}:{} of {filename}: {quote}",
93                        start_line, start_col, end_line, end_col
94                    ),
95                    (None, false) => write!(
96                        f,
97                        "In {}:{}-{}:{}: {quote}",
98                        start_line, start_col, end_line, end_col
99                    ),
100                    (None, true) => {
101                        write!(f, "In {}:{}-{}: {quote}", start_line, start_col, end_col)
102                    }
103                }
104            }
105        }
106    }
107}