bitsy_lang/
loc.rs

1use std::sync::Arc;
2
3type Pos = usize;
4
5/// A [`SourceInfo`] maintains location data for parsed objects.
6/// Maintains the filename (if from a file) or the originating string (if from a string).
7/// Helps with the conversion from byte-position in the source to a [`LineCol`].
8#[derive(Clone, Debug)]
9pub struct SourceInfo {
10    source: Source,
11    linelens: LineLens,
12}
13
14impl SourceInfo {
15    pub fn unknown() -> SourceInfo {
16        SourceInfo {
17            source: Source::Unknown,
18            linelens: LineLens::from(""),
19        }
20    }
21
22    pub fn source(&self) -> &Source {
23        &self.source
24    }
25
26    pub fn from_file(filepath: &std::path::Path, contents: &str) -> SourceInfo {
27        SourceInfo {
28            source: Source::File(Arc::new(filepath.to_owned())),
29            linelens: LineLens::from(contents),
30        }
31    }
32
33    pub fn from_string(contents: &str) -> SourceInfo {
34        SourceInfo {
35            source: Source::String(Arc::new(contents.to_owned())),
36            linelens: LineLens::from(contents),
37        }
38    }
39
40    pub fn start(&self, item: &dyn HasLoc) -> LineCol {
41        self.linelens.linecol(item.loc().start)
42    }
43
44    pub fn end(&self, item: &dyn HasLoc) -> LineCol {
45        self.linelens.linecol(item.loc().end)
46    }
47
48    pub fn linecol_from(&self, pos: usize) -> LineCol {
49        self.linelens.linecol(pos)
50    }
51}
52
53#[derive(Clone, Debug)]
54pub enum Source {
55    File(Arc<std::path::PathBuf>),
56    String(Arc<String>),
57    Unknown,
58}
59
60/// A [`LineCol`] is a container for a line and column.
61#[derive(Clone, Debug, PartialEq, Eq)]
62pub struct LineCol(usize, usize);
63
64impl LineCol {
65    /// The line number. Starts with line 1.
66    pub fn line(&self) -> usize {
67        self.0 + 1
68    }
69
70    /// The column. Starts with column 1.
71    pub fn col(&self) -> usize {
72        self.1 + 1
73    }
74}
75
76impl std::fmt::Display for LineCol {
77    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
78        write!(f, "{}:{}", self.line(), self.col())
79    }
80}
81
82/// A [`Loc`] tracks the span of an object parsed from a source.
83#[derive(Clone)]
84pub struct Loc {
85    start: Pos,
86    end: Pos,
87    source_info: SourceInfo,
88}
89
90impl std::fmt::Debug for Loc {
91    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
92        match &self.source_info.source {
93            Source::File(path) => write!(f, "[{}-{}:{:?}]", self.start(), self.end(), path),
94            Source::String(s) => write!(f, "[{}-{}:{:?}]", self.start(), self.end(), String::from_utf8_lossy(&s.as_bytes()[self.start..self.end])),
95            Source::Unknown => write!(f, "[{}-{}]", self.start(), self.end()),
96        }
97    }
98}
99
100impl Loc {
101    /// When the location of something is unknown, you can use this.
102    pub fn unknown() -> Loc {
103        Loc {
104            start: 0,
105            end: 0,
106            source_info: SourceInfo::unknown(),
107        }
108    }
109
110    pub fn from(source_info: &SourceInfo, start: usize, end: usize) -> Loc {
111        Loc {
112            start,
113            end,
114            source_info: source_info.clone(),
115        }
116    }
117
118    /// The start of the span.
119    pub fn start(&self) -> LineCol {
120        self.source_info.linelens.linecol(self.start)
121    }
122
123    /// The end of the span.
124    pub fn end(&self) -> LineCol {
125        self.source_info.linelens.linecol(self.end)
126    }
127
128    pub fn source(&self) -> &str {
129        if let Source::String(source) = &self.source_info.source {
130            &source[self.start..self.end]
131        } else {
132            ""
133        }
134    }
135}
136
137/// Many objects have location information.
138/// [`HasLoc`] allows you to call [`HasLoc::loc`] to get the span information.
139pub trait HasLoc {
140    fn loc(&self) -> Loc;
141}
142
143#[derive(Clone, Debug)]
144struct LineLens(Vec<usize>);
145
146impl LineLens {
147    fn from(text: &str) -> LineLens {
148        let mut lens = vec![];
149        for line in text.split("\n") {
150            lens.push(line.len() + 1);
151        }
152        LineLens(lens)
153    }
154
155    fn linecol(&self, pos: Pos) -> LineCol {
156        let mut line = 0;
157        let mut col = pos;
158        for line_len in &self.0 {
159            if col >= *line_len {
160                col -= *line_len;
161                line += 1;
162            } else {
163                break
164            }
165        }
166        LineCol(line, col)
167    }
168}
169
170#[test]
171fn linelens() {
172    // TODO Move this to tests.
173    let text = "Hello,
174world!
175How are you?";
176
177    let linelens = LineLens::from(text);
178    assert_eq!(linelens.linecol(0).to_string(), "1:1".to_string());
179    assert_eq!(linelens.linecol(5).to_string(), "1:6".to_string());
180    assert_eq!(linelens.linecol(6).to_string(), "1:7".to_string());
181    assert_eq!(linelens.linecol(7).to_string(), "2:1".to_string());
182    assert_eq!(linelens.linecol(7).to_string(), "2:1".to_string());
183    assert_eq!(linelens.linecol(12).to_string(), "2:6".to_string());
184    assert_eq!(linelens.linecol(13).to_string(), "2:7".to_string());
185    assert_eq!(linelens.linecol(14).to_string(), "3:1".to_string());
186}