use crate::SyntaxKind;
use crate::parser::{Parser, syntax_codes as sc};
use super::{expression, pattern};
pub(crate) fn clause(p: &mut Parser<'_>) {
match p.current() {
SyntaxKind::MATCH_KW => match_clause(p),
SyntaxKind::OPTIONAL_KW => optional_match_clause(p),
SyntaxKind::WHERE_KW => where_clause(p),
SyntaxKind::RETURN_KW => return_clause(p),
SyntaxKind::WITH_KW => with_clause(p),
SyntaxKind::UNWIND_KW => unwind_clause(p),
SyntaxKind::CREATE_KW => create_clause(p),
SyntaxKind::MERGE_KW => merge_clause(p),
SyntaxKind::SET_KW => set_clause(p),
SyntaxKind::REMOVE_KW => remove_clause(p),
SyntaxKind::DELETE_KW | SyntaxKind::DETACH_KW => delete_clause(p),
SyntaxKind::CALL_KW => call_clause(p),
other => unreachable!("clause dispatch on non-clause-start token: {other:?}"),
}
}
fn match_clause(p: &mut Parser<'_>) {
debug_assert!(p.at(SyntaxKind::MATCH_KW));
let m = p.start();
p.bump(SyntaxKind::MATCH_KW);
pattern::pattern_list(p);
if p.at(SyntaxKind::WHERE_KW) {
where_clause(p);
}
m.complete(p, SyntaxKind::MATCH_CLAUSE);
}
fn optional_match_clause(p: &mut Parser<'_>) {
debug_assert!(p.at(SyntaxKind::OPTIONAL_KW));
let m = p.start();
p.bump(SyntaxKind::OPTIONAL_KW);
if !p.eat(SyntaxKind::MATCH_KW) {
p.error_code(
sc::EXPECTED_MATCH_AFTER_OPTIONAL,
"expected MATCH after OPTIONAL",
);
}
pattern::pattern_list(p);
if p.at(SyntaxKind::WHERE_KW) {
where_clause(p);
}
m.complete(p, SyntaxKind::OPTIONAL_MATCH_CLAUSE);
}
fn where_clause(p: &mut Parser<'_>) {
debug_assert!(p.at(SyntaxKind::WHERE_KW));
let m = p.start();
p.bump(SyntaxKind::WHERE_KW);
if expression::expr(p).is_none() {
p.error_code(sc::EXPECTED_WHERE_EXPR, "expected expression after WHERE");
}
m.complete(p, SyntaxKind::WHERE_CLAUSE);
}
fn unwind_clause(p: &mut Parser<'_>) {
debug_assert!(p.at(SyntaxKind::UNWIND_KW));
let m = p.start();
p.bump(SyntaxKind::UNWIND_KW);
if expression::expr(p).is_none() {
p.error_code(sc::EXPECTED_UNWIND_EXPR, "expected expression after UNWIND");
}
if !p.eat(SyntaxKind::AS_KW) {
p.error_code(
sc::EXPECTED_AS_UNWIND,
"expected AS after UNWIND expression",
);
}
name_binder(p);
m.complete(p, SyntaxKind::UNWIND_CLAUSE);
}
fn name_binder(p: &mut Parser<'_>) {
let m = p.start();
if !(p.eat(SyntaxKind::IDENT) || p.eat(SyntaxKind::QUOTED_IDENT)) {
p.error_code(sc::EXPECTED_IDENT, "expected identifier");
}
m.complete(p, SyntaxKind::NAME);
}
fn create_clause(p: &mut Parser<'_>) {
debug_assert!(p.at(SyntaxKind::CREATE_KW));
let m = p.start();
p.bump(SyntaxKind::CREATE_KW);
if p.at(SyntaxKind::L_PAREN) {
pattern::pattern_list(p);
} else {
p.error_code(sc::EXPECTED_CREATE_PATTERN, "expected pattern after CREATE");
}
m.complete(p, SyntaxKind::CREATE_CLAUSE);
}
fn merge_clause(p: &mut Parser<'_>) {
debug_assert!(p.at(SyntaxKind::MERGE_KW));
let m = p.start();
p.bump(SyntaxKind::MERGE_KW);
if p.at(SyntaxKind::L_PAREN) {
pattern::pattern(p);
} else {
p.error_code(sc::EXPECTED_MERGE_PATTERN, "expected pattern after MERGE");
}
while p.at(SyntaxKind::ON_KW) {
merge_action(p);
}
m.complete(p, SyntaxKind::MERGE_CLAUSE);
}
fn merge_action(p: &mut Parser<'_>) {
debug_assert!(p.at(SyntaxKind::ON_KW));
let m = p.start();
p.bump(SyntaxKind::ON_KW);
if !(p.eat(SyntaxKind::CREATE_KW) || p.eat(SyntaxKind::MATCH_KW)) {
p.error_code(
sc::EXPECTED_ON_ACTION,
"expected CREATE or MATCH after ON in MERGE action",
);
}
if p.at(SyntaxKind::SET_KW) {
set_clause(p);
}
m.complete(p, SyntaxKind::MERGE_ACTION);
}
fn set_clause(p: &mut Parser<'_>) {
debug_assert!(p.at(SyntaxKind::SET_KW));
let m = p.start();
p.bump(SyntaxKind::SET_KW);
set_item(p);
while p.at(SyntaxKind::COMMA) {
p.bump(SyntaxKind::COMMA);
set_item(p);
}
m.complete(p, SyntaxKind::SET_CLAUSE);
}
fn set_item(p: &mut Parser<'_>) {
let m = p.start();
if !set_target(p) {
p.error_code(sc::EXPECTED_SET_ITEM, "expected SET item");
m.complete(p, SyntaxKind::SET_ITEM);
return;
}
if p.at(SyntaxKind::COLON) {
label_expr_inline(p);
} else if p.eat(SyntaxKind::EQ) {
if expression::expr(p).is_none() {
p.error_code(sc::EXPECTED_PROP_VALUE, "expected expression for SET value");
}
} else {
p.error_code(
sc::EXPECTED_SET_ITEM,
"expected ':', '=', or '+=' in SET item",
);
}
m.complete(p, SyntaxKind::SET_ITEM);
}
fn set_target(p: &mut Parser<'_>) -> bool {
if !(p.at(SyntaxKind::IDENT) || p.at(SyntaxKind::QUOTED_IDENT)) {
return false;
}
let mut head = {
let m = p.start();
p.bump_any();
m.complete(p, SyntaxKind::VAR_EXPR)
};
loop {
match p.current() {
SyntaxKind::DOT => {
let m = head.precede(p);
p.bump(SyntaxKind::DOT);
if !(p.eat(SyntaxKind::IDENT) || p.eat(SyntaxKind::QUOTED_IDENT)) {
p.error_code(
sc::EXPECTED_PROP_KEY_AFTER_DOT,
"expected property key after '.'",
);
}
head = m.complete(p, SyntaxKind::PROP_ACCESS_EXPR);
}
SyntaxKind::L_BRACK => {
let m = head.precede(p);
p.bump(SyntaxKind::L_BRACK);
if expression::expr(p).is_none() {
p.error_code(sc::EXPECTED_INDEX_EXPR, "expected index expression");
}
if !p.eat(SyntaxKind::R_BRACK) {
p.error_code(
sc::EXPECTED_RBRACK_INDEX,
"expected ']' to close index expression",
);
}
head = m.complete(p, SyntaxKind::SUBSCRIPT_EXPR);
}
_ => break,
}
}
let _ = head;
true
}
fn remove_clause(p: &mut Parser<'_>) {
debug_assert!(p.at(SyntaxKind::REMOVE_KW));
let m = p.start();
p.bump(SyntaxKind::REMOVE_KW);
remove_item(p);
while p.at(SyntaxKind::COMMA) {
p.bump(SyntaxKind::COMMA);
remove_item(p);
}
m.complete(p, SyntaxKind::REMOVE_CLAUSE);
}
fn remove_item(p: &mut Parser<'_>) {
let m = p.start();
if !set_target(p) {
p.error_code(sc::EXPECTED_REMOVE_ITEM, "expected REMOVE item");
m.complete(p, SyntaxKind::REMOVE_ITEM);
return;
}
if p.at(SyntaxKind::COLON) {
label_expr_inline(p);
}
m.complete(p, SyntaxKind::REMOVE_ITEM);
}
fn label_expr_inline(p: &mut Parser<'_>) {
debug_assert!(p.at(SyntaxKind::COLON));
let m = p.start();
while p.at(SyntaxKind::COLON) {
p.bump(SyntaxKind::COLON);
if p.eat(SyntaxKind::IDENT) || p.eat(SyntaxKind::QUOTED_IDENT) {
continue;
}
if p.current().is_keyword() {
p.bump_any();
continue;
}
p.error_code(sc::EXPECTED_LABEL, "expected label after ':'");
break;
}
m.complete(p, SyntaxKind::LABEL_EXPR);
}
fn delete_clause(p: &mut Parser<'_>) {
debug_assert!(p.at(SyntaxKind::DETACH_KW) || p.at(SyntaxKind::DELETE_KW));
let m = p.start();
if p.eat(SyntaxKind::DETACH_KW) && !p.eat(SyntaxKind::DELETE_KW) {
p.error_code(
sc::EXPECTED_DELETE_AFTER_DETACH,
"expected DELETE after DETACH",
);
} else if p.at(SyntaxKind::DELETE_KW) {
p.bump(SyntaxKind::DELETE_KW);
}
if expression::expr(p).is_none() {
p.error_code(sc::EXPECTED_DELETE_EXPR, "expected expression after DELETE");
}
while p.at(SyntaxKind::COMMA) {
p.bump(SyntaxKind::COMMA);
if expression::expr(p).is_none() {
p.error_code(sc::EXPECTED_DELETE_EXPR, "expected expression after DELETE");
break;
}
}
m.complete(p, SyntaxKind::DELETE_CLAUSE);
}
fn with_clause(p: &mut Parser<'_>) {
debug_assert!(p.at(SyntaxKind::WITH_KW));
let m = p.start();
p.bump(SyntaxKind::WITH_KW);
p.eat(SyntaxKind::DISTINCT_KW);
let body = p.start();
let items = p.start();
if p.at(SyntaxKind::STAR) {
let item = p.start();
p.bump(SyntaxKind::STAR);
item.complete(p, SyntaxKind::RETURN_ITEM);
} else {
return_item(p);
while p.at(SyntaxKind::COMMA) {
p.bump(SyntaxKind::COMMA);
return_item(p);
}
}
items.complete(p, SyntaxKind::RETURN_ITEMS);
if p.at(SyntaxKind::WHERE_KW) {
where_clause(p);
}
if p.at(SyntaxKind::ORDER_KW) {
order_by(p);
}
if p.at(SyntaxKind::SKIP_KW) {
skip_subclause(p);
}
if p.at(SyntaxKind::LIMIT_KW) {
limit_subclause(p);
}
body.complete(p, SyntaxKind::RETURN_BODY);
m.complete(p, SyntaxKind::WITH_CLAUSE);
}
fn return_clause(p: &mut Parser<'_>) {
debug_assert!(p.at(SyntaxKind::RETURN_KW));
let m = p.start();
p.bump(SyntaxKind::RETURN_KW);
p.eat(SyntaxKind::DISTINCT_KW);
let body = p.start();
let items = p.start();
if p.at(SyntaxKind::STAR) {
let item = p.start();
p.bump(SyntaxKind::STAR);
item.complete(p, SyntaxKind::RETURN_ITEM);
} else {
return_item(p);
while p.at(SyntaxKind::COMMA) {
p.bump(SyntaxKind::COMMA);
return_item(p);
}
}
items.complete(p, SyntaxKind::RETURN_ITEMS);
if p.at(SyntaxKind::ORDER_KW) {
order_by(p);
}
if p.at(SyntaxKind::SKIP_KW) {
skip_subclause(p);
}
if p.at(SyntaxKind::LIMIT_KW) {
limit_subclause(p);
}
body.complete(p, SyntaxKind::RETURN_BODY);
m.complete(p, SyntaxKind::RETURN_CLAUSE);
}
fn return_item(p: &mut Parser<'_>) {
let m = p.start();
if expression::expr(p).is_none() {
p.error_code(
sc::EXPECTED_RETURN_EXPR,
"expected expression in RETURN item",
);
}
if p.eat(SyntaxKind::AS_KW) {
if !(p.eat(SyntaxKind::IDENT) || p.eat(SyntaxKind::QUOTED_IDENT)) {
p.error_code(sc::EXPECTED_IDENT_AFTER_AS, "expected identifier after AS");
}
}
m.complete(p, SyntaxKind::RETURN_ITEM);
}
fn order_by(p: &mut Parser<'_>) {
debug_assert!(p.at(SyntaxKind::ORDER_KW));
let m = p.start();
p.bump(SyntaxKind::ORDER_KW);
if !p.eat(SyntaxKind::BY_KW) {
p.error_code(sc::EXPECTED_BY_AFTER_ORDER, "expected BY after ORDER");
}
order_item(p);
while p.at(SyntaxKind::COMMA) {
p.bump(SyntaxKind::COMMA);
order_item(p);
}
m.complete(p, SyntaxKind::ORDER_BY);
}
fn order_item(p: &mut Parser<'_>) {
let m = p.start();
if expression::expr(p).is_none() {
p.error_code(sc::EXPECTED_ORDERBY_EXPR, "expected expression in ORDER BY");
}
match p.current() {
SyntaxKind::ASC_KW
| SyntaxKind::ASCENDING_KW
| SyntaxKind::DESC_KW
| SyntaxKind::DESCENDING_KW => p.bump_any(),
_ => {}
}
m.complete(p, SyntaxKind::ORDER_ITEM);
}
fn skip_subclause(p: &mut Parser<'_>) {
debug_assert!(p.at(SyntaxKind::SKIP_KW));
let m = p.start();
p.bump(SyntaxKind::SKIP_KW);
if expression::expr(p).is_none() {
p.error_code(sc::EXPECTED_SKIP_EXPR, "expected expression after SKIP");
}
m.complete(p, SyntaxKind::SKIP_SUBCLAUSE);
}
fn limit_subclause(p: &mut Parser<'_>) {
debug_assert!(p.at(SyntaxKind::LIMIT_KW));
let m = p.start();
p.bump(SyntaxKind::LIMIT_KW);
if expression::expr(p).is_none() {
p.error_code(sc::EXPECTED_LIMIT_EXPR, "expected expression after LIMIT");
}
m.complete(p, SyntaxKind::LIMIT_SUBCLAUSE);
}
fn call_clause(p: &mut Parser<'_>) {
debug_assert!(p.at(SyntaxKind::CALL_KW));
let m = p.start();
p.bump(SyntaxKind::CALL_KW);
procedure_name(p);
if p.at(SyntaxKind::L_PAREN) {
call_arg_list(p);
}
if p.at(SyntaxKind::YIELD_KW) {
yield_subclause(p);
}
m.complete(p, SyntaxKind::CALL_CLAUSE);
}
fn procedure_name(p: &mut Parser<'_>) {
let m = p.start();
if !(p.eat(SyntaxKind::IDENT) || p.eat(SyntaxKind::QUOTED_IDENT)) {
p.error_code(
sc::EXPECTED_PROCEDURE_NAME,
"expected procedure name after CALL",
);
m.complete(p, SyntaxKind::PROCEDURE_NAME);
return;
}
while p.at(SyntaxKind::DOT) {
p.bump(SyntaxKind::DOT);
if !(p.eat(SyntaxKind::IDENT) || p.eat(SyntaxKind::QUOTED_IDENT)) {
p.error_code(
sc::EXPECTED_PROCEDURE_NAME,
"expected identifier after `.` in procedure name",
);
break;
}
}
m.complete(p, SyntaxKind::PROCEDURE_NAME);
}
fn call_arg_list(p: &mut Parser<'_>) {
debug_assert!(p.at(SyntaxKind::L_PAREN));
p.bump(SyntaxKind::L_PAREN);
if !p.at(SyntaxKind::R_PAREN) && expression::expr(p).is_some() {
while p.at(SyntaxKind::COMMA) {
p.bump(SyntaxKind::COMMA);
if expression::expr(p).is_none() {
break;
}
}
}
if !p.eat(SyntaxKind::R_PAREN) {
p.error_code(
sc::EXPECTED_RPAREN_CALL_ARGS,
"expected `)` to close CALL argument list",
);
}
}
fn yield_subclause(p: &mut Parser<'_>) {
debug_assert!(p.at(SyntaxKind::YIELD_KW));
let m = p.start();
p.bump(SyntaxKind::YIELD_KW);
yield_item(p);
while p.at(SyntaxKind::COMMA) {
p.bump(SyntaxKind::COMMA);
yield_item(p);
}
m.complete(p, SyntaxKind::YIELD_SUBCLAUSE);
}
fn yield_item(p: &mut Parser<'_>) {
let m = p.start();
if !(p.eat(SyntaxKind::IDENT) || p.eat(SyntaxKind::QUOTED_IDENT)) {
p.error_code(sc::EXPECTED_YIELD_ITEM, "expected identifier in YIELD item");
m.complete(p, SyntaxKind::YIELD_ITEM);
return;
}
if p.eat(SyntaxKind::AS_KW) && !(p.eat(SyntaxKind::IDENT) || p.eat(SyntaxKind::QUOTED_IDENT)) {
p.error_code(sc::EXPECTED_IDENT_AFTER_AS, "expected identifier after AS");
}
m.complete(p, SyntaxKind::YIELD_ITEM);
}