use crate::SyntaxKind::{
AMP_AMP, ARG_LIST, ARRAY_LITERAL, BANG, BANG_EQ, BOOLEAN_LIT, CALL_EXPR, COLON, COLON_COLON,
COMMA, CONSTRUCT_ENTRY, CONSTRUCT_LITERAL, DOT, EQ_EQ, FLOAT, FLOAT_LIT, GT, GT_EQ, IDENT,
INFIX_EXPR, INTEGER, INTEGER_LIT, KW_FALSE, KW_OR, KW_TRUE, L_BRACE, L_BRACKET, L_PAREN,
LAMBDA_EXPR, LAMBDA_PARAMS, LT, LT_EQ, MINUS, PARAM, PAREN_EXPR, PATH, PATH_EXPR, PATH_SEGMENT,
PERCENT, PIPE, PLUS, PREFIX_EXPR, QUOTE, R_BRACE, R_BRACKET, R_PAREN, SLASH, STAR,
STRING_ESCAPE, STRING_LIT, STRING_TEXT,
};
use super::Parser;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)]
enum Prec {
None = 0,
Coalesce = 1, Or = 2, And = 3, Equality = 4, Comparison = 5, Add = 6, Mul = 7, Prefix = 8, }
impl Prec {
fn next(self) -> Prec {
match self {
Prec::None => Prec::Coalesce,
Prec::Coalesce => Prec::Or,
Prec::Or => Prec::And,
Prec::And => Prec::Equality,
Prec::Equality => Prec::Comparison,
Prec::Comparison => Prec::Add,
Prec::Add => Prec::Mul,
Prec::Mul | Prec::Prefix => Prec::Prefix,
}
}
}
fn infix_binding_power(kind: crate::SyntaxKind) -> Option<Prec> {
Some(match kind {
KW_OR => Prec::Coalesce,
AMP_AMP => Prec::And,
EQ_EQ | BANG_EQ => Prec::Equality,
LT | GT | LT_EQ | GT_EQ => Prec::Comparison,
PLUS | MINUS => Prec::Add,
STAR | SLASH | PERCENT => Prec::Mul,
_ => return None,
})
}
fn is_prefix_op(kind: crate::SyntaxKind) -> bool {
matches!(kind, MINUS | BANG)
}
pub(crate) fn expression(p: &mut Parser<'_, '_>) {
expression_bp(p, Prec::None);
}
fn expression_bp(p: &mut Parser<'_, '_>, min_bp: Prec) {
if !p.enter_depth() {
return;
}
p.skip_ws();
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) {
p.exit_depth();
return;
}
loop {
p.skip_ws();
if p.current() == PIPE && p.nth_raw(1) == PIPE {
if (Prec::Or as u8) < (min_bp as u8) {
break;
}
p.start_node_at(checkpoint, INFIX_EXPR);
p.bump();
p.bump();
p.skip_ws();
expression_bp(p, Prec::Or.next());
p.finish_node();
continue;
}
let Some(prec) = infix_binding_power(p.current()) else {
break;
};
if (prec as u8) < (min_bp as u8) {
break;
}
p.start_node_at(checkpoint, INFIX_EXPR);
p.bump(); p.skip_ws();
expression_bp(p, prec.next());
p.finish_node();
}
p.exit_depth();
}
fn atom(p: &mut Parser<'_, '_>) -> bool {
match p.current() {
INTEGER => {
p.start_node(INTEGER_LIT);
p.bump();
p.finish_node();
true
}
FLOAT => {
p.start_node(FLOAT_LIT);
p.bump();
p.finish_node();
true
}
KW_TRUE | KW_FALSE => {
p.start_node(BOOLEAN_LIT);
p.bump();
p.finish_node();
true
}
QUOTE => {
string_lit(p);
true
}
L_PAREN => {
paren_expr(p);
true
}
PIPE => {
lambda_expr(p);
true
}
IDENT => {
path_or_call(p);
true
}
L_BRACKET => {
array_literal(p);
true
}
L_BRACE => {
super::stmt::stmt_block(p);
true
}
_ => {
p.error(format!("expected an expression, found {:?}", p.current()));
false
}
}
}
fn paren_expr(p: &mut Parser<'_, '_>) {
p.start_node(PAREN_EXPR);
p.expect(L_PAREN);
let saved = p.set_no_construct_literal(false);
expression(p);
p.set_no_construct_literal(saved);
p.expect(R_PAREN);
p.finish_node();
}
fn lambda_expr(p: &mut Parser<'_, '_>) {
p.start_node(LAMBDA_EXPR);
lambda_params(p);
if super::types::at_type_annotation(p) {
super::types::lambda_return_type_annotation(p);
}
p.skip_ws();
expression(p);
p.finish_node();
}
fn lambda_params(p: &mut Parser<'_, '_>) {
p.start_node(LAMBDA_PARAMS);
p.expect(PIPE);
while !p.at(PIPE) && !p.at_eof() {
let before = p.pos();
lambda_param(p);
if p.pos() == before {
p.error_recover("unexpected token in lambda parameter list");
continue;
}
if !p.eat(COMMA) {
break;
}
}
p.expect(PIPE);
p.finish_node();
}
fn lambda_param(p: &mut Parser<'_, '_>) {
if !p.at(IDENT) {
return;
}
p.start_node(PARAM);
p.expect(IDENT);
if super::types::at_type_annotation(p) {
super::types::type_annotation(p);
}
p.finish_node();
}
fn path_or_call(p: &mut Parser<'_, '_>) {
let checkpoint = p.checkpoint();
path(p);
p.skip_ws();
if p.at(L_PAREN) {
p.start_node_at(checkpoint, CALL_EXPR);
arg_list(p);
p.finish_node();
} else if p.at(L_BRACE) && p.construct_literals_allowed() {
p.start_node_at(checkpoint, CONSTRUCT_LITERAL);
construct_entry_list(p);
p.finish_node();
} else {
p.start_node_at(checkpoint, PATH_EXPR);
p.finish_node();
}
}
fn construct_entry_list(p: &mut Parser<'_, '_>) {
p.expect(L_BRACE);
let saved = p.set_no_construct_literal(false);
if p.enter_depth() {
loop {
p.skip_ws_and_newlines();
if p.at(R_BRACE) || p.at_eof() {
break;
}
let before = p.pos();
construct_entry(p);
if p.pos() == before {
p.error_recover("unexpected token in construction literal");
continue;
}
p.skip_ws_and_newlines();
if !p.eat(COMMA) {
break;
}
}
p.exit_depth();
}
p.skip_ws_and_newlines();
p.set_no_construct_literal(saved);
p.expect(R_BRACE);
}
fn construct_entry(p: &mut Parser<'_, '_>) {
p.start_node(CONSTRUCT_ENTRY);
expression(p);
p.skip_ws();
if p.eat(COLON) {
p.skip_ws_and_newlines();
expression(p);
}
p.finish_node();
}
fn array_literal(p: &mut Parser<'_, '_>) {
p.start_node(ARRAY_LITERAL);
p.expect(L_BRACKET);
let saved = p.set_no_construct_literal(false);
if p.enter_depth() {
loop {
p.skip_ws_and_newlines();
if p.at(R_BRACKET) || p.at_eof() {
break;
}
let before = p.pos();
expression(p);
if p.pos() == before {
p.error_recover("unexpected token in array literal");
continue;
}
p.skip_ws_and_newlines();
if !p.eat(COMMA) {
break;
}
}
p.exit_depth();
}
p.skip_ws_and_newlines();
p.set_no_construct_literal(saved);
p.expect(R_BRACKET);
p.finish_node();
}
pub(crate) fn path(p: &mut Parser<'_, '_>) {
p.start_node(PATH);
path_segment(p);
while p.eat(DOT) || p.eat(COLON_COLON) {
path_segment(p);
}
p.finish_node();
}
fn path_segment(p: &mut Parser<'_, '_>) {
p.start_node(PATH_SEGMENT);
p.expect(IDENT);
p.finish_node();
}
pub(crate) fn arg_list(p: &mut Parser<'_, '_>) {
p.start_node(ARG_LIST);
p.expect(L_PAREN);
let saved = p.set_no_construct_literal(false);
p.skip_ws_and_newlines();
while p.peek_skip_nl() != R_PAREN && !p.at_eof() {
let before = p.pos();
expression(p);
if p.pos() == before {
p.error_recover("unexpected token in argument list");
p.skip_ws_and_newlines();
continue;
}
p.skip_ws_and_newlines();
if !p.eat(COMMA) {
break;
}
p.skip_ws_and_newlines();
}
p.set_no_construct_literal(saved);
p.expect(R_PAREN);
p.finish_node();
}
pub(crate) fn string_lit(p: &mut Parser<'_, '_>) {
p.start_node(STRING_LIT);
p.expect(QUOTE);
loop {
match p.current() {
STRING_TEXT | STRING_ESCAPE => p.bump(),
crate::SyntaxKind::L_BRACE => super::content::interpolation(p),
_ => break,
}
}
p.expect(QUOTE);
p.finish_node();
}