sysml_parser/
lexer.rs

1// This is free and unencumbered software released into the public domain.
2
3use super::Keyword;
4use crate::{
5    prelude::{String, Vec},
6    Span, SyntaxError, SyntaxResult,
7};
8use nom::{
9    branch::alt,
10    bytes::complete::{is_not, tag, take_while1},
11    character::complete::{alpha1, alphanumeric1, char, multispace0},
12    combinator::{map, map_res, recognize},
13    error::{context, ErrorKind},
14    multi::many0,
15    sequence::{delimited, pair, terminated},
16};
17use sysml_model::QualifiedName;
18
19#[derive(Clone, Debug, Eq, Hash, PartialEq, PartialOrd)]
20pub enum Token {
21    Keyword(Keyword),
22    Name(String),
23    QualifiedName(QualifiedName),
24}
25
26pub fn tokens<'a>(input: impl Into<Span<'a>>) -> SyntaxResult<'a, (Span<'a>, Vec<Token>)> {
27    context("tokens", many0(delimited(multispace0, token, multispace0)))(input.into())
28}
29
30pub fn token<'a>(input: impl Into<Span<'a>>) -> SyntaxResult<'a, (Span<'a>, Token)> {
31    context(
32        "token",
33        alt((
34            map(reserved_keyword, Token::Keyword),
35            map(name, Token::Name),
36            map(qualified_name, Token::QualifiedName),
37        )),
38    )(input.into())
39}
40
41pub fn qualified_name<'a>(
42    input: impl Into<Span<'a>>,
43) -> SyntaxResult<'a, (Span<'a>, QualifiedName)> {
44    let (input, mut names) = many0(terminated(name, tag("::")))(input.into())?;
45    let (input, name) = name(input)?;
46    names.push(name);
47
48    Ok((input, QualifiedName::new(names)))
49}
50
51pub fn name<'a>(input: impl Into<Span<'a>>) -> SyntaxResult<'a, (Span<'a>, String)> {
52    context("name", alt((basic_name, unrestricted_name)))(input.into())
53}
54
55pub fn basic_name<'a>(input: impl Into<Span<'a>>) -> SyntaxResult<'a, (Span<'a>, String)> {
56    let (input, name) = context(
57        "basic_name",
58        recognize(pair(
59            alt((alpha1, tag("_"))),
60            many0(alt((alphanumeric1, tag("_")))),
61        )),
62    )(input.into())?;
63
64    Ok((input, String::from(name.into_fragment())))
65}
66
67pub fn unrestricted_name<'a>(input: impl Into<Span<'a>>) -> SyntaxResult<'a, (Span<'a>, String)> {
68    let (input, name) = context(
69        "unrestricted_name",
70        delimited(char('\''), is_not("'"), char('\'')),
71    )(input.into())?;
72
73    Ok((input, String::from(name.into_fragment())))
74}
75
76pub fn reserved_keyword<'a>(input: impl Into<Span<'a>>) -> SyntaxResult<'a, (Span<'a>, Keyword)> {
77    use nom::error::ParseError as _; // for from_error_kind
78    use nom::AsChar;
79    context(
80        "reserved_keyword",
81        map_res(
82            take_while1(|c| AsChar::as_char(c).is_ascii_lowercase()),
83            |span: Span| {
84                Keyword::try_from(span)
85                    .or_else(|input| Err(SyntaxError::from_error_kind(input, ErrorKind::Tag)))
86            },
87        ),
88    )(input.into())
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn lex_block_keyword() {
97        let input = r#"block"#;
98        assert_eq!(
99            token(input).map(|(_, token)| token),
100            Ok(Token::Keyword(Keyword::Block))
101        );
102    }
103
104    #[test]
105    fn lex_basic_name() {
106        let input = r#"MyPackage"#;
107        assert_eq!(
108            token(input).map(|(_, token)| token),
109            Ok(Token::Name(String::from("MyPackage")))
110        );
111    }
112
113    #[test]
114    fn lex_quoted_name() {
115        let input = r#"'My Package'"#;
116        assert_eq!(
117            token(input).map(|(_, token)| token),
118            Ok(Token::Name(String::from("My Package")))
119        );
120    }
121}