heraclitus-compiler 1.2.3

Compiler frontend for developing great programming languages
Documentation
use std::collections::HashMap;

/// This is a type of a map that is generated by `generate_region_map` method of region's
pub type RegionMap = HashMap<String,Region>;

/// Convenience macro that creates regions
/// 
/// This macro can create four types of region
/// 1. Global region
/// 2. Simple region with extendable options
/// 3. Region with extendable options and interpolation modules
/// 4. Region with extendable options that references (has the same interpolations as) some other region by id
/// 
/// # Example
/// ## 1. Global Region
/// Global region is the foundation for your entire region tree.
/// You shall use this region only as a base for your region structure.
/// This region's id is called "global" and should remain unique.
/// ```
/// # use heraclitus_compiler::prelude::*;
/// reg![
///     // insert other regions here
/// ];
/// ```
/// 
/// ## 2. Simple Region
/// This is a region that does not interpolate anything inside.
/// It can be extended with additional options such as:
///  - `tokenize`
///  - `allow_left_open`
///  - `singleline`
/// 
/// ```
/// # use heraclitus_compiler::prelude::*;
/// reg!(str as "string literal" => {
///     begin: "'",
///     end: "'",
///     singleline: true
/// });
/// ```
/// 
/// ## 3. Region with Interpolations
/// This is a region that can have multiple interpolations of other regions
/// ```
/// # use heraclitus_compiler::prelude::*;
/// reg!(string as "string literal" => {
///     begin: "'",
///     end: "'"
/// } => [
///     // reg!(...)
/// ]);
/// ```
/// 
/// ## 4. Region Reference
/// This is a region that as interpolation references other region.
/// 
/// ```
/// # use heraclitus_compiler::prelude::*;
/// reg!(string_interp as "String Interpolation" => {
///     begin: "${",
///     end: "}",
///     tokenize: true
/// } ref global);
/// ```
#[macro_export]
macro_rules! reg {
    ($id:tt as $name:expr => {begin: $begin:expr, end: $end:expr $(, $option:tt: $value:expr)*}) => ({
        #[allow(unused_mut)]
        let mut region = Region::new(stringify!($id), $name, $begin, $end, vec![], None);
        $(region.$option = $value;)*
        region
    });
    ($id:tt as $name:expr => {begin: $begin:expr, end: $end:expr $(, $option:tt: $value:expr)*} => [$($exp:expr),*]) => ({
        #[allow(unused_mut)]
        let mut region = Region::new(stringify!($id), $name, $begin, $end, vec![$($exp),*], None);
        $(region.$option = $value;)*
        region
    });
    ($id:tt as $name:expr => {begin: $begin:expr, end: $end:expr $(, $option:tt: $value:expr)*} ref $reference:expr) => ({
        #[allow(unused_mut)]
        let mut region = Region::new(stringify!($id), $name, $begin, $end, vec![], Some(stringify!($reference)));
        $(region.$option = $value;)*
        region
    });
    ($($expr:expr),*) => (
        Region::new_global(vec![$($expr),*])
    );
}

/// Structure that describes isolated text that should not be tokenized such as string or comment
/// 
/// Region can be used to create a way to describe a form of region in your code
/// that should not be parsed. Additionally you can create interpolation rules in order
/// to interpolate code inside.
/// 
/// # Macro
/// Creating regions may be too verbose at times hence you can use defined macro to create regions
/// 
/// # Example
/// ```
/// # use heraclitus_compiler::prelude::*;
/// reg!(str as "string literal" => {
///     begin: "'",
///     end: "'"
/// });
/// ```
/// You can extend region with multiple options.
/// The recommended options are:
///  - `tokenize`
///  - `allow_left_open`
///  - `singleline`
#[derive(Debug, PartialEq, Clone)]
pub struct Region {
    /// identifier that will be used to reference this region in an interpolation
    pub id: String,
    /// human-readable (preferebly all small caps) name of this region
    pub name: String,
    /// String that determines beginning of this region
    pub begin: String,
    /// String that determines end of this region
    pub end: String,
    /// Vector of region interpolations
    pub interp: Vec<Region>,
    /// This field determines if the contents
    /// of the region should be tokenized
    pub tokenize: bool,
    /// This field can allow to leave region 
    /// unclosed after parsing has finished
    pub allow_left_open: bool,
    /// Determines if this region is the global context
    pub global: bool,
    /// Determines if region cannot
    /// go past the new line character
    pub references: Option<String>,
    /// Region can be a reference to some other region
    pub singleline: bool
}

impl Region {
    /// Create a new region by scratch
    pub fn new<T: AsRef<str>>(id: T, name: T, begin: T, end: T, interp: Vec<Region>, references: Option<T>) -> Self {
        Region {
            id: String::from(id.as_ref()),
            name: String::from(name.as_ref()),
            begin: String::from(begin.as_ref()),
            end: String::from(end.as_ref()),
            interp,
            tokenize: false,
            allow_left_open: false,
            global: false,
            singleline: false,
            references: match references {
                Some(value) => Some(String::from(value.as_ref())),
                None => None
            }
        }
    }

    /// Create a new global region
    pub fn new_global(interp: Vec<Region>) -> Region {
        let mut reg = Region::new("global", "Global context", "", "", interp, None);
        reg.allow_left_open = true;
        reg.global = true;
        reg.tokenize = true;
        reg
    }

    /// Generate a region for region handler
    /// 
    /// This functionality is required if we want to reference other regions.
    /// It is not supposed to be used explicitly in the code, however this can be used
    /// to debug complex region dependency tree.
    pub fn generate_region_map(&self) -> RegionMap {
        pub fn generate_region_rec(this: Region, mut map: RegionMap) -> RegionMap {
            map.insert(this.id.clone(), this.clone());
            for child in this.interp.iter() {
                map = generate_region_rec(child.clone(), map);
            }
            map
        }
        generate_region_rec(self.clone(), HashMap::new())
    }
}

#[cfg(test)]
mod test {
    use std::collections::HashMap;
    use super::{ Region, RegionMap };

    #[test]
    fn region_parses_correctly() {
        let expected = Region {
            id: format!("global"),
            name: format!("Global context"),
            begin: format!(""),
            end: format!(""),
            interp: vec![
                Region {
                    id: format!("string"),
                    name: format!("String Literal"),
                    begin: format!("'"),
                    end: format!("'"),
                    interp: vec![
                        Region {
                            id: format!("string_interp"),
                            name: format!("String Interpolation"),
                            begin: format!("${{"),
                            end: format!("}}"),
                            interp: vec![],
                            tokenize: true,
                            allow_left_open: false,
                            singleline: false,
                            global: false,
                            references: Some(format!("global"))
                        }],
                    tokenize: false,
                    allow_left_open: false,
                    singleline: false,
                    global: false,
                    references: None
                }],
            tokenize: true,
            allow_left_open: true,
            global: true,
            singleline: false,
            references: None
        };
        let result = reg![
            reg!(string as "String Literal" => {
                begin: "'",
                end: "'"
            } => [
                reg!(string_interp as "String Interpolation" => {
                    begin: "${",
                    end: "}",
                    tokenize: true
                } ref global)
            ])
        ];
        assert_eq!(expected, result);
    }

    #[test]
    fn region_map_correctly() {
        let mut expected: RegionMap = HashMap::new();
        expected.insert("string_interp".to_string(), Region {
            id: "string_interp".to_string(),
            name: "String Interpolation".to_string(),
            begin: "${".to_string(),
            end: "}".to_string(),
            interp: vec![],
            tokenize: true,
            allow_left_open: false,
            global: false,
            singleline: false,
            references: Some(
                "global".to_string(),
            ),
        });
        expected.insert("global".to_string(), Region {
                id: "global".to_string(),
                name: "Global context".to_string(),
                begin: "".to_string(),
                end: "".to_string(),
                interp: vec![
                    Region {
                        id: "string".to_string(),
                        name: "String Literal".to_string(),
                        begin: "'".to_string(),
                        end: "'".to_string(),
                        interp: vec![
                            Region {
                                id: "string_interp".to_string(),
                                name: "String Interpolation".to_string(),
                                begin: "${".to_string(),
                                end: "}".to_string(),
                                interp: vec![],
                                tokenize: true,
                                allow_left_open: false,
                                global: false,
                                singleline: false,
                                references: Some(
                                    "global".to_string(),
                                ),
                            },
                        ],
                        tokenize: false,
                        allow_left_open: false,
                        global: false,
                        singleline: false,
                        references: None,
                    },
                ],
                tokenize: true,
                allow_left_open: true,
                global: true,
                singleline: false,
                references: None,
        });
        expected.insert("string".to_string(), Region {
            id: "string".to_string(),
            name: "String Literal".to_string(),
            begin: "'".to_string(),
            end: "'".to_string(),
            interp: vec![
                Region {
                    id: "string_interp".to_string(),
                    name: "String Interpolation".to_string(),
                    begin: "${".to_string(),
                    end: "}".to_string(),
                    interp: vec![],
                    tokenize: true,
                    allow_left_open: false,
                    global: false,
                    singleline: false,
                    references: Some(
                        "global".to_string(),
                    ),
                },
            ],
            tokenize: false,
            allow_left_open: false,
            global: false,
            singleline: false,
            references: None,
        });
        let region = reg![
            reg!(string as "String Literal" => {
                begin: "'",
                end: "'"
            } => [
                reg!(string_interp as "String Interpolation" => {
                    begin: "${",
                    end: "}",
                    tokenize: true
                } ref global)
            ])
        ];
        let result = region.generate_region_map();
        assert_eq!(expected, result);
    }
}