use crate::SyntaxKind::{
self, AMP_AMP, BANG, BANG_EQ, BANG_QUESTION, BOOLEAN_LIT, CARET, COMMA, DIVERT,
DIVERT_TARGET_EXPR, DOT, EOF, EQ_EQ, FLOAT, FLOAT_LIT, FUNCTION_CALL, GT, GT_EQ, IDENT,
IDENTIFIER, INFIX_EXPR, INTEGER, INTEGER_LIT, KW_AND, KW_FALSE, KW_HAS, KW_HASNT, KW_MOD,
KW_NOT, KW_OR, KW_TRUE, L_BRACE, L_PAREN, LIST_EXPR, LT, LT_EQ, MINUS, MINUS_EQ, NEWLINE,
PAREN_EXPR, PERCENT, PIPE, PLUS, PLUS_EQ, POSTFIX_EXPR, PREFIX_EXPR, QUESTION, QUOTE, R_PAREN,
SLASH, STAR, STRING_LIT,
};
use super::Parser;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)]
enum Prec {
None = 0,
Assign = 1, Or = 2, And = 3, Equality = 4, Comparison = 5, HasOps = 6, Add = 7, Mul = 8, Intersect = 9, Prefix = 10, }
fn infix_binding_power(kind: SyntaxKind) -> Option<(Prec, bool)> {
Some(match kind {
PLUS_EQ | MINUS_EQ => (Prec::Assign, true),
KW_OR => (Prec::Or, false),
AMP_AMP | KW_AND => (Prec::And, false),
EQ_EQ | BANG_EQ => (Prec::Equality, false),
LT | GT | LT_EQ | GT_EQ => (Prec::Comparison, false),
QUESTION | BANG_QUESTION | KW_HAS | KW_HASNT => (Prec::HasOps, false),
PLUS | MINUS => (Prec::Add, false),
STAR | SLASH | PERCENT | KW_MOD => (Prec::Mul, false),
CARET => (Prec::Intersect, true),
_ => return None,
})
}
fn is_prefix_op(kind: SyntaxKind) -> bool {
matches!(kind, MINUS | BANG | KW_NOT)
}
pub(crate) fn expression(p: &mut Parser<'_, '_>) {
expression_bp(p, Prec::None);
}
fn expression_bp(p: &mut Parser<'_, '_>, min_bp: Prec) {
let checkpoint = p.checkpoint();
if is_prefix_op(p.current()) {
p.start_node_at(checkpoint, PREFIX_EXPR);
p.skip_ws();
p.bump(); p.skip_ws();
expression_bp(p, Prec::Prefix);
p.finish_node();
} else {
if !atom(p) {
return;
}
}
loop {
p.skip_ws();
if p.current() == PLUS && p.nth_raw(1) == PLUS {
p.start_node_at(checkpoint, POSTFIX_EXPR);
p.bump(); p.bump(); p.finish_node();
continue;
}
if p.current() == MINUS && p.nth_raw(1) == MINUS {
p.start_node_at(checkpoint, POSTFIX_EXPR);
p.bump(); p.bump(); p.finish_node();
continue;
}
if p.current() == PIPE && p.nth_raw(1) == PIPE {
let (prec, right_assoc) = (Prec::Or, false);
if prec < min_bp || (prec == min_bp && !right_assoc) {
break;
}
p.start_node_at(checkpoint, INFIX_EXPR);
p.bump(); p.bump(); p.skip_ws();
expression_bp(p, Prec::Or);
p.finish_node();
continue;
}
let Some((prec, right_assoc)) = infix_binding_power(p.current()) else {
break;
};
if prec < min_bp {
break;
}
if prec == min_bp && !right_assoc {
break;
}
p.start_node_at(checkpoint, INFIX_EXPR);
p.skip_ws();
p.bump(); p.skip_ws();
expression_bp(p, prec);
p.finish_node();
}
}
fn atom(p: &mut Parser<'_, '_>) -> bool {
match p.current() {
L_PAREN => {
if looks_like_list_expr(p) {
list_expr(p);
} else {
paren_expr(p);
}
true
}
DIVERT => {
divert_target_expr(p);
true
}
IDENT => {
if p.nth(1) == L_PAREN {
function_call(p);
} else {
super::divert::path(p);
}
true
}
INTEGER => {
p.start_node(INTEGER_LIT);
p.bump();
p.finish_node();
true
}
FLOAT => {
p.start_node(FLOAT_LIT);
p.bump();
p.finish_node();
true
}
QUOTE => {
string_literal(p);
true
}
KW_TRUE | KW_FALSE => {
p.start_node(BOOLEAN_LIT);
p.bump();
p.finish_node();
true
}
_ => false,
}
}
fn looks_like_list_expr(p: &Parser<'_, '_>) -> bool {
if p.nth(1) == R_PAREN {
return true;
}
let mut i = 1;
if p.nth(i) != IDENT {
return false;
}
i += 1;
while p.nth(i) == DOT && p.nth(i + 1) == IDENT {
i += 2;
}
matches!(p.nth(i), COMMA | R_PAREN)
}
fn paren_expr(p: &mut Parser<'_, '_>) {
p.start_node(PAREN_EXPR);
p.bump();
if p.at_depth_limit() {
p.error("nesting depth limit exceeded".into());
let mut depth = 1u32;
while !p.at_eof() && depth > 0 {
match p.current() {
L_PAREN => {
depth += 1;
p.bump();
}
R_PAREN => {
depth -= 1;
if depth > 0 {
p.bump();
}
}
_ => p.bump(),
}
}
if p.current() == R_PAREN {
p.bump();
}
p.finish_node();
return;
}
p.depth += 1;
p.skip_ws();
expression(p);
p.skip_ws();
p.expect(R_PAREN);
p.depth -= 1;
p.finish_node();
}
fn list_expr(p: &mut Parser<'_, '_>) {
p.start_node(LIST_EXPR);
p.bump(); p.skip_ws();
if p.current() != R_PAREN {
super::divert::path(p);
loop {
p.skip_ws();
if !p.eat(COMMA) {
break;
}
p.skip_ws();
super::divert::path(p);
}
}
p.skip_ws();
p.expect(R_PAREN);
p.finish_node();
}
fn divert_target_expr(p: &mut Parser<'_, '_>) {
p.start_node(DIVERT_TARGET_EXPR);
p.bump(); p.skip_ws();
super::divert::path(p);
p.finish_node();
}
fn function_call(p: &mut Parser<'_, '_>) {
p.start_node(FUNCTION_CALL);
p.start_node(IDENTIFIER);
p.bump(); p.finish_node();
p.skip_ws();
p.bump(); p.skip_ws();
if p.current() != R_PAREN {
super::divert::arg_list(p);
}
p.skip_ws();
p.expect(R_PAREN);
p.finish_node();
}
fn string_literal(p: &mut Parser<'_, '_>) {
p.start_node(STRING_LIT);
p.bump();
loop {
match p.nth_raw(0) {
QUOTE => {
p.bump(); break;
}
L_BRACE => {
super::inline::inline_logic(p);
}
NEWLINE | EOF => {
p.error("unterminated string literal".into());
break;
}
_ => {
p.bump();
}
}
}
p.finish_node();
}