cuicui_chirp 0.12.0

A file format based on cuicui_dsl to describe bevy UIs
Documentation
use bevy::utils::HashMap;

use super::*;

macro_rules! hier {
    (@inner [$_:ident]) => {};
    (@inner [$acc:ident] $name:ident ($( $method:ident $methd_arg:literal )*)
        {$($inner:tt)*} $($($rem:tt)+)?
    ) => {
        $acc.push(Hier {
            name: stringify!($name).to_string(),
            methods: {
                let mut r = HashMap::new();
                $(r.insert(
                    stringify!($method).to_string(),
                    $methd_arg.to_string(),
                );)*
                r
            },
            code: HashMap::new(),
            children: {
                let mut r = Vec::new();
                hier!(@inner [r] $($inner)*);
                r
            },
        })
        $(; hier!(@inner $($rem)+))?
    };
    ($name:ident ($( $method:ident $methd_arg:literal )*) {$($inner:tt)*}) => {{
        #[allow(unused_mut)]
        let x = Hier {
            name: stringify!($name).to_string(),
            methods: {
                let mut r = HashMap::new();
                $(r.insert(
                    stringify!($method).to_string(),
                    $methd_arg.to_string(),
                );)*
                r
            },
            code: HashMap::new(),
            children: {
                let mut r = Vec::new();
                hier!(@inner [r] $($inner)*);
                r
            },
        };
        x
    }};
}

#[derive(Debug, Clone, PartialEq, Eq)]
struct Hier {
    name: String,
    methods: HashMap<String, String>,
    code: HashMap<String, Span>,
    children: Vec<Hier>,
}
impl Hier {
    fn insert_code(&mut self, code: &[u8], range: Span) {
        let utf8 = String::from_utf8_lossy(code).to_string();
        self.code.insert(utf8, range);
    }
    fn get_index_mut<'a>(&'a mut self, path: &[usize]) -> &'a mut Self {
        let Some((head, tail)) = path.split_first() else {
            return self;
        };
        if let Some(child) = self.children.get_mut(*head) {
            return child.get_index_mut(tail);
        } else {
            panic!("bad")
        }
    }

    fn new(name: String) -> Self {
        Self {
            name,
            methods: HashMap::new(),
            code: HashMap::new(),
            children: Vec::new(),
        }
    }
}
#[derive(Debug, PartialEq, Eq)]
struct TestState {
    hierarchy: Hier,
    current: Vec<usize>,
}
impl TestState {
    fn new() -> Self {
        Self {
            hierarchy: Hier::new(String::new()),
            current: vec![],
        }
    }
    fn set_name(&mut self, name: &[u8]) {
        let hier = self.hierarchy.get_index_mut(&self.current);
        hier.name = String::from_utf8_lossy(name).to_string();
    }
    fn set_method(&mut self, name: &[u8], args: &Arguments) {
        let hier = self.hierarchy.get_index_mut(&self.current);
        let name = String::from_utf8_lossy(name).to_string();
        let args = args.to_string();
        hier.methods.insert(name, args);
    }
}
#[derive(Debug)]
struct TestInterpreter(TestState);
impl TestInterpreter {
    fn new() -> Self {
        Self(TestState::new())
    }
}

impl<'i, 'a> Interpreter<'i, 'a> for TestInterpreter {
    fn code(&mut self, (code, range): (&[u8], Span)) {
        let current = self.0.hierarchy.get_index_mut(&self.0.current);
        current.insert_code(code, range);
    }

    fn set_name(&mut self, (name, _): Name) {
        self.0.set_name(name);
    }

    fn complete_children(&mut self) {
        if let Some(last) = self.0.current.last_mut() {
            *last += 1;
        }
    }

    fn method(&mut self, (name, _): Name, args: &Arguments) {
        self.0.set_method(name, args);
    }

    fn start_children(&mut self) {
        self.0.current.push(0);
    }

    fn get_template(&mut self, _name: Name<'i>) -> Option<FnIndex<'a>> {
        todo!()
    }

    fn import(&mut self, _name: Name<'i>, _alias: Option<Name<'i>>) {
        todo!()
    }

    fn register_fn(&mut self, _name: Name<'i>, _index: FnIndex<'a>) {
        todo!()
    }
}
#[track_caller]
fn interpret(input: &str) -> Hier {
    let input = Input::new(input.as_bytes(), ());
    let chirp_file = super::chirp_file(input).unwrap();
    let mut state = TestInterpreter::new();
    let file = ChirpFile::new(input, chirp_file.as_ref());
    file.interpret(&mut state);
    state.0.hierarchy
}

#[test]
fn simple() {
    let actual = interpret("Name()");
    assert_eq!(actual, hier!(Name() {}));
}
#[test]
fn padded() {
    let actual = interpret("  Name()  ");
    assert_eq!(actual, hier!(Name() {}));
}
#[test]
fn very_padded() {
    let actual = interpret("  Name     (     )  ");
    assert_eq!(actual, hier!(Name() {}));
}
#[test]
fn comment_padded() {
    let actual = interpret(
        r#"// Foo bar
Name     (     )
// final comment
"#,
    );
    assert_eq!(actual, hier!(Name() {}));
}
#[test]
fn with_method() {
    let actual = interpret("Name(method(10))");
    assert_eq!(actual, hier!(Name(method "(10)") {}));
    let actual = interpret("Name(method  (10)  )");
    assert_eq!(actual, hier!(Name(method "(10)") {}));
}