use crate::SyntaxKind::{
CHOICE, CHOICE_BODY, CHOICE_BRACKET_CONTENT, CHOICE_BULLET, CHOICE_GUARD, CHOICE_INNER_CONTENT,
CHOICE_POINT, CHOICE_START_CONTENT, DOT, ELSE_BRANCH, EOF, HASH, IDENT, KW_ELSE, KW_IF,
L_BRACE, L_BRACKET, L_PAREN, NEWLINE, PLUS, QUESTION, R_BRACE, R_BRACKET, R_PAREN, SPLICE,
STAR, THREAD,
};
use super::Parser;
pub(crate) fn choice_point(p: &mut Parser<'_, '_>) {
p.start_node(CHOICE_POINT);
if !p.enter_depth() {
p.expect(L_BRACE);
p.finish_node();
return;
}
p.expect(L_BRACE);
p.expect(QUESTION);
loop {
p.skip_ws();
match p.current() {
R_BRACE | EOF => break,
NEWLINE => {
p.bump();
}
STAR | PLUS => choice(p),
THREAD => splice(p),
KW_ELSE if p.nth(1) == L_BRACE => else_branch(p),
_ => {
let before = p.pos();
p.error_recover("expected a choice line, splice, or `else` in a choice point");
if p.pos() == before {
break;
}
}
}
}
p.expect(R_BRACE);
p.exit_depth();
p.finish_node();
}
fn choice(p: &mut Parser<'_, '_>) {
p.start_node(CHOICE);
p.start_node(CHOICE_BULLET);
p.bump(); p.finish_node();
p.skip_ws();
if p.at(L_BRACE) && p.nth(1) == KW_IF {
choice_guard(p);
}
p.skip_ws();
if p.at(L_PAREN) {
super::content::label(p);
}
choice_text(p);
if p.at(HASH) {
super::content::tag_line_tail(p);
}
p.skip_ws();
if p.at(L_BRACE) {
super::block::braced_item_list(p, CHOICE_BODY);
}
if p.at(NEWLINE) {
p.skip_ws();
p.bump();
}
p.finish_node();
}
fn choice_guard(p: &mut Parser<'_, '_>) {
p.start_node(CHOICE_GUARD);
p.expect(L_BRACE);
p.expect(KW_IF);
super::expr::expression(p);
p.skip_ws();
if super::binding::at_as_binding(p) {
super::binding::as_binding(p);
}
p.expect(R_BRACE);
p.finish_node();
}
fn choice_text(p: &mut Parser<'_, '_>) {
p.start_node(CHOICE_START_CONTENT);
super::content::content_items_until(p, &[L_BRACKET, NEWLINE, R_BRACE, L_BRACE]);
p.finish_node();
if p.at(L_BRACKET) {
p.start_node(CHOICE_BRACKET_CONTENT);
p.bump(); super::content::content_items_until(p, &[R_BRACKET, NEWLINE, R_BRACE]);
p.expect(R_BRACKET);
p.finish_node();
p.start_node(CHOICE_INNER_CONTENT);
super::content::content_items_until(p, &[NEWLINE, R_BRACE, L_BRACE]);
p.finish_node();
}
}
fn splice(p: &mut Parser<'_, '_>) {
p.start_node(SPLICE);
p.expect(THREAD);
super::expr::path(p);
if p.at(L_PAREN) {
super::expr::arg_list(p);
}
if p.at(NEWLINE) {
p.skip_ws();
p.bump();
}
p.finish_node();
}
pub(crate) fn splice_outside_choice_point(p: &mut Parser<'_, '_>) {
let message = if looks_like_flow_reference(p) {
"`<-` outside a choice point has no effect here; it is treated as \
ordinary text, not a splice. The tokens after `<-` look like a \
knot/flow reference (`<- name` / `<- name(args)`) — if this was \
meant as a splice, move it inside a `{? … }` choice point (charter \
§11); splices are only recognized there."
} else {
"`<-` outside a choice point has no effect here; it is treated as \
ordinary text, not a splice. Splices (`<- flow(args)`) are only \
recognized inside a `{? … }` choice point (charter §11)."
};
p.warning(message.to_owned());
super::content::content_line(p);
}
fn looks_like_flow_reference(p: &Parser<'_, '_>) -> bool {
let mut n = 1;
if p.nth(n) != IDENT {
return false;
}
n += 1;
while p.nth(n) == DOT {
n += 1;
if p.nth(n) != IDENT {
return false;
}
n += 1;
}
if p.nth(n) == L_PAREN {
let mut depth: u32 = 0;
loop {
match p.nth(n) {
L_PAREN => {
depth += 1;
n += 1;
}
R_PAREN => {
depth -= 1;
n += 1;
if depth == 0 {
break;
}
}
NEWLINE | EOF => return false,
_ => n += 1,
}
}
}
matches!(p.nth(n), NEWLINE | EOF | R_BRACE)
}
fn else_branch(p: &mut Parser<'_, '_>) {
p.start_node(ELSE_BRANCH);
p.expect(KW_ELSE);
p.skip_ws();
if p.at(L_BRACE) {
super::block::braced_item_list(p, CHOICE_BODY);
} else {
p.error("expected `{` after a choice point's `else`".into());
}
p.finish_node();
}