use crate::comment::Comment;
use crate::expr::{
CaseBranch, Expr, Function, FunctionImplementation, IfBranch, LetDeclaration, RecordSetter,
Signature,
};
use crate::node::Spanned;
use crate::operator::InfixDirection;
use crate::span::{Position, Span};
use crate::token::Token;
use super::pattern::parse_pattern;
use super::type_annotation::parse_type;
use super::{ParseResult, Parser};
type Cont = Box<dyn FnOnce(&mut Parser, Spanned<Expr>) -> ParseResult<Step>>;
enum Step {
Done(Spanned<Expr>),
NeedExpr(Cont),
}
struct PendingOp {
left: Spanned<Expr>,
op: String,
assoc: InfixDirection,
right_bp: u8,
}
struct IfChainEntry {
start: Position,
condition: Spanned<Expr>,
then_branch: Spanned<Expr>,
trailing_comments: Vec<Spanned<Comment>>,
}
enum RecordContext {
Plain,
Update(Spanned<String>),
}
pub fn parse_expr(p: &mut Parser) -> ParseResult<Spanned<Expr>> {
let mut stack: Vec<Cont> = Vec::new();
'outer: loop {
let step = parse_binary_expr_cps(p)?;
let mut current = step;
loop {
match current {
Step::NeedExpr(cont) => {
if stack.len() >= super::MAX_EXPR_DEPTH {
return Err(p.error(format!(
"expression nesting too deep (limit: {})",
super::MAX_EXPR_DEPTH
)));
}
stack.push(cont);
continue 'outer;
}
Step::Done(expr) => match stack.pop() {
None => return Ok(expr),
Some(cont) => {
current = cont(p, expr)?;
}
},
}
}
}
}
fn parse_binary_expr_cps(p: &mut Parser) -> ParseResult<Step> {
let step = parse_unary_expr_cps(p)?;
match step {
Step::Done(left) => binary_loop(p, Vec::new(), left),
Step::NeedExpr(cont) => Ok(Step::NeedExpr(Box::new(move |p, sub_expr| {
let step = cont(p, sub_expr)?;
binary_after_operand(p, step, Vec::new())
}))),
}
}
fn binary_loop(
p: &mut Parser,
mut pending: Vec<PendingOp>,
mut left: Spanned<Expr>,
) -> ParseResult<Step> {
loop {
let left_end_line = left.span.end.line;
let left_end_offset = left.span.end.offset;
p.skip_whitespace();
let (op, prec, assoc) = match extract_operator(p) {
Some(info) => info,
None => break,
};
let mut step_leading: Vec<Spanned<Comment>> = Vec::new();
{
let mut i = 0;
while i < p.collected_comments.len() {
let c = &p.collected_comments[i];
let is_line = matches!(c.value, Comment::Line(_));
let after_left = c.span.start.offset >= left_end_offset;
let before_op = c.span.end.offset <= p.current().span.start.offset;
let different_line = c.span.start.line > left_end_line;
if is_line && after_left && before_op && different_line {
step_leading.push(p.collected_comments.remove(i));
} else {
i += 1;
}
}
}
let (left_bp, right_bp) = binding_power(prec, &assoc);
while let Some(top) = pending.last() {
if top.right_bp > left_bp {
let top = pending.pop().unwrap();
let start = top.left.span.start;
let end = left.span.end;
let expr = Expr::OperatorApplication {
operator: top.op,
direction: top.assoc,
left: Box::new(top.left),
right: Box::new(left),
};
left = Spanned::new(Span::new(start, end), expr);
} else {
break;
}
}
p.advance();
pending.push(PendingOp {
left,
op,
assoc,
right_bp,
});
let step = parse_unary_expr_cps(p)?;
match step {
Step::Done(mut expr) => {
if !step_leading.is_empty() {
step_leading.extend(expr.comments);
expr.comments = step_leading;
}
left = expr;
}
Step::NeedExpr(cont) => {
let leading = step_leading;
return Ok(Step::NeedExpr(Box::new(move |p, sub_expr| {
let step = cont(p, sub_expr)?;
binary_after_operand_with_leading(p, step, pending, leading)
})));
}
}
}
while let Some(top) = pending.pop() {
let start = top.left.span.start;
let end = left.span.end;
let expr = Expr::OperatorApplication {
operator: top.op,
direction: top.assoc,
left: Box::new(top.left),
right: Box::new(left),
};
left = Spanned::new(Span::new(start, end), expr);
}
Ok(Step::Done(left))
}
fn binary_after_operand(p: &mut Parser, step: Step, pending: Vec<PendingOp>) -> ParseResult<Step> {
match step {
Step::NeedExpr(cont) => Ok(Step::NeedExpr(Box::new(move |p, sub_expr| {
let step = cont(p, sub_expr)?;
binary_after_operand(p, step, pending)
}))),
Step::Done(left) => binary_loop(p, pending, left),
}
}
fn binary_after_operand_with_leading(
p: &mut Parser,
step: Step,
pending: Vec<PendingOp>,
leading: Vec<Spanned<Comment>>,
) -> ParseResult<Step> {
match step {
Step::NeedExpr(cont) => Ok(Step::NeedExpr(Box::new(move |p, sub_expr| {
let step = cont(p, sub_expr)?;
binary_after_operand_with_leading(p, step, pending, leading)
}))),
Step::Done(mut left) => {
if !leading.is_empty() {
let mut combined = leading;
combined.extend(std::mem::take(&mut left.comments));
left.comments = combined;
}
binary_loop(p, pending, left)
}
}
}
fn extract_operator(p: &Parser) -> Option<(String, u8, InfixDirection)> {
match p.peek() {
Token::Operator(op) => {
let (prec, assoc) = operator_info(op);
Some((op.clone(), prec, assoc))
}
Token::Minus => Some(("-".into(), 6, InfixDirection::Left)),
_ => None,
}
}
fn operator_info(op: &str) -> (u8, InfixDirection) {
match op {
"<|" => (0, InfixDirection::Right),
"|>" => (0, InfixDirection::Left),
"||" => (2, InfixDirection::Right),
"&&" => (3, InfixDirection::Right),
"==" | "/=" | "<" | ">" | "<=" | ">=" => (4, InfixDirection::Non),
"::" | "++" => (5, InfixDirection::Right),
"+" | "-" => (6, InfixDirection::Left),
"*" | "/" | "//" => (7, InfixDirection::Left),
"^" => (8, InfixDirection::Right),
"<<" => (9, InfixDirection::Left),
">>" => (9, InfixDirection::Right),
_ => (9, InfixDirection::Left),
}
}
fn binding_power(prec: u8, assoc: &InfixDirection) -> (u8, u8) {
let base = prec * 2;
match assoc {
InfixDirection::Left => (base, base + 1),
InfixDirection::Right => (base, base),
InfixDirection::Non => (base, base + 1),
}
}
fn parse_unary_expr_cps(p: &mut Parser) -> ParseResult<Step> {
p.skip_whitespace();
let start = p.current_pos();
if matches!(p.peek(), Token::Minus) {
let minus_span = p.peek_span();
p.advance();
let step = parse_application_cps(p)?;
match step {
Step::Done(operand) => {
let expr = Expr::Negation(Box::new(operand));
Ok(Step::Done(Spanned::new(
minus_span.merge(p.span_from(start)),
expr,
)))
}
Step::NeedExpr(cont) => Ok(Step::NeedExpr(Box::new(move |p, sub_expr| {
let step = cont(p, sub_expr)?;
unary_wrap_negation(p, step, start, minus_span)
}))),
}
} else {
parse_application_cps(p)
}
}
fn unary_wrap_negation(
p: &mut Parser,
step: Step,
start: Position,
minus_span: Span,
) -> ParseResult<Step> {
match step {
Step::NeedExpr(cont) => Ok(Step::NeedExpr(Box::new(move |p, sub_expr| {
let step = cont(p, sub_expr)?;
unary_wrap_negation(p, step, start, minus_span)
}))),
Step::Done(operand) => {
let expr = Expr::Negation(Box::new(operand));
Ok(Step::Done(Spanned::new(
minus_span.merge(p.span_from(start)),
expr,
)))
}
}
}
fn parse_application_cps(p: &mut Parser) -> ParseResult<Step> {
let start = p.current_pos();
let ctx_col = p.app_context_col.take();
let first_step = parse_atomic_expr_cps(p)?;
match first_step {
Step::Done(first) => {
let first_line = first.span.start.line;
application_loop(p, start, first_line, vec![first], ctx_col)
}
Step::NeedExpr(cont) => Ok(Step::NeedExpr(Box::new(move |p, sub_expr| {
let step = cont(p, sub_expr)?;
app_after_first_atom(p, step, start, ctx_col)
}))),
}
}
fn app_after_first_atom(
p: &mut Parser,
step: Step,
start: Position,
ctx_col: Option<u32>,
) -> ParseResult<Step> {
match step {
Step::NeedExpr(cont) => Ok(Step::NeedExpr(Box::new(move |p, sub_expr| {
let step = cont(p, sub_expr)?;
app_after_first_atom(p, step, start, ctx_col)
}))),
Step::Done(first) => {
let first_line = first.span.start.line;
application_loop(p, start, first_line, vec![first], ctx_col)
}
}
}
fn application_loop(
p: &mut Parser,
start: Position,
first_line: u32,
mut args: Vec<Spanned<Expr>>,
ctx_col: Option<u32>,
) -> ParseResult<Step> {
loop {
let pre_ws_snapshot = p.pending_comments_snapshot();
p.skip_whitespace();
let prev_end = args.last().map(|a| a.span.end.offset).unwrap_or(0);
let has_ws_before_minus = p.current().span.start.offset > prev_end;
let is_unary_neg_arg = has_ws_before_minus && is_unary_minus_arg(p);
if !can_start_atomic_expr(p.peek()) && !is_unary_neg_arg {
break;
}
let arg_col = p.current_column();
let arg_line = p.current_pos().line;
let ref_col = ctx_col.unwrap_or(start.column);
if arg_line != first_line && arg_col <= ref_col {
break;
}
let mut inter_arg_comments: Vec<Spanned<Comment>> = Vec::new();
if let Some(prev) = args.last() {
let prev_end_line = prev.span.end.line;
let mut i = pre_ws_snapshot;
while i < p.collected_comments.len() {
let c = &p.collected_comments[i];
let is_inline_block = matches!(c.value, Comment::Block(_))
&& c.span.start.line == prev_end_line
&& c.span.end.line == prev_end_line;
let is_on_own_line = c.span.start.line > prev_end_line;
if is_inline_block || is_on_own_line {
inter_arg_comments.push(p.collected_comments.remove(i));
} else {
i += 1;
}
}
}
let step = if is_unary_neg_arg {
let minus_start = p.current_pos();
let minus_span = p.peek_span();
p.advance();
let inner_step = parse_atomic_expr_cps(p)?;
match inner_step {
Step::Done(operand) => {
let expr = Expr::Negation(Box::new(operand));
Step::Done(Spanned::new(
minus_span.merge(p.span_from(minus_start)),
expr,
))
}
Step::NeedExpr(cont) => Step::NeedExpr(Box::new(move |p, sub_expr| {
let inner = cont(p, sub_expr)?;
unary_wrap_negation(p, inner, minus_start, minus_span)
})),
}
} else {
parse_atomic_expr_cps(p)?
};
match step {
Step::Done(mut arg) => {
if !inter_arg_comments.is_empty() {
inter_arg_comments.extend(arg.comments);
arg.comments = inter_arg_comments;
}
args.push(arg);
}
Step::NeedExpr(cont) => {
let leading = inter_arg_comments;
return Ok(Step::NeedExpr(Box::new(move |p, sub_expr| {
let step = cont(p, sub_expr)?;
app_after_arg_with_leading(p, step, start, first_line, args, ctx_col, leading)
})));
}
}
}
if args.len() == 1 {
Ok(Step::Done(args.into_iter().next().unwrap()))
} else {
Ok(Step::Done(p.spanned_from(start, Expr::Application(args))))
}
}
fn app_after_arg_with_leading(
p: &mut Parser,
step: Step,
start: Position,
first_line: u32,
args: Vec<Spanned<Expr>>,
ctx_col: Option<u32>,
leading: Vec<Spanned<Comment>>,
) -> ParseResult<Step> {
match step {
Step::NeedExpr(cont) => Ok(Step::NeedExpr(Box::new(move |p, sub_expr| {
let step = cont(p, sub_expr)?;
app_after_arg_with_leading(p, step, start, first_line, args, ctx_col, leading)
}))),
Step::Done(mut arg) => {
if !leading.is_empty() {
let mut combined = leading;
combined.extend(std::mem::take(&mut arg.comments));
arg.comments = combined;
}
let mut args = args;
args.push(arg);
application_loop(p, start, first_line, args, ctx_col)
}
}
}
fn parse_atomic_expr_cps(p: &mut Parser) -> ParseResult<Step> {
p.skip_whitespace();
let start = p.current_pos();
let step = match p.peek().clone() {
Token::Literal(lit) => {
p.advance();
Step::Done(p.spanned_from(start, Expr::Literal(lit)))
}
Token::LowerName(name) => {
p.advance();
Step::Done(p.spanned_from(
start,
Expr::FunctionOrValue {
module_name: Vec::new(),
name,
},
))
}
Token::UpperName(_) => {
let (module_name, name) = parse_qualified_value(p)?;
Step::Done(p.spanned_from(start, Expr::FunctionOrValue { module_name, name }))
}
Token::LeftParen => parse_paren_cps(p, start)?,
Token::LeftBrace => parse_record_expr_cps(p)?,
Token::LeftBracket => parse_list_cps(p, start)?,
Token::Glsl(src) => {
p.advance();
Step::Done(p.spanned_from(start, Expr::GLSLExpression(src)))
}
Token::If => parse_if_expr_cps(p)?,
Token::Case => parse_case_expr_cps(p)?,
Token::Let => parse_let_expr_cps(p)?,
Token::Backslash => parse_lambda_expr_cps(p)?,
Token::Dot => {
p.advance();
match p.peek().clone() {
Token::LowerName(name) => {
p.advance();
Step::Done(p.spanned_from(start, Expr::RecordAccessFunction(name)))
}
_ => return Err(p.error("expected field name after `.`")),
}
}
_ => {
return Err(p.error(format!(
"expected expression, found {}",
super::describe(p.peek())
)));
}
};
Ok(match step {
Step::Done(expr) => Step::Done(parse_record_access_chain(p, expr)?),
Step::NeedExpr(cont) => Step::NeedExpr(Box::new(move |p, sub_expr| {
let inner = cont(p, sub_expr)?;
apply_record_access(p, inner)
})),
})
}
fn apply_record_access(p: &mut Parser, step: Step) -> ParseResult<Step> {
match step {
Step::Done(expr) => Ok(Step::Done(parse_record_access_chain(p, expr)?)),
Step::NeedExpr(cont) => Ok(Step::NeedExpr(Box::new(move |p, sub_expr| {
let inner = cont(p, sub_expr)?;
apply_record_access(p, inner)
}))),
}
}
fn parse_record_access_chain(
p: &mut Parser,
mut expr: Spanned<Expr>,
) -> ParseResult<Spanned<Expr>> {
while matches!(p.peek(), Token::Dot) {
let dot_end = p.peek_span().start;
let expr_end = expr.span.end;
if dot_end.offset != expr_end.offset {
break;
}
p.advance();
match p.peek().clone() {
Token::LowerName(name) => {
let start = expr.span.start;
let name_tok = p.advance();
let field = Spanned::new(name_tok.span, name);
expr = p.spanned_from(
start,
Expr::RecordAccess {
record: Box::new(expr),
field,
},
);
}
_ => {
return Err(p.error("expected field name after `.`"));
}
}
}
Ok(expr)
}
fn parse_if_expr_cps(p: &mut Parser) -> ParseResult<Step> {
let start = p.current_pos();
p.expect(&Token::If)?;
let cond_snapshot = p.pending_comments_snapshot();
Ok(Step::NeedExpr(Box::new(move |p, mut condition| {
attach_pre_body_comments(p, &mut condition, cond_snapshot);
if_after_condition(p, start, Vec::new(), condition)
})))
}
fn if_after_condition(
p: &mut Parser,
start: Position,
chain: Vec<IfChainEntry>,
condition: Spanned<Expr>,
) -> ParseResult<Step> {
p.expect(&Token::Then)?;
let then_snapshot = p.pending_comments_snapshot();
p.skip_whitespace();
Ok(Step::NeedExpr(Box::new(move |p, then_branch| {
if_after_then(p, start, chain, condition, then_branch, then_snapshot)
})))
}
fn if_after_then(
p: &mut Parser,
start: Position,
mut chain: Vec<IfChainEntry>,
condition: Spanned<Expr>,
mut then_branch: Spanned<Expr>,
then_snapshot: usize,
) -> ParseResult<Step> {
attach_pre_body_comments(p, &mut then_branch, then_snapshot);
p.expect(&Token::Else)?;
let trailing_comments = p.take_pending_comments_since(then_snapshot);
chain.push(IfChainEntry {
start,
condition,
then_branch,
trailing_comments,
});
let else_snapshot = p.pending_comments_snapshot();
p.skip_whitespace();
if matches!(p.peek(), Token::If) {
let next_start = p.current_pos();
p.expect(&Token::If)?;
Ok(Step::NeedExpr(Box::new(move |p, condition| {
if_after_condition(p, next_start, chain, condition)
})))
} else {
Ok(Step::NeedExpr(Box::new(move |p, else_branch| {
let mut result = else_branch;
attach_pre_body_comments(p, &mut result, else_snapshot);
for entry in chain.into_iter().rev() {
result = p.spanned_from(
entry.start,
Expr::IfElse {
branches: vec![IfBranch {
condition: entry.condition,
then_branch: entry.then_branch,
trailing_comments: entry.trailing_comments,
}],
else_branch: Box::new(result),
},
);
}
Ok(Step::Done(result))
})))
}
}
pub(super) fn attach_pre_body_comments(p: &mut Parser, body: &mut Spanned<Expr>, snapshot: usize) {
let body_start = body.span.start.offset;
let pending = p.take_pending_comments_since(snapshot);
if pending.is_empty() {
return;
}
let mut pre: Vec<Spanned<Comment>> = Vec::new();
let mut post: Vec<Spanned<Comment>> = Vec::new();
for c in pending {
if c.span.end.offset <= body_start {
pre.push(c);
} else {
post.push(c);
}
}
if !post.is_empty() {
p.restore_pending_comments(post);
}
if !pre.is_empty() {
let mut combined = pre;
combined.extend(std::mem::take(&mut body.comments));
body.comments = combined;
}
}
fn parse_case_expr_cps(p: &mut Parser) -> ParseResult<Step> {
let start = p.current_pos();
let comments_snapshot = p.pending_comments_snapshot();
p.expect(&Token::Case)?;
Ok(Step::NeedExpr(Box::new(move |p, subject| {
case_after_subject(p, start, subject, comments_snapshot)
})))
}
fn case_after_subject(
p: &mut Parser,
start: Position,
subject: Spanned<Expr>,
comments_snapshot: usize,
) -> ParseResult<Step> {
p.expect(&Token::Of)?;
p.skip_whitespace();
let branch_col = p.current_column();
case_next_branch(p, start, subject, Vec::new(), branch_col, comments_snapshot)
}
fn case_next_branch(
p: &mut Parser,
start: Position,
subject: Spanned<Expr>,
branches: Vec<CaseBranch>,
branch_col: u32,
comments_snapshot: usize,
) -> ParseResult<Step> {
p.skip_whitespace();
if !case_should_continue(p, &branches, branch_col, start) {
if branches.is_empty() {
return Err(p.error("expected at least one case branch"));
}
return Ok(Step::Done(p.spanned_from(
start,
Expr::CaseOf {
expr: Box::new(subject),
branches,
},
)));
}
let branch_comments = p.take_pending_comments_since(comments_snapshot);
let mut pat = parse_pattern(p)?;
if !branch_comments.is_empty() {
pat.comments = branch_comments;
}
p.expect(&Token::Arrow)?;
p.app_context_col = None;
let body_snapshot = p.pending_comments_snapshot();
Ok(Step::NeedExpr(Box::new(move |p, mut body| {
attach_pre_body_comments(p, &mut body, body_snapshot);
let mut branches = branches;
branches.push(CaseBranch { pattern: pat, body });
case_next_branch(p, start, subject, branches, branch_col, comments_snapshot)
})))
}
fn case_should_continue(
p: &mut Parser,
branches: &[CaseBranch],
branch_col: u32,
start: Position,
) -> bool {
if p.is_eof() {
return false;
}
let col = p.current_column();
if p.in_paren_context() {
if !branches.is_empty() && col != branch_col {
return false;
}
} else {
if col < branch_col {
return false;
}
if col < start.column + 1 && !branches.is_empty() {
return false;
}
}
can_start_pattern(p.peek())
}
fn parse_let_expr_cps(p: &mut Parser) -> ParseResult<Step> {
let start = p.current_pos();
let comments_snapshot = p.pending_comments_snapshot();
p.expect(&Token::Let)?;
let let_col = start.column;
p.skip_whitespace();
let decl_col = p.current_column();
let_next_decl(p, start, let_col, decl_col, Vec::new(), comments_snapshot)
}
fn let_next_decl(
p: &mut Parser,
start: Position,
let_col: u32,
decl_col: u32,
declarations: Vec<Spanned<LetDeclaration>>,
comments_snapshot: usize,
) -> ParseResult<Step> {
p.skip_whitespace();
if p.is_eof()
|| matches!(p.peek(), Token::In)
|| (!p.in_paren_context() && p.current_column() < decl_col)
|| (!p.in_paren_context() && p.current_column() < let_col + 1)
{
let trailing_comments = p.take_pending_comments_since(comments_snapshot);
p.expect(&Token::In)?;
p.app_context_col = None;
let in_body_snapshot = p.pending_comments_snapshot();
return Ok(Step::NeedExpr(Box::new(move |p, mut body| {
attach_pre_body_comments(p, &mut body, in_body_snapshot);
Ok(Step::Done(p.spanned_from(
start,
Expr::LetIn {
declarations,
body: Box::new(body),
trailing_comments,
},
)))
})));
}
let let_decl_comments = p.take_pending_comments_since(comments_snapshot);
let decl_start = p.current_pos();
match p.peek().clone() {
Token::LowerName(_) => {
let next = p.peek_nth_past_whitespace(1);
if matches!(next, Token::Colon) {
let sig = parse_signature(p)?;
p.skip_whitespace();
let impl_start = p.current_pos();
let name = p.expect_lower_name()?;
let mut args = Vec::new();
loop {
p.skip_whitespace();
if matches!(p.peek(), Token::Equals) {
break;
}
if !can_start_pattern(p.peek()) {
break;
}
args.push(parse_pattern(p)?);
}
p.expect(&Token::Equals)?;
p.app_context_col = None;
let body_snapshot = p.pending_comments_snapshot();
Ok(Step::NeedExpr(Box::new(move |p, mut body| {
attach_pre_body_comments(p, &mut body, body_snapshot);
let implementation = FunctionImplementation { name, args, body };
let func = Function {
documentation: None,
signature: Some(sig),
declaration: p.spanned_from(impl_start, implementation),
};
let decl = LetDeclaration::Function(Box::new(func));
let mut spanned_decl = p.spanned_from(decl_start, decl);
if !let_decl_comments.is_empty() {
spanned_decl.comments = let_decl_comments;
}
let mut declarations = declarations;
declarations.push(spanned_decl);
let_next_decl(p, start, let_col, decl_col, declarations, comments_snapshot)
})))
} else {
let impl_start = p.current_pos();
let name = p.expect_lower_name()?;
let mut args = Vec::new();
loop {
p.skip_whitespace();
if matches!(p.peek(), Token::Equals) {
break;
}
if !can_start_pattern(p.peek()) {
break;
}
args.push(parse_pattern(p)?);
}
p.expect(&Token::Equals)?;
p.app_context_col = None;
let body_snapshot = p.pending_comments_snapshot();
Ok(Step::NeedExpr(Box::new(move |p, mut body| {
attach_pre_body_comments(p, &mut body, body_snapshot);
let implementation = FunctionImplementation { name, args, body };
let func = Function {
documentation: None,
signature: None,
declaration: p.spanned_from(impl_start, implementation),
};
let decl = LetDeclaration::Function(Box::new(func));
let mut spanned_decl = p.spanned_from(decl_start, decl);
if !let_decl_comments.is_empty() {
spanned_decl.comments = let_decl_comments;
}
let mut declarations = declarations;
declarations.push(spanned_decl);
let_next_decl(p, start, let_col, decl_col, declarations, comments_snapshot)
})))
}
}
_ => {
let pattern = parse_pattern(p)?;
p.expect(&Token::Equals)?;
p.app_context_col = None;
let body_snapshot = p.pending_comments_snapshot();
Ok(Step::NeedExpr(Box::new(move |p, mut body| {
attach_pre_body_comments(p, &mut body, body_snapshot);
let decl = LetDeclaration::Destructuring {
pattern: Box::new(pattern),
body: Box::new(body),
};
let mut spanned_decl = p.spanned_from(decl_start, decl);
if !let_decl_comments.is_empty() {
spanned_decl.comments = let_decl_comments;
}
let mut declarations = declarations;
declarations.push(spanned_decl);
let_next_decl(p, start, let_col, decl_col, declarations, comments_snapshot)
})))
}
}
}
fn parse_signature(p: &mut Parser) -> ParseResult<Spanned<Signature>> {
let start = p.current_pos();
let name = p.expect_lower_name()?;
p.expect(&Token::Colon)?;
let type_annotation = parse_type(p)?;
let trailing_comment = {
let end_line = type_annotation.span.end.line;
let end_offset = type_annotation.span.end.offset;
if let Some(last) = p.collected_comments.last()
&& last.span.start.line == end_line
&& last.span.start.offset >= end_offset
{
p.collected_comments.pop()
} else {
None
}
};
Ok(p.spanned_from(
start,
Signature {
name,
type_annotation,
trailing_comment,
},
))
}
fn parse_lambda_expr_cps(p: &mut Parser) -> ParseResult<Step> {
let start = p.current_pos();
p.expect(&Token::Backslash)?;
let mut args = Vec::new();
loop {
p.skip_whitespace();
if matches!(p.peek(), Token::Arrow) {
break;
}
args.push(parse_pattern(p)?);
}
if args.is_empty() {
return Err(p.error("expected at least one argument in lambda"));
}
p.expect(&Token::Arrow)?;
let body_snapshot = p.pending_comments_snapshot();
Ok(Step::NeedExpr(Box::new(move |p, mut body| {
attach_pre_body_comments(p, &mut body, body_snapshot);
Ok(Step::Done(p.spanned_from(
start,
Expr::Lambda {
args,
body: Box::new(body),
},
)))
})))
}
fn parse_paren_cps(p: &mut Parser, start: Position) -> ParseResult<Step> {
p.advance(); let first_snapshot = p.pending_comments_snapshot();
p.skip_whitespace();
if matches!(p.peek(), Token::RightParen) {
p.advance();
return Ok(Step::Done(p.spanned_from(start, Expr::Unit)));
}
match p.peek().clone() {
Token::Operator(op) => {
p.advance();
p.skip_whitespace();
if matches!(p.peek(), Token::RightParen) {
p.advance();
return Ok(Step::Done(p.spanned_from(start, Expr::PrefixOperator(op))));
}
return Err(p.error("expected `)` after operator in prefix expression"));
}
Token::Minus => {
let minus_start = p.current_pos();
p.advance();
p.skip_whitespace();
if matches!(p.peek(), Token::RightParen) {
p.advance();
return Ok(Step::Done(
p.spanned_from(start, Expr::PrefixOperator("-".into())),
));
}
let app_step = parse_application_cps(p)?;
return paren_neg_after_app(p, start, minus_start, app_step);
}
_ => {}
}
Ok(Step::NeedExpr(Box::new(move |p, first| {
paren_after_first(p, start, first, first_snapshot)
})))
}
fn paren_neg_after_app(
p: &mut Parser,
paren_start: Position,
minus_start: Position,
step: Step,
) -> ParseResult<Step> {
match step {
Step::NeedExpr(cont) => Ok(Step::NeedExpr(Box::new(move |p, sub_expr| {
let step = cont(p, sub_expr)?;
paren_neg_after_app(p, paren_start, minus_start, step)
}))),
Step::Done(operand) => {
let neg_span = Span::new(minus_start, operand.span.end);
let neg_spanned = Spanned::new(neg_span, Expr::Negation(Box::new(operand)));
let bin_step = binary_loop(p, Vec::new(), neg_spanned)?;
let snap = p.pending_comments_snapshot();
paren_after_binary(p, paren_start, bin_step, snap)
}
}
}
fn paren_after_binary(
p: &mut Parser,
paren_start: Position,
step: Step,
first_snapshot: usize,
) -> ParseResult<Step> {
match step {
Step::NeedExpr(cont) => Ok(Step::NeedExpr(Box::new(move |p, sub_expr| {
let step = cont(p, sub_expr)?;
paren_after_binary(p, paren_start, step, first_snapshot)
}))),
Step::Done(first) => paren_after_first(p, paren_start, first, first_snapshot),
}
}
fn paren_after_first(
p: &mut Parser,
start: Position,
mut first: Spanned<Expr>,
first_snapshot: usize,
) -> ParseResult<Step> {
attach_pre_body_comments(p, &mut first, first_snapshot);
p.skip_whitespace();
match p.peek() {
Token::Comma => {
p.advance(); let elements = vec![first];
let next_snapshot = p.pending_comments_snapshot();
Ok(Step::NeedExpr(Box::new(move |p, next| {
tuple_after_element(p, start, elements, next, next_snapshot)
})))
}
Token::RightParen => {
let first_end = first.span.end.offset;
let rparen_start = p.peek_span().start.offset;
let all = p.take_pending_comments_since(0);
let (trailing, keep): (Vec<_>, Vec<_>) = all.into_iter().partition(|c| {
c.span.start.offset >= first_end && c.span.end.offset <= rparen_start
});
p.restore_pending_comments(keep);
p.advance();
Ok(Step::Done(p.spanned_from(
start,
Expr::Parenthesized {
expr: Box::new(first),
trailing_comments: trailing,
},
)))
}
_ => Err(p.error("expected `,` or `)` in expression")),
}
}
fn tuple_after_element(
p: &mut Parser,
start: Position,
mut elements: Vec<Spanned<Expr>>,
mut elem: Spanned<Expr>,
elem_snapshot: usize,
) -> ParseResult<Step> {
let next_snapshot = elem_snapshot;
attach_pre_body_comments(p, &mut elem, elem_snapshot);
elements.push(elem);
p.skip_whitespace();
if p.eat(&Token::Comma) {
Ok(Step::NeedExpr(Box::new(move |p, next| {
tuple_after_element(p, start, elements, next, next_snapshot)
})))
} else {
p.expect(&Token::RightParen)?;
Ok(Step::Done(p.spanned_from(start, Expr::Tuple(elements))))
}
}
fn parse_list_cps(p: &mut Parser, start: Position) -> ParseResult<Step> {
let bracket_col = start.column;
p.advance(); let elem_snapshot = p.pending_comments_snapshot();
p.skip_whitespace();
if matches!(p.peek(), Token::RightBracket) {
p.advance();
return Ok(Step::Done(p.spanned_from(
start,
Expr::List {
elements: Vec::new(),
element_inline_comments: Vec::new(),
trailing_comments: Vec::new(),
},
)));
}
p.app_context_col = Some(bracket_col);
Ok(Step::NeedExpr(Box::new(move |p, first| {
list_after_element(
p,
start,
bracket_col,
Vec::new(),
Vec::new(),
first,
elem_snapshot,
)
})))
}
fn take_element_inline_trailing(p: &mut Parser, elem: &Spanned<Expr>) -> Option<Spanned<Comment>> {
let elem_end_line = elem.span.end.line;
let all = p.take_pending_comments_since(0);
let mut inline: Option<Spanned<Comment>> = None;
let mut keep: Vec<Spanned<Comment>> = Vec::with_capacity(all.len());
for c in all {
if inline.is_none()
&& c.span.start.line == elem_end_line
&& c.span.start.offset >= elem.span.end.offset
{
inline = Some(c);
} else {
keep.push(c);
}
}
p.restore_pending_comments(keep);
inline
}
fn list_after_element(
p: &mut Parser,
start: Position,
bracket_col: u32,
mut elements: Vec<Spanned<Expr>>,
mut element_inline_comments: Vec<Option<Spanned<Comment>>>,
mut elem: Spanned<Expr>,
elem_snapshot: usize,
) -> ParseResult<Step> {
let next_snapshot = elem_snapshot;
attach_pre_body_comments(p, &mut elem, elem_snapshot);
let inline = take_element_inline_trailing(p, &elem);
element_inline_comments.push(inline);
elements.push(elem);
if p.eat(&Token::Comma) {
p.app_context_col = Some(bracket_col);
Ok(Step::NeedExpr(Box::new(move |p, next| {
list_after_element(
p,
start,
bracket_col,
elements,
element_inline_comments,
next,
next_snapshot,
)
})))
} else {
let last_line = elements.last().map(|e| e.span.end.line).unwrap_or(0);
let rbracket_line = p.peek_span().start.line;
let all = p.take_pending_comments_since(0);
let (trailing, keep): (Vec<_>, Vec<_>) = all
.into_iter()
.partition(|c| c.span.start.line > last_line && c.span.end.line < rbracket_line);
p.restore_pending_comments(keep);
p.expect(&Token::RightBracket)?;
let element_inline_comments = if element_inline_comments.iter().all(|c| c.is_none()) {
Vec::new()
} else {
element_inline_comments
};
Ok(Step::Done(p.spanned_from(
start,
Expr::List {
elements,
element_inline_comments,
trailing_comments: trailing,
},
)))
}
}
fn parse_record_expr_cps(p: &mut Parser) -> ParseResult<Step> {
let start = p.current_pos();
p.expect(&Token::LeftBrace)?;
p.skip_whitespace();
if matches!(p.peek(), Token::RightBrace) {
p.advance();
return Ok(Step::Done(p.spanned_from(start, Expr::Record(Vec::new()))));
}
if matches!(p.peek(), Token::LowerName(_)) {
let save_pos = p.pos;
if let Ok(base_name) = p.expect_lower_name() {
p.skip_whitespace();
if matches!(p.peek(), Token::Pipe) {
p.advance(); let first_boundary = p.prev_token_end_offset();
return record_parse_setter(
p,
start,
Vec::new(),
RecordContext::Update(base_name),
first_boundary,
);
}
p.pos = save_pos;
} else {
p.pos = save_pos;
}
}
let first_boundary = start.offset + 1;
record_parse_setter(p, start, Vec::new(), RecordContext::Plain, first_boundary)
}
fn record_parse_setter(
p: &mut Parser,
rec_start: Position,
setters: Vec<Spanned<RecordSetter>>,
context: RecordContext,
prev_boundary_offset: usize,
) -> ParseResult<Step> {
let setter_start = p.current_pos();
let field = p.expect_lower_name()?;
p.expect(&Token::Equals)?;
p.app_context_col = Some(rec_start.column);
Ok(Step::NeedExpr(Box::new(move |p, value| {
record_after_value(
p,
rec_start,
setters,
setter_start,
field,
value,
context,
prev_boundary_offset,
)
})))
}
#[allow(clippy::too_many_arguments)]
fn record_after_value(
p: &mut Parser,
rec_start: Position,
mut setters: Vec<Spanned<RecordSetter>>,
setter_start: Position,
field: Spanned<String>,
value: Spanned<Expr>,
context: RecordContext,
prev_boundary_offset: usize,
) -> ParseResult<Step> {
let value_end_offset = value.span.end.offset;
let trailing_comment = {
let value_end_line = value.span.end.line;
let pending = &p.collected_comments;
if let Some(last) = pending.last()
&& last.span.start.line == value_end_line
&& last.span.start.offset >= value_end_offset
{
p.collected_comments.pop()
} else {
None
}
};
let field_start_offset = setter_start.offset;
let mut leading: Vec<Spanned<Comment>> = Vec::new();
let mut i = 0;
while i < p.collected_comments.len() {
let c = &p.collected_comments[i];
if c.span.start.offset >= prev_boundary_offset && c.span.end.offset <= field_start_offset {
leading.push(p.collected_comments.remove(i));
} else {
i += 1;
}
}
let value_start_offset = value.span.start.offset;
let mut value_leading: Vec<Spanned<Comment>> = Vec::new();
let mut i = 0;
while i < p.collected_comments.len() {
let c = &p.collected_comments[i];
if c.span.start.offset >= field_start_offset && c.span.end.offset <= value_start_offset {
value_leading.push(p.collected_comments.remove(i));
} else {
i += 1;
}
}
let mut value = value;
if !value_leading.is_empty() {
let mut merged = value_leading;
merged.extend(value.comments);
value.comments = merged;
}
let setter = RecordSetter {
field,
value,
trailing_comment,
};
let mut spanned = p.spanned_from(setter_start, setter);
if !leading.is_empty() {
spanned.comments = leading;
}
setters.push(spanned);
let next_boundary_offset = value_end_offset;
if p.eat(&Token::Comma) {
record_parse_setter(p, rec_start, setters, context, next_boundary_offset)
} else {
p.expect(&Token::RightBrace)?;
match context {
RecordContext::Plain => {
Ok(Step::Done(p.spanned_from(rec_start, Expr::Record(setters))))
}
RecordContext::Update(base) => Ok(Step::Done(p.spanned_from(
rec_start,
Expr::RecordUpdate {
base,
updates: setters,
},
))),
}
}
}
fn parse_qualified_value(p: &mut Parser) -> ParseResult<(Vec<String>, String)> {
let first = p.expect_upper_name()?;
let mut parts: Vec<String> = vec![first.value];
while matches!(p.peek(), Token::Dot) {
let next = p.peek_nth_past_whitespace(1);
match next {
Token::UpperName(_) | Token::LowerName(_) => {}
_ => break,
}
let dot_pos = p.pos;
p.advance();
match p.peek().clone() {
Token::UpperName(name) => {
p.advance();
parts.push(name);
}
Token::LowerName(name) => {
p.advance();
parts.push(name);
break;
}
_ => {
p.pos = dot_pos;
break;
}
}
}
let name = parts.pop().unwrap();
Ok((parts, name))
}
fn can_start_pattern(tok: &Token) -> bool {
matches!(
tok,
Token::Underscore
| Token::LowerName(_)
| Token::UpperName(_)
| Token::Literal(_)
| Token::Minus
| Token::LeftParen
| Token::LeftBrace
| Token::LeftBracket
)
}
fn can_start_atomic_expr(tok: &Token) -> bool {
matches!(
tok,
Token::Literal(_)
| Token::LowerName(_)
| Token::UpperName(_)
| Token::LeftParen
| Token::LeftBrace
| Token::LeftBracket
| Token::Dot
| Token::Glsl(_)
| Token::If
| Token::Case
| Token::Let
| Token::Backslash
)
}
fn is_unary_minus_arg(p: &Parser) -> bool {
if !matches!(p.peek(), Token::Minus) {
return false;
}
let minus = p.current();
let next = p.peek_raw_next();
if !can_start_atomic_expr(&next.value) {
return false;
}
minus.span.end.offset == next.span.start.offset
}