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}