surrealguard_syntax/ast/expr.rs
1//! Expressions, idioms (field/graph paths), and literals.
2//!
3//! The idiom model is informed by SurrealDB's own `Part` enum (the ground
4//! truth for which path constructs exist): idioms may start from a value,
5//! indices are expressions, graph steps are mini-selections, and method
6//! calls are path parts. We model the subset analysis consumes and keep the
7//! rest explicit via `Partial`.
8
9use super::{Block, PartialNode, Spanned, Statement, TypeExpr};
10
11/// A value-producing expression.
12#[derive(Clone, Debug, PartialEq)]
13pub enum Expr {
14 /// A literal value.
15 Literal(Literal),
16 /// A field path / graph traversal, rooted in the current row context or
17 /// in a leading value (see [`IdiomPart::Start`]).
18 Idiom(Idiom),
19 /// A bare `$name` — parameter or `LET` binding reference (without `$`).
20 /// `$name.field` chains lower as `Idiom` with a `Start` part instead.
21 Param(String),
22 /// A table reference. Only produced in source/target positions
23 /// (`FROM person`, `CREATE person`): a bare identifier in expression
24 /// position is a field path, not a table.
25 Table(Spanned<String>),
26 /// A record id literal: `person:one`. The id's internal structure is
27 /// kept opaque (ids can be composite; nothing consumes their shape).
28 RecordId {
29 /// The table portion (`person` in `person:one`).
30 table: Spanned<String>,
31 /// The id portion, kept as an opaque span.
32 id: crate::span::ByteRange,
33 },
34 /// A binary operation: `a + b`, `a AND b`.
35 Binary {
36 /// Left operand.
37 lhs: Box<Spanned<Expr>>,
38 /// The operator.
39 op: Spanned<BinaryOp>,
40 /// Right operand.
41 rhs: Box<Spanned<Expr>>,
42 },
43 /// A prefix operation: `!x`, `-x`.
44 Prefix {
45 /// The operator.
46 op: Spanned<PrefixOp>,
47 /// The operand.
48 expr: Box<Spanned<Expr>>,
49 },
50 /// A function call.
51 Call(Call),
52 /// An object literal: `{ a: 1, b: 2 }`.
53 Object(Vec<(Spanned<String>, Spanned<Expr>)>),
54 /// An array literal: `[1, 2, 3]`.
55 Array(Vec<Spanned<Expr>>),
56 /// A parenthesized statement used as a value: `(SELECT ...)`.
57 Subquery(Box<Spanned<Statement>>),
58 /// A `{ ...; ... }` block used as a value.
59 Block(Block),
60 /// A type cast: `<int> $x`.
61 Cast {
62 /// The target type.
63 ty: Spanned<TypeExpr>,
64 /// The expression being cast.
65 expr: Box<Spanned<Expr>>,
66 },
67 /// A closure value.
68 Closure(Closure),
69 /// An expression that failed to lower.
70 Partial(PartialNode),
71}
72
73/// A closure value: `|$x: int| $x + 1` / `|$x| -> int { ... }`.
74#[derive(Clone, Debug, PartialEq)]
75pub struct Closure {
76 /// Parameters with their declared types, if any.
77 pub params: Vec<(Spanned<String>, Option<Spanned<TypeExpr>>)>,
78 /// The declared return type (`-> int`), if any.
79 pub return_ty: Option<Spanned<TypeExpr>>,
80 /// The body — a block lowers to [`Expr::Block`].
81 pub body: Box<Spanned<Expr>>,
82}
83
84/// A literal value. Carries only what analysis consumes: the `Int` payload
85/// feeds `LIMIT`/array-length facts; the rest matter for their *kind*.
86/// Datetime/Uuid/Regex arise from prefixed strings (`d'…'`/`u'…'`/`r'…'`),
87/// normalized during lowering.
88#[derive(Clone, Debug, PartialEq)]
89pub enum Literal {
90 /// An integer literal; its value feeds `LIMIT`/array-length facts.
91 Int(i64),
92 /// A floating-point literal.
93 Float(f64),
94 /// A `decimal` literal — only its kind matters, so no value is kept.
95 Decimal,
96 /// A string literal's contents.
97 String(String),
98 /// A boolean literal.
99 Bool(bool),
100 /// The `NONE` literal.
101 None,
102 /// The `NULL` literal.
103 Null,
104 /// A duration literal, carrying its raw text (`1w2d`).
105 Duration(String),
106 /// A datetime literal's inner text (`2024-01-01T00:00:00Z`).
107 Datetime(String),
108 /// A uuid literal's inner text.
109 Uuid(String),
110 /// A regex literal's pattern text.
111 Regex(String),
112}
113
114/// A dotted / graph path: `profile.email`, `->likes->post.{title, id}`,
115/// `$user.name`, `tags[WHERE active]`. The load-bearing type of the AST —
116/// every part is individually spanned so diagnostics can point at one arrow
117/// or one field.
118#[derive(Clone, Debug, PartialEq)]
119pub struct Idiom {
120 /// The path segments, in order; each is individually spanned.
121 pub parts: Vec<Spanned<IdiomPart>>,
122}
123
124/// One segment of an [`Idiom`] path.
125#[derive(Clone, Debug, PartialEq)]
126pub enum IdiomPart {
127 /// A leading value the rest of the path is applied to: `$user.name`,
128 /// `(SELECT ...)[0]`. Always the first part when present.
129 Start(Box<Spanned<Expr>>),
130 /// A plain field segment: `name`.
131 Field(String),
132 /// Index by expression: `tags[0]`, `tags[$i]`.
133 Index(Box<Spanned<Expr>>),
134 /// `.*` / `[*]`.
135 All,
136 /// `[$]` — last element.
137 Last,
138 /// One graph step: `->likes` / `<-likes` / `<->likes`, possibly a full
139 /// inline selection (`->(likes WHERE since > $x)`).
140 Graph {
141 /// Direction, spanned to the arrow token itself.
142 dir: Spanned<GraphDir>,
143 /// The edge selection reached by the step.
144 step: GraphStep,
145 },
146 /// Brace selection: `.{name, age}` — real sub-idioms, not comma-split text.
147 Destructure(Vec<Spanned<Idiom>>),
148 /// Inline filter: `[WHERE ...]`.
149 Where(Box<Spanned<Expr>>),
150 /// Method call as a path part: `foo.len()`.
151 Method {
152 /// The method name.
153 name: Spanned<String>,
154 /// The call arguments.
155 args: Vec<Spanned<Expr>>,
156 },
157 /// Graph recursion: `.{1..3}` / `.{..}` — `bounded` is whether an
158 /// upper bound was written.
159 Recurse {
160 /// Whether an upper recursion bound was written.
161 bounded: bool,
162 },
163 /// Optional chaining marker: `foo?.bar`.
164 Optional,
165 /// A path segment that failed to lower.
166 Partial(PartialNode),
167}
168
169/// Direction of a graph traversal step.
170#[derive(Clone, Copy, Debug, PartialEq, Eq)]
171pub enum GraphDir {
172 /// `->` — outgoing edges.
173 Out,
174 /// `<-` — incoming edges.
175 In,
176 /// `<->` — edges in either direction.
177 Both,
178}
179
180/// The selection inside one graph step. SurrealDB allows a full mini-select
181/// here (multiple edge tables, WHERE, ORDER, LIMIT, alias, ...) — we model
182/// what analysis consumes (targets + filter) and bucket the rest.
183#[derive(Clone, Debug, PartialEq)]
184pub struct GraphStep {
185 /// One or more edge tables: `->likes` or `->(likes, follows)`.
186 pub targets: Vec<Spanned<String>>,
187 /// `->(likes WHERE since > $x)` / `->likes[WHERE ...]`.
188 pub where_clause: Option<Box<Spanned<Expr>>>,
189 /// The step is a record-reference traversal (`<~`), not a graph-edge
190 /// traversal (`<-`/`->`/`<->`). Reference steps follow `REFERENCE` fields
191 /// rather than relation tables, so typing resolves them differently.
192 pub reference: bool,
193}
194
195/// A function call. `path` is pre-normalized during lowering
196/// (`type::is::record` → `type::is_record`); argument expressions keep their
197/// own spans so per-argument diagnostics need no re-derivation.
198#[derive(Clone, Debug, PartialEq)]
199pub struct Call {
200 /// The canonicalized function path (`type::is_record`).
201 pub path: Spanned<String>,
202 /// The argument expressions, each retaining its own span.
203 pub args: Vec<Spanned<Expr>>,
204}
205
206/// Binary operators the analyzers understand, plus an explicit escape hatch
207/// for everything else — an unknown operator is a fact, not a guess.
208#[derive(Clone, Debug, PartialEq, Eq)]
209pub enum BinaryOp {
210 /// `+`
211 Add,
212 /// `-`
213 Sub,
214 /// `*`
215 Mul,
216 /// `/`
217 Div,
218 /// `=` / `==`
219 Eq,
220 /// `!=`
221 NotEq,
222 /// `<`
223 Lt,
224 /// `<=`
225 LtEq,
226 /// `>`
227 Gt,
228 /// `>=`
229 GtEq,
230 /// `AND` / `&&`
231 And,
232 /// `OR` / `||`
233 Or,
234 /// `??` — null coalescing.
235 NullCoalesce,
236 /// Any operator not modeled above, kept as raw text.
237 Other(String),
238}
239
240/// Prefix operators, with an explicit escape hatch for the unmodeled.
241#[derive(Clone, Debug, PartialEq, Eq)]
242pub enum PrefixOp {
243 /// `!` — logical negation.
244 Not,
245 /// `-` — arithmetic negation.
246 Neg,
247 /// `+` — unary plus.
248 Pos,
249 /// Any operator not modeled above, kept as raw text.
250 Other(String),
251}