use reblessive::Stk;
use crate::gql::ast::{BinaryOp, GqlExpr, GqlLiteral, Ident, SetQuantifier, TruthValue, UnaryOp};
use crate::gql::lexer::Lexer;
use crate::gql::parser::mac::{enter_object_recursion, unexpected};
use crate::gql::parser::{ParseResult, Parser};
use crate::gql::token::{Keyword, NumberKind, NumberSuffix, Span, Token, TokenKind, t};
use crate::syn::error::{bail, syntax_error};
impl Parser<'_> {
fn enter_expr_depth(&mut self) -> ParseResult<()> {
if self.settings.expr_recursion_limit == 0 {
bail!("Exceeded expression recursion depth limit",
@self.last_span() => "this expression nests or chains operators too deeply");
}
self.settings.expr_recursion_limit -= 1;
Ok(())
}
pub(super) async fn parse_expr(&mut self, stk: &mut Stk) -> ParseResult<GqlExpr> {
let restore_to = self.settings.expr_recursion_limit;
self.enter_expr_depth()?;
let res = self.parse_expr_inner(stk).await;
self.settings.expr_recursion_limit = restore_to;
res
}
async fn parse_expr_inner(&mut self, stk: &mut Stk) -> ParseResult<GqlExpr> {
let mut left = self.parse_and_expr(stk).await?;
loop {
let op = match self.peek_kind() {
t!("OR") => BinaryOp::Or,
t!("XOR") => BinaryOp::Xor,
_ => break,
};
self.pop_peek();
self.enter_expr_depth()?;
let right = self.parse_and_expr(stk).await?;
let span = left.span().covers(right.span());
left = GqlExpr::Binary {
left: Box::new(left),
op,
right: Box::new(right),
span,
};
}
Ok(left)
}
async fn parse_and_expr(&mut self, stk: &mut Stk) -> ParseResult<GqlExpr> {
let mut left = self.parse_is_expr(stk).await?;
while self.eat(t!("AND")) {
self.enter_expr_depth()?;
let right = self.parse_is_expr(stk).await?;
let span = left.span().covers(right.span());
left = GqlExpr::Binary {
left: Box::new(left),
op: BinaryOp::And,
right: Box::new(right),
span,
};
}
Ok(left)
}
async fn parse_is_expr(&mut self, stk: &mut Stk) -> ParseResult<GqlExpr> {
let mut expr = self.parse_not_expr(stk).await?;
while self.peek_kind() == t!("IS") {
self.enter_expr_depth()?;
let is_token = self.pop_peek();
let negated = self.eat(t!("NOT"));
let token = self.next();
let value = match token.kind {
t!("TRUE") => TruthValue::True,
t!("FALSE") => TruthValue::False,
t!("UNKNOWN") => TruthValue::Unknown,
t!("NULL") => {
bail!(
"`IS NULL` may only directly follow a simple expression",
@is_token.span.covers(token.span),
@expr.span() => "wrap this expression in parentheses: `(…) IS NULL`"
);
}
t!("TYPED") => {
bail!(
"`IS [NOT] TYPED` type predicates are not supported yet",
@is_token.span.covers(token.span)
);
}
t!("NORMALIZED") | t!("NFC") | t!("NFD") | t!("NFKC") | t!("NFKD") => {
bail!(
"`IS [NOT] NORMALIZED` predicates are not supported yet",
@is_token.span.covers(token.span)
);
}
t!("LABELED") => {
bail!(
"`IS [NOT] LABELED` predicates are not supported yet",
@is_token.span.covers(token.span)
);
}
t!("DIRECTED") => {
bail!(
"`IS [NOT] DIRECTED` predicates are not supported yet",
@is_token.span.covers(token.span)
);
}
t!("SOURCE") | t!("DESTINATION") => {
bail!(
"`IS [NOT] SOURCE/DESTINATION OF` predicates are not supported yet",
@is_token.span.covers(token.span)
);
}
_ => unexpected!(self, token, "`TRUE`, `FALSE`, `UNKNOWN` or `NULL`"),
};
let span = expr.span().covers(token.span);
expr = GqlExpr::IsBool {
expr: Box::new(expr),
value,
negated,
span,
};
}
Ok(expr)
}
async fn parse_not_expr(&mut self, stk: &mut Stk) -> ParseResult<GqlExpr> {
let token = self.peek();
if token.kind == t!("NOT") {
self.pop_peek();
self.enter_expr_depth()?;
let expr = stk.run(|stk| self.parse_not_expr(stk)).await?;
let span = token.span.covers(expr.span());
return Ok(GqlExpr::Unary {
op: UnaryOp::Not,
expr: Box::new(expr),
span,
});
}
self.parse_comparison_expr(stk).await
}
async fn parse_comparison_expr(&mut self, stk: &mut Stk) -> ParseResult<GqlExpr> {
let left = self.parse_concat_expr(stk).await?;
self.check_rejected_operator()?;
let Some(op) = self.peek_comparison_op() else {
return Ok(left);
};
self.pop_peek();
let start = self.peek().span;
let right = self.parse_concat_expr(stk).await?;
Self::check_operand_null_test(&right, start)?;
if self.peek_comparison_op().is_some() {
bail!(
"Comparison operators cannot be chained; use AND to combine comparisons",
@self.peek().span
);
}
self.check_rejected_operator()?;
let span = left.span().covers(right.span());
Ok(GqlExpr::Binary {
left: Box::new(left),
op,
right: Box::new(right),
span,
})
}
fn check_operand_null_test(operand: &GqlExpr, start: Span) -> ParseResult<()> {
if let GqlExpr::IsNull {
span,
..
} = operand
&& span.offset == start.offset
{
bail!(
"`IS NULL` may only directly follow a simple expression",
@*span => "wrap the left-hand expression in parentheses: `(…) IS NULL`"
);
}
Ok(())
}
fn peek_comparison_op(&mut self) -> Option<BinaryOp> {
match self.peek_kind() {
t!("=") => Some(BinaryOp::Eq),
t!("<>") => Some(BinaryOp::Neq),
t!("<") => Some(BinaryOp::Lt),
t!("<=") => Some(BinaryOp::Lte),
t!(">") => Some(BinaryOp::Gt),
t!(">=") => Some(BinaryOp::Gte),
_ => None,
}
}
fn check_rejected_operator(&mut self) -> ParseResult<()> {
let token = self.peek();
match token.kind {
t!("!") if self.peek1().kind == t!("=") => {
bail!(
"GQL uses `<>` for inequality",
@token.span.covers(self.peek1().span) => "replace `!=` with `<>`"
);
}
t!("IN") => {
bail!(
"GQL has no `IN` membership operator",
@token.span => "compare against the values individually, combined with OR"
);
}
t!("LIKE") => {
bail!("GQL has no `LIKE` operator", @token.span);
}
TokenKind::Identifier => {
let text = self.span_str(token.span);
if text.eq_ignore_ascii_case("STARTS") && self.peek1().kind == t!("WITH") {
bail!(
"GQL has no `STARTS WITH` operator",
@token.span.covers(self.peek1().span)
);
}
if text.eq_ignore_ascii_case("ENDS") && self.peek1().kind == t!("WITH") {
bail!(
"GQL has no `ENDS WITH` operator",
@token.span.covers(self.peek1().span)
);
}
if text.eq_ignore_ascii_case("CONTAINS") {
bail!("GQL has no `CONTAINS` operator", @token.span);
}
Ok(())
}
_ => Ok(()),
}
}
async fn parse_concat_expr(&mut self, stk: &mut Stk) -> ParseResult<GqlExpr> {
let mut left = self.parse_additive_expr(stk).await?;
while self.eat(t!("||")) {
self.enter_expr_depth()?;
let start = self.peek().span;
let right = self.parse_additive_expr(stk).await?;
Self::check_operand_null_test(&right, start)?;
let span = left.span().covers(right.span());
left = GqlExpr::Binary {
left: Box::new(left),
op: BinaryOp::Concat,
right: Box::new(right),
span,
};
}
Ok(left)
}
async fn parse_additive_expr(&mut self, stk: &mut Stk) -> ParseResult<GqlExpr> {
let mut left = self.parse_multiplicative_expr(stk).await?;
loop {
let op = match self.peek_kind() {
t!("+") => BinaryOp::Add,
t!("-") => BinaryOp::Sub,
_ => break,
};
self.pop_peek();
self.enter_expr_depth()?;
let start = self.peek().span;
let right = self.parse_multiplicative_expr(stk).await?;
Self::check_operand_null_test(&right, start)?;
let span = left.span().covers(right.span());
left = GqlExpr::Binary {
left: Box::new(left),
op,
right: Box::new(right),
span,
};
}
Ok(left)
}
async fn parse_multiplicative_expr(&mut self, stk: &mut Stk) -> ParseResult<GqlExpr> {
let mut left = self.parse_unary_expr(stk).await?;
loop {
let op = match self.peek_kind() {
t!("*") => BinaryOp::Mul,
t!("/") => BinaryOp::Div,
_ => break,
};
self.pop_peek();
self.enter_expr_depth()?;
let start = self.peek().span;
let right = self.parse_unary_expr(stk).await?;
Self::check_operand_null_test(&right, start)?;
let span = left.span().covers(right.span());
left = GqlExpr::Binary {
left: Box::new(left),
op,
right: Box::new(right),
span,
};
}
Ok(left)
}
async fn parse_unary_expr(&mut self, stk: &mut Stk) -> ParseResult<GqlExpr> {
let token = self.peek();
let op = match token.kind {
t!("+") => UnaryOp::Plus,
t!("-") => UnaryOp::Neg,
_ => return self.parse_primary_expr(stk).await,
};
self.pop_peek();
self.enter_expr_depth()?;
let start = self.peek().span;
let expr = stk.run(|stk| self.parse_unary_expr(stk)).await?;
Self::check_operand_null_test(&expr, start)?;
let span = token.span.covers(expr.span());
Ok(GqlExpr::Unary {
op,
expr: Box::new(expr),
span,
})
}
async fn parse_primary_expr(&mut self, stk: &mut Stk) -> ParseResult<GqlExpr> {
enter_object_recursion!(this = self => {
let expr = this.parse_primary_atom(stk).await?;
this.parse_primary_postfix(expr)
})
}
fn parse_primary_postfix(&mut self, mut expr: GqlExpr) -> ParseResult<GqlExpr> {
while self.eat(t!(".")) {
let name = self.parse_ident()?;
let span = expr.span().covers(name.span);
expr = GqlExpr::Property(Box::new(expr), name, span);
}
if self.peek_kind() == t!("IS") {
let negated = match (self.peek1().kind, self.peek2().kind) {
(t!("NULL"), _) => {
self.pop_peek();
self.pop_peek();
Some(false)
}
(t!("NOT"), t!("NULL")) => {
self.pop_peek();
self.pop_peek();
self.pop_peek();
Some(true)
}
_ => None,
};
if let Some(negated) = negated {
let span = expr.span().covers(self.last_span());
expr = GqlExpr::IsNull {
expr: Box::new(expr),
negated,
span,
};
}
}
Ok(expr)
}
async fn parse_primary_atom(&mut self, stk: &mut Stk) -> ParseResult<GqlExpr> {
let token = self.peek();
match token.kind {
t!("(") => {
let open = self.pop_peek().span;
let expr = stk.run(|stk| self.parse_expr(stk)).await?;
self.expect_closing_delimiter(t!(")"), open)?;
Ok(expr)
}
t!("[") => {
let open = self.pop_peek().span;
let mut items = Vec::new();
if self.peek_kind() != t!("]") {
loop {
items.push(stk.run(|stk| self.parse_expr(stk)).await?);
if !self.eat(t!(",")) {
break;
}
}
}
self.expect_closing_delimiter(t!("]"), open)?;
Ok(GqlExpr::List(items, open.covers(self.last_span())))
}
t!("{") => {
let open = self.pop_peek().span;
let mut fields = Vec::new();
if self.peek_kind() != t!("}") {
loop {
let key = self.parse_ident()?;
let colon = self.peek();
if colon.kind != t!(":") {
unexpected!(self, colon, "`:`");
}
self.pop_peek();
let value = stk.run(|stk| self.parse_expr(stk)).await?;
fields.push((key, value));
if !self.eat(t!(",")) {
break;
}
}
}
self.expect_closing_delimiter(t!("}"), open)?;
Ok(GqlExpr::Map(fields, open.covers(self.last_span())))
}
TokenKind::Parameter => {
self.pop_peek();
let name = self.parse_parameter_name(token)?;
Ok(GqlExpr::Param {
name,
span: token.span,
})
}
TokenKind::SubstitutedParameter => {
bail!(
"Substituted parameters (`$$name`) are not supported yet",
@token.span => "use a general `$name` parameter"
);
}
TokenKind::Number {
kind,
suffix,
} => {
self.pop_peek();
let literal = self.parse_number_literal(token, kind, suffix)?;
Ok(GqlExpr::Literal(literal, token.span))
}
TokenKind::SingleQuoted {
..
}
| TokenKind::DoubleQuoted {
..
} => {
self.pop_peek();
let value = Lexer::unescape_quoted_span(self.span_str(token.span), token.span)?;
Ok(GqlExpr::Literal(GqlLiteral::String(value), token.span))
}
TokenKind::AccentQuoted {
..
} => {
self.pop_peek();
let name = Lexer::unescape_quoted_span(self.span_str(token.span), token.span)?;
Ok(GqlExpr::Variable(Ident {
name,
span: token.span,
}))
}
t!("TRUE") => {
self.pop_peek();
Ok(GqlExpr::Literal(GqlLiteral::Bool(true), token.span))
}
t!("FALSE") => {
self.pop_peek();
Ok(GqlExpr::Literal(GqlLiteral::Bool(false), token.span))
}
t!("UNKNOWN") | t!("NULL") => {
self.pop_peek();
Ok(GqlExpr::Literal(GqlLiteral::Null, token.span))
}
t!("EXISTS") => {
bail!("`EXISTS` predicates are not supported yet", @token.span);
}
t!("CASE") => {
bail!("`CASE` expressions are not supported yet", @token.span);
}
t!("CAST") => {
bail!("`CAST` expressions are not supported yet", @token.span);
}
TokenKind::Identifier => {
if self.peek1().kind == t!("(") {
return self.parse_function_call(stk).await;
}
self.pop_peek();
Ok(GqlExpr::Variable(Ident {
name: self.span_str(token.span).to_owned(),
span: token.span,
}))
}
TokenKind::Keyword(keyword) => {
if self.peek1().kind == t!("(") {
return self.parse_function_call(stk).await;
}
if keyword.is_non_reserved() {
self.pop_peek();
return Ok(GqlExpr::Variable(Ident {
name: self.span_str(token.span).to_owned(),
span: token.span,
}));
}
if matches!(
keyword,
Keyword::Date
| Keyword::Time | Keyword::Datetime
| Keyword::Timestamp
| Keyword::Duration
) && matches!(
self.peek1().kind,
TokenKind::SingleQuoted { .. } | TokenKind::DoubleQuoted { .. }
) {
bail!(
"Typed temporal literals (`{} '…'`) are not supported yet",
self.span_str(token.span),
@token.span.covers(self.peek1().span)
);
}
if keyword == Keyword::SessionUser {
bail!(
"The `SESSION_USER` value specification is not supported yet",
@token.span
);
}
bail!(
"`{}` is a reserved word and cannot be used as a variable name",
self.span_str(token.span),
@token.span => "use a `\"…\"` or `` `…` `` delimited identifier instead"
);
}
_ => unexpected!(self, token, "an expression"),
}
}
async fn parse_function_call(&mut self, stk: &mut Stk) -> ParseResult<GqlExpr> {
let name_token = self.pop_peek();
let name = Ident {
name: self.span_str(name_token.span).to_owned(),
span: name_token.span,
};
let open = self.pop_peek().span;
let star = if self.peek_kind() == t!("*") && self.peek1().kind == t!(")") {
Some(self.pop_peek().span)
} else {
None
};
let quantifier = if star.is_some() {
None
} else if self.eat(t!("DISTINCT")) {
Some(SetQuantifier::Distinct)
} else if self.eat(t!("ALL")) {
Some(SetQuantifier::All)
} else {
None
};
let mut args = Vec::new();
if star.is_none() && (quantifier.is_some() || self.peek_kind() != t!(")")) {
loop {
args.push(stk.run(|stk| self.parse_expr(stk)).await?);
if !self.eat(t!(",")) {
break;
}
}
}
self.expect_closing_delimiter(t!(")"), open)?;
Ok(GqlExpr::FunctionCall {
name,
quantifier,
star,
args,
span: name_token.span.covers(self.last_span()),
})
}
pub(super) fn parse_parameter_name(&self, token: Token) -> ParseResult<String> {
Lexer::parameter_name_span(self.span_str(token.span), token.span)
}
fn parse_number_literal(
&self,
token: Token,
kind: NumberKind,
suffix: Option<NumberSuffix>,
) -> ParseResult<GqlLiteral> {
match kind {
NumberKind::Integer | NumberKind::Hex | NumberKind::Octal | NumberKind::Binary => {
match suffix {
None | Some(NumberSuffix::Exact) => {
Ok(GqlLiteral::Integer(self.parse_integer_token(token, kind)?))
}
Some(NumberSuffix::Float | NumberSuffix::Double) => {
Ok(GqlLiteral::Float(self.parse_float_token(token)?))
}
}
}
NumberKind::Float | NumberKind::Scientific => {
Ok(GqlLiteral::Float(self.parse_float_token(token)?))
}
}
}
pub(super) fn parse_integer_token(&self, token: Token, kind: NumberKind) -> ParseResult<i64> {
let (radix, digits) = self.integer_token_digits(token, kind);
i64::from_str_radix(&digits, radix).map_err(|_| {
syntax_error!("Integer literal is too large to fit in a 64-bit integer", @token.span)
})
}
pub(super) fn parse_u32_token(&self, token: Token, kind: NumberKind) -> ParseResult<u32> {
let (radix, digits) = self.integer_token_digits(token, kind);
u32::from_str_radix(&digits, radix)
.map_err(|_| syntax_error!("Quantifier bound is too large", @token.span))
}
fn integer_token_digits(&self, token: Token, kind: NumberKind) -> (u32, String) {
let text = self.number_token_text(token);
let (radix, digits) = match kind {
NumberKind::Hex => (16, text.strip_prefix("0x").unwrap_or(text)),
NumberKind::Octal => (8, text.strip_prefix("0o").unwrap_or(text)),
NumberKind::Binary => (2, text.strip_prefix("0b").unwrap_or(text)),
_ => (10, text),
};
(radix, digits.replace('_', ""))
}
fn parse_float_token(&self, token: Token) -> ParseResult<f64> {
let text = self.number_token_text(token).replace('_', "");
text.parse::<f64>().map_err(|_| {
syntax_error!("Invalid numeric literal", @token.span)
})
}
fn number_token_text(&self, token: Token) -> &str {
let text = self.span_str(token.span);
if let TokenKind::Number {
suffix: Some(_),
..
} = token.kind
{
&text[..text.len() - 1]
} else {
text
}
}
}