1use std::fmt;
8
9use crate::span::Span;
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
14pub struct Token {
15 pub kind: TokenKind,
17 pub span: Span,
19}
20
21impl Token {
22 #[must_use]
24 pub const fn new(kind: TokenKind, span: Span) -> Self {
25 Self { kind, span }
26 }
27
28 #[must_use]
30 pub fn text(self, source: &str) -> Option<&str> {
31 self.span.text(source)
32 }
33
34 #[must_use]
36 pub const fn is_trivia(self) -> bool {
37 self.kind.is_trivia()
38 }
39}
40
41#[non_exhaustive]
46#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
47#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
48pub enum TokenKind {
49 Keyword(Keyword),
51 Identifier,
53 EscapedIdentifier,
55 Parameter,
58 Integer,
60 HexInteger,
62 OctalInteger,
64 Float,
66 String,
68
69 LeftParen,
70 RightParen,
71 LeftBracket,
72 RightBracket,
73 LeftBrace,
74 RightBrace,
75 Comma,
76 Dot,
77 DotDot,
78 Colon,
79 DoubleColon,
80 Semicolon,
81 Pipe,
82 DoublePipe,
83 Ampersand,
84 Question,
85 Dollar,
88
89 Plus,
90 Minus,
91 Star,
92 Slash,
93 Percent,
94 Caret,
95 Bang,
96 Equal,
97 NotEqual,
98 Less,
99 LessEqual,
100 Greater,
101 GreaterEqual,
102 PlusEqual,
103 FatArrow,
104 RegexMatch,
105 LeftArrow,
108 RightArrow,
111
112 Whitespace,
114 LineComment,
116 BlockComment,
118 Invalid,
120}
121
122impl TokenKind {
123 #[must_use]
125 pub const fn is_trivia(self) -> bool {
126 matches!(
127 self,
128 Self::Whitespace | Self::LineComment | Self::BlockComment
129 )
130 }
131
132 #[must_use]
134 pub const fn is_literal(self) -> bool {
135 matches!(
136 self,
137 Self::Integer
138 | Self::HexInteger
139 | Self::OctalInteger
140 | Self::Float
141 | Self::String
142 | Self::Keyword(Keyword::True | Keyword::False | Keyword::Null)
143 )
144 }
145
146 #[must_use]
148 pub const fn display_name(self) -> &'static str {
149 match self {
150 Self::Keyword(keyword) => keyword.as_str(),
151 Self::Identifier => "an identifier",
152 Self::EscapedIdentifier => "an escaped identifier",
153 Self::Parameter => "a parameter",
154 Self::Integer => "an integer",
155 Self::HexInteger => "a hexadecimal integer",
156 Self::OctalInteger => "an octal integer",
157 Self::Float => "a floating-point number",
158 Self::String => "a string",
159 Self::LeftParen => "`(`",
160 Self::RightParen => "`)`",
161 Self::LeftBracket => "`[`",
162 Self::RightBracket => "`]`",
163 Self::LeftBrace => "`{`",
164 Self::RightBrace => "`}`",
165 Self::Comma => "`,`",
166 Self::Dot => "`.`",
167 Self::DotDot => "`..`",
168 Self::Colon => "`:`",
169 Self::DoubleColon => "`::`",
170 Self::Semicolon => "`;`",
171 Self::Pipe => "`|`",
172 Self::DoublePipe => "`||`",
173 Self::Ampersand => "`&`",
174 Self::Question => "`?`",
175 Self::Dollar => "`$`",
176 Self::Plus => "`+`",
177 Self::Minus => "`-`",
178 Self::Star => "`*`",
179 Self::Slash => "`/`",
180 Self::Percent => "`%`",
181 Self::Caret => "`^`",
182 Self::Bang => "`!`",
183 Self::Equal => "`=`",
184 Self::NotEqual => "`<>` or `!=`",
185 Self::Less => "`<`",
186 Self::LessEqual => "`<=`",
187 Self::Greater => "`>`",
188 Self::GreaterEqual => "`>=`",
189 Self::PlusEqual => "`+=`",
190 Self::FatArrow => "`=>`",
191 Self::RegexMatch => "`=~`",
192 Self::LeftArrow => "`<-`",
193 Self::RightArrow => "`->`",
194 Self::Whitespace => "whitespace",
195 Self::LineComment => "a line comment",
196 Self::BlockComment => "a block comment",
197 Self::Invalid => "an invalid token",
198 }
199 }
200}
201
202impl fmt::Display for TokenKind {
203 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
204 formatter.write_str(self.display_name())
205 }
206}
207
208#[non_exhaustive]
215#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
216#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
217pub enum Keyword {
218 Acyclic,
219 All,
220 AllShortestPaths,
221 And,
222 Any,
223 As,
224 Asc,
225 Ascending,
226 By,
227 Call,
228 Case,
229 Collect,
230 Contains,
231 Count,
232 Create,
233 Delete,
234 Desc,
235 Descending,
236 Detach,
237 Distinct,
238 Else,
239 End,
240 Ends,
241 Exists,
242 False,
243 Group,
244 Groups,
245 In,
246 Inf,
247 Infinity,
248 Is,
249 Limit,
250 Match,
251 Merge,
252 Nan,
253 None,
254 Not,
255 Null,
256 Offset,
257 On,
258 Optional,
259 Or,
260 Order,
261 Path,
262 Paths,
263 Reduce,
264 Remove,
265 Return,
266 Set,
267 Shortest,
268 ShortestPath,
269 Simple,
270 Single,
271 Skip,
272 Starts,
273 Then,
274 Trail,
275 Trim,
276 True,
277 Union,
278 Unwind,
279 Walk,
280 When,
281 Where,
282 With,
283 Xor,
284 Yield,
285}
286
287impl Keyword {
288 #[must_use]
290 pub const fn as_str(self) -> &'static str {
291 match self {
292 Self::Acyclic => "ACYCLIC",
293 Self::All => "ALL",
294 Self::AllShortestPaths => "ALLSHORTESTPATHS",
295 Self::And => "AND",
296 Self::Any => "ANY",
297 Self::As => "AS",
298 Self::Asc => "ASC",
299 Self::Ascending => "ASCENDING",
300 Self::By => "BY",
301 Self::Call => "CALL",
302 Self::Case => "CASE",
303 Self::Collect => "COLLECT",
304 Self::Contains => "CONTAINS",
305 Self::Count => "COUNT",
306 Self::Create => "CREATE",
307 Self::Delete => "DELETE",
308 Self::Desc => "DESC",
309 Self::Descending => "DESCENDING",
310 Self::Detach => "DETACH",
311 Self::Distinct => "DISTINCT",
312 Self::Else => "ELSE",
313 Self::End => "END",
314 Self::Ends => "ENDS",
315 Self::Exists => "EXISTS",
316 Self::False => "FALSE",
317 Self::Group => "GROUP",
318 Self::Groups => "GROUPS",
319 Self::In => "IN",
320 Self::Inf => "INF",
321 Self::Infinity => "INFINITY",
322 Self::Is => "IS",
323 Self::Limit => "LIMIT",
324 Self::Match => "MATCH",
325 Self::Merge => "MERGE",
326 Self::Nan => "NAN",
327 Self::None => "NONE",
328 Self::Not => "NOT",
329 Self::Null => "NULL",
330 Self::Offset => "OFFSET",
331 Self::On => "ON",
332 Self::Optional => "OPTIONAL",
333 Self::Or => "OR",
334 Self::Order => "ORDER",
335 Self::Path => "PATH",
336 Self::Paths => "PATHS",
337 Self::Reduce => "REDUCE",
338 Self::Remove => "REMOVE",
339 Self::Return => "RETURN",
340 Self::Set => "SET",
341 Self::Shortest => "SHORTEST",
342 Self::ShortestPath => "SHORTESTPATH",
343 Self::Simple => "SIMPLE",
344 Self::Single => "SINGLE",
345 Self::Skip => "SKIP",
346 Self::Starts => "STARTS",
347 Self::Then => "THEN",
348 Self::Trail => "TRAIL",
349 Self::Trim => "TRIM",
350 Self::True => "TRUE",
351 Self::Union => "UNION",
352 Self::Unwind => "UNWIND",
353 Self::Walk => "WALK",
354 Self::When => "WHEN",
355 Self::Where => "WHERE",
356 Self::With => "WITH",
357 Self::Xor => "XOR",
358 Self::Yield => "YIELD",
359 }
360 }
361
362 #[must_use]
366 pub fn from_ascii_case_insensitive(text: &str) -> Option<Self> {
367 if !text.is_ascii() {
368 return None;
369 }
370
371 let candidates: &[Keyword] = match text.len() {
372 2 => &[Self::As, Self::By, Self::In, Self::Is, Self::On, Self::Or],
373 3 => &[
374 Self::All,
375 Self::And,
376 Self::Any,
377 Self::Asc,
378 Self::End,
379 Self::Inf,
380 Self::Nan,
381 Self::Not,
382 Self::Set,
383 Self::Xor,
384 ],
385 4 => &[
386 Self::Call,
387 Self::Case,
388 Self::Desc,
389 Self::Else,
390 Self::Ends,
391 Self::None,
392 Self::Null,
393 Self::Path,
394 Self::Skip,
395 Self::Then,
396 Self::Trim,
397 Self::True,
398 Self::Walk,
399 Self::When,
400 Self::With,
401 ],
402 5 => &[
403 Self::Count,
404 Self::False,
405 Self::Group,
406 Self::Limit,
407 Self::Match,
408 Self::Merge,
409 Self::Order,
410 Self::Paths,
411 Self::Trail,
412 Self::Union,
413 Self::Where,
414 Self::Yield,
415 ],
416 6 => &[
417 Self::Create,
418 Self::Delete,
419 Self::Detach,
420 Self::Exists,
421 Self::Groups,
422 Self::Offset,
423 Self::Reduce,
424 Self::Remove,
425 Self::Return,
426 Self::Simple,
427 Self::Single,
428 Self::Starts,
429 Self::Unwind,
430 ],
431 7 => &[Self::Acyclic, Self::Collect],
432 8 => &[
433 Self::Contains,
434 Self::Distinct,
435 Self::Optional,
436 Self::Shortest,
437 Self::Infinity,
438 ],
439 9 => &[Self::Ascending],
440 10 => &[Self::Descending],
441 12 => &[Self::ShortestPath],
442 16 => &[Self::AllShortestPaths],
443 _ => return None,
444 };
445
446 candidates
447 .iter()
448 .copied()
449 .find(|keyword| text.eq_ignore_ascii_case(keyword.as_str()))
450 }
451}
452
453impl fmt::Display for Keyword {
454 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
455 formatter.write_str(self.as_str())
456 }
457}