1use 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 _; 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}