use std::ops::Bound;
use reblessive::Stk;
use super::{ParseResult, Parser};
use crate::sql::lookup::LookupKind;
use crate::sql::{
Closure, Dir, Expr, Function, FunctionCall, Idiom, Kind, Literal, Mock, Param, Part, Script,
};
use crate::syn::error::{SyntaxError, bail};
use crate::syn::lexer::Lexer;
use crate::syn::lexer::compound::{self};
use crate::syn::parser::enter_object_recursion;
use crate::syn::parser::mac::{expected, unexpected};
use crate::syn::token::{Span, TokenKind, t};
use crate::types::{PublicDuration, PublicGeometry};
use crate::val::range::TypedRange;
impl Parser<'_> {
pub(super) fn parse_number_like_prime(&mut self) -> ParseResult<Expr> {
let token = self.peek();
match token.kind {
TokenKind::Infinity => Ok(Expr::Literal(Literal::Float(f64::INFINITY))),
t!("+") | t!("-") | TokenKind::Digits => {
self.pop_peek();
let value = self.lex_compound(token, compound::numeric)?;
self.last_span = value.span;
let v = match value.value {
compound::Numeric::Float(x) => Expr::Literal(Literal::Float(x)),
compound::Numeric::Integer(x) => {
Expr::Literal(Literal::Integer(x.into_int(value.span)?))
}
compound::Numeric::Decimal(x) => Expr::Literal(Literal::Decimal(x)),
compound::Numeric::Duration(x) => {
Expr::Literal(Literal::Duration(PublicDuration::from(x)))
}
};
Ok(v)
}
_ => unexpected!(self, token, "a number"),
}
}
pub(super) async fn parse_prime_expr(&mut self, stk: &mut Stk) -> ParseResult<Expr> {
let token = self.peek();
let value = match token.kind {
t!("@") => {
self.pop_peek();
let mut res = vec![Part::Doc];
if !self.peek_continues_idiom() {
res.push(self.parse_dot_part(stk).await?);
}
Expr::Idiom(Idiom(res))
}
t!("NONE") => {
self.pop_peek();
Expr::Literal(Literal::None)
}
t!("NULL") => {
self.pop_peek();
Expr::Literal(Literal::Null)
}
t!("true") => {
self.pop_peek();
Expr::Literal(Literal::Bool(true))
}
t!("false") => {
self.pop_peek();
Expr::Literal(Literal::Bool(false))
}
t!("<") => {
self.pop_peek();
if let Some(peek) = self.peek_whitespace() {
if peek.kind == t!("~") {
self.pop_peek();
let lookup =
stk.run(|ctx| self.parse_lookup(ctx, LookupKind::Reference)).await?;
Expr::Idiom(Idiom(vec![Part::Graph(Box::new(lookup))]))
} else if peek.kind == t!("-") {
self.pop_peek();
let lookup = stk
.run(|ctx| self.parse_lookup(ctx, LookupKind::Graph(Dir::In)))
.await?;
Expr::Idiom(Idiom(vec![Part::Graph(Box::new(lookup))]))
} else if peek.kind == t!("->") {
self.pop_peek();
let lookup = stk
.run(|ctx| self.parse_lookup(ctx, LookupKind::Graph(Dir::Both)))
.await?;
Expr::Idiom(Idiom(vec![Part::Graph(Box::new(lookup))]))
} else {
unexpected!(self, token, "`<-`, `<->` or `<~`")
}
} else {
unexpected!(self, token, "`<-`, `<->` or `<~`")
}
}
t!("r\"") | t!("r'") => {
self.pop_peek();
let str = self.unescape_string_span(token.span)?;
let mut inner_parser = Parser::new(str.as_bytes());
let record_id = match stk.run(|stk| inner_parser.parse_record_id(stk)).await {
Ok(x) => x,
Err(e) => {
let e = e.update_spans(|span| {
let range = span.to_range();
let start = Lexer::escaped_string_offset(
self.span_str(token.span),
range.start,
);
let end =
Lexer::escaped_string_offset(self.span_str(token.span), range.end);
*span = Span::from_range(
(token.span.offset + start)..(token.span.offset + end),
)
});
return Err(e);
}
};
Expr::Literal(Literal::RecordId(record_id))
}
t!("d\"") | t!("d'") => Expr::Literal(Literal::Datetime(self.next_token_value()?)),
t!("u\"") | t!("u'") => Expr::Literal(Literal::Uuid(self.next_token_value()?)),
t!("b\"") | t!("b'") => Expr::Literal(Literal::Bytes(self.next_token_value()?)),
t!("f\"") | t!("f'") => {
if !self.settings.files_enabled {
unexpected!(self, token, "the experimental files feature to be enabled");
}
let file = self.next_token_value()?;
Expr::Literal(Literal::File(file))
}
t!("'") | t!("\"") => {
if self.settings.legacy_strands {
Expr::Literal(self.reparse_legacy_strand(stk).await?)
} else {
let s = self.parse_string_lit()?;
Expr::Literal(Literal::String(s.into()))
}
}
t!("+") | t!("-") | TokenKind::Digits => self.parse_number_like_prime()?,
TokenKind::Infinity => {
self.pop_peek();
Expr::Literal(Literal::Float(f64::INFINITY))
}
TokenKind::NaN => {
self.pop_peek();
Expr::Literal(Literal::Float(f64::NAN))
}
t!("$param") => Expr::Param(self.next_token_value()?),
t!("FUNCTION") => {
self.pop_peek();
let script = self.parse_script(stk).await?;
Expr::FunctionCall(Box::new(script))
}
t!("->") => {
self.pop_peek();
let lookup =
stk.run(|ctx| self.parse_lookup(ctx, LookupKind::Graph(Dir::Out))).await?;
Expr::Idiom(Idiom(vec![Part::Graph(Box::new(lookup))]))
}
t!("[") => {
self.pop_peek();
self.parse_array(stk, token.span).await.map(|a| Expr::Literal(Literal::Array(a)))?
}
t!("{") => {
self.pop_peek();
self.parse_object_like(stk, token.span).await?
}
t!("|") => {
self.pop_peek();
self.parse_closure_or_mock(stk, token.span).await?
}
t!("||") => {
self.pop_peek();
stk.run(|ctx| self.parse_closure_after_args(ctx, Vec::new())).await?
}
t!("(") => {
self.pop_peek();
self.parse_covered_expr_or_coordinate(stk, token.span).await?
}
t!("/") => {
let regex = self.next_token_value()?;
Expr::Literal(Literal::Regex(regex))
}
t!("fn") if self.peek1().kind == t!("::") => {
self.pop_peek();
self.parse_custom_function(stk).await.map(|x| Expr::FunctionCall(Box::new(x)))?
}
t!("mod") if self.peek1().kind == t!("::") => {
self.pop_peek();
self.parse_module_function(stk).await.map(|x| Expr::FunctionCall(Box::new(x)))?
}
t!("silo") if self.peek1().kind == t!("::") => {
self.pop_peek();
self.parse_silo_function(stk).await.map(|x| Expr::FunctionCall(Box::new(x)))?
}
t!("ml") if self.peek1().kind == t!("::") => {
self.pop_peek();
self.parse_model(stk).await.map(|x| Expr::FunctionCall(Box::new(x)))?
}
t!("IF") => {
self.pop_peek();
let stmt = stk.run(|ctx| self.parse_if_stmt(ctx)).await?;
Expr::IfElse(Box::new(stmt))
}
t!("SELECT") => {
self.pop_peek();
let stmt = self.parse_select_stmt(stk).await?;
Expr::Select(Box::new(stmt))
}
t!("CREATE") => {
self.pop_peek();
let stmt = self.parse_create_stmt(stk).await?;
Expr::Create(Box::new(stmt))
}
t!("EXPLAIN") => {
self.pop_peek();
self.parse_explain_expr(stk).await?
}
t!("UPDATE") => {
self.pop_peek();
let stmt = self.parse_update_stmt(stk).await?;
Expr::Update(Box::new(stmt))
}
t!("UPSERT") => {
self.pop_peek();
let stmt = self.parse_upsert_stmt(stk).await?;
Expr::Upsert(Box::new(stmt))
}
t!("DELETE") => {
self.pop_peek();
let stmt = self.parse_delete_stmt(stk).await?;
Expr::Delete(Box::new(stmt))
}
t!("RELATE") => {
self.pop_peek();
let stmt = self.parse_relate_stmt(stk).await?;
Expr::Relate(Box::new(stmt))
}
t!("INSERT") => {
self.pop_peek();
let stmt = self.parse_insert_stmt(stk).await?;
Expr::Insert(Box::new(stmt))
}
t!("DEFINE") => {
self.pop_peek();
let stmt = self.parse_define_stmt(stk).await?;
Expr::Define(Box::new(stmt))
}
t!("REMOVE") => {
self.pop_peek();
let stmt = self.parse_remove_stmt(stk).await?;
Expr::Remove(Box::new(stmt))
}
t!("REBUILD") => {
self.pop_peek();
let stmt = self.parse_rebuild_stmt()?;
Expr::Rebuild(Box::new(stmt))
}
t!("ALTER") => {
self.pop_peek();
let stmt = self.parse_alter_stmt(stk).await?;
Expr::Alter(Box::new(stmt))
}
t!("INFO") => {
self.pop_peek();
let stmt = self.parse_info_stmt(stk).await?;
Expr::Info(Box::new(stmt))
}
t!("FOR") => {
self.pop_peek();
let stmt = self.parse_for_stmt(stk).await?;
Expr::Foreach(Box::new(stmt))
}
t!("LET") => {
self.pop_peek();
let stmt = self.parse_let_stmt(stk).await?;
Expr::Let(Box::new(stmt))
}
t!("SLEEP") if self.peek1().kind != t!("(") => {
self.pop_peek();
let stmt = self.parse_sleep_stmt()?;
Expr::Sleep(Box::new(stmt))
}
t!("RETURN") => {
self.pop_peek();
let stmt = self.parse_return_stmt(stk).await?;
Expr::Return(Box::new(stmt))
}
t!("THROW") => {
self.pop_peek();
let expr = stk.run(|stk| self.parse_expr_inherit(stk)).await?;
Expr::Throw(Box::new(expr))
}
t!("CONTINUE") => {
self.pop_peek();
Expr::Continue
}
t!("BREAK") => {
self.pop_peek();
Expr::Break
}
x if Self::kind_is_identifier(x) => {
let peek = self.peek1();
match peek.kind {
t!("::") | t!("(") => {
self.pop_peek();
self.parse_builtin(stk, token.span).await?
}
t!(":") => {
let str = self.parse_ident()?;
self.parse_record_id_or_range(stk, str)
.await
.map(|x| Expr::Literal(Literal::RecordId(x)))?
}
_ => {
if self.table_as_field {
Expr::Idiom(Idiom(vec![Part::Field(self.parse_ident_str()?.into())]))
} else {
Expr::Table(self.parse_ident_str()?.into())
}
}
}
}
_ => {
unexpected!(self, token, "an expression")
}
};
if self.peek_continues_idiom() {
match value {
Expr::Idiom(Idiom(x)) => self.parse_remaining_value_idiom(stk, x).await,
Expr::Table(x) => {
self.parse_remaining_value_idiom(stk, vec![Part::Field(x.into())]).await
}
x => self.parse_remaining_value_idiom(stk, vec![Part::Start(x)]).await,
}
} else {
Ok(value)
}
}
pub(crate) async fn parse_array(
&mut self,
stk: &mut Stk,
start: Span,
) -> ParseResult<Vec<Expr>> {
let mut exprs = Vec::new();
enter_object_recursion!(this = self => {
loop {
if this.eat(t!("]")) {
break;
}
let value = stk.run(|ctx| this.parse_expr_inherit(ctx)).await?;
exprs.push(value);
if !this.eat(t!(",")) {
this.expect_closing_delimiter(t!("]"), start)?;
break;
}
}
});
Ok(exprs)
}
pub(super) fn parse_mock(&mut self, start_span: Span) -> ParseResult<Mock> {
let name: crate::val::TableName = self.parse_ident_str()?.into();
expected!(self, t!(":"));
let start = match self.peek_kind() {
t!("..") => {
self.pop_peek();
Bound::Unbounded
}
_ => {
let from = self.next_token_value::<i64>()?;
match self.peek_kind() {
t!("..") => {
self.pop_peek();
Bound::Included(from)
}
t!(">") => {
self.pop_peek();
expected!(self, t!(".."));
Bound::Excluded(from)
}
_ => {
self.expect_closing_delimiter(t!("|"), start_span)?;
return Ok(Mock::Count(name, from));
}
}
}
};
let end = match self.peek_kind() {
t!("|") => {
self.pop_peek();
Bound::Unbounded
}
t!("=") => {
self.pop_peek();
let to = self.next_token_value()?;
self.expect_closing_delimiter(t!("|"), start_span)?;
Bound::Included(to)
}
_ => {
let to = self.next_token_value()?;
self.expect_closing_delimiter(t!("|"), start_span)?;
Bound::Excluded(to)
}
};
Ok(Mock::Range(
name,
TypedRange {
start,
end,
},
))
}
pub(super) async fn parse_closure_or_mock(
&mut self,
stk: &mut Stk,
start: Span,
) -> ParseResult<Expr> {
match self.peek_kind() {
t!("$param") => stk.run(|ctx| self.parse_closure(ctx, start)).await,
_ => self.parse_mock(start).map(Expr::Mock),
}
}
pub(super) async fn parse_closure(&mut self, stk: &mut Stk, start: Span) -> ParseResult<Expr> {
let mut args = Vec::new();
loop {
if self.eat(t!("|")) {
break;
}
let param = self.next_token_value::<Param>()?;
let kind = if self.eat(t!(":")) {
if self.eat(t!("<")) {
let delim = self.last_span();
stk.run(|stk| self.parse_kind(stk, delim)).await?
} else {
stk.run(|stk| self.parse_inner_single_kind(stk)).await?
}
} else {
Kind::Any
};
args.push((param, kind));
if !self.eat(t!(",")) {
self.expect_closing_delimiter(t!("|"), start)?;
break;
}
}
self.parse_closure_after_args(stk, args).await
}
pub(super) async fn parse_closure_after_args(
&mut self,
stk: &mut Stk,
args: Vec<(Param, Kind)>,
) -> ParseResult<Expr> {
let (returns, body) = if self.eat(t!("->")) {
let returns = Some(stk.run(|ctx| self.parse_inner_kind(ctx)).await?);
let start = expected!(self, t!("{")).span;
let body = Expr::Block(Box::new(stk.run(|ctx| self.parse_block(ctx, start)).await?));
(returns, body)
} else {
let body = stk.run(|stk| self.parse_expr_inherit(stk)).await?;
(None, body)
};
Ok(Expr::Closure(Box::new(Closure {
args,
returns,
body,
})))
}
async fn parse_covered_expr_or_coordinate(
&mut self,
stk: &mut Stk,
start: Span,
) -> ParseResult<Expr> {
let peek = self.peek();
match peek.kind {
t!("+") | t!("-") | TokenKind::NaN | TokenKind::Infinity | TokenKind::Digits => {
if let Some(x) = self
.speculate(stk, async |_, this| {
let Ok(x) = this.next_token_value::<f64>() else {
return Ok(None);
};
if !this.eat(t!(",")) {
return Ok(None);
}
let y = this.next_token_value::<f64>()?;
this.expect_closing_delimiter(t!(")"), start)?;
Ok(Some(Expr::Literal(Literal::Geometry(PublicGeometry::Point(
geo::Point::new(x, y),
)))))
})
.await?
{
return Ok(x);
};
}
_ => {}
};
let res = stk.run(|ctx| self.parse_expr_inherit(ctx)).await?;
let token = self.peek();
if token.kind != t!(")")
&& Self::starts_disallowed_subquery_statement(peek.kind)
&& let Expr::Idiom(Idiom(ref idiom)) = res
&& idiom.len() == 1
{
bail!("Unexpected token `{}` expected `)`",peek.kind,
@token.span,
@peek.span => "This is a reserved keyword here and can't be an identifier");
}
self.expect_closing_delimiter(t!(")"), start)?;
if self.peek_continues_idiom() {
self.parse_remaining_value_idiom(stk, vec![Part::Start(res)]).await
} else {
Ok(res)
}
}
pub(super) async fn reparse_legacy_strand(
&mut self,
stk: &mut Stk,
) -> Result<Literal, SyntaxError> {
let token = self.next();
assert!(matches!(token.kind, t!("'") | t!("\"")));
let str = self.unescape_string_span(token.span)?;
if let Ok(x) = Lexer::lex_uuid(str) {
return Ok(Literal::Uuid(x));
}
if let Ok(x) = Lexer::lex_datetime(str) {
return Ok(Literal::Datetime(x));
}
let mut record_id_parser = Parser::new(str.as_bytes());
if let Ok(x) = record_id_parser.parse_record_id(stk).await
&& record_id_parser.peek().is_eof()
{
return Ok(Literal::RecordId(x));
}
Ok(Literal::String(str.into()))
}
async fn parse_script(&mut self, stk: &mut Stk) -> ParseResult<FunctionCall> {
let start = expected!(self, t!("(")).span;
let mut args = Vec::new();
loop {
if self.eat(t!(")")) {
break;
}
let arg = stk.run(|ctx| self.parse_expr_inherit(ctx)).await?;
args.push(arg);
if !self.eat(t!(",")) {
self.expect_closing_delimiter(t!(")"), start)?;
break;
}
}
let token = expected!(self, t!("{"));
let mut span = self.lex_compound(token, compound::javascript)?.span;
span.offset += 1;
span.len -= 2;
let body = self.span_str(span);
let receiver = Function::Script(Script(body.to_string()));
Ok(FunctionCall {
receiver,
arguments: args,
})
}
}
#[cfg(test)]
mod tests {
use surrealdb_types::ToSql;
use super::*;
use crate::syn;
#[test]
fn subquery_expression_statement() {
let sql = "(1 + 2 + 3)";
let out = syn::expr(sql).unwrap();
assert_eq!("1 + 2 + 3", out.to_sql())
}
#[test]
fn subquery_ifelse_statement() {
let sql = "IF true THEN false END";
let out = syn::expr(sql).unwrap();
assert_eq!("IF true THEN false END", out.to_sql())
}
#[test]
fn subquery_select_statement() {
let sql = "(SELECT * FROM test)";
let out = syn::expr(sql).unwrap();
assert_eq!("SELECT * FROM test", out.to_sql())
}
#[test]
fn subquery_define_statement() {
let sql = "(DEFINE EVENT foo ON bar WHEN $event = 'CREATE' THEN (CREATE x SET y = 1))";
let out = syn::expr(sql).unwrap();
assert_eq!(
"DEFINE EVENT foo ON bar WHEN $event = 'CREATE' THEN (CREATE x SET y = 1)",
out.to_sql()
)
}
#[test]
fn subquery_remove_statement() {
let sql = "(REMOVE EVENT foo_event ON foo)";
let out = syn::expr(sql).unwrap();
assert_eq!("REMOVE EVENT foo_event ON foo", out.to_sql())
}
#[test]
fn subquery_insert_statment() {
let sql = "(INSERT INTO test [])";
let out = syn::expr(sql).unwrap();
assert_eq!("INSERT INTO test []", out.to_sql())
}
#[test]
fn mock_count() {
let sql = "|test:1000|";
let out = syn::expr(sql).unwrap();
assert_eq!("|test:1000|", out.to_sql());
assert_eq!(out, Expr::Mock(Mock::Count("test".into(), 1000)));
}
#[test]
fn mock_range() {
let sql = "|test:1..1000|";
let out = syn::expr(sql).unwrap();
assert_eq!("|test:1..1000|", out.to_sql());
assert_eq!(out, Expr::Mock(Mock::Range("test".into(), TypedRange::from_range(1..1000))));
}
#[test]
fn regex_simple() {
let sql = "/test/";
let out = syn::expr(sql).unwrap();
assert_eq!("/test/", out.to_sql());
let Expr::Literal(Literal::Regex(regex)) = out else {
panic!()
};
assert_eq!(regex, "test".parse().unwrap());
}
#[test]
fn regex_complex() {
let sql = r"/(?i)test\/[a-z]+\/\s\d\w{1}.*/";
let out = syn::expr(sql).unwrap();
assert_eq!(r"/(?i)test\/[a-z]+\/\s\d\w{1}.*/", out.to_sql());
let Expr::Literal(Literal::Regex(regex)) = out else {
panic!()
};
assert_eq!(regex, r"(?i)test/[a-z]+/\s\d\w{1}.*".parse().unwrap());
}
#[test]
fn plain_string() {
let sql = r#""hello""#;
let out = syn::expr(sql).unwrap();
assert_eq!(r#"'hello'"#, out.to_sql());
let sql = r#"s"hello""#;
let out = syn::expr(sql).unwrap();
assert_eq!(r#"'hello'"#, out.to_sql());
let sql = r#"s'hello'"#;
let out = syn::expr(sql).unwrap();
assert_eq!(r#"'hello'"#, out.to_sql());
}
#[test]
fn params() {
let sql = "$hello";
let out = syn::expr(sql).unwrap();
assert_eq!("$hello", out.to_sql());
let sql = "$__hello";
let out = syn::expr(sql).unwrap();
assert_eq!("$__hello", out.to_sql());
}
#[test]
fn legacy_strand_partial_record_id_stays_string() {
let sql = "'openai:gpt-5-mini'";
let out = syn::expr_legacy_strand(sql).unwrap();
assert!(
matches!(out, Expr::Literal(Literal::String(_))),
"expected Literal::String, got: {out:?}"
);
assert_eq!("'openai:gpt-5-mini'", out.to_sql());
}
#[test]
fn legacy_strand_full_record_id_is_converted() {
let sql = "'person:test'";
let out = syn::expr_legacy_strand(sql).unwrap();
assert!(
matches!(out, Expr::Literal(Literal::RecordId(_))),
"expected Literal::RecordId, got: {out:?}"
);
}
}