1use std::sync::Arc;
2
3type Pos = usize;
4
5#[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#[derive(Clone, Debug, PartialEq, Eq)]
62pub struct LineCol(usize, usize);
63
64impl LineCol {
65 pub fn line(&self) -> usize {
67 self.0 + 1
68 }
69
70 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#[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 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 pub fn start(&self) -> LineCol {
120 self.source_info.linelens.linecol(self.start)
121 }
122
123 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
137pub 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 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}