1use std::ops::Range;
13
14use logos::Logos;
15
16#[derive(Logos, Debug, Clone, Copy, PartialEq, Eq, Hash)]
18#[logos(skip r"[ \t\r\n]+")]
19#[logos(skip r"/\*([^*]|\*[^/])*\*/")]
20pub enum Kind {
21 #[regex(r"\|[^|]*\|")]
23 Term,
24 #[regex(r#""([^"\\]|\\.)*""#)]
27 String,
28 #[regex(r"[A-Za-z][A-Za-z0-9\-]*#[A-Za-z0-9\-._]+")]
30 AltIdentifier,
31 #[regex(r"[0-9]+")]
33 Integer,
34 #[regex(r"[A-Za-z][A-Za-z0-9_\-]*")]
37 Identifier,
38 #[token("<<!")]
40 ChildOrSelfOf,
41 #[token("<<")]
43 DescendantOrSelfOf,
44 #[token("<!")]
46 ChildOf,
47 #[token("<=")]
49 LessOrEqual,
50 #[token("<")]
52 LessThan,
53 #[token(">>!")]
55 ParentOrSelfOf,
56 #[token(">>")]
58 AncestorOrSelfOf,
59 #[token(">!")]
61 ParentOf,
62 #[token(">=")]
64 GreaterOrEqual,
65 #[token(">")]
67 GreaterThan,
68 #[token("!!>")]
70 Top,
71 #[token("!!<")]
73 Bottom,
74 #[token("!=")]
76 NotEqual,
77 #[token("=")]
79 Equal,
80 #[token("(")]
82 LeftParen,
83 #[token(")")]
85 RightParen,
86 #[token("{{")]
88 DoubleLeftBrace,
89 #[token("}}")]
91 DoubleRightBrace,
92 #[token("{")]
94 LeftBrace,
95 #[token("}")]
97 RightBrace,
98 #[token("[")]
100 LeftBracket,
101 #[token("]")]
103 RightBracket,
104 #[token(":")]
106 Colon,
107 #[token(",")]
109 Comma,
110 #[token("^")]
112 Caret,
113 #[token("..")]
115 To,
116 #[token(".")]
118 Period,
119 #[token("*")]
121 Asterisk,
122 #[token("#")]
124 Hash,
125 #[token("+")]
127 Plus,
128 #[token("-")]
130 Dash,
131}
132
133impl Kind {
134 #[must_use]
136 pub const fn describe(self) -> &'static str {
137 match self {
138 Self::Term => "a term between pipes",
139 Self::String => "a quoted string",
140 Self::AltIdentifier => "an alternate identifier",
141 Self::Integer => "a number",
142 Self::Identifier => "a word",
143 Self::ChildOrSelfOf => "'<<!'",
144 Self::DescendantOrSelfOf => "'<<'",
145 Self::ChildOf => "'<!'",
146 Self::LessOrEqual => "'<='",
147 Self::LessThan => "'<'",
148 Self::ParentOrSelfOf => "'>>!'",
149 Self::AncestorOrSelfOf => "'>>'",
150 Self::ParentOf => "'>!'",
151 Self::GreaterOrEqual => "'>='",
152 Self::GreaterThan => "'>'",
153 Self::Top => "'!!>'",
154 Self::Bottom => "'!!<'",
155 Self::NotEqual => "'!='",
156 Self::Equal => "'='",
157 Self::LeftParen => "'('",
158 Self::RightParen => "')'",
159 Self::DoubleLeftBrace => "'{{'",
160 Self::DoubleRightBrace => "'}}'",
161 Self::LeftBrace => "'{'",
162 Self::RightBrace => "'}'",
163 Self::LeftBracket => "'['",
164 Self::RightBracket => "']'",
165 Self::Colon => "':'",
166 Self::Comma => "','",
167 Self::Caret => "'^'",
168 Self::To => "'..'",
169 Self::Period => "'.'",
170 Self::Asterisk => "'*'",
171 Self::Hash => "'#'",
172 Self::Plus => "'+'",
173 Self::Dash => "'-'",
174 }
175 }
176}
177
178#[derive(Debug, Clone, PartialEq, Eq)]
180pub struct Token<'s> {
181 pub kind: Kind,
183 pub text: &'s str,
185 pub span: Range<usize>,
187}
188
189impl PartialEq<Kind> for Token<'_> {
190 fn eq(&self, other: &Kind) -> bool {
191 self.kind == *other
192 }
193}
194
195#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
197#[error("unexpected character {found:?} at byte {offset}")]
198pub struct LexError {
199 pub offset: usize,
201 pub found: char,
203}
204
205pub fn lex(input: &str) -> Result<Vec<Token<'_>>, LexError> {
212 let mut lexer = Kind::lexer(input);
213 let mut tokens = Vec::new();
214 while let Some(kind) = lexer.next() {
215 let span = lexer.span();
216 let kind = kind.map_err(|()| LexError {
217 offset: span.start,
218 found: lexer.slice().chars().next().unwrap_or('\u{0}'),
219 })?;
220 tokens.push(Token {
221 kind,
222 text: lexer.slice(),
223 span,
224 });
225 }
226 Ok(tokens)
227}
228
229#[cfg(test)]
230mod tests {
231 use super::{Kind, lex};
232
233 fn kinds(input: &str) -> Vec<Kind> {
234 lex(input)
235 .expect("lexes")
236 .into_iter()
237 .map(|t| t.kind)
238 .collect()
239 }
240
241 #[test]
242 fn operators_take_the_longest_match_and_comments_are_skipped() {
243 assert_eq!(
244 kinds("<<! /* c */ 123 |a b| {{ D term = \"x\" }} [1..*] LOINC#54-6 !!>"),
245 [
246 Kind::ChildOrSelfOf,
247 Kind::Integer,
248 Kind::Term,
249 Kind::DoubleLeftBrace,
250 Kind::Identifier,
251 Kind::Identifier,
252 Kind::Equal,
253 Kind::String,
254 Kind::DoubleRightBrace,
255 Kind::LeftBracket,
256 Kind::Integer,
257 Kind::To,
258 Kind::Asterisk,
259 Kind::RightBracket,
260 Kind::AltIdentifier,
261 Kind::Top,
262 ]
263 );
264 assert_eq!(
265 kinds("#-5.5"),
266 [
267 Kind::Hash,
268 Kind::Dash,
269 Kind::Integer,
270 Kind::Period,
271 Kind::Integer
272 ]
273 );
274 let error = lex("< 123 |unterminated").expect_err("refused");
275 assert_eq!(error.offset, 6);
276 assert_eq!(error.found, '|');
277 assert_eq!(lex("a /* open").expect_err("refused").offset, 2);
278 }
279}