1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#[macro_use]
extern crate lazy_static;
extern crate regex;
use std::collections::BTreeMap;
use std::collections::BTreeSet;
use regex::Regex;
#[derive(Debug)]
pub enum Error {
RecursiveInclude(String, String, Vec<String>),
FileNotFound(Option<String>, String),
}
#[derive(Debug)]
pub struct Preprocessor<'a> {
files: BTreeMap<&'a str, &'a str>,
should_generate_source_map: bool,
}
#[derive(Debug)]
pub struct FileLine<'a> {
pub file: Option<&'a str>,
pub line: usize,
}
pub type SourceMap<'a> = Vec<FileLine<'a>>;
impl<'a> Preprocessor<'a> {
pub fn new() -> Preprocessor<'a> {
Preprocessor {
files: BTreeMap::new(),
should_generate_source_map: false,
}
}
pub fn file(mut self, path: &'a str, src: &'a str) -> Self {
self.files.insert(path, src);
self
}
pub fn generate_source_map(mut self) -> Self {
self.should_generate_source_map = true;
self
}
pub fn run(&self, src: &'a str) -> Result<(String, Option<SourceMap<'a>>), Error> {
self.run_raw(src)
.map(|(result, source_map)| (result.join("\n"), source_map))
}
pub fn run_raw(&self, src: &'a str) -> Result<(Vec<&'a str>, Option<SourceMap<'a>>), Error> {
let mut result = Vec::new();
let mut source_map = Vec::new();
self.run_recursive(
None,
src,
&mut result,
&mut source_map,
&mut Vec::new(),
&mut BTreeSet::new(),
).map(move |_| {
(
result,
if self.should_generate_source_map {
Some(source_map)
} else {
None
},
)
})
}
fn run_recursive(
&self,
name: Option<&'a str>,
src: &'a str,
result: &mut Vec<&'a str>,
source_map: &mut SourceMap<'a>,
include_stack: &mut Vec<&'a str>,
include_set: &mut BTreeSet<&'a str>,
) -> Result<(), Error> {
lazy_static! {
static ref INCLUDE_RE : Regex = Regex::new(r#"^\s*#\s*include\s+[<"](?P<file>.*)[>"]"#).expect("failed to compile INCLUDE_RE regex");
}
for line in src.lines() {
if let Some(caps) = INCLUDE_RE.captures(line) {
let cap_match = caps.name("file")
.expect("Could not find capture group with name \"file\"");
let file = cap_match.as_str();
if include_set.contains(&file) {
continue;
}
if include_stack.contains(&file) {
let name = name.expect("impossible").to_string();
let line = line.to_string();
let stack = include_stack.into_iter().map(|s| s.to_string()).collect();
return Err(Error::RecursiveInclude(name, line, stack));
}
include_stack.push(&file);
match self.files.get(file) {
Some(content) => {
self.run_recursive(
Some(file),
content,
result,
source_map,
include_stack,
include_set,
)?;
}
None => {
let name = name.map(|s| s.to_string());
let file = file.to_string();
return Err(Error::FileNotFound(name, file));
}
};
include_stack.pop();
} else {
result.push(line);
if self.should_generate_source_map {
source_map.push(FileLine {
file: name,
line: result.len(),
});
}
}
}
if let Some(name) = name {
include_set.insert(name);
}
Ok(())
}
}