heraclitus_compiler/compiling_rules/region.rs
1use std::collections::HashMap;
2
3#[cfg(feature = "serde")]
4use serde::{Serialize, Deserialize};
5
6/// This is a type of a map that is generated by `generate_region_map` method of region's
7pub type RegionMap = HashMap<String,Region>;
8
9/// Convenience macro that creates regions
10///
11/// This macro can create four types of region
12/// 1. Global region
13/// 2. Simple region with extendable options
14/// 3. Region with extendable options and interpolation modules
15/// 4. Region with extendable options that references (has the same interpolations as) some other region by id
16///
17/// # Example
18/// ## 1. Global Region
19/// Global region is the foundation for your entire region tree.
20/// You shall use this region only as a base for your region structure.
21/// This region's id is called "global" and should remain unique.
22/// ```
23/// # use heraclitus_compiler::prelude::*;
24/// reg![
25/// // insert other regions here
26/// ];
27/// ```
28///
29/// ## 2. Simple Region
30/// This is a region that does not interpolate anything inside.
31/// It can be extended with additional options such as:
32/// - `tokenize`
33/// - `allow_unclosed_region`
34/// - `singleline`
35///
36/// ```
37/// # use heraclitus_compiler::prelude::*;
38/// reg!(str as "string literal" => {
39/// begin: "'",
40/// end: "'",
41/// singleline: true
42/// });
43/// ```
44///
45/// ## 3. Region with Interpolations
46/// This is a region that can have multiple interpolations of other regions
47/// ```
48/// # use heraclitus_compiler::prelude::*;
49/// reg!(string as "string literal" => {
50/// begin: "'",
51/// end: "'"
52/// } => [
53/// // reg!(...)
54/// ]);
55/// ```
56///
57/// ## 4. Region Reference
58/// This is a region that as interpolation references other region.
59///
60/// ```
61/// # use heraclitus_compiler::prelude::*;
62/// reg!(string_interp as "String Interpolation" => {
63/// begin: "${",
64/// end: "}",
65/// tokenize: true
66/// } ref global);
67/// ```
68#[macro_export]
69macro_rules! reg {
70 ($id:tt as $name:expr => {begin: $begin:expr, end: $end:expr $(, $option:tt: $value:expr)*}) => ({
71 #[allow(unused_mut)]
72 let mut region = Region::new(stringify!($id), $name, $begin, $end, vec![], None);
73 $(region.$option = $value;)*
74 region
75 });
76 ($id:tt as $name:expr => {begin: $begin:expr, end: $end:expr $(, $option:tt: $value:expr)*} => [$($exp:expr),*]) => ({
77 #[allow(unused_mut)]
78 let mut region = Region::new(stringify!($id), $name, $begin, $end, vec![$($exp),*], None);
79 $(region.$option = $value;)*
80 region
81 });
82 ($id:tt as $name:expr => {begin: $begin:expr, end: $end:expr $(, $option:tt: $value:expr)*} ref $reference:expr) => ({
83 #[allow(unused_mut)]
84 let mut region = Region::new(stringify!($id), $name, $begin, $end, vec![], Some(stringify!($reference)));
85 $(region.$option = $value;)*
86 region
87 });
88 ($($expr:expr),*) => (
89 Region::new_global(vec![$($expr),*])
90 );
91}
92
93/// Structure that describes isolated text that should not be tokenized such as string or comment
94///
95/// Region can be used to create a way to describe a form of region in your code
96/// that should not be parsed. Additionally you can create interpolation rules in order
97/// to interpolate code inside.
98///
99/// # Macro
100/// Creating regions may be too verbose at times hence you can use defined macro to create regions
101///
102/// # Example
103/// ```
104/// # use heraclitus_compiler::prelude::*;
105/// reg!(str as "string literal" => {
106/// begin: "'",
107/// end: "'"
108/// });
109/// ```
110/// You can extend region with multiple options.
111/// The recommended options are:
112/// - `tokenize`
113/// - `allow_unclosed_region`
114/// - `singleline`
115#[derive(Debug, PartialEq, Clone)]
116#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
117pub struct Region {
118 /// identifier that will be used to reference this region in an interpolation
119 pub id: String,
120 /// human-readable (preferebly all small caps) name of this region
121 pub name: String,
122 /// String that determines beginning of this region
123 pub begin: String,
124 /// String that determines end of this region
125 pub end: String,
126 /// Vector of region interpolations
127 pub interp: Vec<Region>,
128 /// This field determines if the contents
129 /// of the region should be tokenized
130 pub tokenize: bool,
131 /// This field can allow to leave region
132 /// unclosed after parsing has finished
133 pub allow_unclosed_region: bool,
134 /// Determines if this region is the global context
135 pub global: bool,
136 /// Region can be a reference to some other region
137 pub references: Option<String>,
138 /// Determines if region cannot go past the new line character
139 pub singleline: bool,
140 /// Whether to ignore escaped characters for this region only
141 pub ignore_escaped: bool,
142}
143
144impl Region {
145 /// Create a new region by scratch
146 pub fn new<T: AsRef<str>>(id: T, name: T, begin: T, end: T, interp: Vec<Region>, references: Option<T>) -> Self {
147 Region {
148 id: String::from(id.as_ref()),
149 name: String::from(name.as_ref()),
150 begin: String::from(begin.as_ref()),
151 end: String::from(end.as_ref()),
152 interp,
153 tokenize: false,
154 allow_unclosed_region: false,
155 global: false,
156 singleline: false,
157 references: references.map(|value| String::from(value.as_ref())),
158 ignore_escaped: false
159 }
160 }
161
162 /// Create a new global region
163 pub fn new_global(interp: Vec<Region>) -> Region {
164 let mut reg = Region::new("global", "Global context", "", "", interp, None);
165 reg.allow_unclosed_region = true;
166 reg.global = true;
167 reg.tokenize = true;
168 reg
169 }
170
171 /// Generate a region for region handler
172 ///
173 /// This functionality is required if we want to reference other regions.
174 /// It is not supposed to be used explicitly in the code, however this can be used
175 /// to debug complex region dependency tree.
176 pub fn generate_region_map(&self) -> RegionMap {
177 pub fn generate_region_rec(this: Region, mut map: RegionMap) -> RegionMap {
178 map.insert(this.id.clone(), this.clone());
179 for child in this.interp.iter() {
180 map = generate_region_rec(child.clone(), map);
181 }
182 map
183 }
184 generate_region_rec(self.clone(), HashMap::new())
185 }
186}
187
188#[cfg(test)]
189mod test {
190 use std::collections::HashMap;
191 use super::{ Region, RegionMap };
192
193 #[test]
194 fn region_parses_correctly() {
195 let expected = Region {
196 id: format!("global"),
197 name: format!("Global context"),
198 begin: format!(""),
199 end: format!(""),
200 interp: vec![
201 Region {
202 id: format!("string"),
203 name: format!("String Literal"),
204 begin: format!("'"),
205 end: format!("'"),
206 interp: vec![
207 Region {
208 id: format!("string_interp"),
209 name: format!("String Interpolation"),
210 begin: format!("${{"),
211 end: format!("}}"),
212 interp: vec![],
213 tokenize: true,
214 allow_unclosed_region: false,
215 singleline: false,
216 global: false,
217 references: Some(format!("global")),
218 ignore_escaped: false,
219 }],
220 tokenize: false,
221 allow_unclosed_region: false,
222 singleline: false,
223 global: false,
224 references: None,
225 ignore_escaped: false
226 }],
227 tokenize: true,
228 allow_unclosed_region: true,
229 global: true,
230 singleline: false,
231 references: None,
232 ignore_escaped: false
233 };
234 let result = reg![
235 reg!(string as "String Literal" => {
236 begin: "'",
237 end: "'"
238 } => [
239 reg!(string_interp as "String Interpolation" => {
240 begin: "${",
241 end: "}",
242 tokenize: true
243 } ref global)
244 ])
245 ];
246 assert_eq!(expected, result);
247 }
248
249 #[test]
250 fn region_map_correctly() {
251 let mut expected: RegionMap = HashMap::new();
252 expected.insert("string_interp".to_string(), Region {
253 id: "string_interp".to_string(),
254 name: "String Interpolation".to_string(),
255 begin: "${".to_string(),
256 end: "}".to_string(),
257 interp: vec![],
258 tokenize: true,
259 allow_unclosed_region: false,
260 global: false,
261 singleline: false,
262 references: Some(
263 "global".to_string(),
264 ),
265 ignore_escaped: false,
266 });
267 expected.insert("global".to_string(), Region {
268 id: "global".to_string(),
269 name: "Global context".to_string(),
270 begin: "".to_string(),
271 end: "".to_string(),
272 interp: vec![
273 Region {
274 id: "string".to_string(),
275 name: "String Literal".to_string(),
276 begin: "'".to_string(),
277 end: "'".to_string(),
278 interp: vec![
279 Region {
280 id: "string_interp".to_string(),
281 name: "String Interpolation".to_string(),
282 begin: "${".to_string(),
283 end: "}".to_string(),
284 interp: vec![],
285 tokenize: true,
286 allow_unclosed_region: false,
287 global: false,
288 singleline: false,
289 references: Some(
290 "global".to_string(),
291 ),
292 ignore_escaped: false,
293 },
294 ],
295 tokenize: false,
296 allow_unclosed_region: false,
297 global: false,
298 singleline: false,
299 references: None,
300 ignore_escaped: false,
301 },
302 ],
303 tokenize: true,
304 allow_unclosed_region: true,
305 global: true,
306 singleline: false,
307 references: None,
308 ignore_escaped: false,
309 });
310 expected.insert("string".to_string(), Region {
311 id: "string".to_string(),
312 name: "String Literal".to_string(),
313 begin: "'".to_string(),
314 end: "'".to_string(),
315 interp: vec![
316 Region {
317 id: "string_interp".to_string(),
318 name: "String Interpolation".to_string(),
319 begin: "${".to_string(),
320 end: "}".to_string(),
321 interp: vec![],
322 tokenize: true,
323 allow_unclosed_region: false,
324 global: false,
325 singleline: false,
326 references: Some(
327 "global".to_string(),
328 ),
329 ignore_escaped: false,
330 },
331 ],
332 tokenize: false,
333 allow_unclosed_region: false,
334 global: false,
335 singleline: false,
336 references: None,
337 ignore_escaped: false,
338 });
339 let region = reg![
340 reg!(string as "String Literal" => {
341 begin: "'",
342 end: "'"
343 } => [
344 reg!(string_interp as "String Interpolation" => {
345 begin: "${",
346 end: "}",
347 tokenize: true
348 } ref global)
349 ])
350 ];
351 let result = region.generate_region_map();
352 assert_eq!(expected, result);
353 }
354}