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#[cfg(feature = "std")]
21use std::path::Path;
22
23use crate as rune;
24#[cfg(feature = "std")]
25use crate::alloc::borrow::Cow;
26use crate::alloc::prelude::*;
27use crate::alloc::{self, Box};
28use crate::ast::Span;
29#[cfg(feature = "emit")]
30use crate::termcolor::{self, WriteColor};
31
32#[derive(Debug)]
34pub struct FromPathError {
35 kind: FromPathErrorKind,
36}
37
38impl From<alloc::Error> for FromPathError {
39 fn from(error: alloc::Error) -> Self {
40 Self {
41 kind: FromPathErrorKind::Alloc(error),
42 }
43 }
44}
45
46#[cfg(feature = "std")]
47impl From<std::io::Error> for FromPathError {
48 fn from(error: std::io::Error) -> Self {
49 Self {
50 kind: FromPathErrorKind::Io(error),
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 #[cfg(feature = "std")]
91 path: Option<Box<Path>>,
92 line_starts: Box<[usize]>,
94}
95
96impl Source {
97 pub fn new(name: impl AsRef<str>, source: impl AsRef<str>) -> alloc::Result<Self> {
99 let name = Box::try_from(name.as_ref())?;
100 let source = source.as_ref();
101 let line_starts = line_starts(source).try_collect::<Box<[_]>>()?;
102
103 Ok(Self {
104 name: SourceName::Name(name),
105 source: source.try_into()?,
106 #[cfg(feature = "std")]
107 path: None,
108 line_starts,
109 })
110 }
111
112 pub fn memory(source: impl AsRef<str>) -> alloc::Result<Self> {
124 let source = source.as_ref();
125 let line_starts = line_starts(source).try_collect::<Box<[_]>>()?;
126
127 Ok(Self {
128 name: SourceName::Memory,
129 source: source.try_into()?,
130 #[cfg(feature = "std")]
131 path: None,
132 line_starts,
133 })
134 }
135
136 #[cfg(feature = "std")]
138 #[cfg_attr(rune_docsrs, doc(cfg(feature = "std")))]
139 pub fn from_path(path: impl AsRef<Path>) -> Result<Self, FromPathError> {
140 let name = Box::try_from(Cow::try_from(path.as_ref().to_string_lossy())?)?;
141 let source = Box::try_from(std::fs::read_to_string(path.as_ref())?)?;
142 let path = Some(path.as_ref().try_into()?);
143 let line_starts = line_starts(source.as_ref()).try_collect::<Box<[_]>>()?;
144
145 Ok(Self {
146 name: SourceName::Name(name),
147 source,
148 path,
149 line_starts,
150 })
151 }
152
153 #[cfg(feature = "std")]
167 #[cfg_attr(rune_docsrs, doc(cfg(feature = "std")))]
168 pub fn with_path(
169 name: impl AsRef<str>,
170 source: impl AsRef<str>,
171 path: impl AsRef<Path>,
172 ) -> alloc::Result<Self> {
173 let name = Box::try_from(name.as_ref())?;
174 let source = Box::try_from(source.as_ref())?;
175 let path = Some(path.as_ref().try_into()?);
176 let line_starts = line_starts(source.as_ref()).try_collect::<Box<[_]>>()?;
177
178 Ok(Self {
179 name: SourceName::Name(name),
180 source,
181 path,
182 line_starts,
183 })
184 }
185
186 pub(crate) fn line_starts(&self) -> &[usize] {
188 &self.line_starts
189 }
190
191 pub fn name(&self) -> &str {
193 match &self.name {
194 SourceName::Memory => "<memory>",
195 SourceName::Name(name) => name,
196 }
197 }
198
199 pub(crate) fn get<I>(&self, i: I) -> Option<&I::Output>
201 where
202 I: slice::SliceIndex<str>,
203 {
204 self.source.get(i)
205 }
206
207 pub(crate) fn as_str(&self) -> &str {
209 &self.source
210 }
211
212 #[cfg(feature = "std")]
226 #[cfg_attr(rune_docsrs, doc(cfg(feature = "std")))]
227 pub fn path(&self) -> Option<&Path> {
228 self.path.as_deref()
229 }
230
231 pub fn find_line_column(&self, position: usize) -> (usize, usize) {
238 let (line, offset, rest) = self.position(position);
239 let col = rest.char_indices().take_while(|&(n, _)| n < offset).count();
240 (line, col)
241 }
242
243 pub fn find_utf16cu_line_column(&self, position: usize) -> (usize, usize) {
251 let (line, offset, rest) = self.position(position);
252
253 let col = rest
254 .char_indices()
255 .flat_map(|(n, c)| (n < offset).then(|| c.encode_utf16(&mut [0u16; 2]).len()))
256 .sum();
257
258 (line, col)
259 }
260
261 pub fn source_line(&self, span: Span) -> Option<SourceLine<'_>> {
263 let (line, column, text, _span) = line_for(self, span)?;
264
265 Some(SourceLine {
266 #[cfg(feature = "emit")]
267 name: self.name(),
268 line,
269 column,
270 text,
271 #[cfg(feature = "emit")]
272 span: _span,
273 })
274 }
275
276 #[cfg(feature = "emit")]
278 pub(crate) fn line_index(&self, byte_index: usize) -> usize {
279 self.line_starts
280 .binary_search(&byte_index)
281 .unwrap_or_else(|next_line| next_line.saturating_sub(1))
282 }
283
284 #[cfg(feature = "emit")]
286 pub(crate) fn line_range(&self, line_index: usize) -> Option<Range<usize>> {
287 let line_start = self.line_start(line_index)?;
288 let next_line_start = self.line_start(line_index.saturating_add(1))?;
289 Some(line_start..next_line_start)
290 }
291
292 #[cfg(feature = "emit")]
294 pub(crate) fn line_count(&self) -> usize {
295 self.line_starts.len()
296 }
297
298 #[cfg(feature = "emit")]
300 pub(crate) fn line(&self, span: Span) -> Option<(usize, usize, [&str; 3])> {
301 let from = span.range();
302 let (lin, col) = self.find_line_column(from.start);
303 let line = self.line_range(lin)?;
304
305 let start = from.start.checked_sub(line.start)?;
306 let end = from.end.checked_sub(line.start)?;
307
308 let text = self.source.get(line)?;
309 let prefix = text.get(..start)?;
310 let mid = text.get(start..end)?;
311 let suffix = text.get(end..)?;
312
313 Some((lin, col, [prefix, mid, suffix]))
314 }
315
316 fn position(&self, offset: usize) -> (usize, usize, &str) {
317 if offset == 0 {
318 return Default::default();
319 }
320
321 let line = match self.line_starts.binary_search(&offset) {
322 Ok(exact) => exact,
323 Err(0) => return Default::default(),
324 Err(n) => n - 1,
325 };
326
327 let line_start = self.line_starts[line];
328
329 let rest = &self.source[line_start..];
330 let offset = offset.saturating_sub(line_start);
331 (line, offset, rest)
332 }
333
334 #[cfg(feature = "emit")]
335 fn line_start(&self, line_index: usize) -> Option<usize> {
336 match line_index.cmp(&self.line_starts.len()) {
337 cmp::Ordering::Less => self.line_starts.get(line_index).copied(),
338 cmp::Ordering::Equal => Some(self.source.as_ref().len()),
339 cmp::Ordering::Greater => None,
340 }
341 }
342
343 pub(crate) fn len(&self) -> usize {
344 self.source.len()
345 }
346}
347
348impl fmt::Debug for Source {
349 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
350 let mut st = f.debug_struct("Source");
351 st.field("name", &self.name);
352 #[cfg(feature = "std")]
353 st.field("path", &self.path);
354 st.finish()
355 }
356}
357
358pub struct SourceLine<'a> {
360 #[cfg(feature = "emit")]
361 name: &'a str,
362 pub line: usize,
364 pub column: usize,
366 pub text: &'a str,
368 #[cfg(feature = "emit")]
369 span: Span,
370}
371
372impl SourceLine<'_> {
373 #[cfg(feature = "emit")]
375 pub(crate) fn write(&self, o: &mut dyn WriteColor) -> io::Result<()> {
376 let mut highlight = termcolor::ColorSpec::new();
377 highlight.set_fg(Some(termcolor::Color::Yellow));
378
379 let mut new_line = termcolor::ColorSpec::new();
380 new_line.set_fg(Some(termcolor::Color::Red));
381
382 let text = self.text.trim_end();
383 let end = self.span.end.into_usize().min(text.len());
384
385 let before = &text[0..self.span.start.into_usize()].trim_start();
386 let inner = &text[self.span.start.into_usize()..end];
387 let after = &text[end..];
388
389 {
390 let name = self.name;
391 let line = self.line + 1;
392 let start = self.column + 1;
393 let end = start + inner.chars().count();
394 write!(o, "{name}:{line}:{start}-{end}: ")?;
395 }
396
397 write!(o, "{before}")?;
398 o.set_color(&highlight)?;
399 write!(o, "{inner}")?;
400 o.reset()?;
401 write!(o, "{after}")?;
402
403 if self.span.end != end {
404 o.set_color(&new_line)?;
405 write!(o, "\\n")?;
406 o.reset()?;
407 }
408
409 Ok(())
410 }
411}
412
413#[derive(Default, Debug, TryClone, PartialEq, Eq)]
415enum SourceName {
416 #[default]
419 Memory,
420 Name(Box<str>),
422}
423
424#[inline(always)]
425fn line_starts(source: &str) -> impl Iterator<Item = usize> + '_ {
426 iter::once(0).chain(source.match_indices('\n').map(|(i, _)| i + 1))
427}
428
429fn line_for(source: &Source, span: Span) -> Option<(usize, usize, &str, Span)> {
431 let line_starts = source.line_starts();
432
433 let line = match line_starts.binary_search(&span.start.into_usize()) {
434 Ok(n) => n,
435 Err(n) => n.saturating_sub(1),
436 };
437
438 let start = *line_starts.get(line)?;
439 let end = line.checked_add(1)?;
440
441 let s = if let Some(end) = line_starts.get(end) {
442 source.get(start..*end)?
443 } else {
444 source.get(start..)?
445 };
446
447 let line_end = span.start.into_usize().saturating_sub(start);
448
449 let column = s
450 .get(..line_end)
451 .into_iter()
452 .flat_map(|s| s.chars())
453 .count();
454
455 let start = start.try_into().unwrap();
456
457 Some((
458 line,
459 column,
460 s,
461 Span::new(
462 span.start.saturating_sub(start),
463 span.end.saturating_sub(start),
464 ),
465 ))
466}