Skip to main content

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}