1use alloc::boxed::Box;
16use alloc::format;
17use alloc::string::{String, ToString};
18use alloc::vec::Vec;
19
20use crate::code::{ChunkId, Id, LambdaId, ModId, NodeId, PatId, StaticChunk, Text};
21use crate::Ctx;
22
23pub type Pos = u32;
25pub type Len = u32;
27pub type Line = u32;
29
30#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
33pub struct Loc(Pos);
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
39pub struct ChunkLoc(Pos);
40
41macro_rules! loc {
42 ($name:ident) => {
43 impl $name {
44 pub const NONE: Self = Self(Pos::MAX);
46
47 pub fn get(self) -> Option<Self> {
49 (self != Self::NONE).then_some(self)
50 }
51 }
52 };
53}
54
55loc!(Loc);
56loc!(ChunkLoc);
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
60pub struct CtxSpan(pub Loc, pub Len);
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
64pub struct ChunkSpan(pub ChunkLoc, pub Len);
65
66impl ChunkSpan {
67 pub const NONE: Self = ChunkSpan(ChunkLoc::NONE, 0);
69
70 pub(crate) fn new(at: Pos, len: Len) -> Self {
71 ChunkSpan(ChunkLoc(at), len)
72 }
73
74 pub fn get(self) -> Option<Self> {
76 self.0.get().map(|_| self)
77 }
78
79 pub(crate) fn in_doc(self, start: Pos) -> Option<muon::Span> {
82 let ChunkSpan(at, length) = self.get()?;
83 Some(muon::Span {
84 offset: at.0 - start,
85 length,
86 })
87 }
88
89 pub(crate) fn contains(self, other: ChunkSpan) -> bool {
92 self.0 .0 <= other.0 .0 && other.end() <= self.end()
93 }
94
95 fn end(self) -> Pos {
96 self.0 .0 + self.1
97 }
98
99 pub(crate) fn join(self, other: ChunkSpan) -> ChunkSpan {
101 if self.get().is_none() {
102 return other;
103 }
104 if other.get().is_none() {
105 return self;
106 }
107 let at = self.0 .0.min(other.0 .0);
108 ChunkSpan(ChunkLoc(at), self.end().max(other.end()) - at)
109 }
110}
111
112#[derive(Debug, Clone, PartialEq, Eq)]
115pub enum DocSource {
116 Path(Text),
118 Embedder(u64),
121 Anonymous(u32),
124}
125
126#[derive(Debug, Clone, PartialEq, Eq)]
129pub struct Doc {
130 source: DocSource,
131 start: Pos,
132 len: Len,
133 first_line: u32,
135 lines: u32,
136}
137
138impl Doc {
139 pub fn source(&self) -> &DocSource {
140 &self.source
141 }
142
143 pub fn len(&self) -> Len {
145 self.len
146 }
147
148 pub fn is_empty(&self) -> bool {
149 self.len == 0
150 }
151
152 fn end(&self) -> Pos {
153 self.start + self.len
154 }
155}
156
157#[derive(Debug, Clone, Copy, PartialEq, Eq)]
159pub struct Location {
160 pub chunk: ChunkId,
161 pub doc: u32,
163 pub pos: Pos,
165 pub line_num: Line,
166 pub line_start: Pos,
168}
169
170impl Location {
171 pub fn column(&self, text: &str) -> Option<u32> {
176 let line = text.get(self.line_start as usize..self.pos as usize)?;
177 Some(line.chars().count() as u32 + 1)
178 }
179}
180
181pub fn report(name: &str, text: &str, span: muon::Span, message: &str) -> String {
187 let mut at = (span.offset as usize).min(text.len());
188 while !text.is_char_boundary(at) {
189 at -= 1;
190 }
191 let start = text[..at].rfind('\n').map_or(0, |i| i + 1);
192 let end = text[at..].find('\n').map_or(text.len(), |i| at + i);
193 let line = text[start..end].trim_end_matches('\r');
194 let line_num = text[..start].matches('\n').count() + 1;
195 let before = &text[start..at];
196 let column = before.chars().count() + 1;
197 let indent: String = before
200 .chars()
201 .map(|c| if c == '\t' { '\t' } else { ' ' })
202 .collect();
203 let under = (at + span.length as usize).min(start + line.len()).max(at);
204 let marks = "^".repeat(text[at..under].chars().count().max(1));
205 let gutter = " ".repeat(line_num.to_string().len());
206 format!(
207 "{name}:{line_num}:{column}: {message}\n {line_num} | {line}\n {gutter} | {indent}{marks}"
208 )
209}
210
211#[derive(Debug)]
220pub(crate) struct ChunkLocs {
221 pub(crate) node_spans: Box<[ChunkSpan]>,
222 pub(crate) pat_spans: Box<[ChunkSpan]>,
223 pub(crate) lambda_spans: Box<[ChunkSpan]>,
224 pub(crate) member_spans: Box<[ChunkSpan]>,
225 pub(crate) docs: Box<[Doc]>,
226 pub(crate) line_starts: Box<[Pos]>,
228 pub(crate) base: Loc,
231}
232
233#[derive(Debug, Default)]
235pub(crate) struct DocsBuilder {
236 docs: Vec<(Option<DocSource>, Pos, Len, u32, u32)>,
239 line_starts: Vec<Pos>,
240}
241
242impl DocsBuilder {
243 pub(crate) fn add(&mut self, source: Option<DocSource>, text: &str) -> Pos {
248 let start = match self.docs.last() {
249 Some(&(_, start, len, _, _)) => start + len + 1,
250 None => 0,
251 };
252 let len = Len::try_from(text.len()).expect("a document outgrew u32");
253 start
254 .checked_add(len)
255 .expect("a chunk's documents outgrew u32");
256 let first_line = crate::code::len32(self.line_starts.len());
257 self.line_starts.push(start);
258 for (i, b) in text.bytes().enumerate() {
259 if b == b'\n' {
260 self.line_starts.push(start + i as Pos + 1);
261 }
262 }
263 let lines = crate::code::len32(self.line_starts.len()) - first_line;
264 self.docs.push((source, start, len, first_line, lines));
265 start
266 }
267
268 pub(crate) fn has_anonymous(&self) -> bool {
270 self.docs.iter().any(|d| d.0.is_none())
271 }
272
273 pub(crate) fn finish(self, made: Option<u32>) -> (Box<[Doc]>, Box<[Pos]>) {
275 let docs = self
276 .docs
277 .into_iter()
278 .map(|(source, start, len, first_line, lines)| Doc {
279 source: source.unwrap_or_else(|| {
280 DocSource::Anonymous(made.expect("an anonymous document has a number"))
281 }),
282 start,
283 len,
284 first_line,
285 lines,
286 })
287 .collect();
288 (docs, self.line_starts.into_boxed_slice())
289 }
290}
291
292impl StaticChunk {
293 pub fn documents(&self) -> &[Doc] {
296 &self.locs.docs
297 }
298
299 pub(crate) fn extent(&self) -> Pos {
302 self.locs.docs.last().map_or(0, |doc| doc.end() + 1)
303 }
304
305 pub(crate) fn locate(&self, at: ChunkLoc) -> Option<(u32, Pos, Line, Pos)> {
308 let at = at.get()?.0;
309 let i = self.locs.docs.partition_point(|doc| doc.end() < at);
310 let doc = self.locs.docs.get(i)?;
311 if at < doc.start {
312 return None;
313 }
314 let first = doc.first_line as usize;
315 let starts = &self.locs.line_starts[first..first + doc.lines as usize];
316 let line = starts.partition_point(|&s| s <= at) - 1;
319 Some((
320 i as u32,
321 at - doc.start,
322 line as Line + 1,
323 starts[line] - doc.start,
324 ))
325 }
326
327 pub fn node_ids(&self) -> impl Iterator<Item = NodeId> {
329 (0..self.locs.node_spans.len()).map(NodeId::at)
330 }
331
332 pub fn pat_ids(&self) -> impl Iterator<Item = PatId> {
334 (0..self.locs.pat_spans.len()).map(PatId::at)
335 }
336
337 pub fn lambda_ids(&self) -> impl Iterator<Item = LambdaId> {
339 (0..self.locs.lambda_spans.len()).map(LambdaId::at)
340 }
341
342 pub fn node_span(&self, id: NodeId) -> Option<ChunkSpan> {
344 self.locs.node_spans[id.index()].get()
345 }
346
347 pub fn pat_span(&self, id: PatId) -> Option<ChunkSpan> {
349 self.locs.pat_spans[id.index()].get()
350 }
351
352 pub fn lambda_span(&self, id: LambdaId) -> Option<ChunkSpan> {
354 self.locs.lambda_spans[id.index()].get()
355 }
356
357 pub fn member_span(&self, m: ModId, index: u32) -> Option<ChunkSpan> {
360 let members = self.module(m).members;
361 if index >= members.len {
362 return None;
363 }
364 self.locs.member_spans[members.first.index() + index as usize].get()
365 }
366}
367
368impl Ctx {
373 pub fn chunk_bases(&self) -> Vec<Loc> {
377 self.chunks.borrow().iter().map(|c| c.locs.base).collect()
378 }
379
380 pub(crate) fn next_base(&self) -> Loc {
382 match self.chunks.borrow().last() {
383 Some(last) => Loc(last.locs.base.0 + last.extent()),
384 None => Loc(0),
385 }
386 }
387
388 pub fn ctx_span(&self, chunk: ChunkId, span: ChunkSpan) -> Option<CtxSpan> {
390 let span = span.get()?;
391 let base = self.chunk(chunk).locs.base;
392 Some(CtxSpan(Loc(base.0 + span.0 .0), span.1))
393 }
394
395 pub fn node_span(&self, chunk: ChunkId, id: NodeId) -> Option<CtxSpan> {
397 let span = self.chunk(chunk).node_span(id)?;
398 self.ctx_span(chunk, span)
399 }
400
401 pub fn pat_span(&self, chunk: ChunkId, id: PatId) -> Option<CtxSpan> {
403 let span = self.chunk(chunk).pat_span(id)?;
404 self.ctx_span(chunk, span)
405 }
406
407 pub fn lambda_span(&self, chunk: ChunkId, id: LambdaId) -> Option<CtxSpan> {
409 let span = self.chunk(chunk).lambda_span(id)?;
410 self.ctx_span(chunk, span)
411 }
412
413 pub fn member_span(&self, chunk: ChunkId, m: ModId, index: u32) -> Option<CtxSpan> {
416 let span = self.chunk(chunk).member_span(m, index)?;
417 self.ctx_span(chunk, span)
418 }
419
420 pub fn locate(&self, at: Loc) -> Option<Location> {
424 let at = at.get()?;
425 let chunks = self.chunks.borrow();
426 let mut i = chunks.partition_point(|chunk| chunk.locs.base <= at);
427 while i > 0 {
429 i -= 1;
430 let chunk = &chunks[i];
431 if chunk.locs.docs.is_empty() {
432 continue;
433 }
434 let (doc, pos, line_num, line_start) =
435 chunk.locate(ChunkLoc(at.0 - chunk.locs.base.0))?;
436 return Some(Location {
437 chunk: ChunkId::at(i),
438 doc,
439 pos,
440 line_num,
441 line_start,
442 });
443 }
444 None
445 }
446}