graphforge_ast/token.rs
1//! Token contract for the GraphForge Cypher lexer.
2#![allow(missing_docs)]
3//!
4//! This enum is the **frozen ABI** between the lexer (`graphforge-cypher`) and the
5//! differential test harness (`graphforge-cypher/tests/`). Removing or renaming a
6//! variant is a breaking change. Adding new variants is allowed only in
7//! minor releases and must be accompanied by a `#[non_exhaustive]` guard.
8
9use graphforge_core::Span;
10use serde::{Deserialize, Serialize};
11
12// ---------------------------------------------------------------------------
13// Keyword list
14// ---------------------------------------------------------------------------
15
16/// Every reserved keyword in the openCypher grammar.
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
18#[non_exhaustive]
19pub enum Keyword {
20 // Query structure
21 Match,
22 Optional,
23 Where,
24 Return,
25 With,
26 As,
27 Distinct,
28 Union,
29 All,
30 // Write clauses
31 Create,
32 Merge,
33 On,
34 Set,
35 Remove,
36 Delete,
37 Detach,
38 // Sub-queries / procedures
39 Call,
40 Yield,
41 // Iteration
42 Unwind,
43 // Ordering / pagination
44 Order,
45 By,
46 Skip,
47 Limit,
48 // Logical operators
49 Not,
50 And,
51 Or,
52 Xor,
53 // Predicates
54 In,
55 Is,
56 Null,
57 Starts,
58 Ends,
59 Contains,
60 // Conditional
61 Case,
62 When,
63 Then,
64 Else,
65 End,
66 // Boolean literals
67 True,
68 False,
69 // List predicates
70 Any,
71 None,
72 Single,
73 Exists,
74 // Path functions
75 ShortestPath,
76 AllShortestPaths,
77 // Aggregation
78 Count,
79 // Reduce / comprehension
80 Reduce,
81 Filter,
82 Extract,
83}
84
85// ---------------------------------------------------------------------------
86// Token
87// ---------------------------------------------------------------------------
88
89/// A lexed token emitted by the GraphForge Cypher lexer.
90///
91/// Every variant carries a [`Span`] so that downstream consumers can report
92/// precise error locations. The lexer never omits the span — not even for
93/// whitespace tokens that the parser would normally discard.
94///
95/// # Stability
96///
97/// This enum is `#[non_exhaustive]`. Parsers and test harnesses must match
98/// only the variants they care about and use a wildcard arm.
99#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
100#[non_exhaustive]
101pub enum Token {
102 // -----------------------------------------------------------------------
103 // Keywords
104 // -----------------------------------------------------------------------
105 /// A reserved keyword (case-insensitive in openCypher).
106 Keyword(Keyword, Span),
107
108 // -----------------------------------------------------------------------
109 // Literals
110 // -----------------------------------------------------------------------
111 /// A decimal or hexadecimal integer literal.
112 IntLit(i64, Span),
113 /// A floating-point literal.
114 FloatLit(f64, Span),
115 /// A single- or double-quoted string literal (escape sequences resolved).
116 StrLit(String, Span),
117 /// `true` or `false` (also surfaced as `Keyword::True/False` for parity).
118 BoolLit(bool, Span),
119 /// The `null` literal.
120 NullLit(Span),
121
122 // -----------------------------------------------------------------------
123 // Identifiers and parameters
124 // -----------------------------------------------------------------------
125 /// An unquoted or backtick-quoted identifier.
126 Ident(String, Span),
127 /// A Cypher parameter: `$name` or `$0`.
128 Param(String, Span),
129
130 // -----------------------------------------------------------------------
131 // Punctuation
132 // -----------------------------------------------------------------------
133 /// `(`
134 LParen(Span),
135 /// `)`
136 RParen(Span),
137 /// `[`
138 LBracket(Span),
139 /// `]`
140 RBracket(Span),
141 /// `{`
142 LBrace(Span),
143 /// `}`
144 RBrace(Span),
145 /// `.`
146 Dot(Span),
147 /// `,`
148 Comma(Span),
149 /// `:`
150 Colon(Span),
151 /// `;`
152 Semi(Span),
153 /// `|`
154 Pipe(Span),
155 /// `..` (variable-length relationship range separator)
156 DotDot(Span),
157
158 // -----------------------------------------------------------------------
159 // Operators
160 // -----------------------------------------------------------------------
161 /// `=`
162 Eq(Span),
163 /// `<>` or `!=`
164 Neq(Span),
165 /// `<`
166 Lt(Span),
167 /// `<=`
168 Lte(Span),
169 /// `>`
170 Gt(Span),
171 /// `>=`
172 Gte(Span),
173 /// `+`
174 Plus(Span),
175 /// `-`
176 Minus(Span),
177 /// `*`
178 Star(Span),
179 /// `/`
180 Slash(Span),
181 /// `%`
182 Percent(Span),
183 /// `^`
184 Caret(Span),
185 /// `=~` (regular expression match)
186 RegexMatch(Span),
187
188 // -----------------------------------------------------------------------
189 // Trivia (retained for source-accurate round-tripping)
190 // -----------------------------------------------------------------------
191 /// Whitespace (space, tab, newline).
192 Whitespace(Span),
193 /// A single- or multi-line comment.
194 Comment(String, Span),
195
196 // -----------------------------------------------------------------------
197 // Sentinel
198 // -----------------------------------------------------------------------
199 /// End of input.
200 Eof(Span),
201}
202
203impl Token {
204 /// Return the [`Span`] of this token.
205 #[must_use]
206 pub fn span(&self) -> Span {
207 match self {
208 Self::Keyword(_, s)
209 | Self::IntLit(_, s)
210 | Self::FloatLit(_, s)
211 | Self::StrLit(_, s)
212 | Self::BoolLit(_, s)
213 | Self::Param(_, s)
214 | Self::Ident(_, s)
215 | Self::Comment(_, s)
216 | Self::NullLit(s)
217 | Self::LParen(s)
218 | Self::RParen(s)
219 | Self::LBracket(s)
220 | Self::RBracket(s)
221 | Self::LBrace(s)
222 | Self::RBrace(s)
223 | Self::Dot(s)
224 | Self::Comma(s)
225 | Self::Colon(s)
226 | Self::Semi(s)
227 | Self::Pipe(s)
228 | Self::DotDot(s)
229 | Self::Eq(s)
230 | Self::Neq(s)
231 | Self::Lt(s)
232 | Self::Lte(s)
233 | Self::Gt(s)
234 | Self::Gte(s)
235 | Self::Plus(s)
236 | Self::Minus(s)
237 | Self::Star(s)
238 | Self::Slash(s)
239 | Self::Percent(s)
240 | Self::Caret(s)
241 | Self::RegexMatch(s)
242 | Self::Whitespace(s)
243 | Self::Eof(s) => *s,
244 }
245 }
246
247 /// Return `true` if this token is trivia (whitespace or comment) that
248 /// the parser should skip.
249 #[must_use]
250 pub fn is_trivia(&self) -> bool {
251 matches!(self, Self::Whitespace(_) | Self::Comment(_, _))
252 }
253}