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
117
118
119
120
121
// This is free and unencumbered software released into the public domain.

use super::Keyword;
use crate::{
    prelude::{String, Vec},
    Span, SyntaxError, SyntaxResult,
};
use nom::{
    branch::alt,
    bytes::complete::{is_not, tag, take_while1},
    character::complete::{alpha1, alphanumeric1, char, multispace0},
    combinator::{map, map_res, recognize},
    error::{context, ErrorKind},
    multi::many0,
    sequence::{delimited, pair, terminated},
};
use sysml_model::QualifiedName;

#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd)]
pub enum Token {
    Keyword(Keyword),
    Name(String),
    QualifiedName(QualifiedName),
}

pub fn tokens<'a>(input: impl Into<Span<'a>>) -> SyntaxResult<'a, (Span<'a>, Vec<Token>)> {
    context("tokens", many0(delimited(multispace0, token, multispace0)))(input.into())
}

pub fn token<'a>(input: impl Into<Span<'a>>) -> SyntaxResult<'a, (Span<'a>, Token)> {
    context(
        "token",
        alt((
            map(reserved_keyword, Token::Keyword),
            map(name, Token::Name),
            map(qualified_name, Token::QualifiedName),
        )),
    )(input.into())
}

pub fn qualified_name<'a>(
    input: impl Into<Span<'a>>,
) -> SyntaxResult<'a, (Span<'a>, QualifiedName)> {
    let (input, mut names) = many0(terminated(name, tag("::")))(input.into())?;
    let (input, name) = name(input)?;
    names.push(name);

    Ok((input, QualifiedName::new(names)))
}

pub fn name<'a>(input: impl Into<Span<'a>>) -> SyntaxResult<'a, (Span<'a>, String)> {
    context("name", alt((basic_name, unrestricted_name)))(input.into())
}

pub fn basic_name<'a>(input: impl Into<Span<'a>>) -> SyntaxResult<'a, (Span<'a>, String)> {
    let (input, name) = context(
        "basic_name",
        recognize(pair(
            alt((alpha1, tag("_"))),
            many0(alt((alphanumeric1, tag("_")))),
        )),
    )(input.into())?;

    Ok((input, String::from(name.into_fragment())))
}

pub fn unrestricted_name<'a>(input: impl Into<Span<'a>>) -> SyntaxResult<'a, (Span<'a>, String)> {
    let (input, name) = context(
        "unrestricted_name",
        delimited(char('\''), is_not("'"), char('\'')),
    )(input.into())?;

    Ok((input, String::from(name.into_fragment())))
}

pub fn reserved_keyword<'a>(input: impl Into<Span<'a>>) -> SyntaxResult<'a, (Span<'a>, Keyword)> {
    use nom::error::ParseError as _; // for from_error_kind
    use nom::AsChar;
    context(
        "reserved_keyword",
        map_res(
            take_while1(|c| AsChar::as_char(c).is_ascii_lowercase()),
            |span: Span| {
                Keyword::try_from(span)
                    .or_else(|input| Err(SyntaxError::from_error_kind(input, ErrorKind::Tag)))
            },
        ),
    )(input.into())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn lex_block_keyword() {
        let input = r#"block"#;
        assert_eq!(
            token(input).map(|(_, token)| token),
            Ok(Token::Keyword(Keyword::Block))
        );
    }

    #[test]
    fn lex_basic_name() {
        let input = r#"MyPackage"#;
        assert_eq!(
            token(input).map(|(_, token)| token),
            Ok(Token::Name(String::from("MyPackage")))
        );
    }

    #[test]
    fn lex_quoted_name() {
        let input = r#"'My Package'"#;
        assert_eq!(
            token(input).map(|(_, token)| token),
            Ok(Token::Name(String::from("My Package")))
        );
    }
}