use std::collections::HashSet;
use crate::ast::{
BinaryOp, Consumer, CountCmp, Expr, Follow, FollowDestination, LogicalOp, OpNode, OrderSpec,
Query, RelOp, SelectItem, Span, Subquery, UnaryOp, Where,
};
use crate::errors::{OqxError, Result};
use crate::lexer::{TokType, Token, lex_string, lex_template};
use crate::print::print_template;
use crate::resolve::{resolve_select, resolve_where};
use crate::value::Value;
use crate::walk::Node;
const CONSUMERS: [&str; 6] = ["collect", "exists", "none", "count", "first", "single"];
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
enum Clause {
Select,
From,
Where,
Frontier,
Depth,
Follow,
OrderBy,
Limit,
Offset,
}
const CLAUSE_ORDER: [Clause; 9] = [
Clause::Select,
Clause::From,
Clause::Where,
Clause::Frontier,
Clause::Depth,
Clause::Follow,
Clause::OrderBy,
Clause::Limit,
Clause::Offset,
];
impl Clause {
fn as_str(self) -> &'static str {
match self {
Clause::Select => "select",
Clause::From => "from",
Clause::Where => "where",
Clause::Frontier => "frontier",
Clause::Depth => "depth",
Clause::Follow => "follow",
Clause::OrderBy => "order by",
Clause::Limit => "limit",
Clause::Offset => "offset",
}
}
fn destination_only(self) -> bool {
matches!(self, Clause::Depth)
}
}
fn clauses_of(ctx: BodyCtx) -> impl Iterator<Item = Clause> {
let dest = ctx.is_destination();
CLAUSE_ORDER
.into_iter()
.filter(move |c| dest || !c.destination_only())
}
fn clause_order_sentence(ctx: BodyCtx) -> String {
clauses_of(ctx)
.map(|c| c.as_str())
.collect::<Vec<_>>()
.join(", ")
}
const CLAUSE_WORDS: [&str; 18] = [
"collect", "exists", "none", "count", "first", "single", "order", "by", "asc", "desc",
"follow", "distinct", "frontier", "depth", "in", "values", "limit", "offset",
];
const RELOPS: [&str; 6] = ["==", "!=", "<", "<=", ">", ">="];
const CMP_OPS: [&str; 6] = ["==", "!=", "<", "<=", ">", ">="];
const ADD_OPS: [&str; 2] = ["+", "-"];
const MUL_OPS: [&str; 3] = ["*", "/", "%"];
const LITERAL_WORDS: [&str; 3] = ["true", "false", "null"];
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum BodyCtx {
Top,
Block {
op: Consumer,
lifts_allowed: bool,
destination: bool,
},
}
impl BodyCtx {
fn lifts_allowed(self) -> bool {
matches!(
self,
BodyCtx::Block {
lifts_allowed: true,
..
}
)
}
fn is_destination(self) -> bool {
matches!(
self,
BodyCtx::Block {
destination: true,
..
}
)
}
}
pub fn parse_template<S: AsRef<str>>(fragments: &[S], values: usize) -> Result<Query> {
Parser::new(lex_template(fragments, values)?).parse_query()
}
pub fn parse_string(src: &str) -> Result<Query> {
Parser::new(lex_string(src)?).parse_query()
}
fn number_value(text: &str) -> f64 {
text.parse().unwrap_or(f64::NAN)
}
fn is_integer(v: f64) -> bool {
v.is_finite() && v.fract() == 0.0
}
#[derive(Default)]
struct BodyClauses {
froms: Vec<Expr>,
r#where: Option<Where>,
select: Vec<SelectItem>,
order_by: Option<Vec<OrderSpec>>,
follow: Option<Follow>,
frontier: Option<Expr>,
depth: Option<u32>,
distinct: bool,
values: bool,
limit: Option<Expr>,
offset: Option<Expr>,
}
struct Parser {
tokens: Vec<Token>,
pos: usize,
defer_lookup: bool,
depth: usize,
}
enum LeadingRun {
Where(Where),
Projection(Vec<SelectItem>, bool),
}
impl Parser {
fn new(tokens: Vec<Token>) -> Self {
debug_assert!(tokens.last().is_some_and(|t| t.kind == TokType::Eof));
Self {
tokens,
pos: 0,
defer_lookup: false,
depth: 0,
}
}
fn with_depth<T>(&mut self, d: usize, f: impl FnOnce(&mut Self) -> Result<T>) -> Result<T> {
let saved = self.depth;
self.depth = d;
let out = f(self);
self.depth = saved;
out
}
fn peek(&self) -> &Token {
&self.tokens[self.pos.min(self.tokens.len() - 1)]
}
fn peek_at(&self, n: usize) -> Option<&Token> {
self.tokens.get(self.pos + n)
}
fn next(&mut self) -> Token {
let t = self.peek().clone();
self.pos += 1;
t
}
fn at(&self, kind: TokType) -> bool {
self.peek().kind == kind
}
fn at_word(&self, kind: TokType, value: &str) -> bool {
let t = self.peek();
t.kind == kind && t.value == value
}
fn at_op(&self, value: &str) -> bool {
self.at_word(TokType::Op, value)
}
fn fail<T>(&self, msg: impl AsRef<str>) -> Result<T> {
Err(OqxError::parse(format!(
"{} (at offset {})",
msg.as_ref(),
self.peek().pos
)))
}
fn start(&self) -> usize {
self.peek().pos
}
fn end(&self) -> usize {
if self.pos == 0 {
0
} else {
self.tokens[self.pos - 1].end
}
}
fn sp(&self, start: usize) -> Span {
Span::new(start, self.end())
}
fn parse_query(&mut self) -> Result<Query> {
let start = self.start();
if let Some(mut directive) = self.try_op(false, false)? {
if !self.at(TokType::Eof) && (self.at(TokType::LBracket) || self.at_value_directive()) {
let save = self.pos;
match self.parse_postfix(Some(Expr::Op(Box::new(directive.clone())))) {
Ok(Expr::Op(op)) if self.at(TokType::Eof) => directive = *op,
Ok(_) => self.pos = save,
Err(e) if e.stage == crate::errors::Stage::Parse => self.pos = save,
Err(e) => return Err(e),
}
}
if self.at(TokType::Eof) {
return Ok(self.query_from_op(directive, start));
}
return self.fail(format!(
"unexpected {} after the top-level directive",
self.tok_desc()
));
}
if matches!(
self.peek().kind,
TokType::Ident | TokType::Caret | TokType::Binding
) {
let save = self.pos;
match self.parse_postfix(None) {
Ok(Expr::Op(op)) if self.at(TokType::Eof) => {
return Ok(self.query_from_op(*op, start));
}
Ok(_) => {}
Err(e) if e.stage == crate::errors::Stage::Parse => {}
Err(e) => return Err(e),
}
self.pos = save;
}
let mut body = self.parse_body(BodyCtx::Top)?;
if !self.at(TokType::Eof) {
return self.fail(format!(
"unexpected {} after the query — nothing may follow the last clause",
self.tok_desc()
));
}
if body.froms.is_empty() {
return self.fail(
"a query must name its source with `from <collection>` (or be `<collection> <consumer> { … }`)",
);
}
let source = body.froms.remove(0);
Ok(Query {
source,
from: body.froms,
r#where: body.r#where,
select: body.select,
order_by: body.order_by,
consumer: Consumer::Collect,
follow: body.follow,
frontier: body.frontier,
distinct: body.distinct,
values: body.values,
limit: body.limit,
offset: body.offset,
span: self.sp(start),
})
}
fn query_from_op(&self, directive: OpNode, start: usize) -> Query {
let sub = directive.sub;
Query {
source: directive.receiver,
from: sub.from,
r#where: sub.r#where,
select: sub.select,
order_by: sub.order_by,
consumer: directive.op,
follow: sub.follow,
frontier: sub.frontier,
distinct: directive.distinct,
values: sub.values,
limit: sub.limit,
offset: sub.offset,
span: self.sp(start),
}
}
fn tok_desc(&self) -> String {
let t = self.peek();
if t.kind == TokType::Eof {
"end of query".to_string()
} else if t.value.is_empty() {
format!("'{}'", t.kind.as_str())
} else {
format!("'{}'", t.value)
}
}
fn parse_body(&mut self, ctx: BodyCtx) -> Result<BodyClauses> {
let mut body = BodyClauses::default();
let mut stage: Option<Clause> = None;
let row_depth = if ctx == BodyCtx::Top { 1 } else { self.depth };
while !self.at(TokType::Eof) && !self.at(TokType::RBrace) {
if self.at_word(TokType::Kw, "select") {
self.enter(&mut stage, Clause::Select, ctx)?;
self.next();
if self.at_word(TokType::Ident, "distinct") {
self.next();
body.distinct = true;
}
let (items, values) = self.with_depth(row_depth, |p| p.parse_projection(ctx))?;
body.select = items;
body.values = values;
continue;
}
if self.at_word(TokType::Kw, "from") {
self.enter(&mut stage, Clause::From, ctx)?;
self.next();
let e = self.parse_value_expr()?;
body.froms.push(e);
continue;
}
if self.at_word(TokType::Kw, "where") {
self.enter(&mut stage, Clause::Where, ctx)?;
self.next();
body.r#where = Some(self.with_depth(row_depth, |p| p.parse_where())?);
continue;
}
if self.at_word(TokType::Ident, "frontier") && self.peek_at(1).is_some_and(starts_value)
{
self.enter(&mut stage, Clause::Frontier, ctx)?;
self.next();
let d = if ctx.is_destination() {
row_depth.saturating_sub(1)
} else {
row_depth
};
body.frontier = Some(self.with_depth(d, |p| p.parse_value_expr())?);
continue;
}
if ctx.is_destination()
&& self.at_word(TokType::Ident, "depth")
&& self.peek_at(1).is_some_and(|t| t.kind == TokType::Number)
{
self.enter(&mut stage, Clause::Depth, ctx)?;
self.next();
let v = number_value(&self.next().value);
if !is_integer(v) || !(1.0..=8.0).contains(&v) {
return self.fail("follow depth must be an integer between 1 and 8");
}
body.depth = Some(v as u32);
continue;
}
if self.at_follow() {
self.enter(&mut stage, Clause::Follow, ctx)?;
body.follow = Some(self.with_depth(row_depth, |p| p.parse_follow())?);
continue;
}
if self.at_order_by() {
self.enter(&mut stage, Clause::OrderBy, ctx)?;
self.next(); self.next(); body.order_by = Some(self.with_depth(row_depth, |p| p.parse_order_specs())?);
continue;
}
if self.at_bound() {
let is_limit = self.peek().value == "limit";
let clause = if is_limit {
Clause::Limit
} else {
Clause::Offset
};
self.enter(&mut stage, clause, ctx)?;
self.next();
if ctx == BodyCtx::Top && self.at(TokType::Caret) {
return self.fail(format!(
"`{} ^…` at the top level has no enclosing scope — a top-level bound is a number literal or a binding; inside a block `^name` reads the enclosing row",
clause.as_str()
));
}
let e = self.parse_postfix(None)?;
if is_limit {
body.limit = Some(e);
} else {
body.offset = Some(e);
}
continue;
}
if stage.is_none()
&& (self.at(TokType::Ident)
|| self.at(TokType::Binding)
|| self.at(TokType::Caret)
|| self.can_start_value())
{
match self.with_depth(row_depth, |p| p.parse_leading_run(ctx))? {
LeadingRun::Where(w) => {
self.enter(&mut stage, Clause::Where, ctx)?;
body.r#where = Some(w);
}
LeadingRun::Projection(items, values) => {
self.enter(&mut stage, Clause::Select, ctx)?;
body.select = items;
body.values = values;
}
}
continue;
}
return self.fail_unexpected_in_body(stage, ctx);
}
if let Some(w) = &body.r#where {
if let Err(msg) = resolve_where(&body.select, w.clone()) {
return self.fail(msg);
}
}
if let Err(msg) = resolve_select(&body.select) {
return self.fail(msg);
}
if body.frontier.is_some() && body.follow.is_none() && !ctx.is_destination() {
return self.fail(
"`frontier` needs a `follow` — a body's `frontier <expr>` marks which of its rows (the walk's seeds) are frontiers; a destination block's marks the occurrences reached through it",
);
}
Ok(body)
}
fn parse_leading_run(&mut self, ctx: BodyCtx) -> Result<LeadingRun> {
if ctx != BodyCtx::Top {
let save = self.pos;
let first_kind = self.peek().kind;
self.defer_lookup = true;
let speculative = self.parse_where();
self.defer_lookup = false;
let mut spec_err: Option<(OqxError, usize)> = None;
match speculative {
Ok(w) => {
if predicate_shaped(&w, first_kind) && !self.at_word(TokType::Ident, "values") {
if self.at(TokType::Comma) {
return self.fail(format!(
"a leading predicate starts a `where`-first body, which has no projection — write the projection first: `{{ <projection> where {} }}`",
describe(Node::Where(&w))
));
}
self.reject_bare_lookups(&w)?;
return Ok(LeadingRun::Where(w));
}
}
Err(e) if e.stage == crate::errors::Stage::Parse => spec_err = Some((e, self.pos)),
Err(e) => return Err(e),
}
self.pos = save;
if let Some((err, spec_pos)) = spec_err {
return match self.parse_projection(ctx) {
Ok((items, values)) => Ok(LeadingRun::Projection(items, values)),
Err(e) if e.stage == crate::errors::Stage::Parse && self.pos < spec_pos => {
Err(err)
}
Err(e) => Err(e),
};
}
}
let (items, values) = self.parse_projection(ctx)?;
Ok(LeadingRun::Projection(items, values))
}
fn enter(&self, stage: &mut Option<Clause>, clause: Clause, ctx: BodyCtx) -> Result<()> {
if *stage == Some(clause) {
return self.fail(format!("duplicate `{}` clause", clause.as_str()));
}
if stage.is_some_and(|last| clause < last) {
let last = stage.expect("checked above");
return self.fail(format!(
"`{}` must come before `{}` — OQX clause order is {}",
clause.as_str(),
last.as_str(),
clause_order_sentence(ctx)
));
}
*stage = Some(clause);
Ok(())
}
fn fail_unexpected_in_body<T>(&self, stage: Option<Clause>, ctx: BodyCtx) -> Result<T> {
let t = self.peek();
let remaining = clauses_of(ctx)
.filter(|c| stage.is_none_or(|s| *c > s))
.map(|c| c.as_str())
.collect::<Vec<_>>()
.join("/");
let consumer_word = t.kind == TokType::Ident && CONSUMERS.contains(&t.value.as_str());
let v = &t.value;
if let Some(last) = stage {
let last = last.as_str();
let is_word = matches!(
t.kind,
TokType::Ident | TokType::Kw | TokType::Binding | TokType::Caret
);
if !is_word && t.kind != TokType::Comma {
return match ctx {
BodyCtx::Top => self.fail(format!(
"unexpected {} after the query — nothing may follow the last clause (expected {remaining} or the end of the query)",
self.tok_desc()
)),
BodyCtx::Block { op, .. } => self.fail(format!(
"unexpected {} in the {} {{ … }} block — expected {remaining} or '}}' to close the block",
self.tok_desc(),
op.as_str()
)),
};
}
if t.kind == TokType::Ident {
match v.as_str() {
"order" => {
return self.fail(format!(
"unexpected 'order' after `{last}` — an ordering is written `order by <expr> [asc|desc]`"
));
}
"follow" => {
return self.fail(format!(
"unexpected 'follow' after `{last}` — `follow` needs a relation: `follow <relation>` or `follow distinct <relation>`"
));
}
"limit" | "offset" => {
return self.fail(format!(
"unexpected '{v}' after `{last}` — a bound is a non-negative number literal, a binding, or (inside a block) an outer reference `^name`"
));
}
"depth" if !ctx.is_destination() => {
return self.fail(format!(
"unexpected 'depth' after `{last}` — `depth` is a clause of a follow destination block only (`follow <relation> {{ depth 2 }}`)"
));
}
"depth" => {
return self
.fail("expected an integer after `depth` (`depth 1`..`depth 8`)");
}
"frontier" => {
return self.fail(format!(
"unexpected 'frontier' after `{last}` — `frontier` takes an expression (`frontier <expr>`) and belongs to a body that has a `follow`"
));
}
_ => {}
}
}
}
match stage {
Some(Clause::From) if consumer_word => self.fail(format!(
"unexpected `{v}` after `from` — a whole-query consumer is written `<collection> {v} {{ … }}`; to project a field named {v} write `select {v} from …`; a predicate needs `where`"
)),
Some(Clause::From) => self.fail(format!(
"unexpected {} after `from` — a predicate needs `where` (there is no implicit where), and a projection goes before `from` (`select … from …`); expected {remaining}",
self.tok_desc()
)),
Some(Clause::Select) if consumer_word => self.fail(format!(
"unexpected `{v}` after a projection — a consumer test is a predicate: write `where <relation> {v} {{ … }}` after the projection (a block may lead with a predicate only when nothing is projected); a nested block in a projection needs a name (`name: <relation> collect {{ … }}`)"
)),
None => self.fail(format!(
"unexpected {} — expected a projection or {remaining}",
self.tok_desc()
)),
Some(last) => self.fail(format!(
"unexpected {} after `{}` — expected {remaining}",
self.tok_desc(),
last.as_str()
)),
}
}
fn at_bound(&self) -> bool {
let t = self.peek();
if t.kind != TokType::Ident || (t.value != "limit" && t.value != "offset") {
return false;
}
self.peek_at(1).is_some_and(|nx| {
matches!(nx.kind, TokType::Number | TokType::Binding | TokType::Caret)
})
}
fn at_scope_ref_at(&self, n: usize) -> bool {
self.peek_at(n).is_some_and(|t| t.kind == TokType::Caret) || self.at_absolute_ref_at(n)
}
fn at_absolute_ref(&self) -> bool {
self.at_absolute_ref_at(0)
}
fn at_absolute_ref_at(&self, n: usize) -> bool {
let (Some(t), Some(nx)) = (self.peek_at(n), self.peek_at(n + 1)) else {
return false;
};
t.kind == TokType::Number
&& !t.value.is_empty()
&& t.value.bytes().all(|b| b.is_ascii_digit())
&& nx.kind == TokType::Caret
&& nx.pos == t.end
}
fn parse_scope_ref(&mut self) -> Result<usize> {
if self.at_absolute_ref() {
let n = self.peek().clone();
let target: usize = n.value.parse().unwrap_or(usize::MAX);
if target >= self.depth {
return self.fail(format!(
"scope {} does not enclose this block (the current scope is depth {})",
n.value, self.depth
));
}
self.next(); self.next(); return Ok(self.depth - target);
}
Ok(self.parse_carets())
}
fn at_destination_start(&self) -> bool {
matches!(
self.peek().kind,
TokType::Ident | TokType::Binding | TokType::LParen
) || self.at_scope_ref_at(0)
}
fn at_order_by(&self) -> bool {
self.at_word(TokType::Ident, "order")
&& self
.peek_at(1)
.is_some_and(|nx| nx.kind == TokType::Ident && nx.value == "by")
}
fn at_follow(&self) -> bool {
self.at_word(TokType::Ident, "follow")
&& (self.peek_at(1).is_some_and(|nx| {
matches!(nx.kind, TokType::Ident | TokType::Binding | TokType::LParen)
}) || self.at_scope_ref_at(1))
}
fn parse_follow(&mut self) -> Result<Follow> {
let start = self.start();
self.next(); let mut distinct = false;
if self.at_word(TokType::Ident, "distinct") {
self.next();
distinct = true;
if !self.at_destination_start() {
return self.fail(
"expected a relation after `follow distinct` (`follow distinct <relation>`)",
);
}
}
let mut destinations = vec![self.parse_follow_destination()?];
while self.at(TokType::Comma) {
self.next();
if !self.at_destination_start() {
return self
.fail("expected a relation after ',' (`follow <relation>, <relation>`)");
}
destinations.push(self.parse_follow_destination()?);
}
Ok(Follow {
destinations,
distinct,
span: self.sp(start),
})
}
fn parse_follow_destination(&mut self) -> Result<FollowDestination> {
let outer = self.at_scope_ref_at(0);
if let Some(op) = self.try_op(false, true)? {
if !matches!(
op.op,
Consumer::Collect | Consumer::First | Consumer::Single
) {
return self.fail(format!(
"a follow destination must use collect/first/single, not `{}` (exists/none/count are where-position tests)",
op.op.as_str()
));
}
return Ok(FollowDestination::Block(Box::new(op)));
}
if outer {
return self.fail(
"`follow` takes a relation of the current row (`follow <relation>`); an outer reference walks a root collection only as a destination block — `follow ^docs { where … }` or `follow ^docs collect { … }`",
);
}
Ok(FollowDestination::Relation(self.parse_receiver()?))
}
fn parse_receiver(&mut self) -> Result<Expr> {
if self.at(TokType::Binding) {
return Ok(binding_node(&self.next()));
}
if self.at(TokType::LParen) {
if let Some(t) = self.try_tuple()? {
return Ok(t);
}
return self.fail(
"expected a collection navigation (a property/relation name) or a tuple `(a, b)`",
);
}
let start = self.start();
let levels = self.parse_scope_ref()?;
if !self.at(TokType::Ident) {
return self.fail("expected a collection navigation (a property/relation name)");
}
if LITERAL_WORDS.contains(&self.peek().value.as_str()) {
return self.fail(format!(
"`{}` is a literal, not a collection",
self.peek().value
));
}
let head = self.next();
Ok(self.parse_nav_from(head, levels, start)?.0)
}
fn parse_carets(&mut self) -> usize {
let mut levels = 0;
while self.at(TokType::Caret) {
self.next();
levels += 1;
}
levels
}
fn parse_nav_from(
&mut self,
head: Token,
levels: usize,
start: usize,
) -> Result<(Expr, String)> {
let mut expr = if levels > 0 {
Expr::Outer {
levels,
name: head.value.clone(),
span: Span::new(start, head.end),
}
} else {
Expr::Ident {
name: head.value.clone(),
span: Span::new(start, head.end),
}
};
let mut name = head.value;
if levels == 0 && self.at(TokType::LParen) {
let args = self.parse_args()?;
expr = Expr::Call {
recv: None,
name: name.clone(),
args,
span: self.sp(start),
};
}
while self.at(TokType::Dot) {
self.next();
if !self.at(TokType::Ident) {
return self.fail("expected a property name after '.'");
}
name = self.next().value;
expr = Expr::Member {
recv: Box::new(expr),
name: name.clone(),
span: self.sp(start),
};
}
Ok((expr, name))
}
fn parse_projection(&mut self, ctx: BodyCtx) -> Result<(Vec<SelectItem>, bool)> {
let mut items = vec![self.parse_select_item(ctx)?];
while self.at(TokType::Comma) {
self.next();
items.push(self.parse_select_item(ctx)?);
}
let mut values = false;
if self.at_word(TokType::Ident, "values") {
self.next();
values = true;
if items.len() != 1 {
return self.fail(format!(
"`values` projects exactly one expression (got {})",
items.len()
));
}
if matches!(&items[0], SelectItem::Field { lift, .. } if *lift > 0) {
return self.fail("a lift (^name: …) cannot be combined with `values`");
}
} else {
let mut seen: HashSet<String> = HashSet::new();
for it in &items {
if it.name().is_empty() {
if matches!(it, SelectItem::Collect { .. }) {
return self.fail(
"a nested block in a projection needs a name (`name: <relation> collect { … }`); as a predicate, a consumer test is written `where <relation> exists { … }`",
);
}
if ctx == BodyCtx::Top {
return self.fail(
"a leading expression is a projection (select): an item that is not a plain name needs an alias (`name: expr`) or `values`; to filter by it write `where …` after `from` — at the top level a predicate is never implicit (the clause order puts `from` first)",
);
}
return self.fail(
"an item that is not a plain name needs an alias (`name: expr`) or `values`; to filter by it write `where …` after the projection (a block may lead with a predicate only when nothing is projected: `{ age > 18 }`)",
);
}
let lift = match it {
SelectItem::Field { lift, .. } => *lift,
SelectItem::Collect { .. } => 0,
};
let key = format!("{}{}", "^".repeat(lift), it.name());
if !seen.insert(key) {
return self.fail(format!(
"duplicate projection name '{}' — each projected item needs its own name (alias one: `other: expr`)",
it.name()
));
}
}
}
Ok((items, values))
}
fn parse_select_item(&mut self, ctx: BodyCtx) -> Result<SelectItem> {
let start = self.start();
let lift = self.parse_scope_ref()?;
if !self.at(TokType::Ident) && !self.can_start_value() {
return self.fail("expected a projection name");
}
if lift > 0 && !ctx.lifts_allowed() {
let what = if self.at(TokType::Ident) {
format!("^{}", self.peek().value)
} else {
"^name".to_string()
};
return self.fail(format!(
"a lift ({what}) binds a value into the enclosing row and is only valid in a `collect {{ … }}` in where position (`where <relation> collect {{ {what}: … }}`)"
));
}
if self.at(TokType::Ident) && self.peek_at(1).is_some_and(|t| t.kind == TokType::Colon) {
let name = self.next().value;
self.next(); if let Some(op) = self.try_op(false, false)? {
if self.at_scalar_continuation() || self.at_value_directive() {
let expr = self.parse_or(Some(Expr::Op(Box::new(op))))?;
return self.finish_item(name, expr, lift, start);
}
if !matches!(
op.op,
Consumer::Collect | Consumer::First | Consumer::Single
) {
return self.fail(format!(
"projection '{name}' must use collect/first/single, not {} (exists/none/count are where-position tests)",
op.op.as_str()
));
}
return self.finish_item(name, Expr::Op(Box::new(op)), lift, start);
}
let expr = self.parse_value_expr()?;
return self.finish_item(name, expr, lift, start);
}
let expr = self.parse_value_expr()?;
let name = nav_key(&expr).unwrap_or("").to_string();
self.finish_item(name, expr, lift, start)
}
fn finish_item(
&self,
name: String,
expr: Expr,
lift: usize,
start: usize,
) -> Result<SelectItem> {
if let Expr::Op(op) = expr {
if lift > 0 {
return self.fail(format!(
"a lift (^{name}) value must be a scalar expression, not {} {{ … }}",
op.op.as_str()
));
}
return Ok(SelectItem::Collect {
name,
op,
span: self.sp(start),
});
}
Ok(SelectItem::Field {
name,
expr,
lift,
span: self.sp(start),
})
}
fn parse_order_specs(&mut self) -> Result<Vec<OrderSpec>> {
let mut specs = vec![self.parse_order_spec()?];
while self.at(TokType::Comma) {
self.next();
specs.push(self.parse_order_spec()?);
}
Ok(specs)
}
fn parse_order_spec(&mut self) -> Result<OrderSpec> {
let start = self.start();
let expr = self.parse_value_expr()?;
let mut desc = false;
if self.at_word(TokType::Ident, "asc") {
self.next();
} else if self.at_word(TokType::Ident, "desc") {
self.next();
desc = true;
}
Ok(OrderSpec {
expr,
desc,
span: self.sp(start),
})
}
fn parse_where(&mut self) -> Result<Where> {
self.parse_where_or()
}
fn parse_where_or(&mut self) -> Result<Where> {
let start = self.start();
let left = self.parse_where_and()?;
if !self.at_or() {
return Ok(left);
}
let mut parts = vec![left];
while self.at_or() {
self.next();
parts.push(self.parse_where_and()?);
}
Ok(Where::Or {
parts,
span: self.sp(start),
})
}
fn parse_where_and(&mut self) -> Result<Where> {
let start = self.start();
let left = self.parse_where_primary()?;
if !self.at_and() {
return Ok(left);
}
let mut parts = vec![left];
while self.at_and() {
self.next();
parts.push(self.parse_where_primary()?);
}
Ok(Where::And {
parts,
span: self.sp(start),
})
}
fn at_and(&self) -> bool {
self.at_op("&&") || self.at_word(TokType::Kw, "and")
}
fn at_or(&self) -> bool {
self.at_op("||") || self.at_word(TokType::Kw, "or")
}
fn parse_where_primary(&mut self) -> Result<Where> {
let start = self.pos;
let mut not_starts: Vec<usize> = Vec::new();
loop {
if self.at_op("!") || self.at_word(TokType::Kw, "not") {
not_starts.push(self.next().pos);
} else if self.at_word(TokType::Kw, "is") {
let p = self.next().pos;
not_starts.push(p);
not_starts.push(p);
} else {
break;
}
}
if self.at(TokType::LParen) {
if let Some(tuple) = self.try_tuple()? {
if let Some(op) = self.at_block() {
let op = self.parse_consumer_block(tuple, op, true, false)?;
let op = self.finish_where_op(op)?;
return Ok(wrap_not(Where::Op(Box::new(op)), ¬_starts));
}
let mut e = self.parse_postfix(Some(tuple))?;
for &at in not_starts.iter().rev() {
let span = Span::new(at, e.span().end);
e = Expr::Unary {
op: UnaryOp::Not,
expr: Box::new(e),
span,
};
}
let expr = self.parse_cmp(Some(e))?;
let span = expr.span();
return Ok(Where::Scalar { expr, span });
}
let lparen = self.next().pos;
let inner = self.parse_where()?;
if !self.at(TokType::RParen) {
return self.fail("expected ')' to close a grouped where expression");
}
self.next();
let group = Span::new(lparen, self.end());
if self.at_scalar_continuation() {
let promoted = self.where_to_expr(inner)?.with_span(group);
let mut e = self.parse_postfix(Some(promoted))?;
for &at in not_starts.iter().rev() {
let span = Span::new(at, e.span().end);
e = Expr::Unary {
op: UnaryOp::Not,
expr: Box::new(e),
span,
};
}
let expr = self.parse_cmp(Some(e))?;
let span = expr.span();
return Ok(Where::Scalar { expr, span });
}
return Ok(wrap_not(inner.with_span(group), ¬_starts));
}
if let Some(op) = self.try_op(true, false)?
&& !matches!(op.op, Consumer::First | Consumer::Single)
{
let op = self.finish_where_op(op)?;
return Ok(wrap_not(Where::Op(Box::new(op)), ¬_starts));
}
self.pos = start;
let expr = self.parse_cmp(None)?;
let leaf = scalar_leaf(expr);
if !self.defer_lookup {
self.reject_bare_lookup(&leaf)?;
}
Ok(leaf)
}
fn reject_bare_lookup(&self, w: &Where) -> Result<()> {
let mut core = w;
while let Where::Not { expr, .. } = core {
core = expr;
}
let Where::Scalar { expr, .. } = core else {
return Ok(());
};
let mut e = expr;
while let Expr::Unary {
op: UnaryOp::Not,
expr,
..
} = e
{
e = expr;
}
if let Expr::Op(op) = e
&& matches!(op.op, Consumer::First | Consumer::Single)
{
return self.fail(format!(
"{} {{ … }} is a select-position lookup; in where use exists {{ … }} / none {{ … }} or count {{ … }} <op> N (`x[p]` is `x first {{ where p }}`; a lookup is a value — compare it, navigate it or require it: `x[p].name == …`, `x[0]!`)",
op.op.as_str()
));
}
Ok(())
}
fn reject_bare_lookups(&self, w: &Where) -> Result<()> {
match w {
Where::And { parts, .. } | Where::Or { parts, .. } => {
parts.iter().try_for_each(|p| self.reject_bare_lookups(p))
}
Where::Not { .. } | Where::Scalar { .. } => self.reject_bare_lookup(w),
Where::Op(_) => Ok(()),
}
}
fn at_scalar_continuation(&self) -> bool {
let t = self.peek();
match t.kind {
TokType::Op => {
let v = t.value.as_str();
CMP_OPS.contains(&v) || ADD_OPS.contains(&v) || MUL_OPS.contains(&v) || v == "!"
}
TokType::Kw => t.value == "is",
TokType::Dot | TokType::Range | TokType::LBracket => true,
TokType::Ident => t.value == "in",
_ => false,
}
}
fn where_to_expr(&self, w: Where) -> Result<Expr> {
Ok(match w {
Where::Scalar { expr, .. } => expr,
Where::Not { expr, span } => Expr::Unary {
op: UnaryOp::Not,
expr: Box::new(self.where_to_expr(*expr)?),
span,
},
Where::And { parts, .. } => self.fold_logical(LogicalOp::And, parts)?,
Where::Or { parts, .. } => self.fold_logical(LogicalOp::Or, parts)?,
Where::Op(op) => {
let hint = if op.op == Consumer::Count {
format!(
" — write `<relation> count {{ … }} {} N` without the parentheses",
self.peek().value
)
} else {
String::new()
};
return self.fail(format!(
"a consumer test ({} {{ … }}) is a predicate, not a value, so it cannot be compared or operated on{hint}",
op.op.as_str()
));
}
})
}
fn fold_logical(&self, op: LogicalOp, parts: Vec<Where>) -> Result<Expr> {
let mut iter = parts.into_iter();
let first = iter
.next()
.expect("an And/Or node always has at least one part");
let mut acc = self.where_to_expr(first)?;
for p in iter {
let right = self.where_to_expr(p)?;
let span = Span::new(acc.span().start, right.span().end);
acc = Expr::Logical {
op,
left: Box::new(acc),
right: Box::new(right),
span,
};
}
Ok(acc)
}
fn finish_where_op(&mut self, mut op: OpNode) -> Result<OpNode> {
if matches!(op.op, Consumer::First | Consumer::Single) {
return self.fail(format!(
"{} {{ … }} is a select-position lookup; in where use exists {{ … }} / none {{ … }} or count {{ … }} <op> N",
op.op.as_str()
));
}
if op.op == Consumer::Collect {
let all_lift = !op.sub.select.is_empty()
&& op
.sub
.select
.iter()
.all(|s| matches!(s, SelectItem::Field { lift, .. } if *lift > 0));
if !all_lift {
return self.fail(
"collect { … } in where must project only ^lift values (else use exists/count)",
);
}
}
if self.peek().kind == TokType::Op && RELOPS.contains(&self.peek().value.as_str()) {
if op.op != Consumer::Count {
return self.fail(format!(
"only count {{ … }} is comparable; '{} {{ … }} <op> N' is not valid",
op.op.as_str()
));
}
let relop = self.next().value;
if !self.at(TokType::Number) {
return self.fail(format!("expected an integer after 'count {{ … }} {relop}'"));
}
let v = number_value(&self.next().value);
if !is_integer(v) {
return self.fail("count comparison takes an integer");
}
let op_enum = RelOp::from_word(&relop).expect("RELOPS membership was checked");
op.count_cmp = Some(CountCmp {
op: op_enum,
value: v,
});
op.span = Span::new(op.span.start, self.end());
}
Ok(op)
}
fn try_op(&mut self, in_where: bool, destination: bool) -> Result<Option<OpNode>> {
let start = self.pos;
let start_pos = self.start();
let mut scope_err: Option<OqxError> = None;
let receiver = if self.at(TokType::Binding) {
binding_node(&self.next())
} else if self.at(TokType::LParen) {
match self.try_tuple()? {
Some(t) => t,
None => {
self.pos = start;
return Ok(None);
}
}
} else if self.at(TokType::Ident) || self.at_scope_ref_at(0) {
let levels = match self.parse_scope_ref() {
Ok(levels) => levels,
Err(e) if e.stage == crate::errors::Stage::Parse => {
scope_err = Some(e);
self.next(); self.next();
1
}
Err(e) => return Err(e),
};
if !self.at(TokType::Ident) {
self.pos = start;
return Ok(None);
}
let head = self.next();
self.parse_nav_from(head, levels, start_pos)?.0
} else {
return Ok(None);
};
if let Some(op) = self.at_block() {
if let Some(e) = scope_err {
return Err(e);
}
if let Expr::Ident { name, .. } = &receiver {
if LITERAL_WORDS.contains(&name.as_str()) {
return self.fail(format!("`{name}` is a literal, not a collection"));
}
if op == Consumer::Collect
&& CONSUMERS.contains(&name.as_str())
&& self.at(TokType::LBrace)
{
return self.fail(format!(
"unexpected '{{' after `{name}` — a consumer needs a receiver: `<collection> {name} {{ … }}`"
));
}
}
return Ok(Some(self.parse_consumer_block(
receiver,
op,
in_where,
destination,
)?));
}
self.pos = start;
Ok(None)
}
fn at_block(&self) -> Option<Consumer> {
if self.at(TokType::LBrace) {
return Some(Consumer::Collect);
}
if self.at_word(TokType::Ident, "distinct")
&& self.peek_at(1).is_some_and(|t| t.kind == TokType::LBrace)
{
return Some(Consumer::Collect);
}
self.at_consumer_block()
}
fn try_tuple(&mut self) -> Result<Option<Expr>> {
let save = self.pos;
let lparen = self.next().pos; let attempt = (|| -> Result<Option<Expr>> {
let mut items = vec![self.parse_value_expr()?];
if !self.at(TokType::Comma) {
return Ok(None);
}
while self.at(TokType::Comma) {
self.next();
items.push(self.parse_value_expr()?);
}
if !self.at(TokType::RParen) {
return self.fail("expected ')' to close the tuple");
}
self.next();
Ok(Some(Expr::Tuple {
items,
span: Span::new(lparen, self.end()),
}))
})();
match attempt {
Ok(Some(t)) => Ok(Some(t)),
Ok(None) => {
self.pos = save;
Ok(None)
}
Err(e) if e.stage == crate::errors::Stage::Parse => {
self.pos = save;
Ok(None)
}
Err(e) => Err(e),
}
}
fn at_value_directive(&self) -> bool {
matches!(
self.at_consumer_block(),
Some(Consumer::Collect | Consumer::First | Consumer::Single)
)
}
fn parse_bracket(&mut self, recv: Expr) -> Result<Expr> {
if matches!(recv, Expr::Lit { .. }) {
return self.fail(format!(
"`{}` is a literal, not a collection",
describe(Node::Expr(&recv))
));
}
let start = recv.span().start;
let lbracket = self.next().pos; let mut index: Option<Expr> = None;
let mut r#where: Option<Where> = None;
let next_is = |p: &Self, n: usize, k: TokType| p.peek_at(n).is_some_and(|t| t.kind == k);
if self.at(TokType::Number) && next_is(self, 1, TokType::RBracket) {
let t = self.next();
let v = number_value(&t.value);
if !is_integer(v) {
return self.fail(format!(
"an index is a non-negative integer literal or a binding (got {})",
t.value
));
}
index = Some(Expr::Lit {
value: Value::Number(v),
span: Span::new(t.pos, t.end),
});
} else if self.at(TokType::Binding) && next_is(self, 1, TokType::RBracket) {
index = Some(binding_node(&self.next()));
} else if self.at_op("-")
&& next_is(self, 1, TokType::Number)
&& next_is(self, 2, TokType::RBracket)
{
return self.fail(
"negative indices are not supported (`x[-1]`): an index is a non-negative integer literal or a binding",
);
} else {
let defer = self.defer_lookup;
self.defer_lookup = false;
let depth = self.depth + 1;
let w = self.with_depth(depth, |p| p.parse_where());
self.defer_lookup = defer;
r#where = Some(w?);
}
if !self.at(TokType::RBracket) {
return self.fail("expected ']' to close the bracket");
}
self.next();
let sub_span = Span::new(lbracket, self.end());
let sub = |r#where: Option<Where>, offset: Option<Expr>| Subquery {
from: Vec::new(),
r#where,
select: Vec::new(),
order_by: None,
follow: None,
frontier: None,
depth: None,
values: false,
limit: None,
offset,
span: sub_span,
};
let bang = if self.at_op("!") {
Some(self.next())
} else {
None
};
let op = |consumer: Consumer, sub: Subquery, end: usize| OpNode {
receiver: recv,
op: consumer,
sub,
count_cmp: None,
distinct: false,
span: Span::new(start, end),
};
if let Some(index) = index {
let first = op(Consumer::First, sub(None, Some(index)), sub_span.end);
return Ok(match bang {
Some(b) => Expr::Required {
expr: Box::new(Expr::Op(Box::new(first))),
span: Span::new(start, b.end),
},
None => Expr::Op(Box::new(first)),
});
}
Ok(match bang {
Some(b) => Expr::Required {
expr: Box::new(Expr::Op(Box::new(op(
Consumer::Single,
sub(r#where, None),
b.end,
)))),
span: Span::new(start, b.end),
},
None => Expr::Op(Box::new(op(
Consumer::First,
sub(r#where, None),
sub_span.end,
))),
})
}
fn at_consumer_block(&self) -> Option<Consumer> {
if !self.at(TokType::Ident) {
return None;
}
let op = Consumer::from_word(&self.peek().value)?;
let after = self.peek_at(1);
let op_then_brace = after.is_some_and(|a| a.kind == TokType::LBrace);
let op_distinct_brace = after
.is_some_and(|a| a.kind == TokType::Ident && a.value == "distinct")
&& self.peek_at(2).is_some_and(|a| a.kind == TokType::LBrace);
(op_then_brace || op_distinct_brace).then_some(op)
}
fn parse_consumer_block(
&mut self,
receiver: Expr,
op: Consumer,
in_where: bool,
destination: bool,
) -> Result<OpNode> {
let start = receiver.span().start;
if self.at(TokType::Ident) && CONSUMERS.contains(&self.peek().value.as_str()) {
self.next(); }
let mut distinct = false;
if self.at_word(TokType::Ident, "distinct") {
self.next();
distinct = true;
}
let lbrace = self.next().pos; let ctx = BodyCtx::Block {
op,
lifts_allowed: in_where && op == Consumer::Collect,
destination,
};
let defer = self.defer_lookup;
self.defer_lookup = false;
let depth = self.depth + 1;
let body = self.with_depth(depth, |p| p.parse_body(ctx));
self.defer_lookup = defer;
let body = body?;
if !self.at(TokType::RBrace) {
return self.fail(format!(
"expected '}}' to close the {} {{ … }} block",
op.as_str()
));
}
self.next();
let sub = Subquery {
from: body.froms,
r#where: body.r#where,
select: body.select,
order_by: body.order_by,
follow: body.follow,
frontier: body.frontier,
depth: body.depth,
values: body.values,
limit: body.limit,
offset: body.offset,
span: self.sp(lbrace),
};
Ok(OpNode {
receiver,
op,
sub,
count_cmp: None,
distinct: distinct || body.distinct,
span: self.sp(start),
})
}
fn parse_value_expr(&mut self) -> Result<Expr> {
self.parse_or(None)
}
fn parse_or(&mut self, left: Option<Expr>) -> Result<Expr> {
let mut left = self.parse_and(left)?;
while self.at_or() {
self.next();
let right = self.parse_and(None)?;
let span = Span::new(left.span().start, right.span().end);
left = Expr::Logical {
op: LogicalOp::Or,
left: Box::new(left),
right: Box::new(right),
span,
};
}
Ok(left)
}
fn parse_and(&mut self, left: Option<Expr>) -> Result<Expr> {
let mut left = self.parse_cmp(left)?;
while self.at_and() {
self.next();
let right = self.parse_cmp(None)?;
let span = Span::new(left.span().start, right.span().end);
left = Expr::Logical {
op: LogicalOp::And,
left: Box::new(left),
right: Box::new(right),
span,
};
}
Ok(left)
}
fn parse_cmp(&mut self, left: Option<Expr>) -> Result<Expr> {
let lhs = self.parse_range(left)?;
if !self.at_cmp() {
return Ok(lhs);
}
let first = self.next().value;
let word = if first == "is" && self.at_word(TokType::Kw, "not") {
self.next();
"is not".to_string()
} else {
first.clone()
};
let rhs = self.parse_range(None)?;
let span = Span::new(lhs.span().start, rhs.span().end);
let result = if first == "in" {
Expr::In {
left: Box::new(lhs),
right: Box::new(rhs),
span,
}
} else {
Expr::Binary {
op: BinaryOp::from_word(&word).expect("CMP_OPS membership was checked"),
left: Box::new(lhs),
right: Box::new(rhs),
span,
}
};
if self.at_cmp() {
return self.fail(format!(
"comparisons do not chain: `a {first} b {} c` — write two comparisons joined with `&&`",
self.peek().value
));
}
Ok(result)
}
fn at_cmp(&self) -> bool {
let t = self.peek();
(t.kind == TokType::Op && CMP_OPS.contains(&t.value.as_str()))
|| self.at_word(TokType::Ident, "in")
|| self.at_word(TokType::Kw, "is")
}
fn parse_range(&mut self, left: Option<Expr>) -> Result<Expr> {
if left.is_none() && self.at(TokType::Range) {
let tok = self.next();
let exclusive_end = tok.value == "...";
if !self.can_start_value() {
return self.fail("a range needs at least one bound: `lo..hi`, `lo..`, or `..hi`");
}
let hi = self.parse_add(None)?;
let span = Span::new(tok.pos, hi.span().end);
return Ok(Expr::Range {
lo: None,
hi: Some(Box::new(hi)),
exclusive_end,
span,
});
}
let lo = self.parse_add(left)?;
if self.at(TokType::Range) {
let tok = self.next();
let exclusive_end = tok.value == "...";
let hi = if self.can_start_value() {
Some(Box::new(self.parse_add(None)?))
} else {
None
};
let span = Span::new(
lo.span().start,
hi.as_ref().map_or(tok.end, |h| h.span().end),
);
return Ok(Expr::Range {
lo: Some(Box::new(lo)),
hi,
exclusive_end,
span,
});
}
Ok(lo)
}
fn can_start_value(&self) -> bool {
starts_value(self.peek())
}
fn parse_add(&mut self, left: Option<Expr>) -> Result<Expr> {
let mut left = self.parse_mul(left)?;
while self.peek().kind == TokType::Op && ADD_OPS.contains(&self.peek().value.as_str()) {
let op =
BinaryOp::from_word(&self.next().value).expect("ADD_OPS membership was checked");
let right = self.parse_mul(None)?;
let span = Span::new(left.span().start, right.span().end);
left = Expr::Binary {
op,
left: Box::new(left),
right: Box::new(right),
span,
};
}
Ok(left)
}
fn parse_mul(&mut self, left: Option<Expr>) -> Result<Expr> {
let mut left = self.parse_unary(left)?;
while self.peek().kind == TokType::Op && MUL_OPS.contains(&self.peek().value.as_str()) {
let op =
BinaryOp::from_word(&self.next().value).expect("MUL_OPS membership was checked");
let right = self.parse_unary(None)?;
let span = Span::new(left.span().start, right.span().end);
left = Expr::Binary {
op,
left: Box::new(left),
right: Box::new(right),
span,
};
}
Ok(left)
}
fn parse_unary(&mut self, left: Option<Expr>) -> Result<Expr> {
if left.is_some() {
return self.parse_postfix(left);
}
if self.at_op("!")
|| self.at_op("-")
|| self.at_word(TokType::Kw, "is")
|| self.at_word(TokType::Kw, "not")
{
let tok = self.next();
let expr = self.parse_unary(None)?;
let span = Span::new(tok.pos, expr.span().end);
if tok.kind == TokType::Kw {
let not = Expr::Unary {
op: UnaryOp::Not,
expr: Box::new(expr),
span,
};
return Ok(if tok.value == "is" {
Expr::Unary {
op: UnaryOp::Not,
expr: Box::new(not),
span,
}
} else {
not
});
}
let op = UnaryOp::from_word(&tok.value).expect("checked above");
return Ok(Expr::Unary {
op,
expr: Box::new(expr),
span,
});
}
self.parse_postfix(None)
}
fn parse_postfix(&mut self, left: Option<Expr>) -> Result<Expr> {
let bare_head = left.is_none();
let mut expr = match left {
Some(e) => e,
None => self.parse_primary()?,
};
let start = expr.span().start;
loop {
if self.at(TokType::Dot) {
self.next();
if !self.at(TokType::Ident) {
return self.fail("expected a property name after '.'");
}
let name = self.next().value;
if self.at(TokType::LParen) {
let args = self.parse_args()?;
expr = Expr::Call {
recv: Some(Box::new(expr)),
name,
args,
span: self.sp(start),
};
} else {
expr = Expr::Member {
recv: Box::new(expr),
name,
span: self.sp(start),
};
}
} else if self.at(TokType::LParen) && bare_head && matches!(expr, Expr::Ident { .. }) {
let Expr::Ident { name, .. } = expr else {
unreachable!()
};
let args = self.parse_args()?;
expr = Expr::Call {
recv: None,
name,
args,
span: self.sp(start),
};
} else if self.at(TokType::LBracket) {
expr = self.parse_bracket(expr)?;
} else if self.at_op("!") {
let bang = self.next();
expr = Expr::Required {
expr: Box::new(expr),
span: Span::new(start, bang.end),
};
} else if self.at_value_directive()
|| self.at(TokType::LBrace)
|| (self.at_word(TokType::Ident, "distinct")
&& self.peek_at(1).is_some_and(|t| t.kind == TokType::LBrace))
{
if matches!(expr, Expr::Lit { .. }) {
return self.fail(format!(
"`{}` is a literal, not a collection",
describe(Node::Expr(&expr))
));
}
let op = self.at_block().expect("checked above");
expr = Expr::Op(Box::new(self.parse_consumer_block(expr, op, false, false)?));
} else {
break;
}
}
Ok(expr)
}
fn parse_args(&mut self) -> Result<Vec<Expr>> {
self.next(); let mut args = Vec::new();
if !self.at(TokType::RParen) {
args.push(self.parse_value_expr()?);
while self.at(TokType::Comma) {
self.next();
args.push(self.parse_value_expr()?);
}
}
if !self.at(TokType::RParen) {
return self.fail("expected ')' to close call arguments");
}
self.next();
Ok(args)
}
fn parse_primary(&mut self) -> Result<Expr> {
let t = self.peek().clone();
match t.kind {
TokType::Caret | TokType::Number
if t.kind == TokType::Caret || self.at_absolute_ref() =>
{
let levels = self.parse_scope_ref()?;
if !self.at(TokType::Ident) {
return self.fail("expected an identifier after '^' (an outer reference)");
}
let name = self.next();
Ok(Expr::Outer {
levels,
name: name.value,
span: Span::new(t.pos, name.end),
})
}
TokType::Number => {
self.next();
Ok(Expr::Lit {
value: Value::Number(number_value(&t.value)),
span: Span::new(t.pos, t.end),
})
}
TokType::Str => {
self.next();
Ok(Expr::Lit {
value: Value::Str(t.value),
span: Span::new(t.pos, t.end),
})
}
TokType::Binding => {
self.next();
Ok(binding_node(&t))
}
TokType::LParen => {
self.next();
let e = self.parse_value_expr()?;
if self.at(TokType::Comma) {
let mut items = vec![e];
while self.at(TokType::Comma) {
self.next();
items.push(self.parse_value_expr()?);
}
if !self.at(TokType::RParen) {
return self.fail("expected ')' to close the tuple");
}
self.next();
return Ok(Expr::Tuple {
items,
span: Span::new(t.pos, self.end()),
});
}
if !self.at(TokType::RParen) {
return self.fail("expected ')'");
}
self.next();
Ok(e.with_span(Span::new(t.pos, self.end())))
}
TokType::Ident => {
self.next();
let span = Span::new(t.pos, t.end);
Ok(match t.value.as_str() {
"true" => Expr::Lit {
value: Value::Bool(true),
span,
},
"false" => Expr::Lit {
value: Value::Bool(false),
span,
},
"null" => Expr::Lit {
value: Value::Null,
span,
},
_ => Expr::Ident {
name: t.value,
span,
},
})
}
_ => self.fail(format!("unexpected {} — expected a value", self.tok_desc())),
}
}
}
fn starts_value(t: &Token) -> bool {
match t.kind {
TokType::Ident => !CLAUSE_WORDS.contains(&t.value.as_str()),
TokType::Kw => t.value == "is" || t.value == "not", TokType::Number | TokType::Str | TokType::Binding | TokType::LParen | TokType::Caret => {
true
}
TokType::Op => t.value == "-" || t.value == "!",
_ => false,
}
}
fn binding_node(t: &Token) -> Expr {
Expr::Binding {
index: t.index.expect("a binding token always carries its index"),
span: Span::new(t.pos, t.end),
}
}
fn wrap_not(mut w: Where, not_starts: &[usize]) -> Where {
for &at in not_starts.iter().rev() {
let span = Span::new(at, w.span().end);
w = Where::Not {
expr: Box::new(w),
span,
};
}
w
}
fn scalar_leaf(e: Expr) -> Where {
match e {
Expr::Unary {
op: UnaryOp::Not,
expr,
span,
} => Where::Not {
expr: Box::new(scalar_leaf(*expr)),
span,
},
expr => {
let span = expr.span();
Where::Scalar { expr, span }
}
}
}
fn nav_key(e: &Expr) -> Option<&str> {
match e {
Expr::Ident { name, .. } | Expr::Outer { name, .. } | Expr::Member { name, .. } => {
Some(name)
}
Expr::Required { expr, .. } => nav_key(expr),
_ => None,
}
}
fn predicate_shaped(w: &Where, first: TokType) -> bool {
if first == TokType::LParen {
return true;
}
match w {
Where::Scalar { expr, .. } => !is_navigation(expr),
_ => true,
}
}
fn is_navigation(e: &Expr) -> bool {
match e {
Expr::Ident { .. } | Expr::Outer { .. } => true,
Expr::Member { recv, .. } => is_navigation(recv),
_ => false,
}
}
fn describe(node: Node<'_>) -> String {
let t = print_template(node);
let mut out = String::new();
for (i, s) in t.strings.iter().enumerate() {
out.push_str(s);
if i < t.count {
out.push_str(&format!("${{{}}}", t.indices[i]));
}
}
out
}