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
use crate::errors::parser_error::ParserError;
use crate::parser::constants::{MENU_KEYWORD, DEPENDS_KEYWORD, VISIBLE_KEYWORD};
use crate::parser::kconfig_parser_impl::parser_traits::{Parseable, ParseableFromLine, ParsingContext};
use crate::parser::utils::tokenizer::LineKConfigTokenizerIterator;
use crate::structure::atoms::KconfigDependency;
use crate::structure::kconfig_node_children::KconfigNodeChildren;
use crate::structure::nodes::KconfigMenuNode;

fn get_empty_menu_node_from_header(
    context: &ParsingContext
) -> Result<KconfigMenuNode, ParserError> {
    let span = context.span;
    let header_line = span.get_source_span()[0];
    let mut header_tokens = LineKConfigTokenizerIterator::from_line(header_line);

    if !header_tokens.next()
        .contains(&MENU_KEYWORD) {
        return Err(ParserError::syntax_in_span_at("Expected menu keyword", &span, 0));
    }

    let mut menu_node = KconfigMenuNode::new_empty();
    menu_node.name = header_tokens.next()
        .ok_or(ParserError::syntax_in_span_at("Expected menu name", &span, 0))?
        .to_string();

    Ok(menu_node)
}

fn set_menu_properties_and_get_properties_end(
    context: &ParsingContext,
    menu_node: &mut KconfigMenuNode,
) -> Result<usize, ParserError> {
    let property_span = context.span.get_with_bounds(1, context.span.len() - 1);
    let mut prop_end = 0;
    for (line_index, property_line) in property_span
        .get_source_span()
        .iter()
        .enumerate() {
        let mut property_line_tokens = LineKConfigTokenizerIterator::from_line(*property_line);
        if let Some(keyword) = property_line_tokens.next() {
            match keyword {
                DEPENDS_KEYWORD => {
                    let line_span = property_span.get_line_span_at(
                        line_index,
                    );
                    let depends = KconfigDependency::parse_from_line(
                        &context.get_line_context_with_span(&line_span),
                    )?;
                    menu_node.dependencies.add_dependency(depends);
                }
                VISIBLE_KEYWORD => {}
                _ => {
                    prop_end = line_index;
                    break;
                }
            }
        } else {
            continue;
        }
    }

    Ok(prop_end + 1)
}

impl Parseable for KconfigMenuNode {
    fn parse(context: &ParsingContext) -> Result<Self, ParserError> {
        let span = context.span;
        span.non_empty_or()?;
        let mut menu_node = get_empty_menu_node_from_header(
            context
        )?;

        let prop_end =
            set_menu_properties_and_get_properties_end(context, &mut menu_node)?;
        let child_span = span.get_with_bounds(prop_end, span.len() - 2);
        let node_child = KconfigNodeChildren::parse(
            &context.with_different_span(&child_span),
        )?;
        menu_node.children = node_child;

        Ok(menu_node)
    }
}

#[cfg(test)]
mod test {
    use crate::parser::kconfig_parser_impl::parser_traits::{Parseable, ParsingContext};
    use crate::parser::utils::parse_span::ParseSpan;
    use crate::structure::nodes::KconfigMenuNode;

    #[test]
    fn happy_path_menu_parsing() {
        let source = "menu \"Keksajtos kifliallarc\"\n\
        \tdepends on SAJTOS && KIFLI\n\
        endmenu\n\
        ";
        let lines_iter = source.lines().collect::<Vec<&str>>();
        let span = ParseSpan::from_source(&lines_iter[..], "test");
        let context = ParsingContext {
            config: &Default::default(),
            span: &span,
        };

        let menu_node = KconfigMenuNode::parse(&context).unwrap();
        assert_eq!(menu_node.name, "\"Keksajtos kifliallarc\"");
        assert_eq!(menu_node.dependencies.dependencies.len(), 1);
    }
}