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

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

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

pub fn tokens(input: &str) -> IResult<&str, Vec<Token>> {
    many0(delimited(multispace0, token, multispace0))(input)
}

pub fn token(input: &str) -> IResult<&str, Token> {
    alt((
        map(reserved_keyword, Token::Keyword),
        map(name, Token::Name),
        map(qualified_name, Token::QualifiedName),
    ))(input)
}

pub fn qualified_name(input: &str) -> IResult<&str, QualifiedName> {
    let (input, mut names) = many0(terminated(name, tag("::")))(input)?;
    let (input, name) = name(input)?;
    names.push(name);
    Ok((input, QualifiedName::new(names)))
}

pub fn name(input: &str) -> IResult<&str, String> {
    alt((basic_name, unrestricted_name))(input)
}

pub fn basic_name(input: &str) -> IResult<&str, String> {
    let (input, name) = recognize(pair(
        alt((alpha1, tag("_"))),
        many0(alt((alphanumeric1, tag("_")))),
    ))(input)?;

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

pub fn unrestricted_name(input: &str) -> IResult<&str, String> {
    let (input, name) = delimited(char('\''), is_not("'"), char('\''))(input)?;

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

pub fn reserved_keyword(input: &str) -> IResult<&str, Keyword> {
    map_res(take_while1(|c: char| c.is_ascii_lowercase()), |s: &str| {
        Keyword::try_from(s).or_else(|_| Err(Error::new(input, ErrorKind::Tag)))
    })(input)
}

#[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")))
        );
    }
}