1use core::fmt::Display;
4
5pub use logos::Span;
6
7pub type Lexer<'source> = logos::Lexer<'source, Token>;
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq, logos::Logos)]
12#[logos(error = Option<Span>)]
13#[logos(skip r"[ \t\n\r]+")]
14#[logos(skip(r"//", skip_comment))]
15#[logos(subpattern first_label_word = r"[a-z][a-z0-9]*|[A-Z][A-Z0-9]*")]
16#[logos(subpattern label_word = r"[a-z0-9]+|[A-Z0-9]+")]
17#[logos(subpattern char_escape = r#"\\['"tnr\\]|\\u\{[0-9a-fA-F]{1,6}\}"#)]
18pub enum Token {
19 #[token("{")]
21 BraceOpen,
22 #[token("}")]
24 BraceClose,
25
26 #[token("(")]
28 ParenOpen,
29 #[token(")")]
31 ParenClose,
32
33 #[token("[")]
35 BracketOpen,
36 #[token("]")]
38 BracketClose,
39
40 #[token(":")]
42 Colon,
43
44 #[token(",")]
46 Comma,
47
48 #[regex(r"-?(0|([1-9][0-9]*))(\.[0-9]+)?([eE][-+]?[0-9]+)?")]
50 #[token("-inf")]
51 Number,
52
53 #[regex(r"%?(?&first_label_word)(-(?&label_word))*")]
55 LabelOrKeyword,
56
57 #[regex(r#"'([^\\'\n]{1,4}|(?&char_escape))'"#, validate_char)]
59 Char,
60
61 #[regex(r#""([^\\"\n]|(?&char_escape))*""#)]
63 String,
64
65 #[token(r#"""""#, lex_multiline_string)]
67 MultilineString,
68}
69
70impl Display for Token {
71 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
72 write!(f, "{self:?}")
73 }
74}
75
76fn skip_comment(lex: &mut Lexer) {
77 let comment = lex.remainder().split_inclusive('\n').next().unwrap();
78 lex.bump(comment.len());
79}
80
81fn validate_char(lex: &mut Lexer) -> Result<(), Option<Span>> {
82 let s = &lex.slice()[1..lex.slice().len() - 1];
83 if s.starts_with('\\') || s.chars().count() == 1 {
84 Ok(())
85 } else {
86 Err(Some(lex.span()))
87 }
88}
89
90fn lex_multiline_string(lex: &mut Lexer) -> bool {
91 if let Some(end) = lex.remainder().find(r#"""""#) {
92 lex.bump(end + 3);
93 true
94 } else {
95 false
96 }
97}
98
99#[derive(Clone, Copy, Debug, PartialEq)]
101#[allow(missing_docs)]
102pub enum Keyword {
103 True,
104 False,
105 Some,
106 None,
107 Ok,
108 Err,
109 Inf,
110 Nan,
111}
112
113impl Keyword {
114 pub fn decode(raw_label: &str) -> Option<Self> {
116 Some(match raw_label {
117 "true" => Self::True,
118 "false" => Self::False,
119 "some" => Self::Some,
120 "none" => Self::None,
121 "ok" => Self::Ok,
122 "err" => Self::Err,
123 "inf" => Self::Inf,
124 "nan" => Self::Nan,
125 _ => return None,
126 })
127 }
128}
129
130impl Display for Keyword {
131 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
132 f.write_str(match self {
133 Keyword::True => "true",
134 Keyword::False => "false",
135 Keyword::Some => "some",
136 Keyword::None => "none",
137 Keyword::Ok => "ok",
138 Keyword::Err => "err",
139 Keyword::Inf => "inf",
140 Keyword::Nan => "nan",
141 })
142 }
143}
144
145pub type FuncNameLexer<'source> = logos::Lexer<'source, FuncNameToken>;
148#[derive(Clone, Copy, Debug, PartialEq, Eq, logos::Logos)]
150#[logos(error = Option<logos::Span>)]
151#[logos(subpattern first_label_word = r"[a-z][a-z0-9]*|[A-Z][A-Z0-9]*")]
152#[logos(subpattern label_word = r"[a-z0-9]+|[A-Z0-9]+")]
153#[logos(subpattern label = r"(?&first_label_word)(\-(?&label_word))*")]
154#[logos(subpattern package_label = r"(?&label)\:(?&label)")]
155#[logos(subpattern semver = r"(0|([1-9][0-9]*))\.(0|([1-9][0-9]*))\.(0|([1-9][0-9]*))")]
156pub enum FuncNameToken {
157 #[regex(r"((?&package_label)\/)?(?&label)(\.(?&label))?(\@(?&semver))?")]
160 FunctionName,
161}