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
109
110
111
112
113
114
115
116
use crate::puppet_parser::{
    common::{space0_delimimited, space1_delimimited},
    {range::Range, IResult, ParseError, Span},
};

use nom::{
    branch::alt,
    bytes::complete::tag,
    combinator::{eof, map, opt},
    sequence::{pair, preceded, terminated, tuple},
};
use crate::puppet_lang::toplevel::{FunctionDef, Toplevel, ToplevelVariant};

pub fn parse_typedef(input: Span) -> IResult<crate::puppet_lang::toplevel::TypeDef<Range>> {
    map(
        tuple((
            tag("type"),
            space1_delimimited(crate::puppet_parser::identifier::camelcase_identifier_with_ns_located),
            ParseError::protect(|_| "'=' expected".to_string(), tag("=")),
            ParseError::protect(
                |_| "Type specification expected".to_string(),
                space0_delimimited(crate::puppet_parser::typing::parse_type_specification),
            ),
        )),
        |(keyword, identifier, _, value)| crate::puppet_lang::toplevel::TypeDef {
            extra: Range::from((keyword, &value.extra)),
            identifier,
            value,
        },
    )(input)
}
pub fn parse_functiondef(input: Span) -> IResult<FunctionDef<Range>> {
    map(
        tuple((
            tag("function"),
            preceded(super::common::separator1, crate::puppet_parser::class::parse_header),
            ParseError::protect(
                |_| "'{' or '>>' expected".to_string(),
                pair(
                    space0_delimimited(opt(preceded(
                        tag(">>"),
                        ParseError::protect(
                            |_| "Failed to parse return type".to_owned(),
                            space0_delimimited(crate::puppet_parser::typing::parse_type_specification),
                        ),
                    ))),
                    crate::puppet_parser::statement::parse_statement_block,
                ),
            ),
        )),
        |(keyword, (identifier, arguments), (return_type, (_left_curly, body, right_curly)))| {
            FunctionDef {
                identifier,
                arguments,
                return_type,
                body,
                extra: Range::from((keyword, right_curly)),
            }
        },
    )(input)
}

pub fn parse(input: Span) -> IResult<Toplevel<Range>> {
    crate::puppet_parser::common::space0_delimimited(alt((
        map(crate::puppet_parser::class::parse_class, |v| Toplevel {
            extra: v.extra.clone(),
            data: ToplevelVariant::Class(v),
        }),
        map(crate::puppet_parser::class::parse_definition, |v| Toplevel {
            extra: v.extra.clone(),
            data: ToplevelVariant::Definition(v),
        }),
        map(crate::puppet_parser::class::parse_plan, |v| Toplevel {
            extra: v.extra.clone(),
            data: ToplevelVariant::Plan(v),
        }),
        map(parse_typedef, |v| Toplevel {
            extra: v.extra.clone(),
            data: ToplevelVariant::TypeDef(v),
        }),
        map(parse_functiondef, |v| Toplevel {
            extra: v.extra.clone(),
            data: ToplevelVariant::FunctionDef(v),
        }),
    )))(input)
}

pub fn parse_file(
    input: Span,
) -> IResult<crate::puppet_lang::List<Range, crate::puppet_lang::statement::Statement<Range>>> {
    terminated(crate::puppet_parser::statement::parse_statement_list, eof)(input)
}

#[test]
fn test_toplevel() {
    assert!(crate::puppet_parser::statement::parse_statement_list(Span::new(
        "# @summary Install and enroll client to freeipa cluster
#
# A description of what this class does
#
# @example
#   include freeipa::install::client
class freeipa::install::client {
}"
    ))
    .is_ok())
}

#[test]
fn test_function() {
    assert!(parse(Span::new("function abc::def () {}")).is_ok());

    assert!(parse(Span::new("function abc::def ($a, $b) {}")).is_ok());

    assert!(parse(Span::new("function abc::def ($a, $b) >> String {}")).is_ok());
}