turtle/interpreter/
source.rs

1use 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}