hemtt_preprocessor/
map.rs

1use std::path::PathBuf;
2
3use hemtt_tokens::{Symbol, Token};
4use serde::Serialize;
5
6/// Output of preprocessing a file
7pub struct Processed {
8    sources: Vec<(String, String)>,
9    mappings: Vec<Vec<Mapping>>,
10    output: String,
11}
12
13impl Processed {
14    #[must_use]
15    /// Get the output of preprocessing a file
16    pub fn output(&self) -> &str {
17        &self.output
18    }
19
20    #[must_use]
21    /// Get the source map for the processed file
22    /// Work in progress, does not produce a valid source map yet
23    ///
24    /// # Panics
25    /// Panics if the processed file is not in the same directory as the source file
26    pub fn get_source_map(&self, processed: PathBuf) -> String {
27        #[derive(Serialize)]
28        struct Intermediate {
29            version: u8,
30            file: PathBuf,
31            sources: Vec<String>,
32            names: Vec<()>,
33            mappings: Vec<Vec<(usize, usize, usize, usize)>>,
34        }
35        serde_json::to_string(&Intermediate {
36            version: 3,
37            names: Vec::new(),
38            file: processed,
39            sources: self.sources.iter().map(|(path, _)| path.clone()).collect(),
40            mappings: {
41                self.mappings
42                    .iter()
43                    .map(|o| {
44                        o.iter()
45                            .map(|i| {
46                                (
47                                    i.processed_column,
48                                    i.source,
49                                    i.original_line,
50                                    i.original_column,
51                                )
52                            })
53                            .collect::<Vec<(usize, usize, usize, usize)>>()
54                    })
55                    .collect::<Vec<Vec<(usize, usize, usize, usize)>>>()
56            },
57        })
58        .unwrap()
59    }
60}
61
62#[allow(clippy::fallible_impl_from)] // TODO
63impl From<Vec<Token>> for Processed {
64    fn from(tokens: Vec<Token>) -> Self {
65        let mut sources: Vec<(String, String)> = Vec::new();
66        let mut mappings = Vec::new();
67        let mut output = String::new();
68        let mut mapping = Vec::new();
69        let mut next_offset = 0;
70        for token in tokens {
71            let source = token.source();
72            let symbol = token.symbol();
73            let render = symbol.output();
74            if render.is_empty() {
75                continue;
76            }
77            let original_line = source.start().1 .0;
78            let original_column = source.start().1 .1;
79            let source = sources
80                .iter()
81                .position(|(name, _)| name == &source.path().to_string())
82                .map_or_else(
83                    || {
84                        sources.push((source.path().to_string(), {
85                            if source.path() == "%builtin%" {
86                                String::new()
87                            } else {
88                                std::fs::read_to_string(source.path()).unwrap()
89                            }
90                        }));
91                        sources.len() - 1
92                    },
93                    |index| index,
94                );
95            if symbol == &Symbol::Newline {
96                mappings.push(mapping);
97                mapping = Vec::new();
98                next_offset = 0;
99            } else {
100                mapping.push(Mapping {
101                    processed_column: next_offset,
102                    source,
103                    original_line,
104                    original_column,
105                });
106                next_offset = render.len();
107            }
108            output.push_str(render.as_str());
109        }
110        Self {
111            sources,
112            mappings,
113            output,
114        }
115    }
116}
117
118/// Mapping of a processed token to its source
119pub struct Mapping {
120    processed_column: usize,
121    source: usize,
122    original_line: usize,
123    original_column: usize,
124}
125
126impl Mapping {
127    #[must_use]
128    /// Get the column of the processed token
129    pub const fn processed_column(&self) -> usize {
130        self.processed_column
131    }
132
133    #[must_use]
134    /// Get the source of the processed token
135    pub const fn source(&self) -> usize {
136        self.source
137    }
138
139    #[must_use]
140    /// Get the line of the original token
141    pub const fn original_line(&self) -> usize {
142        self.original_line
143    }
144
145    #[must_use]
146    /// Get the column of the original token
147    pub const fn original_column(&self) -> usize {
148        self.original_column
149    }
150}