use brink_syntax::SyntaxKind;
use brink_syntax::ast::{self, AstNode};
use crate::Provenance;
use crate::provenance::{KindToken, NodeClass};
use crate::{Block, CondBranch, CondKind, Conditional, DiagnosticCode, Expr, Stmt};
use super::super::block::{lower_branch_body, wrap_content_as_block};
use super::super::context::{LowerScope, LowerSink, Lowered};
use super::super::expr::LowerExpr;
use super::LowerConditional;
impl LowerConditional for ast::ConditionalWithExpr {
fn lower_conditional(
&self,
scope: &LowerScope,
sink: &mut impl LowerSink,
) -> Lowered<Conditional> {
let ptr = scope.prov(NodeClass::Conditional, self.syntax());
let range = self.syntax().text_range();
let condition = self
.condition()
.ok_or_else(|| sink.diagnose(range, DiagnosticCode::E020))
.and_then(|e| e.lower_expr(scope, sink))?;
Ok(lower_conditional_with_expr(
self, &condition, ptr, scope, sink,
))
}
}
fn lower_conditional_with_expr(
cond: &ast::ConditionalWithExpr,
condition: &Expr,
ptr: Provenance,
scope: &LowerScope,
sink: &mut impl LowerSink,
) -> Conditional {
let mut branches = Vec::new();
if let Some(body) = cond.branchless_body() {
return lower_branchless_body(&body, condition, ptr, scope, sink);
}
if let Some(inline_branches) = cond.inline_branches() {
let mut first = true;
for b in inline_branches.branches() {
let cond_expr = if first {
first = false;
Some(condition.clone())
} else {
None
};
branches.push(CondBranch {
ptr: scope.prov(NodeClass::ConditionalBranch, b.syntax()),
condition: cond_expr,
binding: None,
body: wrap_content_as_block(b.syntax(), scope, sink),
container_id: None,
});
}
return Conditional {
ptr,
kind: CondKind::InitialCondition,
branches,
};
}
if let Some(ml_branches) = cond.multiline_branches() {
let all_have_conditions = ml_branches
.branches()
.all(|b| b.is_else() || b.condition().is_some());
for b in ml_branches.branches() {
let cond_expr = if b.is_else() {
None
} else {
b.condition().and_then(|e| e.lower_expr(scope, sink).ok())
};
let mut body = b.body().map_or_else(Block::default, |body| {
lower_branch_body(body.syntax(), scope, sink)
});
body.stmts.insert(0, Stmt::EndOfLine);
branches.push(CondBranch {
ptr: scope.prov(NodeClass::ConditionalBranch, b.syntax()),
condition: cond_expr,
binding: None,
body,
container_id: None,
});
}
let kind = if all_have_conditions {
CondKind::Switch(condition.clone())
} else {
if let Some(first_no_cond) = branches.iter_mut().find(|b| b.condition.is_none()) {
first_no_cond.condition = Some(condition.clone());
}
CondKind::InitialCondition
};
return Conditional {
ptr,
kind,
branches,
};
}
branches.push(CondBranch {
ptr,
condition: Some(condition.clone()),
binding: None,
body: Block::default(),
container_id: None,
});
Conditional {
ptr,
kind: CondKind::InitialCondition,
branches,
}
}
fn lower_branchless_body(
body: &ast::BranchlessCondBody,
condition: &Expr,
ptr: Provenance,
scope: &LowerScope,
sink: &mut impl LowerSink,
) -> Conditional {
use super::super::block::LowerBlock;
let mut branches = Vec::new();
let branch_ptr = branchless_first_arm_span(body, scope);
let block = body.lower_block(scope, sink).unwrap_or_default();
branches.push(CondBranch {
ptr: branch_ptr,
condition: Some(condition.clone()),
binding: None,
body: block,
container_id: None,
});
if let Some(else_branch) = body.else_branch()
&& let Some(ml_branch) = else_branch.branch()
{
let else_ptr = scope.prov(NodeClass::ConditionalBranch, ml_branch.syntax());
let mut else_body = ml_branch.body().map_or_else(Block::default, |body| {
lower_branch_body(body.syntax(), scope, sink)
});
else_body.stmts.insert(0, Stmt::EndOfLine);
branches.push(CondBranch {
ptr: else_ptr,
condition: None,
binding: None,
body: else_body,
container_id: None,
});
}
Conditional {
ptr,
kind: CondKind::InitialCondition,
branches,
}
}
fn branchless_first_arm_span(body: &ast::BranchlessCondBody, scope: &LowerScope) -> Provenance {
let range = body
.syntax()
.children()
.filter(|n| n.kind() != SyntaxKind::ELSE_BRANCH)
.map(|n| n.text_range())
.fold(None::<rowan::TextRange>, |acc, r| {
Some(acc.map_or(r, |a| a.cover(r)))
});
Provenance::new(
scope.file_id,
range.unwrap_or_else(|| body.syntax().text_range()),
KindToken::synthetic(NodeClass::ConditionalBranch),
)
}