use crate::parser::context::ParserCtx;
use crate::parser::diagnostics::{DiagnosticKind, ParseDiagnostic, push_diagnostic};
use crate::parser::events::{Event, ExprParse, push_range};
use crate::parser::lexer::{TokKind, Token, is_op_suffix_char};
use crate::parser::recovery::{error_expr_to_line_end, error_expr_with_range};
use crate::parser::structural::{
KwStmt, is_op_name, parse_abstract_type, parse_begin_expr, parse_do_block, parse_for_expr,
parse_function_expr, parse_if_expr, parse_import_stmt, parse_keyword_stmt, parse_let_expr,
parse_macro_def, parse_module_expr, parse_name_list_stmt, parse_primitive_type,
parse_quote_expr, parse_struct_expr, parse_try_expr, parse_while_expr,
};
use crate::syntax::SyntaxKind;
#[derive(Clone, Copy, Default)]
struct ExprFlags {
inside_brackets: bool,
no_range: bool,
array_mode: bool,
end_marker: bool,
begin_marker: bool,
public_context: bool,
stmt_comma: bool,
no_word_op: bool,
no_where: bool,
no_decl_where: bool,
name_context: bool,
field_access_rhs: bool,
}
const PREFIX_BP: u8 = 28;
const TERNARY_L: u8 = 3;
const TERNARY_R: u8 = 3;
const JUXTAPOSE_L: u8 = 34;
const JUXTAPOSE_R: u8 = 33;
const WHERE_BP: u8 = 31;
const WHERE_BOUND_BP: u8 = 10;
const WORD_OP_L: u8 = 10;
const WORD_OP_R: u8 = 11;
const COMMA_BP: u8 = 2;
const COMMA_ITEM_BP: u8 = 3;
const SPLAT_BP: u8 = 14;
pub(crate) fn parse_expr(
tokens: &[Token],
start: usize,
min_bp: u8,
diagnostics: &mut Vec<ParseDiagnostic>,
) -> Option<ExprParse> {
parse_expr_in(tokens, start, min_bp, diagnostics, ExprFlags::default())
}
pub(crate) fn parse_stmt(
tokens: &[Token],
start: usize,
diagnostics: &mut Vec<ParseDiagnostic>,
) -> Option<ExprParse> {
parse_block_stmt(tokens, start, true, diagnostics)
}
pub(crate) fn parse_block_stmt(
tokens: &[Token],
start: usize,
public_context: bool,
diagnostics: &mut Vec<ParseDiagnostic>,
) -> Option<ExprParse> {
let flags = ExprFlags {
public_context,
stmt_comma: true,
..ExprFlags::default()
};
parse_expr_in(tokens, start, 0, diagnostics, flags)
}
pub(crate) fn parse_for_binding(
tokens: &[Token],
start: usize,
diagnostics: &mut Vec<ParseDiagnostic>,
) -> Option<ExprParse> {
let flags = ExprFlags {
no_word_op: true,
..ExprFlags::default()
};
parse_expr_in(tokens, start, 0, diagnostics, flags)
}
pub(crate) fn parse_expr_in_brackets(
tokens: &[Token],
start: usize,
min_bp: u8,
end_marker: bool,
diagnostics: &mut Vec<ParseDiagnostic>,
) -> Option<ExprParse> {
let flags = ExprFlags {
inside_brackets: true,
end_marker,
begin_marker: end_marker,
..ExprFlags::default()
};
parse_expr_in(tokens, start, min_bp, diagnostics, flags)
}
pub(crate) fn parse_signature_expr(
tokens: &[Token],
start: usize,
diagnostics: &mut Vec<ParseDiagnostic>,
) -> Option<ExprParse> {
let flags = ExprFlags {
no_decl_where: true,
name_context: true,
..ExprFlags::default()
};
parse_expr_in(tokens, start, 0, diagnostics, flags)
}
pub(crate) fn parse_name_signature_expr(
tokens: &[Token],
start: usize,
min_bp: u8,
diagnostics: &mut Vec<ParseDiagnostic>,
) -> Option<ExprParse> {
let flags = ExprFlags {
name_context: true,
..ExprFlags::default()
};
parse_expr_in(tokens, start, min_bp, diagnostics, flags)
}
fn parse_expr_in(
tokens: &[Token],
start: usize,
min_bp: u8,
diagnostics: &mut Vec<ParseDiagnostic>,
flags: ExprFlags,
) -> Option<ExprParse> {
let ExprFlags {
inside_brackets,
no_range,
array_mode,
end_marker: _,
begin_marker,
public_context,
stmt_comma,
no_word_op,
no_where,
no_decl_where,
name_context,
field_access_rhs: _,
} = flags;
let ctx = ParserCtx::new(tokens);
if public_context && is_public_keyword(&ctx, start) {
return parse_name_list_stmt(tokens, start, SyntaxKind::PUBLIC_STMT, diagnostics);
}
let name_error_atom = (name_context
&& ctx
.token(start)
.is_some_and(|t| is_name_error_keyword(t.kind)))
.then(|| {
let pos = ctx.token(start).map_or(start, |t| t.start);
push_diagnostic(
diagnostics,
DiagnosticKind::InvalidNameKeyword,
"reserved keyword used as a name",
pos,
ctx.token(start).map_or(pos, |t| t.end),
);
keyword_name_error_atom(start)
});
let block_form = if name_error_atom.is_some() {
None
} else if let Some(decl_word) = type_decl_keyword(&ctx, start) {
Some(match decl_word {
TypeDecl::Abstract => parse_abstract_type(tokens, start, diagnostics),
TypeDecl::Primitive => parse_primitive_type(tokens, start, diagnostics),
})
} else {
match ctx.token(start).map(|t| t.kind) {
Some(TokKind::IfKw) => Some(parse_if_expr(tokens, start, diagnostics)),
Some(TokKind::FunctionKw) => Some(parse_function_expr(tokens, start, diagnostics)),
Some(TokKind::MacroKw) => Some(parse_macro_def(tokens, start, diagnostics)),
Some(TokKind::BeginKw) if !begin_marker => {
Some(parse_begin_expr(tokens, start, diagnostics))
}
Some(TokKind::QuoteKw) => Some(parse_quote_expr(tokens, start, diagnostics)),
Some(TokKind::WhileKw) => Some(parse_while_expr(tokens, start, diagnostics)),
Some(TokKind::ForKw) => Some(parse_for_expr(tokens, start, diagnostics)),
Some(TokKind::LetKw) => Some(parse_let_expr(tokens, start, diagnostics)),
Some(TokKind::TryKw) => Some(parse_try_expr(tokens, start, diagnostics)),
Some(TokKind::StructKw | TokKind::MutableKw) => {
Some(parse_struct_expr(tokens, start, diagnostics))
}
Some(TokKind::ModuleKw | TokKind::BaremoduleKw) => {
Some(parse_module_expr(tokens, start, diagnostics))
}
_ => None,
}
};
if block_form.is_none() {
match ctx.token(start).map(|t| t.kind) {
Some(TokKind::ReturnKw) => {
return parse_keyword_stmt(
tokens,
start,
SyntaxKind::RETURN_EXPR,
KwStmt::ExprTuple,
true,
diagnostics,
);
}
Some(TokKind::BreakKw) => {
return parse_keyword_stmt(
tokens,
start,
SyntaxKind::BREAK_EXPR,
KwStmt::Bare,
false,
diagnostics,
);
}
Some(TokKind::ContinueKw) => {
return parse_keyword_stmt(
tokens,
start,
SyntaxKind::CONTINUE_EXPR,
KwStmt::Bare,
false,
diagnostics,
);
}
Some(TokKind::ConstKw) => {
return parse_keyword_stmt(
tokens,
start,
SyntaxKind::CONST_STMT,
KwStmt::ExprTuple,
false,
diagnostics,
);
}
Some(TokKind::GlobalKw) => {
return parse_keyword_stmt(
tokens,
start,
SyntaxKind::GLOBAL_STMT,
KwStmt::Expr,
false,
diagnostics,
);
}
Some(TokKind::LocalKw) => {
return parse_keyword_stmt(
tokens,
start,
SyntaxKind::LOCAL_STMT,
KwStmt::Expr,
false,
diagnostics,
);
}
Some(TokKind::ImportKw) => {
return parse_import_stmt(tokens, start, SyntaxKind::IMPORT_STMT, diagnostics);
}
Some(TokKind::UsingKw) => {
return parse_import_stmt(tokens, start, SyntaxKind::USING_STMT, diagnostics);
}
Some(TokKind::ExportKw) => {
return parse_name_list_stmt(tokens, start, SyntaxKind::EXPORT_STMT, diagnostics);
}
_ => {}
}
}
let (mut lhs, mut lhs_is_block_keyword) = match (name_error_atom, block_form) {
(Some(atom), _) => (atom, false),
(None, Some(parsed)) => (parsed?, true),
(None, None) => (parse_prefix(&ctx, start, diagnostics, flags)?, false),
};
let mut glued_colon_done = false;
loop {
if !lhs_is_block_keyword {
lhs = parse_postfix_chain(&ctx, lhs, array_mode, flags.end_marker, diagnostics);
}
if !lhs_is_block_keyword
&& should_juxtapose_string_error(&ctx, &lhs, min_bp)
&& let Some(rhs) = parse_expr_in(tokens, lhs.end, JUXTAPOSE_R, diagnostics, flags)
{
let pos = tokens[lhs.end - 1].end;
push_diagnostic(
diagnostics,
DiagnosticKind::StringJuxtapose,
"invalid juxtaposition",
pos,
pos,
);
lhs = build_binary(SyntaxKind::JUXTAPOSE_EXPR, lhs, rhs);
continue;
}
if !lhs_is_block_keyword
&& should_juxtapose(&ctx, &lhs, min_bp)
&& let Some(rhs) = parse_expr_in(tokens, lhs.end, JUXTAPOSE_R, diagnostics, flags)
{
lhs = build_binary(SyntaxKind::JUXTAPOSE_EXPR, lhs, rhs);
continue;
}
lhs_is_block_keyword = false;
if SPLAT_BP >= min_bp {
let splat_idx = ctx.skip_ws(lhs.end);
if ctx.token(splat_idx).map(|t| t.kind) == Some(TokKind::DotDotDot) {
let mut events = vec![Event::Start(SyntaxKind::SPLAT_EXPR)];
events.extend(lhs.events);
push_range(&mut events, lhs.end, splat_idx);
events.push(Event::Tok(splat_idx));
events.push(Event::Finish);
lhs = ExprParse {
start: lhs.start,
end: splat_idx + 1,
events,
};
continue;
}
}
if !no_where
&& WHERE_BP >= min_bp
&& let Some((where_idx, TokKind::WhereKw)) =
next_operator(&ctx, lhs.end, inside_brackets)
{
lhs = parse_where_chain(tokens, &ctx, lhs, where_idx, diagnostics, flags);
continue;
}
if stmt_comma
&& min_bp <= COMMA_BP
&& ctx.token(ctx.skip_ws(lhs.end)).map(|t| t.kind) == Some(TokKind::Comma)
{
lhs = parse_comma_tuple(tokens, &ctx, lhs, diagnostics, flags);
continue;
}
if !no_word_op && let Some(op_idx) = word_operator(&ctx, lhs.end, inside_brackets) {
if WORD_OP_L < min_bp {
break;
}
let rhs_operand = ctx.skip_trivia(op_idx + 1);
let Some(rhs) = parse_expr_in(tokens, rhs_operand, WORD_OP_R, diagnostics, flags)
else {
let op = &tokens[op_idx];
push_diagnostic(
diagnostics,
DiagnosticKind::MissingOperand,
"expected right-hand side for operator",
op.start,
op.end,
);
return Some(error_expr_to_line_end(tokens, lhs.start, op_idx + 1));
};
lhs = build_binary(SyntaxKind::BINARY_EXPR, lhs, rhs);
continue;
}
let Some((op_idx, op_kind)) = next_operator(&ctx, lhs.end, inside_brackets) else {
break;
};
if array_mode && array_element_boundary(&ctx, lhs.end, op_idx) {
break;
}
if op_kind == TokKind::Dot
&& ctx.token(op_idx + 1).map(|t| t.kind) == Some(TokKind::Transpose)
{
break;
}
if op_kind == TokKind::Dot
&& ctx.token(ctx.skip_trivia(op_idx + 1)).map(|t| t.kind) == Some(TokKind::At)
{
lhs = parse_qualified_macro(&ctx, lhs, op_idx, diagnostics, inside_brackets);
continue;
}
if no_range && op_kind == TokKind::Colon {
break;
}
if op_kind == TokKind::Colon
&& matches!(
ctx.token(op_idx + 1).map(|t| t.kind),
Some(TokKind::Lt | TokKind::Gt)
)
{
if glued_colon_done {
break;
}
let (l_bp, r_bp) = infix_binding_power(TokKind::Colon).expect("colon binds");
if l_bp < min_bp {
break;
}
let colon = &tokens[op_idx];
push_diagnostic(
diagnostics,
DiagnosticKind::InvalidGluedOperator,
"invalid operator",
colon.start,
colon.start,
);
let lt_idx = op_idx + 1;
let rhs_operand = ctx.skip_trivia(lt_idx + 1);
lhs = match parse_expr_in(tokens, rhs_operand, r_bp, diagnostics, flags) {
Some(rhs) => build_binary(SyntaxKind::BINARY_EXPR, lhs, rhs),
None => {
let lt = &tokens[lt_idx];
push_diagnostic(
diagnostics,
DiagnosticKind::MissingOperand,
"expected right-hand side for operator",
colon.start,
lt.end,
);
build_binary_missing_rhs(SyntaxKind::BINARY_EXPR, lhs, lt_idx + 1)
}
};
glued_colon_done = true;
continue;
}
if op_kind == TokKind::Colon {
if glued_colon_done {
break;
}
let (l_bp, _) = infix_binding_power(TokKind::Colon).expect("colon binds");
if l_bp < min_bp {
break;
}
lhs = parse_colon_range(tokens, &ctx, lhs, op_idx, diagnostics, flags);
continue;
}
if is_comparison_op(op_kind) {
let (l_bp, _) = infix_binding_power(op_kind).expect("comparison binds");
if l_bp < min_bp {
break;
}
lhs = parse_comparison_chain(tokens, &ctx, lhs, op_idx, diagnostics, flags);
continue;
}
if is_flat_arith_op(&tokens[op_idx]) {
let (l_bp, _) = infix_binding_power(op_kind).expect("arith binds");
if l_bp < min_bp {
break;
}
lhs = parse_flat_arith_chain(tokens, &ctx, lhs, op_idx, diagnostics, flags);
continue;
}
if op_kind == TokKind::Question {
if TERNARY_L < min_bp {
break;
}
lhs = match parse_ternary(&ctx, lhs, op_idx, diagnostics, flags) {
Ok(node) => node,
Err(done) => return Some(done),
};
continue;
}
let (l_bp, r_bp) = if is_assignment_op(op_kind) {
(2, 1)
} else {
match infix_binding_power(op_kind) {
Some(bp) => bp,
None => break,
}
};
if l_bp < min_bp {
break;
}
let rhs_operand = ctx.skip_trivia(op_idx + 1);
let rhs_result = if op_kind == TokKind::Dot {
parse_prefix(
&ctx,
rhs_operand,
diagnostics,
ExprFlags {
field_access_rhs: true,
..flags
},
)
} else {
parse_expr_in(tokens, rhs_operand, r_bp, diagnostics, flags)
};
let Some(mut rhs) = rhs_result else {
let op = &tokens[op_idx];
push_diagnostic(
diagnostics,
DiagnosticKind::MissingOperand,
"expected right-hand side for operator",
op.start,
op.end,
);
lhs = build_binary_missing_rhs(operator_node_kind(op_kind), lhs, rhs_operand);
continue;
};
if op_kind == TokKind::ColonColon
&& !no_where
&& !no_decl_where
&& let Some((where_idx, TokKind::WhereKw)) =
next_operator(&ctx, rhs.end, inside_brackets)
{
rhs = parse_where_chain(tokens, &ctx, rhs, where_idx, diagnostics, flags);
}
let node = operator_node_kind(op_kind);
if op_kind == TokKind::Dot && op_idx > lhs.end {
let op = &tokens[op_idx];
push_diagnostic(
diagnostics,
DiagnosticKind::DotWhitespace,
"whitespace before `.`",
op.end,
op.end,
);
}
lhs = build_binary(node, lhs, rhs);
}
Some(lhs)
}
fn parse_where_chain(
tokens: &[Token],
ctx: &ParserCtx<'_>,
mut lhs: ExprParse,
mut where_idx: usize,
diagnostics: &mut Vec<ParseDiagnostic>,
flags: ExprFlags,
) -> ExprParse {
let bound_flags = ExprFlags {
no_where: true,
..flags
};
loop {
let bound_start = ctx.skip_trivia(where_idx + 1);
let Some(bound) = parse_expr_in(
tokens,
bound_start,
WHERE_BOUND_BP,
diagnostics,
bound_flags,
) else {
let op = &tokens[where_idx];
push_diagnostic(
diagnostics,
DiagnosticKind::MissingWhereBound,
"expected type bound after `where`",
op.start,
op.end,
);
return error_expr_to_line_end(tokens, lhs.start, where_idx + 1);
};
lhs = build_binary(SyntaxKind::WHERE_EXPR, lhs, bound);
match next_operator(ctx, lhs.end, flags.inside_brackets) {
Some((idx, TokKind::WhereKw)) => where_idx = idx,
_ => return lhs,
}
}
}
enum TypeDecl {
Abstract,
Primitive,
}
fn type_decl_keyword(ctx: &ParserCtx<'_>, start: usize) -> Option<TypeDecl> {
let word = match ctx.token(start) {
Some(t) if t.kind == TokKind::Ident && t.text == "abstract" => TypeDecl::Abstract,
Some(t) if t.kind == TokKind::Ident && t.text == "primitive" => TypeDecl::Primitive,
_ => return None,
};
let next = ctx.token(ctx.skip_trivia(start + 1))?;
(next.kind == TokKind::Ident && next.text == "type").then_some(word)
}
fn is_public_keyword(ctx: &ParserCtx<'_>, start: usize) -> bool {
match ctx.token(start) {
Some(t) if t.kind == TokKind::Ident && t.text == "public" => {}
_ => return false,
}
match ctx.token(ctx.skip_trivia(start + 1)).map(|t| t.kind) {
Some(TokKind::LParen | TokKind::Eq | TokKind::LBracket) | None => false,
Some(_) => true,
}
}
fn build_binary(kind: SyntaxKind, lhs: ExprParse, rhs: ExprParse) -> ExprParse {
let mut events = vec![Event::Start(kind)];
events.extend(lhs.events);
push_range(&mut events, lhs.end, rhs.start);
events.extend(rhs.events);
events.push(Event::Finish);
ExprParse {
start: lhs.start,
end: rhs.end,
events,
}
}
fn operator_node_kind(op_kind: TokKind) -> SyntaxKind {
match op_kind {
k if is_assignment_op(k) => SyntaxKind::ASSIGNMENT_EXPR,
TokKind::Arrow => SyntaxKind::ARROW_EXPR,
TokKind::ColonColon => SyntaxKind::TYPE_ANNOTATION,
_ => SyntaxKind::BINARY_EXPR,
}
}
fn build_binary_missing_rhs(kind: SyntaxKind, lhs: ExprParse, gap_end: usize) -> ExprParse {
let mut events = vec![Event::Start(kind)];
let start = lhs.start;
events.extend(lhs.events);
push_range(&mut events, lhs.end, gap_end);
events.push(Event::Finish);
ExprParse {
start,
end: gap_end,
events,
}
}
fn parse_comma_tuple(
tokens: &[Token],
ctx: &ParserCtx<'_>,
first: ExprParse,
diagnostics: &mut Vec<ParseDiagnostic>,
flags: ExprFlags,
) -> ExprParse {
let start = first.start;
let mut events = vec![Event::Start(SyntaxKind::BARE_TUPLE_EXPR)];
let mut end = first.end;
events.extend(first.events);
loop {
let comma_idx = ctx.skip_ws(end);
if ctx.token(comma_idx).map(|t| t.kind) != Some(TokKind::Comma) {
break;
}
push_range(&mut events, end, comma_idx);
events.push(Event::Tok(comma_idx));
end = comma_idx + 1;
let item_start = ctx.skip_ws(end);
if ctx
.token(item_start)
.is_some_and(|t| is_lone_error_operator(t.kind))
{
break;
}
match parse_expr_in(tokens, item_start, COMMA_ITEM_BP, diagnostics, flags) {
Some(item) => {
push_range(&mut events, end, item.start);
events.extend(item.events);
end = item.end;
}
None => break,
}
}
events.push(Event::Finish);
ExprParse { start, end, events }
}
fn build_range3(a: ExprParse, b: ExprParse, c: ExprParse) -> ExprParse {
let mut events = vec![Event::Start(SyntaxKind::RANGE_EXPR)];
events.extend(a.events);
push_range(&mut events, a.end, b.start);
events.extend(b.events);
push_range(&mut events, b.end, c.start);
events.extend(c.events);
events.push(Event::Finish);
ExprParse {
start: a.start,
end: c.end,
events,
}
}
fn build_range3_missing_rhs(a: ExprParse, b: ExprParse, gap_end: usize) -> ExprParse {
let mut events = vec![Event::Start(SyntaxKind::RANGE_EXPR)];
events.extend(a.events);
push_range(&mut events, a.end, b.start);
events.extend(b.events);
push_range(&mut events, b.end, gap_end);
events.push(Event::Finish);
ExprParse {
start: a.start,
end: gap_end,
events,
}
}
fn parse_colon_range(
tokens: &[Token],
ctx: &ParserCtx<'_>,
lhs: ExprParse,
first_colon: usize,
diagnostics: &mut Vec<ParseDiagnostic>,
flags: ExprFlags,
) -> ExprParse {
let (_, r_bp) = infix_binding_power(TokKind::Colon).expect("colon binds");
let mut head = lhs;
let mut step: Option<ExprParse> = None;
let mut op_idx = first_colon;
loop {
let newline_significant = !flags.inside_brackets || flags.array_mode;
let newline_stop = newline_significant
&& ctx.token(ctx.skip_ws(op_idx + 1)).map(|t| t.kind) == Some(TokKind::Newline);
let rhs_operand = ctx.skip_trivia(op_idx + 1);
let rhs = if newline_stop {
None
} else {
parse_expr_in(tokens, rhs_operand, r_bp, diagnostics, flags)
};
let Some(rhs) = rhs else {
let op = &tokens[op_idx];
push_diagnostic(
diagnostics,
DiagnosticKind::MissingOperand,
"expected right-hand side for operator",
op.start,
op.end,
);
return match step.take() {
None => build_binary_missing_rhs(SyntaxKind::BINARY_EXPR, head, op_idx + 1),
Some(mid) => build_range3_missing_rhs(head, mid, op_idx + 1),
};
};
let last_end = rhs.end;
match step.take() {
Some(mid) => head = build_range3(head, mid, rhs),
None => step = Some(rhs),
}
let continues = match next_operator(ctx, last_end, flags.inside_brackets) {
Some((idx, TokKind::Colon)) if !flags.no_range => {
let split = flags.array_mode && array_element_boundary(ctx, last_end, idx);
(!split).then_some(idx)
}
_ => None,
};
match continues {
Some(idx) => op_idx = idx,
None => break,
}
}
match step {
Some(mid) => build_binary(SyntaxKind::BINARY_EXPR, head, mid),
None => head,
}
}
fn is_comparison_op(kind: TokKind) -> bool {
matches!(
kind,
TokKind::EqEq
| TokKind::NotEq
| TokKind::EqEqEq
| TokKind::NotEqEq
| TokKind::Lt
| TokKind::Le
| TokKind::Gt
| TokKind::Ge
| TokKind::Subtype
| TokKind::Supertype
| TokKind::DotEqEq
| TokKind::DotNotEq
| TokKind::DotEqEqEq
| TokKind::DotNotEqEq
| TokKind::DotLt
| TokKind::DotLe
| TokKind::DotGt
| TokKind::DotGe
| TokKind::DotSubtype
| TokKind::DotSupertype
| TokKind::UniComparison
)
}
fn build_flat(kind: SyntaxKind, operands: Vec<ExprParse>) -> ExprParse {
let mut iter = operands.into_iter();
let first = iter.next().expect("flat node needs at least one operand");
let start = first.start;
let mut end = first.end;
let mut events = vec![Event::Start(kind)];
events.extend(first.events);
for operand in iter {
push_range(&mut events, end, operand.start);
events.extend(operand.events);
end = operand.end;
}
events.push(Event::Finish);
ExprParse { start, end, events }
}
fn build_flat_missing_rhs(kind: SyntaxKind, operands: Vec<ExprParse>, gap_end: usize) -> ExprParse {
let mut iter = operands.into_iter();
let first = iter.next().expect("flat node needs at least one operand");
let start = first.start;
let mut end = first.end;
let mut events = vec![Event::Start(kind)];
events.extend(first.events);
for operand in iter {
push_range(&mut events, end, operand.start);
events.extend(operand.events);
end = operand.end;
}
push_range(&mut events, end, gap_end);
events.push(Event::Finish);
ExprParse {
start,
end: gap_end,
events,
}
}
fn parse_comparison_chain(
tokens: &[Token],
ctx: &ParserCtx<'_>,
lhs: ExprParse,
first_op: usize,
diagnostics: &mut Vec<ParseDiagnostic>,
flags: ExprFlags,
) -> ExprParse {
let first_op_kind = tokens[first_op].kind;
let mut operands = vec![lhs];
let mut op_count = 0usize;
let mut op_idx = first_op;
loop {
let (_, r_bp) = infix_binding_power(tokens[op_idx].kind).expect("comparison binds");
let rhs_operand = ctx.skip_trivia(op_idx + 1);
let Some(rhs) = parse_expr_in(tokens, rhs_operand, r_bp, diagnostics, flags) else {
let op = &tokens[op_idx];
push_diagnostic(
diagnostics,
DiagnosticKind::MissingOperand,
"expected right-hand side for operator",
op.start,
op.end,
);
op_count += 1;
return if op_count == 1 {
build_binary_missing_rhs(
operator_node_kind(first_op_kind),
pop_one(operands),
op_idx + 1,
)
} else {
build_flat_missing_rhs(SyntaxKind::COMPARISON_EXPR, operands, op_idx + 1)
};
};
let last_end = rhs.end;
operands.push(rhs);
op_count += 1;
let continues = match next_operator(ctx, last_end, flags.inside_brackets) {
Some((idx, kind)) if is_comparison_op(kind) => {
let split = flags.array_mode && array_element_boundary(ctx, last_end, idx);
(!split).then_some(idx)
}
_ => None,
};
match continues {
Some(idx) => op_idx = idx,
None => break,
}
}
if op_count == 1 {
let mut iter = operands.into_iter();
let a = iter.next().expect("lhs present");
let b = iter.next().expect("one rhs collected");
build_binary(operator_node_kind(first_op_kind), a, b)
} else {
build_flat(SyntaxKind::COMPARISON_EXPR, operands)
}
}
fn pop_one(operands: Vec<ExprParse>) -> ExprParse {
operands.into_iter().next().expect("comparison lhs present")
}
fn is_flat_arith_op(tok: &Token) -> bool {
matches!(tok.kind, TokKind::Plus | TokKind::Star) && !tok.text.chars().any(is_op_suffix_char)
}
fn parse_flat_arith_chain(
tokens: &[Token],
ctx: &ParserCtx<'_>,
lhs: ExprParse,
first_op: usize,
diagnostics: &mut Vec<ParseDiagnostic>,
flags: ExprFlags,
) -> ExprParse {
let first_op_kind = tokens[first_op].kind;
let mut operands = vec![lhs];
let mut op_count = 0usize;
let mut op_idx = first_op;
loop {
let (_, r_bp) = infix_binding_power(tokens[op_idx].kind).expect("arith binds");
let rhs_operand = ctx.skip_trivia(op_idx + 1);
let Some(rhs) = parse_expr_in(tokens, rhs_operand, r_bp, diagnostics, flags) else {
let op = &tokens[op_idx];
push_diagnostic(
diagnostics,
DiagnosticKind::MissingOperand,
"expected right-hand side for operator",
op.start,
op.end,
);
op_count += 1;
return if op_count == 1 {
build_binary_missing_rhs(SyntaxKind::BINARY_EXPR, pop_one(operands), op_idx + 1)
} else {
build_flat_missing_rhs(SyntaxKind::BINARY_EXPR, operands, op_idx + 1)
};
};
let last_end = rhs.end;
operands.push(rhs);
op_count += 1;
let continues = match next_operator(ctx, last_end, flags.inside_brackets) {
Some((idx, kind)) if kind == first_op_kind && is_flat_arith_op(&tokens[idx]) => {
let split = flags.array_mode && array_element_boundary(ctx, last_end, idx);
(!split).then_some(idx)
}
_ => None,
};
match continues {
Some(idx) => op_idx = idx,
None => break,
}
}
if op_count == 1 {
let mut iter = operands.into_iter();
let a = iter.next().expect("lhs present");
let b = iter.next().expect("one rhs collected");
build_binary(SyntaxKind::BINARY_EXPR, a, b)
} else {
build_flat(SyntaxKind::BINARY_EXPR, operands)
}
}
fn parse_prefix(
ctx: &ParserCtx<'_>,
start: usize,
diagnostics: &mut Vec<ParseDiagnostic>,
flags: ExprFlags,
) -> Option<ExprParse> {
let tok = ctx.token(start)?;
match tok.kind {
k if is_curly_operator_name(k)
&& ctx.token(start + 1).map(|t| t.kind) == Some(TokKind::LBrace) =>
{
Some(ExprParse {
start,
end: start + 1,
events: vec![Event::Tok(start)],
})
}
TokKind::EndKw if flags.end_marker => Some(atom(SyntaxKind::END_MARKER, start)),
TokKind::BeginKw if flags.begin_marker => Some(atom(SyntaxKind::BEGIN_MARKER, start)),
k if matches!(k, TokKind::Plus | TokKind::Minus) && signed_literal_fold(ctx, start) => {
let num = start + 1;
Some(ExprParse {
start,
end: num + 1,
events: vec![
Event::Start(SyntaxKind::LITERAL),
Event::Tok(start),
Event::Tok(num),
Event::Finish,
],
})
}
TokKind::Plus
| TokKind::Minus
| TokKind::DotPlus
| TokKind::DotMinus
| TokKind::Bang
| TokKind::Tilde
| TokKind::DotTilde
| TokKind::Amp
| TokKind::Subtype
| TokKind::Supertype
| TokKind::ColonColon
| TokKind::UniRadical => {
let op_suffixed = matches!(
tok.kind,
TokKind::Plus | TokKind::Minus | TokKind::DotPlus | TokKind::DotMinus
) && tok.text.chars().any(is_op_suffix_char);
let is_unary_paren_op = matches!(
tok.kind,
TokKind::Plus
| TokKind::Minus
| TokKind::DotPlus
| TokKind::DotMinus
| TokKind::Bang
| TokKind::Tilde
| TokKind::DotTilde
| TokKind::Subtype
| TokKind::Supertype
);
let paren_idx = ctx.skip_ws(start + 1);
let spaced = paren_idx > start + 1;
let next_is_lparen = ctx.token(paren_idx).map(|t| t.kind) == Some(TokKind::LParen);
let glued_call =
!spaced && (op_suffixed || unary_op_paren_is_call(ctx, paren_idx));
let spaced_call = spaced && !op_suffixed && unary_op_paren_is_call(ctx, paren_idx);
if is_unary_paren_op && next_is_lparen && (glued_call || spaced_call) {
if spaced_call {
let opener = &ctx.tokens()[paren_idx];
push_diagnostic(
diagnostics,
DiagnosticKind::PrefixOpenerWhitespace,
"whitespace before opener",
opener.start,
opener.start,
);
}
let (list_events, end) = parse_arg_list(
ctx,
paren_idx,
TokKind::RParen,
SyntaxKind::ARG_LIST,
flags.end_marker,
diagnostics,
);
let mut events = vec![Event::Start(SyntaxKind::CALL_EXPR), Event::Tok(start)];
push_range(&mut events, start + 1, paren_idx);
events.extend(list_events);
events.push(Event::Finish);
return Some(ExprParse { start, end, events });
}
let node = if tok.kind == TokKind::ColonColon {
SyntaxKind::TYPE_ANNOTATION
} else {
SyntaxKind::UNARY_EXPR
};
let operand_start = ctx.skip_trivia(start + 1);
if ctx.token(operand_start).map(|t| t.kind) == Some(TokKind::Eq)
&& !matches!(tok.kind, TokKind::Amp | TokKind::ColonColon)
{
return Some(atom(SyntaxKind::OPERATOR_ATOM, start));
}
let is_subtype = matches!(tok.kind, TokKind::Subtype | TokKind::Supertype);
let operand_flags = ExprFlags {
no_range: false,
array_mode: false,
no_where: !is_subtype || flags.no_where,
..flags
};
let operand_bp = if is_subtype { WHERE_BP } else { PREFIX_BP };
let Some(operand) = parse_expr_in(
ctx.tokens(),
operand_start,
operand_bp,
diagnostics,
operand_flags,
) else {
if tok.kind == TokKind::ColonColon {
return Some(error_expr_with_range(start, start + 1));
}
return Some(atom(SyntaxKind::OPERATOR_ATOM, start));
};
if op_suffixed {
push_diagnostic(
diagnostics,
DiagnosticKind::InvalidPrefixOperator,
"invalid operator in prefix position",
tok.start,
tok.end,
);
let mut events = vec![
Event::Start(SyntaxKind::UNARY_EXPR),
Event::Start(SyntaxKind::ERROR),
Event::Start(SyntaxKind::OPERATOR_ATOM),
Event::Tok(start),
Event::Finish, Event::Finish, ];
push_range(&mut events, start + 1, operand.start);
events.extend(operand.events);
events.push(Event::Finish); return Some(ExprParse {
start,
end: operand.end,
events,
});
}
let mut events = vec![Event::Start(node)];
push_range(&mut events, start, operand.start);
events.extend(operand.events);
events.push(Event::Finish);
Some(ExprParse {
start,
end: operand.end,
events,
})
}
k if is_operator_call_name(k)
&& ctx.token(start + 1).map(|t| t.kind) == Some(TokKind::LParen) =>
{
let (list_events, end) = parse_arg_list(
ctx,
start + 1,
TokKind::RParen,
SyntaxKind::ARG_LIST,
flags.end_marker,
diagnostics,
);
let mut events = vec![Event::Start(SyntaxKind::CALL_EXPR), Event::Tok(start)];
events.extend(list_events);
events.push(Event::Finish);
Some(ExprParse { start, end, events })
}
TokKind::Colon => {
parse_quote_sym(ctx, start, diagnostics, !flags.field_access_rhs, flags.end_marker)
.or_else(|| Some(atom(SyntaxKind::OPERATOR_ATOM, start)))
}
TokKind::Dollar => Some(parse_prefix_interpolation(ctx, start, diagnostics)),
TokKind::At => Some(parse_macro(ctx, start, diagnostics, flags.inside_brackets)),
TokKind::LParen => parse_paren(ctx, start, flags.end_marker, diagnostics),
TokKind::LBracket => Some(parse_bracket_literal(ctx, start, flags.end_marker, diagnostics)),
TokKind::LBrace => parse_braces(ctx, start, flags.end_marker, diagnostics),
TokKind::Ident => Some(atom(SyntaxKind::NAME, start)),
TokKind::StringPrefix | TokKind::StringDelimOpen | TokKind::CmdDelimOpen => {
Some(parse_string_literal(ctx, start, diagnostics))
}
TokKind::Char => {
let tok = &ctx.tokens()[start];
if !char_token_terminated(&tok.text) {
push_diagnostic(
diagnostics,
DiagnosticKind::UnterminatedLiteral,
"unterminated character literal",
tok.start,
tok.start,
);
}
Some(atom(SyntaxKind::LITERAL, start))
}
TokKind::Integer
| TokKind::BinInt
| TokKind::OctInt
| TokKind::HexInt
| TokKind::Float
| TokKind::Float32
| TokKind::TrueKw
| TokKind::FalseKw => Some(atom(SyntaxKind::LITERAL, start)),
k if is_lone_error_operator(k) => {
let op = &ctx.tokens()[start];
push_diagnostic(
diagnostics,
DiagnosticKind::LoneOperator,
"operator is not a valid value",
op.start,
op.end,
);
Some(error_operator_atom(start))
}
k if is_value_operator(k) || k == TokKind::Question => {
let operand_start = ctx.skip_trivia(start + 1);
if k != TokKind::Question
&& ctx.token(operand_start).map(|t| t.kind) == Some(TokKind::Eq)
{
return Some(atom(SyntaxKind::OPERATOR_ATOM, start));
}
let operand_flags = ExprFlags {
no_range: false,
array_mode: false,
..flags
};
match parse_expr_in(
ctx.tokens(),
operand_start,
PREFIX_BP,
diagnostics,
operand_flags,
) {
Some(operand) => {
let op = &ctx.tokens()[start];
push_diagnostic(
diagnostics,
DiagnosticKind::InvalidPrefixOperator,
"invalid operator in prefix position",
op.start,
op.end,
);
let mut events = vec![
Event::Start(SyntaxKind::UNARY_EXPR),
Event::Start(SyntaxKind::ERROR),
Event::Start(SyntaxKind::OPERATOR_ATOM),
Event::Tok(start),
Event::Finish, Event::Finish, ];
push_range(&mut events, start + 1, operand.start);
events.extend(operand.events);
events.push(Event::Finish); Some(ExprParse {
start,
end: operand.end,
events,
})
}
None if matches!(ctx.tokens()[start].kind, TokKind::Question) => {
let op = &ctx.tokens()[start];
push_diagnostic(
diagnostics,
DiagnosticKind::LoneOperator,
"operator is not a valid value",
op.start,
op.end,
);
Some(error_operator_atom(start))
}
None => Some(atom(SyntaxKind::OPERATOR_ATOM, start)),
}
}
_ => None,
}
}
fn keyword_name_error_atom(start: usize) -> ExprParse {
ExprParse {
start,
end: start + 1,
events: vec![
Event::Start(SyntaxKind::ERROR),
Event::Start(SyntaxKind::NAME),
Event::Tok(start),
Event::Finish, Event::Finish, ],
}
}
fn is_name_error_keyword(kind: TokKind) -> bool {
kind.is_keyword()
&& !matches!(
kind,
TokKind::MutableKw | TokKind::WhereKw | TokKind::TrueKw | TokKind::FalseKw
)
}
fn char_token_terminated(text: &str) -> bool {
text.len() >= 2 && text.ends_with('\'')
}
fn error_operator_atom(start: usize) -> ExprParse {
ExprParse {
start,
end: start + 1,
events: vec![
Event::Start(SyntaxKind::ERROR),
Event::Start(SyntaxKind::OPERATOR_ATOM),
Event::Tok(start),
Event::Finish, Event::Finish, ],
}
}
fn is_lone_error_operator(kind: TokKind) -> bool {
use TokKind::*;
is_assignment_op(kind) || matches!(kind, AndAnd | OrOr | Arrow | DotDotDot)
}
fn is_value_operator(kind: TokKind) -> bool {
use TokKind::*;
(is_op_name(kind) && !matches!(kind, AndAnd | OrOr | Arrow))
|| matches!(
kind,
Colon
| DotDot
| UniRadical
| DotPlus
| DotMinus
| DotStar
| DotSlash
| DotSlashSlash
| DotCaret
| DotPercent
| DotTilde
| DotEqEq
| DotNotEq
| DotEqEqEq
| DotNotEqEq
| DotLt
| DotLe
| DotGt
| DotGe
| DotSubtype
| DotSupertype
| DotFatArrow
| DotLongArrow
| DotPipeGt
| DotAmp
| DotPipe
)
}
fn is_quotable_operator(kind: TokKind) -> bool {
use TokKind::*;
matches!(
kind,
DotDot
| UniRadical
| UniArrow
| UniComparison
| UniColon
| UniPlus
| UniTimes
| UniPower
| Question
)
}
fn is_closing_block_keyword(kind: TokKind) -> bool {
matches!(
kind,
TokKind::EndKw
| TokKind::ElseKw
| TokKind::ElseifKw
| TokKind::CatchKw
| TokKind::FinallyKw
)
}
pub(super) fn parse_quote_sym(
ctx: &ParserCtx<'_>,
start: usize,
diagnostics: &mut Vec<ParseDiagnostic>,
value_position: bool,
end_marker: bool,
) -> Option<ExprParse> {
let next = ctx.skip_trivia(start + 1);
if value_position
&& next > start + 1
&& ctx
.token(next)
.is_some_and(|t| is_closing_block_keyword(t.kind))
&& !(ctx.token(next).map(|t| t.kind) == Some(TokKind::EndKw) && end_marker)
{
return None;
}
let mut events = vec![Event::Start(SyntaxKind::QUOTE_SYM), Event::Tok(start)];
push_range(&mut events, start + 1, next);
if next > start + 1 {
let colon = &ctx.tokens()[start];
push_diagnostic(
diagnostics,
DiagnosticKind::QuoteColonWhitespace,
"whitespace after `:`",
colon.end,
colon.end,
);
}
match ctx.token(next).map(|t| t.kind)? {
TokKind::LParen
if {
let op = ctx.skip_trivia(next + 1);
is_paren_quotable_op(ctx.token(op).map(|t| t.kind))
&& ctx.token(ctx.skip_trivia(op + 1)).map(|t| t.kind) == Some(TokKind::RParen)
} =>
{
let op = ctx.skip_trivia(next + 1);
let rparen = ctx.skip_trivia(op + 1);
events.push(Event::Start(SyntaxKind::PAREN_EXPR));
push_range(&mut events, next, rparen + 1);
events.push(Event::Finish); events.push(Event::Finish); Some(ExprParse {
start,
end: rparen + 1,
events,
})
}
TokKind::LParen
if ctx
.token(ctx.skip_trivia(next + 1))
.is_some_and(|t| is_closing_block_keyword(t.kind)) =>
{
events.push(Event::Tok(next)); events.push(Event::Finish); let lparen = &ctx.tokens()[next];
push_diagnostic(
diagnostics,
DiagnosticKind::EmptyQuoteParen,
"expected expression after `:(`",
lparen.end,
lparen.end,
);
Some(ExprParse {
start,
end: next + 1,
events,
})
}
TokKind::LParen => {
let paren = parse_paren(ctx, next, false, diagnostics)?;
let end = paren.end;
events.extend(paren.events);
events.push(Event::Finish);
Some(ExprParse { start, end, events })
}
TokKind::Ident => {
events.push(Event::Start(SyntaxKind::NAME));
events.push(Event::Tok(next));
events.push(Event::Finish);
events.push(Event::Finish);
Some(ExprParse {
start,
end: next + 1,
events,
})
}
_ if ctx
.token(next)
.is_some_and(|t| is_dotted_broadcast_text(&t.text)) =>
{
events.push(Event::Start(SyntaxKind::OPERATOR_ATOM));
events.push(Event::Tok(next));
events.push(Event::Finish); events.push(Event::Finish); Some(ExprParse {
start,
end: next + 1,
events,
})
}
k if is_op_name(k) || is_assignment_op(k) || is_quotable_operator(k) => {
events.push(Event::Tok(next));
events.push(Event::Finish);
Some(ExprParse {
start,
end: next + 1,
events,
})
}
k if k.is_keyword() => {
events.push(Event::Tok(next));
events.push(Event::Finish);
Some(ExprParse {
start,
end: next + 1,
events,
})
}
_ => None,
}
}
fn parse_string_literal(
ctx: &ParserCtx<'_>,
start: usize,
diagnostics: &mut Vec<ParseDiagnostic>,
) -> ExprParse {
let mut i = start;
let mut var_prefix = false;
let mut has_prefix = false;
if ctx.token(i).map(|t| t.kind) == Some(TokKind::StringPrefix) {
has_prefix = true;
var_prefix = ctx.token(i).map(|t| t.text.as_str()) == Some("var");
i += 1;
}
let single_quote_open = matches!(
ctx.token(i),
Some(t) if t.kind == TokKind::StringDelimOpen && t.text.len() == 1
);
let node = match ctx.token(i).map(|t| t.kind) {
Some(TokKind::CmdDelimOpen) => SyntaxKind::CMD_LITERAL,
Some(TokKind::StringDelimOpen) if var_prefix && single_quote_open => {
SyntaxKind::NONSTANDARD_IDENTIFIER
}
_ => SyntaxKind::STRING_LITERAL,
};
let close_kind = if node == SyntaxKind::CMD_LITERAL {
TokKind::CmdDelimClose
} else {
TokKind::StringDelimClose
};
let mut events = vec![Event::Start(node)];
for idx in start..=i {
events.push(Event::Tok(idx));
}
i += 1;
loop {
match ctx.token(i).map(|t| t.kind) {
Some(TokKind::StringContent) => {
events.push(Event::Tok(i));
i += 1;
}
Some(TokKind::Dollar) => {
i = parse_interpolation(ctx, &mut events, i, diagnostics);
}
Some(k) if k == close_kind => {
events.push(Event::Tok(i));
i += 1;
let suffix = ctx.token(i).map(|t| t.kind);
if node == SyntaxKind::NONSTANDARD_IDENTIFIER {
if matches!(
suffix,
Some(
TokKind::StringSuffix
| TokKind::Integer
| TokKind::Float
| TokKind::Float32
| TokKind::BinInt
| TokKind::OctInt
| TokKind::HexInt
)
) {
events.push(Event::Tok(i));
i += 1;
let lit = &ctx.tokens()[start];
push_diagnostic(
diagnostics,
DiagnosticKind::StringSuffixSpace,
"invalid string-macro suffix",
lit.start,
lit.start,
);
}
break;
}
let is_flag = suffix == Some(TokKind::StringSuffix);
let is_numeric = has_prefix
&& node == SyntaxKind::STRING_LITERAL
&& matches!(
suffix,
Some(TokKind::Integer | TokKind::Float | TokKind::Float32)
);
if is_flag || is_numeric {
events.push(Event::Tok(i));
i += 1;
}
break;
}
_ => {
let lit = &ctx.tokens()[start];
push_diagnostic(
diagnostics,
DiagnosticKind::UnterminatedLiteral,
"unterminated literal",
lit.start,
lit.start,
);
break;
}
}
}
events.push(Event::Finish);
ExprParse {
start,
end: i,
events,
}
}
pub(super) fn parse_prefix_interpolation(
ctx: &ParserCtx<'_>,
dollar: usize,
diagnostics: &mut Vec<ParseDiagnostic>,
) -> ExprParse {
let next = dollar + 1;
if matches!(
ctx.token(next).map(|t| t.kind),
Some(TokKind::LParen | TokKind::Ident)
) {
let mut events = Vec::new();
let end = parse_interpolation(ctx, &mut events, dollar, diagnostics);
return ExprParse {
start: dollar,
end,
events,
};
}
let mut events = vec![Event::Start(SyntaxKind::INTERPOLATION), Event::Tok(dollar)];
match parse_prefix(ctx, next, diagnostics, ExprFlags::default()) {
Some(operand) => {
push_range(&mut events, next, operand.start);
let end = operand.end;
events.extend(operand.events);
events.push(Event::Finish);
ExprParse {
start: dollar,
end,
events,
}
}
None => {
events.push(Event::Finish);
ExprParse {
start: dollar,
end: next,
events,
}
}
}
}
fn parse_interpolation(
ctx: &ParserCtx<'_>,
events: &mut Vec<Event>,
dollar: usize,
diagnostics: &mut Vec<ParseDiagnostic>,
) -> usize {
events.push(Event::Start(SyntaxKind::INTERPOLATION));
events.push(Event::Tok(dollar)); let next = dollar + 1;
match ctx.token(next).map(|t| t.kind) {
Some(TokKind::LParen) => {
let Some(inner) = parse_paren(ctx, next, false, diagnostics) else {
events.push(Event::Finish);
return next + 1;
};
if matches!(
inner.events.first(),
Some(Event::Start(
SyntaxKind::PAREN_BLOCK | SyntaxKind::TUPLE_EXPR | SyntaxKind::GENERATOR
))
) {
let dollar_tok = &ctx.tokens()[dollar];
push_diagnostic(
diagnostics,
DiagnosticKind::InvalidInterpolation,
"interpolation expects a single expression",
dollar_tok.start,
dollar_tok.end,
);
}
let end = inner.end;
events.extend(inner.events);
events.push(Event::Finish);
end
}
Some(TokKind::Ident) => {
events.push(Event::Tok(next));
events.push(Event::Finish);
next + 1
}
_ => {
events.push(Event::Finish);
next
}
}
}
fn atom(kind: SyntaxKind, idx: usize) -> ExprParse {
ExprParse {
start: idx,
end: idx + 1,
events: vec![Event::Start(kind), Event::Tok(idx), Event::Finish],
}
}
pub(crate) fn parse_paren(
ctx: &ParserCtx<'_>,
start: usize,
end_marker: bool,
diagnostics: &mut Vec<ParseDiagnostic>,
) -> Option<ExprParse> {
let inner_start = ctx.skip_trivia(start + 1);
match ctx.token(inner_start).map(|t| t.kind) {
Some(TokKind::RParen) => {
let mut events = vec![Event::Start(SyntaxKind::TUPLE_EXPR)];
push_range(&mut events, start, inner_start + 1);
events.push(Event::Finish);
return Some(ExprParse {
start,
end: inner_start + 1,
events,
});
}
Some(TokKind::Semicolon) => {
let (events, end) = parse_arg_list(
ctx,
start,
TokKind::RParen,
paren_list_kind(ctx, start),
end_marker,
diagnostics,
);
return Some(ExprParse { start, end, events });
}
_ => {}
}
if is_paren_value_op(ctx.token(inner_start).map(|t| t.kind)) {
let close = ctx.skip_trivia(inner_start + 1);
if ctx.token(close).map(|t| t.kind) == Some(TokKind::RParen) {
let mut events = vec![Event::Start(SyntaxKind::PAREN_EXPR)];
push_range(&mut events, start, close + 1);
events.push(Event::Finish);
return Some(ExprParse {
start,
end: close + 1,
events,
});
}
}
let Some(inner) = parse_expr_in_brackets(ctx.tokens(), inner_start, 0, end_marker, diagnostics)
else {
return Some(error_expr_with_range(start, inner_start));
};
let sep = ctx.skip_trivia(inner.end);
if ctx.token(sep).map(|t| t.kind) == Some(TokKind::ForKw) {
return Some(parse_comprehension(
ctx,
start,
inner,
SyntaxKind::GENERATOR,
TokKind::RParen,
diagnostics,
));
}
if matches!(
ctx.token(sep).map(|t| t.kind),
Some(TokKind::Comma | TokKind::Semicolon)
) {
let (events, end) = parse_arg_list(
ctx,
start,
TokKind::RParen,
paren_list_kind(ctx, start),
end_marker,
diagnostics,
);
return Some(ExprParse { start, end, events });
}
let mut events = vec![Event::Start(SyntaxKind::PAREN_EXPR)];
push_range(&mut events, start, inner.start);
events.extend(inner.events);
let close = ctx.skip_trivia(inner.end);
if ctx.token(close).map(|t| t.kind) == Some(TokKind::RParen) {
push_range(&mut events, inner.end, close + 1);
events.push(Event::Finish);
Some(ExprParse {
start,
end: close + 1,
events,
})
} else {
let open = &ctx.tokens()[start];
push_diagnostic(
diagnostics,
DiagnosticKind::UnclosedParen,
"unclosed `(`",
open.start,
open.end,
);
push_range(&mut events, inner.end, close);
events.push(Event::Finish);
Some(ExprParse {
start,
end: close,
events,
})
}
}
fn array_element_boundary(ctx: &ParserCtx<'_>, operand_end: usize, op_idx: usize) -> bool {
let space_before = op_idx > operand_end;
if !space_before {
return false;
}
let Some(op) = ctx.token(op_idx) else {
return false;
};
if !op_can_lead_array_element(op) {
return false;
}
!matches!(
ctx.token(op_idx + 1).map(|t| t.kind),
Some(TokKind::Whitespace | TokKind::Newline) | None
)
}
fn op_can_lead_array_element(op: &Token) -> bool {
matches!(
op.kind,
TokKind::Plus
| TokKind::Minus
| TokKind::DotPlus
| TokKind::DotMinus
| TokKind::Tilde
| TokKind::DotTilde
| TokKind::Amp
| TokKind::Colon
) && !op.text.chars().next_back().is_some_and(is_op_suffix_char)
}
fn parse_element(
tokens: &[Token],
start: usize,
end_marker: bool,
diagnostics: &mut Vec<ParseDiagnostic>,
) -> Option<ExprParse> {
let flags = ExprFlags {
array_mode: true,
end_marker,
begin_marker: end_marker,
..ExprFlags::default()
};
parse_expr_in(tokens, start, 0, diagnostics, flags)
}
fn push_element_arg(events: &mut Vec<Event>, el: ExprParse) -> usize {
let end = el.end;
events.push(Event::Start(SyntaxKind::ARG));
events.extend(el.events);
events.push(Event::Finish);
end
}
fn newline_run_precedes_comma(ctx: &ParserCtx<'_>, look: usize) -> bool {
let mut peek = look;
while matches!(
ctx.token(peek).map(|t| t.kind),
Some(TokKind::Whitespace | TokKind::Comment | TokKind::BlockComment | TokKind::Newline)
) {
peek += 1;
}
ctx.token(peek).map(|t| t.kind) == Some(TokKind::Comma)
}
fn newline_run_precedes_for(ctx: &ParserCtx<'_>, look: usize) -> bool {
let mut peek = look;
while matches!(
ctx.token(peek).map(|t| t.kind),
Some(TokKind::Whitespace | TokKind::Comment | TokKind::BlockComment | TokKind::Newline)
) {
peek += 1;
}
ctx.token(peek).map(|t| t.kind) == Some(TokKind::ForKw)
}
fn parse_bracket_literal(
ctx: &ParserCtx<'_>,
lbrk: usize,
end_marker: bool,
diagnostics: &mut Vec<ParseDiagnostic>,
) -> ExprParse {
let tokens = ctx.tokens();
let vect = |diagnostics: &mut Vec<ParseDiagnostic>| {
let (events, end) = parse_arg_list(
ctx,
lbrk,
TokKind::RBracket,
SyntaxKind::VECT_EXPR,
end_marker,
diagnostics,
);
ExprParse {
start: lbrk,
end,
events,
}
};
let first_start = ctx.skip_trivia(lbrk + 1);
if ctx.token(first_start).map(|t| t.kind) == Some(TokKind::RBracket) {
return vect(diagnostics);
}
if let Some(empty) = parse_empty_ncat(
ctx,
lbrk,
first_start,
TokKind::RBracket,
SyntaxKind::MATRIX_EXPR,
) {
return empty;
}
let Some(first) = parse_element(tokens, first_start, end_marker, diagnostics) else {
return vect(diagnostics);
};
let mut look = first.end;
while matches!(
ctx.token(look).map(|t| t.kind),
Some(TokKind::Whitespace | TokKind::Comment | TokKind::BlockComment)
) {
look += 1;
}
match ctx.token(look).map(|t| t.kind) {
Some(TokKind::ForKw) => parse_comprehension(
ctx,
lbrk,
first,
SyntaxKind::COMPREHENSION,
TokKind::RBracket,
diagnostics,
),
None | Some(TokKind::RBracket | TokKind::Comma) => vect(diagnostics),
Some(TokKind::Newline) if newline_run_precedes_for(ctx, look) => parse_comprehension(
ctx,
lbrk,
first,
SyntaxKind::COMPREHENSION,
TokKind::RBracket,
diagnostics,
),
Some(TokKind::Newline) if newline_run_precedes_comma(ctx, look) => vect(diagnostics),
_ => parse_matrix(
ctx,
lbrk,
first,
TokKind::RBracket,
SyntaxKind::VECT_EXPR,
SyntaxKind::MATRIX_EXPR,
end_marker,
diagnostics,
),
}
}
#[derive(PartialEq)]
enum ArrayOrder {
Unknown,
RowMajor,
ColumnMajor,
}
struct SepRun {
toks: Vec<usize>,
semis: usize,
has_newline: bool,
newline_after_semis: bool,
continuation: bool,
}
impl SepRun {
fn dim(&self, between: bool) -> usize {
if self.continuation {
0
} else if self.semis > 0 {
self.semis
} else if self.has_newline && between {
1
} else {
0
}
}
}
fn parse_empty_ncat(
ctx: &ParserCtx<'_>,
lbrk: usize,
first_start: usize,
close: TokKind,
node_kind: SyntaxKind,
) -> Option<ExprParse> {
let mut q = first_start;
let mut saw_semi = false;
while let Some(k) = ctx.token(q).map(|t| t.kind) {
match k {
TokKind::Semicolon => {
saw_semi = true;
q += 1;
}
_ if k.is_trivia() => q += 1,
_ => break,
}
}
if !saw_semi || ctx.token(q).map(|t| t.kind) != Some(close) {
return None;
}
let mut events = vec![Event::Start(node_kind), Event::Tok(lbrk)];
push_range(&mut events, lbrk + 1, q + 1);
events.push(Event::Finish); Some(ExprParse {
start: lbrk,
end: q + 1,
events,
})
}
#[allow(clippy::too_many_arguments)]
fn parse_matrix(
ctx: &ParserCtx<'_>,
lbrk: usize,
first: ExprParse,
close: TokKind,
comma_kind: SyntaxKind,
matrix_kind: SyntaxKind,
end_marker: bool,
diagnostics: &mut Vec<ParseDiagnostic>,
) -> ExprParse {
let tokens = ctx.tokens();
let lead_start = first.start;
let mut elems = vec![first];
let mut seps: Vec<SepRun> = Vec::new();
let mut pos = elems[0].end;
let end = loop {
let mut run = SepRun {
toks: Vec::new(),
semis: 0,
has_newline: false,
newline_after_semis: false,
continuation: false,
};
let mut q = pos;
while let Some(k) = ctx.token(q).map(|t| t.kind) {
match k {
TokKind::Semicolon => {
run.semis += 1;
run.newline_after_semis = false;
}
TokKind::Newline => {
run.has_newline = true;
if run.semis > 0 {
run.newline_after_semis = true;
}
}
TokKind::Whitespace | TokKind::Comment | TokKind::BlockComment => {}
_ => break,
}
run.toks.push(q);
q += 1;
}
match ctx.token(q).map(|t| t.kind) {
None => {
seps.push(run);
break q;
}
Some(k) if k == close => {
seps.push(run);
break q + 1;
}
Some(TokKind::EndKw) => {
seps.push(run);
let anchor = tokens[elems[elems.len() - 1].end - 1].end;
push_diagnostic(
diagnostics,
DiagnosticKind::MatrixKeywordRecovery,
"misplaced `end` in array",
anchor,
anchor,
);
break q;
}
Some(TokKind::At) if run.toks.is_empty() => {
seps.push(run);
let mut j = q;
while let Some(k) = ctx.token(j).map(|t| t.kind) {
if k == close {
break;
}
j += 1;
}
let mut events = vec![Event::Start(SyntaxKind::ERROR)];
push_range(&mut events, q, j);
events.push(Event::Finish);
push_diagnostic(
diagnostics,
DiagnosticKind::TrailingJunk,
"trailing tokens in array",
tokens[q].start,
tokens[q].end,
);
elems.push(ExprParse {
start: q,
end: j,
events,
});
pos = j;
}
_ => {
seps.push(run);
let el = match parse_element(tokens, q, end_marker, diagnostics) {
Some(el) => el,
None => ExprParse {
start: q,
end: q + 1,
events: vec![Event::Tok(q)],
},
};
pos = el.end;
elems.push(el);
}
}
};
let n = elems.len();
let close_idx = if ctx.token(end.saturating_sub(1)).map(|t| t.kind) == Some(close) {
Some(end - 1)
} else {
None
};
let mut order = ArrayOrder::Unknown;
for k in 0..n.saturating_sub(1) {
let is_space = seps[k].semis == 0 && !seps[k].has_newline;
let is_double_semi = seps[k].semis == 2;
let newline_after_semis = seps[k].newline_after_semis;
let mut continuation = false;
let conflict = match order {
ArrayOrder::Unknown => {
if is_space {
order = ArrayOrder::RowMajor;
} else if is_double_semi {
order = ArrayOrder::ColumnMajor;
}
false
}
ArrayOrder::RowMajor => {
if is_double_semi && newline_after_semis {
continuation = true;
false
} else {
is_double_semi
}
}
ArrayOrder::ColumnMajor => is_space,
};
seps[k].continuation = continuation;
if conflict {
let anchor = tokens[elems[k].end - 1].end;
push_diagnostic(
diagnostics,
DiagnosticKind::ArraySeparatorMismatch,
"cannot mix space and `;;` separators in an array",
anchor,
anchor,
);
}
}
let top_d = (0..n.saturating_sub(1))
.map(|k| seps[k].dim(true))
.chain(std::iter::once(seps[n - 1].dim(false)))
.max()
.unwrap_or(0);
let node_kind = if n == 1 && top_d == 0 {
comma_kind
} else {
matrix_kind
};
let mut events = vec![Event::Start(node_kind), Event::Tok(lbrk)];
push_range(&mut events, lbrk + 1, lead_start);
emit_cat_groups(&mut events, &elems, &seps, 0, n, top_d);
for &t in &seps[n - 1].toks {
events.push(Event::Tok(t));
}
if let Some(close_idx) = close_idx {
events.push(Event::Tok(close_idx));
}
events.push(Event::Finish); ExprParse {
start: lbrk,
end,
events,
}
}
fn emit_cat_groups(
events: &mut Vec<Event>,
elems: &[ExprParse],
seps: &[SepRun],
lo: usize,
hi: usize,
split_d: usize,
) {
let mut g = lo;
for k in lo..hi {
let is_boundary = k + 1 < hi && seps[k].dim(true) == split_d;
if is_boundary {
emit_cat_child(events, elems, seps, g, k + 1);
for &t in &seps[k].toks {
events.push(Event::Tok(t));
}
g = k + 1;
} else if k + 1 == hi {
emit_cat_child(events, elems, seps, g, hi);
}
}
}
fn emit_cat_child(
events: &mut Vec<Event>,
elems: &[ExprParse],
seps: &[SepRun],
lo: usize,
hi: usize,
) {
if hi - lo == 1 {
push_element_arg(events, elems[lo].clone());
return;
}
let inner_d = (lo..hi - 1).map(|k| seps[k].dim(true)).max().unwrap_or(0);
events.push(Event::Start(SyntaxKind::MATRIX_ROW));
emit_cat_groups(events, elems, seps, lo, hi, inner_d);
events.push(Event::Finish); }
fn parse_comprehension(
ctx: &ParserCtx<'_>,
open: usize,
elem: ExprParse,
node_kind: SyntaxKind,
close: TokKind,
diagnostics: &mut Vec<ParseDiagnostic>,
) -> ExprParse {
let tokens = ctx.tokens();
let mut events = vec![Event::Start(node_kind), Event::Tok(open)];
push_range(&mut events, open + 1, elem.start);
let mut pos = elem.end;
events.extend(elem.events);
loop {
let for_idx = ctx.skip_trivia(pos);
if ctx.token(for_idx).map(|t| t.kind) != Some(TokKind::ForKw) {
break;
}
if for_idx == pos {
let for_tok = &ctx.tokens()[for_idx];
push_diagnostic(
diagnostics,
DiagnosticKind::GluedFor,
"expected whitespace before `for`",
for_tok.start,
for_tok.start,
);
}
push_range(&mut events, pos, for_idx);
events.push(Event::Start(SyntaxKind::FOR_BINDING));
events.push(Event::Tok(for_idx)); pos = parse_for_specs(ctx, for_idx + 1, &mut events, diagnostics);
events.push(Event::Finish);
let if_idx = ctx.skip_trivia(pos);
if ctx.token(if_idx).map(|t| t.kind) == Some(TokKind::IfKw) {
push_range(&mut events, pos, if_idx);
events.push(Event::Start(SyntaxKind::COMPREHENSION_IF));
events.push(Event::Tok(if_idx)); pos = if_idx + 1;
let cond_start = ctx.skip_trivia(pos);
push_range(&mut events, pos, cond_start);
if let Some(cond) = parse_expr_in_brackets(tokens, cond_start, 0, false, diagnostics) {
events.extend(cond.events);
pos = cond.end;
} else {
pos = cond_start;
}
events.push(Event::Finish); }
}
let close_idx = ctx.skip_trivia(pos);
push_range(&mut events, pos, close_idx);
let end = if ctx.token(close_idx).map(|t| t.kind) == Some(close) {
events.push(Event::Tok(close_idx));
close_idx + 1
} else {
let tok = &tokens[open];
push_diagnostic(
diagnostics,
DiagnosticKind::UnclosedComprehension,
"unclosed comprehension",
tok.start,
tok.end,
);
close_idx
};
events.push(Event::Finish); ExprParse {
start: open,
end,
events,
}
}
fn parse_for_specs(
ctx: &ParserCtx<'_>,
mut pos: usize,
events: &mut Vec<Event>,
diagnostics: &mut Vec<ParseDiagnostic>,
) -> usize {
let tokens = ctx.tokens();
loop {
let var_start = ctx.skip_trivia(pos);
push_range(events, pos, var_start);
let var_flags = ExprFlags {
inside_brackets: true,
no_word_op: true,
..ExprFlags::default()
};
if let Some(var) = parse_expr_in(tokens, var_start, 0, diagnostics, var_flags) {
events.extend(var.events);
pos = var.end;
} else {
pos = var_start;
}
let in_idx = ctx.skip_trivia(pos);
if ctx
.token(in_idx)
.is_some_and(|t| t.kind == TokKind::Ident && (t.text == "in" || t.text == "∈"))
{
push_range(events, pos, in_idx);
events.push(Event::Tok(in_idx));
pos = in_idx + 1;
let iter_start = ctx.skip_trivia(pos);
push_range(events, pos, iter_start);
if let Some(iter) = parse_expr_in_brackets(tokens, iter_start, 0, false, diagnostics) {
events.extend(iter.events);
pos = iter.end;
} else {
pos = iter_start;
}
}
let comma_idx = ctx.skip_trivia(pos);
if ctx.token(comma_idx).map(|t| t.kind) == Some(TokKind::Comma) {
push_range(events, pos, comma_idx);
events.push(Event::Tok(comma_idx));
pos = comma_idx + 1;
continue;
}
break;
}
pos
}
fn parse_postfix_chain(
ctx: &ParserCtx<'_>,
mut lhs: ExprParse,
array_mode: bool,
end_marker: bool,
diagnostics: &mut Vec<ParseDiagnostic>,
) -> ExprParse {
loop {
let next = ctx.skip_ws(lhs.end);
if array_mode
&& next > lhs.end
&& matches!(
ctx.token(next).map(|t| t.kind),
Some(TokKind::LParen | TokKind::LBracket | TokKind::LBrace)
)
{
break;
}
if ctx.token(next).map(|t| t.kind) == Some(TokKind::LParen) && lhs_is_number(ctx, &lhs) {
break;
}
match ctx.token(next).map(|t| t.kind) {
Some(TokKind::LParen) => {
lhs = parse_postfix(
ctx,
lhs,
next,
TokKind::RParen,
SyntaxKind::CALL_EXPR,
end_marker,
diagnostics,
)
}
Some(TokKind::LBracket) => {
lhs = parse_postfix(
ctx,
lhs,
next,
TokKind::RBracket,
SyntaxKind::INDEX_EXPR,
end_marker,
diagnostics,
)
}
Some(TokKind::LBrace) => {
lhs = parse_postfix(
ctx,
lhs,
next,
TokKind::RBrace,
SyntaxKind::CURLY_EXPR,
end_marker,
diagnostics,
)
}
Some(TokKind::Dot)
if ctx.token(ctx.skip_ws(next + 1)).map(|t| t.kind) == Some(TokKind::LParen) =>
{
let lparen = ctx.skip_ws(next + 1);
let (list_events, end) = parse_arg_list(
ctx,
lparen,
TokKind::RParen,
SyntaxKind::ARG_LIST,
end_marker,
diagnostics,
);
let lhs_is_macrocall = matches!(
lhs.events.first(),
Some(Event::Start(SyntaxKind::MACRO_CALL))
);
let mut events = vec![Event::Start(SyntaxKind::DOT_CALL_EXPR)];
events.extend(lhs.events);
push_range(&mut events, lhs.end, next);
events.push(Event::Tok(next)); push_range(&mut events, next + 1, lparen);
if lparen > next + 1 {
let opener = &ctx.tokens()[lparen];
push_diagnostic(
diagnostics,
DiagnosticKind::OpenerWhitespace,
"whitespace before opener",
opener.start,
opener.start,
);
}
if lhs_is_macrocall {
let opener = &ctx.tokens()[lparen];
push_diagnostic(
diagnostics,
DiagnosticKind::MacroDotBroadcast,
"broadcast call on a macro name",
opener.start,
opener.start,
);
}
events.extend(list_events);
events.push(Event::Finish);
lhs = ExprParse {
start: lhs.start,
end,
events,
};
}
Some(TokKind::Transpose) => {
let mut events = vec![Event::Start(SyntaxKind::POSTFIX_EXPR)];
events.extend(lhs.events);
push_range(&mut events, lhs.end, next);
events.push(Event::Tok(next));
events.push(Event::Finish);
lhs = ExprParse {
start: lhs.start,
end: next + 1,
events,
};
}
_ => break,
}
}
let next = ctx.skip_ws(lhs.end);
if ctx.token(next).map(|t| t.kind) == Some(TokKind::DoKw) {
lhs = parse_do_block(ctx, lhs, next, diagnostics);
}
lhs
}
fn parse_postfix(
ctx: &ParserCtx<'_>,
lhs: ExprParse,
open_idx: usize,
close: TokKind,
node: SyntaxKind,
end_marker: bool,
diagnostics: &mut Vec<ParseDiagnostic>,
) -> ExprParse {
let first_start = ctx.skip_trivia(open_idx + 1);
if ctx.token(first_start).map(|t| t.kind) != Some(close) {
let gen_end_marker = end_marker || close == TokKind::RBracket;
let flags = ExprFlags {
inside_brackets: true,
end_marker: gen_end_marker,
begin_marker: gen_end_marker,
..ExprFlags::default()
};
let diag_mark = diagnostics.len();
if let Some(first) = parse_expr_in(ctx.tokens(), first_start, 0, diagnostics, flags)
&& ctx.token(ctx.skip_trivia(first.end)).map(|t| t.kind) == Some(TokKind::ForKw)
{
let generator = parse_comprehension(
ctx,
open_idx,
first,
SyntaxKind::GENERATOR,
close,
diagnostics,
);
let node_kind = if node == SyntaxKind::CALL_EXPR {
SyntaxKind::CALL_EXPR
} else {
SyntaxKind::TYPED_COMPREHENSION
};
let mut events = vec![Event::Start(node_kind)];
events.extend(lhs.events);
push_range(&mut events, lhs.end, open_idx);
events.extend(generator.events);
events.push(Event::Finish);
return ExprParse {
start: lhs.start,
end: generator.end,
events,
};
}
diagnostics.truncate(diag_mark);
}
if close == TokKind::RBracket
&& let Some(typed) = parse_typed_concat(ctx, &lhs, open_idx, end_marker, diagnostics)
{
return typed;
}
let (list_events, end) = parse_arg_list(
ctx,
open_idx,
close,
SyntaxKind::ARG_LIST,
end_marker,
diagnostics,
);
let mut events = vec![Event::Start(node)];
events.extend(lhs.events);
push_range(&mut events, lhs.end, open_idx);
if open_idx > lhs.end {
let opener = &ctx.tokens()[open_idx];
push_diagnostic(
diagnostics,
DiagnosticKind::OpenerWhitespace,
"whitespace before opener",
opener.start,
opener.start,
);
}
events.extend(list_events);
events.push(Event::Finish);
ExprParse {
start: lhs.start,
end,
events,
}
}
fn parse_typed_concat(
ctx: &ParserCtx<'_>,
lhs: &ExprParse,
open_idx: usize,
end_marker: bool,
diagnostics: &mut Vec<ParseDiagnostic>,
) -> Option<ExprParse> {
let diag_mark = diagnostics.len();
let first_start = ctx.skip_trivia(open_idx + 1);
if ctx.token(first_start).map(|t| t.kind) == Some(TokKind::RBracket) {
return None;
}
let wrap = |body: ExprParse| {
let mut events = vec![Event::Start(SyntaxKind::TYPED_MATRIX_EXPR)];
events.extend(lhs.events.iter().cloned());
push_range(&mut events, lhs.end, open_idx);
let end = body.end;
events.extend(body.events);
events.push(Event::Finish);
ExprParse {
start: lhs.start,
end,
events,
}
};
if let Some(empty) = parse_empty_ncat(
ctx,
open_idx,
first_start,
TokKind::RBracket,
SyntaxKind::MATRIX_EXPR,
) {
return Some(wrap(empty));
}
let first = parse_element(ctx.tokens(), first_start, true, diagnostics)?;
let mut look = first.end;
while matches!(
ctx.token(look).map(|t| t.kind),
Some(TokKind::Whitespace | TokKind::Comment | TokKind::BlockComment)
) {
look += 1;
}
match ctx.token(look).map(|t| t.kind) {
None | Some(TokKind::RBracket | TokKind::Comma | TokKind::ForKw) => {
diagnostics.truncate(diag_mark);
None
}
_ => {
let mut body = parse_matrix(
ctx,
open_idx,
first,
TokKind::RBracket,
SyntaxKind::VECT_EXPR,
SyntaxKind::MATRIX_EXPR,
end_marker,
diagnostics,
);
let recovered = diagnostics[diag_mark..]
.iter()
.any(|d| d.kind == DiagnosticKind::MatrixKeywordRecovery);
if let (true, Some(Event::Start(k @ SyntaxKind::VECT_EXPR))) =
(recovered, body.events.first_mut())
{
*k = SyntaxKind::MATRIX_EXPR;
}
if matches!(
body.events.first(),
Some(Event::Start(SyntaxKind::VECT_EXPR))
) {
diagnostics.truncate(diag_mark);
None
} else {
Some(wrap(body))
}
}
}
}
fn parse_macro(
ctx: &ParserCtx<'_>,
at_idx: usize,
diagnostics: &mut Vec<ParseDiagnostic>,
inside_brackets: bool,
) -> ExprParse {
let mut events = vec![Event::Start(SyntaxKind::MACRO_CALL)];
events.push(Event::Start(SyntaxKind::MACRO_NAME));
events.push(Event::Tok(at_idx)); let name_end = parse_macro_name_body(ctx, &mut events, at_idx + 1, diagnostics);
events.push(Event::Finish);
let end = parse_macro_args(ctx, &mut events, name_end, diagnostics, inside_brackets);
events.push(Event::Finish); ExprParse {
start: at_idx,
end,
events,
}
}
fn parse_qualified_macro(
ctx: &ParserCtx<'_>,
lhs: ExprParse,
dot_idx: usize,
diagnostics: &mut Vec<ParseDiagnostic>,
inside_brackets: bool,
) -> ExprParse {
let mut events = vec![Event::Start(SyntaxKind::MACRO_CALL)];
events.push(Event::Start(SyntaxKind::MACRO_NAME));
events.extend(lhs.events);
push_range(&mut events, lhs.end, dot_idx);
events.push(Event::Tok(dot_idx)); let at_idx = ctx.skip_trivia(dot_idx + 1);
push_range(&mut events, dot_idx + 1, at_idx);
events.push(Event::Tok(at_idx)); if ctx.token(at_idx + 1).map(|t| t.kind) == Some(TokKind::Ident)
&& ctx.token(at_idx + 2).map(|t| t.kind) == Some(TokKind::Dot)
&& ctx.token(at_idx + 3).map(|t| t.kind) == Some(TokKind::Ident)
{
let at_start = ctx.tokens()[at_idx].start;
push_diagnostic(
diagnostics,
DiagnosticKind::MacroSigilTrailing,
"misplaced macro sigil",
at_start,
at_start,
);
}
let name_end = parse_macro_name_body(ctx, &mut events, at_idx + 1, diagnostics);
events.push(Event::Finish);
let end = parse_macro_args(ctx, &mut events, name_end, diagnostics, inside_brackets);
events.push(Event::Finish); ExprParse {
start: lhs.start,
end,
events,
}
}
pub(crate) fn push_var_macro_name(
ctx: &ParserCtx<'_>,
events: &mut Vec<Event>,
i: usize,
diagnostics: &mut Vec<ParseDiagnostic>,
) -> Option<usize> {
let is_var = ctx.token(i).map(|t| t.kind) == Some(TokKind::StringPrefix)
&& ctx.token(i).map(|t| t.text.as_str()) == Some("var");
let single_quote = matches!(
ctx.token(i + 1),
Some(t) if t.kind == TokKind::StringDelimOpen && t.text.len() == 1
);
if is_var && single_quote {
let lit = parse_string_literal(ctx, i, diagnostics);
let end = lit.end;
events.extend(lit.events);
Some(end)
} else {
None
}
}
fn parse_macro_name_body(
ctx: &ParserCtx<'_>,
events: &mut Vec<Event>,
start: usize,
diagnostics: &mut Vec<ParseDiagnostic>,
) -> usize {
if let Some(end) = push_var_macro_name(ctx, events, start, diagnostics) {
return end;
}
match ctx.token(start).map(|t| t.kind) {
Some(TokKind::Dot) => {
events.push(Event::Tok(start));
start + 1
}
Some(TokKind::Ident) => {
events.push(Event::Tok(start));
let mut i = start + 1;
let mut saw_dollar_final = false;
let mut saw_inner_at = false;
while ctx.token(i).map(|t| t.kind) == Some(TokKind::Dot) {
match ctx.token(i + 1).map(|t| t.kind) {
Some(TokKind::Ident) => {
events.push(Event::Tok(i)); events.push(Event::Tok(i + 1)); i += 2;
saw_dollar_final = false;
}
Some(TokKind::Dollar)
if ctx.token(i + 2).map(|t| t.kind) == Some(TokKind::Ident) =>
{
events.push(Event::Tok(i)); events.push(Event::Tok(i + 1)); events.push(Event::Tok(i + 2)); i += 3;
saw_dollar_final = true;
}
Some(TokKind::At)
if ctx.token(i + 2).map(|t| t.kind) == Some(TokKind::Ident) =>
{
events.push(Event::Tok(i)); events.push(Event::Tok(i + 1)); events.push(Event::Tok(i + 2)); i += 3;
saw_dollar_final = false;
saw_inner_at = true;
}
_ => break,
}
}
if saw_dollar_final || saw_inner_at {
let at_start = ctx.tokens()[start - 1].start;
push_diagnostic(
diagnostics,
DiagnosticKind::MacroSigilLeading,
"invalid qualified macro name",
at_start,
at_start,
);
}
i
}
Some(TokKind::LParen) => {
let inner = ctx.skip_ws(start + 1);
if ctx.token(inner).map(|t| t.kind) == Some(TokKind::Ident) {
let after = ctx.skip_ws(inner + 1);
if ctx.token(after).map(|t| t.kind) == Some(TokKind::RParen) {
push_range(events, start, after + 1);
return after + 1;
}
}
start
}
Some(TokKind::LBracket | TokKind::LBrace) => {
if let Some(expr) = parse_prefix(ctx, start, diagnostics, ExprFlags::default()) {
let name_start = ctx.tokens()[start].start;
let end = expr.end;
events.extend(expr.events);
push_diagnostic(
diagnostics,
DiagnosticKind::InvalidMacroName,
"invalid macro name",
name_start,
name_start,
);
end
} else {
start
}
}
Some(k) if is_macro_name_token(k) => {
events.push(Event::Tok(start));
start + 1
}
_ => start,
}
}
fn is_macro_name_token(kind: TokKind) -> bool {
!matches!(kind, TokKind::Dot | TokKind::Colon)
&& (is_op_name(kind)
|| is_value_operator(kind)
|| kind == TokKind::Dollar
|| kind.is_keyword())
}
fn parse_macro_args(
ctx: &ParserCtx<'_>,
events: &mut Vec<Event>,
name_end: usize,
diagnostics: &mut Vec<ParseDiagnostic>,
inside_brackets: bool,
) -> usize {
if ctx.token(name_end).map(|t| t.kind) == Some(TokKind::LParen) {
let (list_events, end) = parse_arg_list(
ctx,
name_end,
TokKind::RParen,
SyntaxKind::ARG_LIST,
false,
diagnostics,
);
events.extend(list_events);
return end;
}
if matches!(
ctx.token(name_end).map(|t| t.kind),
Some(TokKind::LBracket | TokKind::LBrace)
) {
let arg_flags = ExprFlags {
inside_brackets,
..ExprFlags::default()
};
if let Some(arg) = parse_prefix(ctx, name_end, diagnostics, arg_flags) {
events.extend(arg.events);
return arg.end;
}
}
let mut pos = name_end;
let mut n_args = 0;
loop {
let next = ctx.skip_ws(pos);
match ctx.token(next).map(|t| t.kind) {
None
| Some(
TokKind::Newline
| TokKind::Comma
| TokKind::RParen
| TokKind::RBracket
| TokKind::RBrace
| TokKind::Semicolon,
) => break,
_ => {
push_range(events, pos, next);
let arg_flags = ExprFlags {
inside_brackets,
..ExprFlags::default()
};
match parse_expr_in(ctx.tokens(), next, 0, diagnostics, arg_flags) {
Some(arg) => {
events.extend(arg.events);
pos = arg.end;
n_args += 1;
}
None => break,
}
}
}
}
if n_args == 1 && macro_leaf_is_doc(ctx, name_end) {
let nl = ctx.skip_ws(pos);
if ctx.token(nl).map(|t| t.kind) == Some(TokKind::Newline) {
let after = ctx.skip_ws(nl + 1);
let extend = !matches!(
ctx.token(after).map(|t| t.kind),
None | Some(
TokKind::Newline
| TokKind::Comma
| TokKind::Semicolon
| TokKind::RParen
| TokKind::RBracket
| TokKind::RBrace
| TokKind::EndKw
| TokKind::ElseKw
| TokKind::ElseifKw
| TokKind::CatchKw
| TokKind::FinallyKw
)
);
if extend {
push_range(events, pos, after);
let arg_flags = ExprFlags {
inside_brackets,
stmt_comma: true,
..ExprFlags::default()
};
if let Some(arg) = parse_expr_in(ctx.tokens(), after, 0, diagnostics, arg_flags) {
events.extend(arg.events);
pos = arg.end;
}
}
}
}
pos
}
fn macro_leaf_is_doc(ctx: &ParserCtx<'_>, name_end: usize) -> bool {
name_end > 0
&& ctx
.token(name_end - 1)
.is_some_and(|t| t.kind == TokKind::Ident && t.text == "doc")
}
fn parse_braces(
ctx: &ParserCtx<'_>,
start: usize,
end_marker: bool,
diagnostics: &mut Vec<ParseDiagnostic>,
) -> Option<ExprParse> {
let tokens = ctx.tokens();
let braces = |diagnostics: &mut Vec<ParseDiagnostic>| {
let (events, end) = parse_arg_list(
ctx,
start,
TokKind::RBrace,
SyntaxKind::BRACES,
end_marker,
diagnostics,
);
ExprParse { start, end, events }
};
let first_start = ctx.skip_trivia(start + 1);
if ctx.token(first_start).map(|t| t.kind) == Some(TokKind::RBrace) {
return Some(braces(diagnostics));
}
if let Some(empty) = parse_empty_ncat(
ctx,
start,
first_start,
TokKind::RBrace,
SyntaxKind::BRACESCAT_EXPR,
) {
return Some(empty);
}
let Some(first) = parse_element(tokens, first_start, end_marker, diagnostics) else {
return Some(braces(diagnostics));
};
let mut look = first.end;
while matches!(
ctx.token(look).map(|t| t.kind),
Some(TokKind::Whitespace | TokKind::Comment | TokKind::BlockComment)
) {
look += 1;
}
match ctx.token(look).map(|t| t.kind) {
Some(TokKind::ForKw) => Some(parse_comprehension(
ctx,
start,
first,
SyntaxKind::BRACES_COMPREHENSION,
TokKind::RBrace,
diagnostics,
)),
None | Some(TokKind::RBrace | TokKind::Comma) => Some(braces(diagnostics)),
_ => Some(parse_matrix(
ctx,
start,
first,
TokKind::RBrace,
SyntaxKind::BRACES,
SyntaxKind::BRACESCAT_EXPR,
end_marker,
diagnostics,
)),
}
}
fn parse_arg_list(
ctx: &ParserCtx<'_>,
open_idx: usize,
close: TokKind,
list_kind: SyntaxKind,
inherited_end_marker: bool,
diagnostics: &mut Vec<ParseDiagnostic>,
) -> (Vec<Event>, usize) {
let tokens = ctx.tokens();
let end_marker =
inherited_end_marker || (close == TokKind::RBracket && list_kind == SyntaxKind::ARG_LIST);
let begin_marker = end_marker;
let mut events = vec![Event::Start(list_kind), Event::Tok(open_idx)];
let mut i = open_idx + 1;
let mut in_params = false;
let mut slot_empty = true;
let mut parsed_element = false;
loop {
while matches!(tokens.get(i).map(|t| t.kind), Some(k) if k.is_trivia()) {
events.push(Event::Tok(i));
i += 1;
}
match tokens.get(i).map(|t| t.kind) {
None => {
if in_params {
events.push(Event::Finish); in_params = false;
}
let opener = &tokens[open_idx];
push_diagnostic(
diagnostics,
DiagnosticKind::UnterminatedArgList,
"unterminated argument list",
opener.start,
opener.start,
);
break;
}
Some(k) if k == close => {
if in_params {
events.push(Event::Finish); in_params = false;
}
events.push(Event::Tok(i));
i += 1;
break;
}
Some(TokKind::Comma) => {
if slot_empty && parsed_element {
let mut j = i;
while let Some(k) = tokens.get(j).map(|t| t.kind) {
if k == close {
break;
}
j += 1;
}
if in_params {
events.push(Event::Finish); in_params = false;
}
events.push(Event::Start(SyntaxKind::ERROR));
push_range(&mut events, i, j);
events.push(Event::Finish);
push_diagnostic(
diagnostics,
DiagnosticKind::TrailingJunk,
"extra comma in list",
tokens[i].start,
tokens[i].end,
);
i = j;
if tokens.get(i).map(|t| t.kind) == Some(close) {
events.push(Event::Tok(i));
i += 1;
}
break;
}
events.push(Event::Tok(i));
i += 1;
slot_empty = true;
}
Some(TokKind::Semicolon) => {
if in_params {
events.push(Event::Finish); }
events.push(Event::Start(SyntaxKind::PARAMETERS));
in_params = true;
events.push(Event::Tok(i));
i += 1;
slot_empty = true;
}
Some(TokKind::EndKw) if !end_marker && (parsed_element || close == TokKind::RParen) => {
if in_params {
events.push(Event::Finish); in_params = false;
}
let opener = &tokens[open_idx];
push_diagnostic(
diagnostics,
DiagnosticKind::UnterminatedArgList,
"unterminated argument list",
opener.start,
opener.start,
);
break;
}
Some(_) => {
i = parse_one_arg(ctx, &mut events, i, end_marker, begin_marker, diagnostics);
slot_empty = false;
parsed_element = true;
}
}
}
if in_params {
events.push(Event::Finish); }
events.push(Event::Finish); (events, i)
}
fn parse_one_arg(
ctx: &ParserCtx<'_>,
events: &mut Vec<Event>,
i: usize,
end_marker: bool,
begin_marker: bool,
diagnostics: &mut Vec<ParseDiagnostic>,
) -> usize {
let tokens = ctx.tokens();
let flags = ExprFlags {
inside_brackets: true,
end_marker,
begin_marker,
..ExprFlags::default()
};
let parse_arg_expr = |tokens: &[Token], start, diagnostics: &mut Vec<ParseDiagnostic>| {
parse_expr_in(tokens, start, 0, diagnostics, flags)
};
if let Some(eq_idx) = kwarg_eq(ctx, i) {
events.push(Event::Start(SyntaxKind::KEYWORD_ARG));
events.push(Event::Start(SyntaxKind::NAME));
events.push(Event::Tok(i));
events.push(Event::Finish);
push_range(events, i + 1, eq_idx);
events.push(Event::Tok(eq_idx));
let val_start = ctx.skip_trivia(eq_idx + 1);
push_range(events, eq_idx + 1, val_start);
let end = match parse_arg_expr(tokens, val_start, diagnostics) {
Some(val) => {
events.extend(val.events);
val.end
}
None => val_start,
};
events.push(Event::Finish);
end
} else if let Some(arg) = parse_arg_expr(tokens, i, diagnostics) {
events.push(Event::Start(SyntaxKind::ARG));
events.extend(arg.events);
events.push(Event::Finish);
arg.end
} else {
events.push(Event::Tok(i));
i + 1
}
}
fn kwarg_eq(ctx: &ParserCtx<'_>, i: usize) -> Option<usize> {
if ctx.token(i).map(|t| t.kind) != Some(TokKind::Ident) {
return None;
}
let eq = ctx.skip_ws(i + 1);
(ctx.token(eq).map(|t| t.kind) == Some(TokKind::Eq)).then_some(eq)
}
fn next_operator(
ctx: &ParserCtx<'_>,
from: usize,
inside_brackets: bool,
) -> Option<(usize, TokKind)> {
let op_idx = ctx.skip_ws(from);
let op = ctx.token(op_idx)?;
if op.kind == TokKind::Newline {
if !inside_brackets {
return None;
}
let next_idx = ctx.skip_ws_and_newlines(from);
let next = ctx.token(next_idx)?;
return is_operator(next.kind).then_some((next_idx, next.kind));
}
is_operator(op.kind).then_some((op_idx, op.kind))
}
fn word_operator(ctx: &ParserCtx<'_>, from: usize, inside_brackets: bool) -> Option<usize> {
let op_idx = ctx.skip_ws(from);
let op = ctx.token(op_idx)?;
let op_idx = if op.kind == TokKind::Newline {
if !inside_brackets {
return None;
}
ctx.skip_ws_and_newlines(from)
} else {
op_idx
};
let op = ctx.token(op_idx)?;
(op.kind == TokKind::Ident && (op.text == "in" || op.text == "isa")).then_some(op_idx)
}
fn is_operator(kind: TokKind) -> bool {
matches!(kind, TokKind::Question | TokKind::WhereKw)
|| is_assignment_op(kind)
|| infix_binding_power(kind).is_some()
}
fn signed_literal_fold(ctx: &ParserCtx<'_>, op_idx: usize) -> bool {
let Some(op) = ctx.token(op_idx) else {
return false;
};
if op.text.chars().next_back().is_some_and(is_op_suffix_char) {
return false;
}
let Some(num) = ctx.token(op_idx + 1) else {
return false;
};
let folds = match op.kind {
TokKind::Minus => matches!(
num.kind,
TokKind::Integer | TokKind::Float | TokKind::Float32
),
TokKind::Plus => is_number_tok(num.kind),
_ => return false,
};
if !folds {
return false;
}
let k3 = ctx.token(ctx.skip_ws(op_idx + 2)).map(|t| t.kind);
!matches!(
k3,
Some(
TokKind::Caret
| TokKind::DotCaret
| TokKind::UniPower
| TokKind::LBracket
| TokKind::LBrace
)
)
}
fn is_number_tok(kind: TokKind) -> bool {
matches!(
kind,
TokKind::Integer
| TokKind::BinInt
| TokKind::OctInt
| TokKind::HexInt
| TokKind::Float
| TokKind::Float32
)
}
fn lhs_is_number(ctx: &ParserCtx<'_>, lhs: &ExprParse) -> bool {
if lhs.end == lhs.start + 1 && ctx.token(lhs.start).is_some_and(|t| is_number_tok(t.kind)) {
return true;
}
matches!(lhs.events.first(), Some(Event::Start(SyntaxKind::LITERAL)))
&& lhs.end == lhs.start + 2
&& ctx
.token(lhs.start + 1)
.is_some_and(|t| is_number_tok(t.kind))
}
fn lhs_value_close(lhs: &ExprParse) -> bool {
matches!(
lhs.events.first(),
Some(Event::Start(
SyntaxKind::PAREN_EXPR
| SyntaxKind::CALL_EXPR
| SyntaxKind::INDEX_EXPR
| SyntaxKind::CURLY_EXPR
| SyntaxKind::VECT_EXPR
| SyntaxKind::MATRIX_EXPR
| SyntaxKind::TYPED_MATRIX_EXPR
| SyntaxKind::BRACESCAT_EXPR
| SyntaxKind::POSTFIX_EXPR
))
)
}
fn lhs_is_paren_block(lhs: &ExprParse) -> bool {
if !matches!(
lhs.events.first(),
Some(Event::Start(SyntaxKind::PAREN_EXPR))
) {
return false;
}
lhs.events
.iter()
.filter_map(|e| match e {
Event::Start(k) => Some(*k),
_ => None,
})
.nth(1)
.is_some_and(is_block_form_kind)
}
fn is_block_form_kind(kind: SyntaxKind) -> bool {
matches!(
kind,
SyntaxKind::IF_EXPR
| SyntaxKind::FUNCTION_DEF
| SyntaxKind::MACRO_DEF
| SyntaxKind::BEGIN_EXPR
| SyntaxKind::QUOTE_EXPR
| SyntaxKind::WHILE_EXPR
| SyntaxKind::FOR_EXPR
| SyntaxKind::LET_EXPR
| SyntaxKind::TRY_EXPR
| SyntaxKind::STRUCT_DEF
| SyntaxKind::MODULE_DEF
| SyntaxKind::ABSTRACT_DEF
| SyntaxKind::PRIMITIVE_DEF
)
}
fn is_juxtapose_closing(kind: TokKind) -> bool {
matches!(
kind,
TokKind::RParen | TokKind::RBracket | TokKind::RBrace | TokKind::Comma | TokKind::Semicolon
)
}
fn is_closing_token(kind: TokKind) -> bool {
is_juxtapose_closing(kind)
|| matches!(
kind,
TokKind::EndKw
| TokKind::ElseKw
| TokKind::ElseifKw
| TokKind::CatchKw
| TokKind::FinallyKw
)
}
fn lhs_is_plain_string(ctx: &ParserCtx<'_>, lhs: &ExprParse) -> bool {
if !matches!(
lhs.events.first(),
Some(Event::Start(SyntaxKind::STRING_LITERAL))
) {
return false;
}
let first_tok = lhs.events.iter().find_map(|e| match e {
Event::Tok(idx) => Some(*idx),
_ => None,
});
match first_tok {
Some(idx) => ctx.token(idx).map(|t| t.kind) != Some(TokKind::StringPrefix),
None => true,
}
}
fn should_juxtapose_string_error(ctx: &ParserCtx<'_>, lhs: &ExprParse, min_bp: u8) -> bool {
if JUXTAPOSE_L < min_bp {
return false;
}
if lhs_is_paren_block(lhs) {
return false;
}
let Some(next) = ctx.token(lhs.end) else {
return false;
};
let k = next.kind;
if k.is_trivia() || is_operator(k) || k == TokKind::At || is_closing_token(k) {
return false;
}
if lhs_is_plain_string(ctx, lhs) {
return !is_number_tok(k);
}
matches!(k, TokKind::StringDelimOpen | TokKind::CmdDelimOpen)
&& (lhs_is_number(ctx, lhs) || lhs_value_close(lhs))
}
fn should_juxtapose(ctx: &ParserCtx<'_>, lhs: &ExprParse, min_bp: u8) -> bool {
if JUXTAPOSE_L < min_bp {
return false;
}
let Some(next) = ctx.token(lhs.end) else {
return false;
};
let k = next.kind;
if k.is_trivia() {
return false;
}
if is_operator(k) || is_juxtapose_closing(k) || k.is_keyword() || k == TokKind::At {
return false;
}
if lhs_is_number(ctx, lhs) {
return true;
}
!is_number_tok(k) && lhs_value_close(lhs) && !lhs_is_paren_block(lhs)
}
fn is_dotted_broadcast_text(text: &str) -> bool {
text.as_bytes().first() == Some(&b'.') && text.len() > 1 && text.as_bytes()[1] != b'.'
}
fn is_assignment_op(kind: TokKind) -> bool {
matches!(
kind,
TokKind::Eq
| TokKind::DotEq
| TokKind::PlusEq
| TokKind::MinusEq
| TokKind::StarEq
| TokKind::SlashEq
| TokKind::SlashSlashEq
| TokKind::CaretEq
| TokKind::PercentEq
| TokKind::PipeEq
| TokKind::AmpEq
| TokKind::DotPlusEq
| TokKind::DotMinusEq
| TokKind::DotStarEq
| TokKind::DotSlashEq
| TokKind::DotSlashSlashEq
| TokKind::DotCaretEq
| TokKind::DotPercentEq
)
}
fn unary_op_paren_is_call(ctx: &ParserCtx<'_>, lparen_idx: usize) -> bool {
let first = ctx.skip_trivia(lparen_idx + 1);
match ctx.token(first).map(|t| t.kind) {
Some(TokKind::RParen) => return true,
Some(TokKind::Semicolon) => return !paren_is_block(ctx, lparen_idx),
_ => {}
}
let mut depth = 0i32;
let mut i = first;
while let Some(tok) = ctx.token(i) {
match tok.kind {
TokKind::LParen | TokKind::LBracket | TokKind::LBrace => depth += 1,
TokKind::RParen | TokKind::RBracket | TokKind::RBrace => {
if depth == 0 {
return false;
}
depth -= 1;
}
TokKind::Comma | TokKind::DotDotDot if depth == 0 => return true,
_ => {}
}
i += 1;
}
false
}
fn paren_list_kind(ctx: &ParserCtx<'_>, lparen_idx: usize) -> SyntaxKind {
if paren_is_block(ctx, lparen_idx) {
SyntaxKind::PAREN_BLOCK
} else {
SyntaxKind::TUPLE_EXPR
}
}
fn paren_is_block(ctx: &ParserCtx<'_>, lparen_idx: usize) -> bool {
let first = ctx.skip_trivia(lparen_idx + 1);
let initial_semi = ctx.token(first).map(|t| t.kind) == Some(TokKind::Semicolon);
let mut depth = 0i32;
let mut had_commas = false;
let mut had_splat = false;
let mut num_semis = 0u32;
let mut num_subexprs = 0u32;
let mut in_subexpr = false;
let mut i = first;
while let Some(tok) = ctx.token(i) {
match tok.kind {
TokKind::LParen | TokKind::LBracket | TokKind::LBrace => {
if !in_subexpr {
num_subexprs += 1;
in_subexpr = true;
}
depth += 1;
}
TokKind::RParen | TokKind::RBracket | TokKind::RBrace => {
if depth == 0 {
break;
}
depth -= 1;
}
TokKind::Comma if depth == 0 => {
had_commas = true;
in_subexpr = false;
}
TokKind::Semicolon if depth == 0 => {
num_semis += 1;
in_subexpr = false;
}
TokKind::DotDotDot if depth == 0 => had_splat = true,
k if !k.is_trivia() && !in_subexpr => {
num_subexprs += 1;
in_subexpr = true;
}
_ => {}
}
i += 1;
}
let is_tuple = had_commas
|| (had_splat && num_semis >= 1)
|| (initial_semi && (num_semis == 1 || num_subexprs > 0));
!is_tuple && num_semis > 0
}
fn is_operator_call_name(kind: TokKind) -> bool {
use TokKind::*;
matches!(
kind,
Star | Slash
| SlashSlash
| Caret
| Percent
| EqEq
| NotEq
| EqEqEq
| NotEqEq
| Lt
| Le
| Gt
| Ge
| Pipe
| Shl
| Shr
| UShr
| PipeGt
| PipeLt
| FatArrow
| LongArrow
| LeftRightArrow
| DotStar
| DotSlash
| DotSlashSlash
| DotCaret
| DotPercent
| DotEqEq
| DotNotEq
| DotEqEqEq
| DotNotEqEq
| DotLt
| DotLe
| DotGt
| DotGe
| DotSubtype
| DotSupertype
| DotFatArrow
| DotLongArrow
| DotPipeGt
| DotAmp
| DotPipe
)
}
fn is_curly_operator_name(kind: TokKind) -> bool {
use TokKind::*;
is_operator_call_name(kind)
|| matches!(
kind,
Plus | Minus | DotPlus | DotMinus | Bang | Tilde | DotTilde | Subtype | Supertype
)
}
fn is_paren_value_op(kind: Option<TokKind>) -> bool {
let Some(k) = kind else { return false };
use TokKind::*;
(is_op_name(k) && !matches!(k, AndAnd | OrOr | Arrow)) || k == Colon
}
fn is_paren_quotable_op(kind: Option<TokKind>) -> bool {
let Some(k) = kind else { return false };
use TokKind::*;
is_op_name(k)
|| matches!(
k,
Eq | PlusEq
| MinusEq
| StarEq
| SlashEq
| SlashSlashEq
| CaretEq
| PercentEq
| PipeEq
| AmpEq
| ColonColon
| Colon
)
}
fn parse_ternary(
ctx: &ParserCtx<'_>,
cond: ExprParse,
q_idx: usize,
diagnostics: &mut Vec<ParseDiagnostic>,
flags: ExprFlags,
) -> Result<ExprParse, ExprParse> {
let tokens = ctx.tokens();
let inside_brackets = flags.inside_brackets;
let ws_after = |idx: usize| ctx.token(idx + 1).is_some_and(|t| t.kind.is_trivia());
let q_errors = usize::from(q_idx == cond.end) + usize::from(!ws_after(q_idx));
let then_start = ctx.skip_trivia(q_idx + 1);
let then_flags = ExprFlags {
no_range: true,
array_mode: false,
..flags
};
let Some(then_br) = parse_expr_in(tokens, then_start, TERNARY_R, diagnostics, then_flags)
else {
let op = &tokens[q_idx];
push_diagnostic(
diagnostics,
DiagnosticKind::MissingTernaryTrue,
"expected expression after `?`",
op.start,
op.end,
);
return Err(error_expr_to_line_end(tokens, cond.start, q_idx + 1));
};
let colon = if inside_brackets {
ctx.skip_ws_and_newlines(then_br.end)
} else {
ctx.skip_ws(then_br.end)
};
let has_colon = ctx.token(colon).map(|t| t.kind) == Some(TokKind::Colon);
let (colon_errors, else_start) = if has_colon {
let errors = usize::from(colon == then_br.end) + usize::from(!ws_after(colon));
(errors, ctx.skip_trivia(colon + 1))
} else {
(1, ctx.skip_trivia(then_br.end))
};
let else_flags = ExprFlags {
array_mode: false,
..flags
};
let Some(else_br) = parse_expr_in(tokens, else_start, TERNARY_R, diagnostics, else_flags)
else {
let terminator_is_closer = ctx
.token(else_start)
.is_some_and(|t| is_closing_block_keyword(t.kind));
if terminator_is_closer {
let markers = if has_colon { 1 } else { 2 };
let q_end = tokens[q_idx].end;
for _ in 0..markers {
push_diagnostic(
diagnostics,
DiagnosticKind::IncompleteTernaryIf,
"incomplete ternary recovered as `if`",
q_end,
q_end,
);
}
let mut events = vec![Event::Start(SyntaxKind::TERNARY_EXPR)];
events.extend(cond.events);
push_range(&mut events, cond.end, q_idx);
events.push(Event::Tok(q_idx)); push_range(&mut events, q_idx + 1, then_br.start);
events.extend(then_br.events);
let end = if has_colon {
push_range(&mut events, then_br.end, colon);
events.push(Event::Tok(colon)); colon + 1
} else {
then_br.end
};
events.push(Event::Finish);
return Ok(ExprParse {
start: cond.start,
end,
events,
});
}
if has_colon {
let op = &tokens[colon];
push_diagnostic(
diagnostics,
DiagnosticKind::MissingTernaryFalse,
"expected expression after `:`",
op.start,
op.end,
);
return Err(error_expr_to_line_end(tokens, cond.start, colon + 1));
}
let op = &tokens[q_idx];
push_diagnostic(
diagnostics,
DiagnosticKind::MissingTernaryColon,
"expected `:` in ternary expression",
op.start,
op.end,
);
let mut events = vec![Event::Start(SyntaxKind::TERNARY_EXPR)];
events.extend(cond.events);
push_range(&mut events, cond.end, q_idx);
events.push(Event::Tok(q_idx));
push_range(&mut events, q_idx + 1, then_br.start);
events.extend(then_br.events);
events.push(Event::Finish);
return Ok(ExprParse {
start: cond.start,
end: then_br.end,
events,
});
};
let q_end = tokens[q_idx].end;
for _ in 0..q_errors {
push_diagnostic(
diagnostics,
DiagnosticKind::TernaryQWhitespace,
"whitespace around `?`",
q_end,
q_end,
);
}
let then_end = tokens[then_br.end - 1].end;
for _ in 0..colon_errors {
push_diagnostic(
diagnostics,
DiagnosticKind::TernaryColonWhitespace,
"whitespace around `:`",
then_end,
then_end,
);
}
let mut events = vec![Event::Start(SyntaxKind::TERNARY_EXPR)];
events.extend(cond.events);
push_range(&mut events, cond.end, q_idx);
events.push(Event::Tok(q_idx)); push_range(&mut events, q_idx + 1, then_br.start);
events.extend(then_br.events);
if has_colon {
push_range(&mut events, then_br.end, colon);
events.push(Event::Tok(colon)); push_range(&mut events, colon + 1, else_br.start);
} else {
push_range(&mut events, then_br.end, else_br.start);
}
events.extend(else_br.events);
events.push(Event::Finish);
Ok(ExprParse {
start: cond.start,
end: else_br.end,
events,
})
}
fn infix_binding_power(kind: TokKind) -> Option<(u8, u8)> {
Some(match kind {
TokKind::Tilde | TokKind::DotTilde => (2, 1),
TokKind::UniAssign => (2, 1),
TokKind::UniArrow => (4, 3),
TokKind::UniComparison => (10, 11),
TokKind::UniColon => (14, 15),
TokKind::UniPlus => (20, 21),
TokKind::UniTimes => (24, 25),
TokKind::UniPower => (34, 33),
TokKind::Arrow
| TokKind::FatArrow
| TokKind::DotFatArrow
| TokKind::LongArrow
| TokKind::LeftRightArrow
| TokKind::DotLongArrow => (4, 3),
TokKind::OrOr | TokKind::DotOrOr => (6, 5),
TokKind::AndAnd | TokKind::DotAndAnd => (8, 7),
TokKind::EqEq
| TokKind::NotEq
| TokKind::EqEqEq
| TokKind::NotEqEq
| TokKind::Lt
| TokKind::Le
| TokKind::Gt
| TokKind::Ge
| TokKind::Subtype
| TokKind::Supertype
| TokKind::DotEqEq
| TokKind::DotNotEq
| TokKind::DotEqEqEq
| TokKind::DotNotEqEq
| TokKind::DotLt
| TokKind::DotLe
| TokKind::DotGt
| TokKind::DotGe
| TokKind::DotSubtype
| TokKind::DotSupertype => (10, 11),
TokKind::PipeLt => (12, 11),
TokKind::PipeGt | TokKind::DotPipeGt => (13, 14),
TokKind::Colon | TokKind::DotDot => (14, 15),
TokKind::StarStar | TokKind::MinusMinus | TokKind::DotStarStar | TokKind::DotMinusMinus => {
(18, 19)
}
TokKind::Plus
| TokKind::Minus
| TokKind::DotPlus
| TokKind::DotMinus
| TokKind::Pipe
| TokKind::DotPipe => (20, 21),
TokKind::Star
| TokKind::Slash
| TokKind::Percent
| TokKind::Amp
| TokKind::DotAmp
| TokKind::DotStar
| TokKind::DotSlash
| TokKind::DotPercent => (24, 25),
TokKind::SlashSlash | TokKind::DotSlashSlash => (28, 29),
TokKind::Shl | TokKind::Shr | TokKind::UShr => (30, 31),
TokKind::Caret | TokKind::DotCaret => (34, 33),
TokKind::ColonColon => (36, 37),
TokKind::Dot => (40, 41),
_ => return None,
})
}