Skip to main content

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}