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