use crate::SyntaxKind::{
self, ALTERNATION_BLOCK, ALTERNATION_MARKER, AMP, AT_L_BRACKET, BANG, COMMA, CONDITIONAL_BLOCK,
DIVERT, ELSE_BRANCH, ENTRY, EOF, FAT_ARROW, HASH, IF_ARM, KW_ELSE, KW_IF, KW_MATCH, KW_RETURN,
L_BRACE, MATCH_ARM, MATCH_PATTERN, MINUS, NEWLINE, PIPE, QUESTION, R_BRACE, THREAD, TILDE,
};
use super::Parser;
pub(crate) fn at_choice_point(p: &Parser<'_, '_>) -> bool {
p.at(L_BRACE) && p.nth(1) == QUESTION
}
pub(crate) fn at_conditional(p: &Parser<'_, '_>) -> bool {
p.at(L_BRACE) && matches!(p.nth(1), KW_IF | KW_MATCH)
}
pub(crate) fn at_alternation(p: &Parser<'_, '_>) -> bool {
p.at(L_BRACE) && matches!(p.nth(1), TILDE | AMP | BANG | PIPE)
}
pub(crate) fn conditional_block(p: &mut Parser<'_, '_>) {
p.start_node(CONDITIONAL_BLOCK);
if !p.enter_depth() {
p.expect(L_BRACE);
p.finish_node();
return;
}
p.expect(L_BRACE);
conditional_body(p);
p.expect(R_BRACE);
p.exit_depth();
p.finish_node();
}
fn conditional_body(p: &mut Parser<'_, '_>) {
if p.eat(KW_IF) {
head_expression(p);
p.skip_ws();
if super::binding::at_as_binding(p) {
super::binding::as_binding(p);
}
if_arm(p);
if at_else_arm(p) {
else_branch(p);
}
} else if p.eat(KW_MATCH) {
head_expression(p);
match_arm_list(p);
} else {
p.error("expected `if` or `match` after `{`".into());
}
}
fn head_expression(p: &mut Parser<'_, '_>) {
let saved = p.set_no_construct_literal(true);
super::expr::expression(p);
p.set_no_construct_literal(saved);
}
fn if_arm(p: &mut Parser<'_, '_>) {
p.start_node(IF_ARM);
arm_body(p);
p.finish_node();
}
pub(crate) fn at_else_arm(p: &Parser<'_, '_>) -> bool {
p.at(KW_ELSE) && matches!(p.nth(1), L_BRACE | SyntaxKind::COLON | KW_IF)
}
fn else_branch(p: &mut Parser<'_, '_>) {
p.start_node(ELSE_BRANCH);
p.expect(KW_ELSE);
if p.at(KW_IF) {
p.start_node(CONDITIONAL_BLOCK);
if p.enter_depth() {
conditional_body(p);
p.exit_depth();
}
p.finish_node();
} else {
arm_body(p);
}
p.finish_node();
}
fn arm_body(p: &mut Parser<'_, '_>) {
if p.eat(SyntaxKind::COLON) {
colon_body(p);
} else if p.at(L_BRACE) {
super::block::braced_item_list(p, crate::SyntaxKind::BLOCK);
} else {
p.error("expected `:` or `{` to open the arm body".into());
}
}
fn colon_body(p: &mut Parser<'_, '_>) {
loop {
p.skip_ws();
if p.at(R_BRACE) || p.at_eof() || at_else_arm(p) {
break;
}
let before = p.pos();
colon_body_line(p);
if p.pos() == before {
p.error_recover("unexpected token in conditional body");
}
}
}
fn colon_body_line(p: &mut Parser<'_, '_>) {
match p.current() {
NEWLINE => {
p.bump();
}
HASH => super::content::tag_line(p),
AT_L_BRACKET => super::annotation::annotation_line(p),
DIVERT => super::divert::divert_or_tunnel(p),
KW_RETURN => super::divert::return_stmt(p),
TILDE => super::stmt::logic_line(p),
THREAD => super::choice::splice_outside_choice_point(p),
L_BRACE if at_choice_point(p) => super::choice::choice_point(p),
L_BRACE if at_conditional(p) => conditional_block(p),
L_BRACE if at_alternation(p) => alternation_block(p),
EOF => {}
_ => super::content::content_line_else_boundary(p),
}
}
fn match_arm_list(p: &mut Parser<'_, '_>) {
p.expect(L_BRACE);
if p.enter_depth() {
p.skip_ws_and_newlines();
loop {
if p.peek_skip_nl() == R_BRACE || p.at_eof() {
break;
}
let before = p.pos();
match_arm(p);
if p.pos() == before {
p.error_recover("unexpected token in match arm list");
p.skip_ws_and_newlines();
continue;
}
p.skip_ws_and_newlines();
p.eat(COMMA);
p.skip_ws_and_newlines();
}
p.exit_depth();
}
p.expect(R_BRACE);
}
fn match_arm(p: &mut Parser<'_, '_>) {
p.start_node(MATCH_ARM);
p.start_node(MATCH_PATTERN);
super::expr::expression(p);
p.finish_node();
p.expect(FAT_ARROW);
if p.at(L_BRACE) {
super::block::braced_item_list(p, crate::SyntaxKind::BLOCK);
} else {
super::expr::expression(p);
}
p.finish_node();
}
pub(crate) fn alternation_block(p: &mut Parser<'_, '_>) {
p.start_node(ALTERNATION_BLOCK);
if !p.enter_depth() {
p.expect(L_BRACE);
p.finish_node();
return;
}
p.expect(L_BRACE);
alternation_marker(p);
let has_branches = if is_multiline(p) {
multiline_entries(p)
} else {
inline_alternatives(p)
};
if !has_branches {
p.error("empty alternation: `{~}`/`{&}`/`{!}`/`{|}` need at least one branch".into());
}
p.expect(R_BRACE);
p.exit_depth();
p.finish_node();
}
fn alternation_marker(p: &mut Parser<'_, '_>) -> SyntaxKind {
p.start_node(ALTERNATION_MARKER);
p.skip_ws();
let kind = p.current();
p.bump();
p.finish_node();
kind
}
fn is_multiline(p: &Parser<'_, '_>) -> bool {
peek_is_newline(p, 0)
}
pub(crate) fn peek_is_newline(p: &Parser<'_, '_>, mut i: usize) -> bool {
loop {
match p.nth_raw(i) {
SyntaxKind::WHITESPACE | SyntaxKind::LINE_COMMENT | SyntaxKind::BLOCK_COMMENT => {
i += 1;
}
NEWLINE => return true,
_ => return false,
}
}
}
fn multiline_entries(p: &mut Parser<'_, '_>) -> bool {
let mut has_entry = false;
loop {
p.skip_ws();
if p.at(R_BRACE) || p.at_eof() {
break;
}
if p.at(MINUS) {
entry(p);
has_entry = true;
} else {
let before = p.pos();
super::block::body_line(p);
if p.pos() == before {
p.error_recover("unexpected token before the first `-` entry");
}
}
}
has_entry
}
fn entry(p: &mut Parser<'_, '_>) {
p.start_node(ENTRY);
p.expect(MINUS);
loop {
p.skip_ws();
if p.at(R_BRACE) || p.at(MINUS) || p.at_eof() {
break;
}
let before = p.pos();
super::block::body_line(p);
if p.pos() == before {
p.error_recover("unexpected token in entry");
}
}
p.finish_node();
}
fn inline_alternatives(p: &mut Parser<'_, '_>) -> bool {
let mut has_any = false;
loop {
p.skip_ws();
match p.current() {
R_BRACE | EOF | NEWLINE => break,
PIPE => {
has_any = true;
p.bump();
}
SyntaxKind::HASH => {
has_any = true;
super::content::tag_line_tail(p);
}
_ => {
has_any = true;
super::content::content_items_until(p, &[PIPE, R_BRACE, NEWLINE]);
}
}
}
has_any
}