turtle/interpreter/
source.rs1use crate::parser::Rule;
2use ansi_term::{Color, Style};
3use pest::iterators::Pair;
4use std::fmt;
5
6use crate::Locker;
7
8#[derive(Debug, Clone)]
9pub struct Source {
10 text: String,
11 location: String,
12}
13
14impl Source {
15 pub fn new(text: String, location: String) -> Self {
16 Self { text, location }
17 }
18}
19
20#[derive(Debug, Clone)]
21pub struct SourcePosition {
22 start_pos: usize,
23 end_pos: usize,
24 text: Locker<Source>,
25}
26
27impl SourcePosition {
28 pub fn new(start_pos: usize, end_pos: usize, text: Locker<Source>) -> Self {
29 Self {
30 start_pos,
31 end_pos,
32 text,
33 }
34 }
35
36 pub fn from_pair(pair: &Pair<'_, Rule>, source: &Locker<Source>) -> Self {
37 Self::new(
38 pair.as_span().start_pos().pos(),
39 pair.as_span().end_pos().pos(),
40 source.clone(),
41 )
42 }
43
44 pub fn location(&self) -> Option<String> {
45 match self.text.read() {
46 Ok(text) => Some(text.location.clone()),
47 Err(_) => None,
48 }
49 }
50}
51
52impl fmt::Display for SourcePosition {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54 let source = match self.text.read() {
55 Ok(text) => text,
56 Err(_) => return Err(fmt::Error),
57 };
58 let line_number =
59 &source.text[0..self.start_pos]
60 .chars()
61 .fold(0, |acc, c| match c == '\n' {
62 true => acc + 1,
63 false => acc,
64 })
65 + 1;
66
67 let lines = source.text.split('\n');
68
69 let mut relevant_lines_formatted: Vec<(usize, String)> = Vec::new();
70
71 let mut chars_seen = 0;
72 for (i, line) in lines.enumerate() {
73 let eol_pos = chars_seen + line.len() + 1;
74 if self.start_pos < eol_pos && self.end_pos > chars_seen {
75 let mut inner_start_pos: isize = self.start_pos as isize - chars_seen as isize;
76 if inner_start_pos < 0 {
77 inner_start_pos = 0;
78 }
79
80 let mut inner_end_pos = self.end_pos - chars_seen;
81 if inner_end_pos > line.len() {
82 inner_end_pos = line.len();
83 }
84 if inner_start_pos as usize != inner_end_pos && !line.is_empty() {
85 relevant_lines_formatted.push((
86 i + 1,
87 format!(
88 "{}{}{}",
89 &line[0..inner_start_pos as usize],
90 Color::Purple
91 .paint(&line[inner_start_pos as usize..inner_end_pos as usize]),
92 &line[inner_end_pos..]
93 ),
94 ));
95 }
96 }
97 chars_seen += line.len() + 1;
98 }
99 fn indent(n: usize) -> String {
100 String::from_utf8(vec![b' '; n]).unwrap()
101 }
102
103 let mut indentation = format!("{}", line_number).len() + 2;
104 if indentation < 6 {
105 indentation = 6;
106 }
107
108 writeln!(
109 f,
110 "{}{} {}",
111 indent(indentation),
112 Color::Blue.bold().paint("├"),
113 Style::default()
114 .dimmed()
115 .paint(format!("{}:{} ↴", source.location, line_number)),
116 )?;
117
118 for (line_no, line) in relevant_lines_formatted {
119 let line_no_str = format!("{}", line_no);
120 let line_no_indentation = indent(indentation - line_no_str.len() - 1);
121 writeln!(
122 f,
123 "{}{} {}",
124 line_no_indentation,
125 Color::Blue.bold().paint(format!("{} │", line_no_str)),
126 line
127 )?;
128 }
129 write!(f, "")
130 }
131}
132
133impl Source {
134 pub fn location(&self) -> &str {
135 &self.location
136 }
137}