1#[cfg(feature = "emit")]
11use core::cmp;
12use core::fmt;
13use core::iter;
14#[cfg(feature = "emit")]
15use core::ops::Range;
16use core::slice;
17
18#[cfg(feature = "emit")]
19use std::io;
20
21use crate as rune;
22#[cfg(feature = "std")]
23use crate::alloc::borrow::Cow;
24use crate::alloc::path::Path;
25use crate::alloc::prelude::*;
26use crate::alloc::{self, Box};
27use crate::ast::Span;
28#[cfg(feature = "emit")]
29use crate::termcolor::{self, WriteColor};
30
31#[derive(Debug)]
33pub struct FromPathError {
34 kind: FromPathErrorKind,
35}
36
37impl From<alloc::Error> for FromPathError {
38 fn from(error: alloc::Error) -> Self {
39 Self {
40 kind: FromPathErrorKind::Alloc(error),
41 }
42 }
43}
44
45cfg_std! {
46 impl From<std::io::Error> for FromPathError {
47 fn from(error: std::io::Error) -> Self {
48 Self {
49 kind: FromPathErrorKind::Io(error),
50 }
51 }
52 }
53}
54
55impl fmt::Display for FromPathError {
56 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
57 match &self.kind {
58 FromPathErrorKind::Alloc(error) => error.fmt(f),
59 #[cfg(feature = "std")]
60 FromPathErrorKind::Io(error) => error.fmt(f),
61 }
62 }
63}
64
65#[derive(Debug)]
66enum FromPathErrorKind {
67 Alloc(alloc::Error),
68 #[cfg(feature = "std")]
69 Io(std::io::Error),
70}
71
72impl core::error::Error for FromPathError {
73 fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
74 match &self.kind {
75 FromPathErrorKind::Alloc(error) => Some(error),
76 #[cfg(feature = "std")]
77 FromPathErrorKind::Io(error) => Some(error),
78 }
79 }
80}
81
82#[derive(Default, TryClone)]
84pub struct Source {
85 name: SourceName,
87 source: Box<str>,
89 path: Option<Box<Path>>,
91 line_starts: Box<[usize]>,
93}
94
95impl Source {
96 pub fn new(name: impl AsRef<str>, source: impl AsRef<str>) -> alloc::Result<Self> {
98 let name = Box::try_from(name.as_ref())?;
99 let source = source.as_ref();
100 let line_starts = line_starts(source).try_collect::<Box<[_]>>()?;
101
102 Ok(Self {
103 name: SourceName::Name(name),
104 source: source.try_into()?,
105 path: None,
106 line_starts,
107 })
108 }
109
110 pub fn memory(source: impl AsRef<str>) -> alloc::Result<Self> {
122 let source = source.as_ref();
123 let line_starts = line_starts(source).try_collect::<Box<[_]>>()?;
124
125 Ok(Self {
126 name: SourceName::Memory,
127 source: source.try_into()?,
128 path: None,
129 line_starts,
130 })
131 }
132
133 cfg_std! {
134 pub fn from_path(path: impl AsRef<Path>) -> Result<Self, FromPathError> {
136 let name = Box::try_from(Cow::try_from(path.as_ref().to_string_lossy())?)?;
137 let source = Box::try_from(std::fs::read_to_string(path.as_ref())?)?;
138 let path = Some(path.as_ref().try_into()?);
139 let line_starts = line_starts(source.as_ref()).try_collect::<Box<[_]>>()?;
140
141 Ok(Self {
142 name: SourceName::Name(name),
143 source,
144 path,
145 line_starts,
146 })
147 }
148 }
149
150 pub fn with_path(
164 name: impl AsRef<str>,
165 source: impl AsRef<str>,
166 path: impl AsRef<Path>,
167 ) -> alloc::Result<Self> {
168 let name = Box::try_from(name.as_ref())?;
169 let source = Box::try_from(source.as_ref())?;
170 let path = Some(path.as_ref().try_into()?);
171 let line_starts = line_starts(source.as_ref()).try_collect::<Box<[_]>>()?;
172
173 Ok(Self {
174 name: SourceName::Name(name),
175 source,
176 path,
177 line_starts,
178 })
179 }
180
181 pub(crate) fn line_starts(&self) -> &[usize] {
183 &self.line_starts
184 }
185
186 pub fn name(&self) -> &str {
188 match &self.name {
189 SourceName::Memory => "<memory>",
190 SourceName::Name(name) => name,
191 }
192 }
193
194 pub(crate) fn get<I>(&self, i: I) -> Option<&I::Output>
196 where
197 I: slice::SliceIndex<str>,
198 {
199 self.source.get(i)
200 }
201
202 pub(crate) fn as_str(&self) -> &str {
204 &self.source
205 }
206
207 pub fn path(&self) -> Option<&Path> {
221 self.path.as_deref()
222 }
223
224 pub fn find_line_column(&self, position: usize) -> (usize, usize) {
231 let (line, offset, rest) = self.position(position);
232 let col = rest.char_indices().take_while(|&(n, _)| n < offset).count();
233 (line, col)
234 }
235
236 pub fn find_utf16cu_line_column(&self, position: usize) -> (usize, usize) {
244 let (line, offset, rest) = self.position(position);
245
246 let col = rest
247 .char_indices()
248 .flat_map(|(n, c)| (n < offset).then(|| c.encode_utf16(&mut [0u16; 2]).len()))
249 .sum();
250
251 (line, col)
252 }
253
254 pub fn source_line(&self, span: Span) -> Option<SourceLine<'_>> {
256 let (line, column, text, _span) = line_for(self, span)?;
257
258 Some(SourceLine {
259 #[cfg(feature = "emit")]
260 name: self.name(),
261 line,
262 column,
263 text,
264 #[cfg(feature = "emit")]
265 span: _span,
266 })
267 }
268
269 #[cfg(feature = "emit")]
271 pub(crate) fn line_index(&self, byte_index: usize) -> usize {
272 self.line_starts
273 .binary_search(&byte_index)
274 .unwrap_or_else(|next_line| next_line.saturating_sub(1))
275 }
276
277 #[cfg(feature = "emit")]
279 pub(crate) fn line_range(&self, line_index: usize) -> Option<Range<usize>> {
280 let line_start = self.line_start(line_index)?;
281 let next_line_start = self.line_start(line_index.saturating_add(1))?;
282 Some(line_start..next_line_start)
283 }
284
285 #[cfg(feature = "emit")]
287 pub(crate) fn line_count(&self) -> usize {
288 self.line_starts.len()
289 }
290
291 #[cfg(feature = "emit")]
293 pub(crate) fn line(&self, span: Span) -> Option<(usize, usize, [&str; 3])> {
294 let from = span.range();
295 let (lin, col) = self.find_line_column(from.start);
296 let line = self.line_range(lin)?;
297
298 let start = from.start.checked_sub(line.start)?;
299 let end = from.end.checked_sub(line.start)?;
300
301 let text = self.source.get(line)?;
302 let prefix = text.get(..start)?;
303 let mid = text.get(start..end)?;
304 let suffix = text.get(end..)?;
305
306 Some((lin, col, [prefix, mid, suffix]))
307 }
308
309 fn position(&self, offset: usize) -> (usize, usize, &str) {
310 if offset == 0 {
311 return Default::default();
312 }
313
314 let line = match self.line_starts.binary_search(&offset) {
315 Ok(exact) => exact,
316 Err(0) => return Default::default(),
317 Err(n) => n - 1,
318 };
319
320 let line_start = self.line_starts[line];
321
322 let rest = &self.source[line_start..];
323 let offset = offset.saturating_sub(line_start);
324 (line, offset, rest)
325 }
326
327 #[cfg(feature = "emit")]
328 fn line_start(&self, line_index: usize) -> Option<usize> {
329 match line_index.cmp(&self.line_starts.len()) {
330 cmp::Ordering::Less => self.line_starts.get(line_index).copied(),
331 cmp::Ordering::Equal => Some(self.source.as_ref().len()),
332 cmp::Ordering::Greater => None,
333 }
334 }
335
336 pub(crate) fn len(&self) -> usize {
337 self.source.len()
338 }
339}
340
341impl fmt::Debug for Source {
342 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
343 f.debug_struct("Source")
344 .field("name", &self.name)
345 .field("path", &self.path)
346 .finish()
347 }
348}
349
350pub struct SourceLine<'a> {
352 #[cfg(feature = "emit")]
353 name: &'a str,
354 pub line: usize,
356 pub column: usize,
358 pub text: &'a str,
360 #[cfg(feature = "emit")]
361 span: Span,
362}
363
364impl SourceLine<'_> {
365 #[cfg(feature = "emit")]
367 pub(crate) fn write(&self, o: &mut dyn WriteColor) -> io::Result<()> {
368 let mut highlight = termcolor::ColorSpec::new();
369 highlight.set_fg(Some(termcolor::Color::Yellow));
370
371 let mut new_line = termcolor::ColorSpec::new();
372 new_line.set_fg(Some(termcolor::Color::Red));
373
374 let text = self.text.trim_end();
375 let end = self.span.end.into_usize().min(text.len());
376
377 let before = &text[0..self.span.start.into_usize()].trim_start();
378 let inner = &text[self.span.start.into_usize()..end];
379 let after = &text[end..];
380
381 {
382 let name = self.name;
383 let line = self.line + 1;
384 let start = self.column + 1;
385 let end = start + inner.chars().count();
386 write!(o, "{name}:{line}:{start}-{end}: ")?;
387 }
388
389 write!(o, "{before}")?;
390 o.set_color(&highlight)?;
391 write!(o, "{inner}")?;
392 o.reset()?;
393 write!(o, "{after}")?;
394
395 if self.span.end != end {
396 o.set_color(&new_line)?;
397 write!(o, "\\n")?;
398 o.reset()?;
399 }
400
401 Ok(())
402 }
403}
404
405#[derive(Default, Debug, TryClone, PartialEq, Eq)]
407enum SourceName {
408 #[default]
411 Memory,
412 Name(Box<str>),
414}
415
416#[inline(always)]
417fn line_starts(source: &str) -> impl Iterator<Item = usize> + '_ {
418 iter::once(0).chain(source.match_indices('\n').map(|(i, _)| i + 1))
419}
420
421fn line_for(source: &Source, span: Span) -> Option<(usize, usize, &str, Span)> {
423 let line_starts = source.line_starts();
424
425 let line = match line_starts.binary_search(&span.start.into_usize()) {
426 Ok(n) => n,
427 Err(n) => n.saturating_sub(1),
428 };
429
430 let start = *line_starts.get(line)?;
431 let end = line.checked_add(1)?;
432
433 let s = if let Some(end) = line_starts.get(end) {
434 source.get(start..*end)?
435 } else {
436 source.get(start..)?
437 };
438
439 let line_end = span.start.into_usize().saturating_sub(start);
440
441 let column = s
442 .get(..line_end)
443 .into_iter()
444 .flat_map(|s| s.chars())
445 .count();
446
447 let start = start.try_into().unwrap();
448
449 Some((
450 line,
451 column,
452 s,
453 Span::new(
454 span.start.saturating_sub(start),
455 span.end.saturating_sub(start),
456 ),
457 ))
458}