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 match (&span.file.name, start_line == end_line) {
78 (Some(filename), true) => write!(
79 f,
80 "In {start_line}:{start_col}-{end_col} of {filename}: {quote}"
81 ),
82 (Some(filename), false) => write!(
83 f,
84 "In {start_line}:{start_col}-{end_line}:{end_col} of {filename}: {quote}"
85 ),
86 (None, false) => write!(
87 f,
88 "In {start_line}:{start_col}-{end_line}:{end_col}: {quote}"
89 ),
90 (None, true) => {
91 write!(f, "In {start_line}:{start_col}-{end_col}: {quote}")
92 }
93 }
94 }
95 }
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn egglog_span_display_preserves_full_path() {
105 let path = "/tmp/egglog/full/path/example.egg".to_string();
106 let span = Span::Egglog(Arc::new(EgglogSpan {
107 file: Arc::new(SrcFile {
108 name: Some(path.clone()),
109 contents: "(bad)".to_string(),
110 }),
111 i: 0,
112 j: 5,
113 }));
114
115 assert_eq!(span.to_string(), format!("In 1:1-5 of {path}: (bad)"));
116 }
117}