use brink_syntax_native::SyntaxKind as N;
use brink_syntax_native::ast::{self, AstNode as _};
use brink_syntax_native::{SyntaxNode, SyntaxToken};
use crate::hir::FileId;
use crate::provenance::{KindToken, NodeClass, Provenance};
use crate::{
Block, CondBranch, CondKind, Conditional, Diagnostic, DiagnosticCode, Sequence, SequenceBranch,
SequenceType, Stmt,
};
use super::body::{lower_block, lower_items};
use super::element::Elements;
use super::expr::lower_expr;
use super::provenance::native_provenance;
fn diag(file: FileId, range: rowan::TextRange, code: DiagnosticCode) -> Diagnostic {
Diagnostic {
file,
range,
message: code.title().to_string(),
code,
}
}
pub(super) fn lower_conditional(
file_id: FileId,
cb: &ast::ConditionalBlock,
elements: &mut Elements,
diags: &mut Vec<Diagnostic>,
) -> Conditional {
let ptr = native_provenance(file_id, NodeClass::Conditional, cb.syntax());
if cb.is_if() {
let Some(cond_node) = cb.condition() else {
diags.push(diag(
file_id,
cb.syntax().text_range(),
DiagnosticCode::E020,
));
return Conditional {
ptr,
kind: CondKind::InitialCondition,
branches: Vec::new(),
};
};
let condition = lower_expr(file_id, &cond_node, diags);
let binding = super::control_flow::lower_as_binding(
file_id,
cb.as_binding().as_ref(),
&condition,
diags,
);
let mut branches = Vec::new();
let if_arm = cb.if_arm();
let if_ptr = if_arm.as_ref().map_or(ptr, |arm| {
native_provenance(file_id, NodeClass::ConditionalBranch, arm.syntax())
});
let if_body = if_arm.map_or_else(Block::default, |arm| {
lower_arm_items(file_id, arm.syntax(), elements, diags)
});
branches.push(CondBranch {
ptr: if_ptr,
condition: Some(condition),
binding,
body: if_body,
container_id: None,
});
if let Some(eb) = cb.else_arm() {
let else_ptr = native_provenance(file_id, NodeClass::ConditionalBranch, eb.syntax());
let else_body = lower_arm_items(file_id, eb.syntax(), elements, diags);
branches.push(CondBranch {
ptr: else_ptr,
condition: None,
binding: None,
body: else_body,
container_id: None,
});
}
return Conditional {
ptr,
kind: CondKind::InitialCondition,
branches,
};
}
if cb.is_match() {
let subject = if let Some(n) = cb.condition() {
lower_expr(file_id, &n, diags)
} else {
diags.push(diag(
file_id,
cb.syntax().text_range(),
DiagnosticCode::E020,
));
crate::Expr::Null
};
let branches: Vec<CondBranch> = cb
.match_arms()
.map(|arm| lower_match_arm(file_id, &arm, elements, diags))
.collect();
return Conditional {
ptr,
kind: CondKind::Switch(subject),
branches,
};
}
Conditional {
ptr,
kind: CondKind::IfElse,
branches: Vec::new(),
}
}
fn lower_match_arm(
file_id: FileId,
arm: &ast::MatchArm,
elements: &mut Elements,
diags: &mut Vec<Diagnostic>,
) -> CondBranch {
let condition = arm.pattern_expr().map(|n| lower_expr(file_id, &n, diags));
let body = if let Some(block) = arm.block() {
lower_block(file_id, &block, elements, diags)
} else if let Some(expr_node) = arm.bare_expr() {
let stmts = vec![Stmt::ExprStmt(lower_expr(file_id, &expr_node, diags))];
let tail = crate::tail_from_stmts(&stmts);
Block {
label: None,
stmts,
container_id: None,
tail,
}
} else {
diags.push(diag(
file_id,
arm.syntax().text_range(),
DiagnosticCode::E020,
));
Block::default()
};
CondBranch {
ptr: native_provenance(file_id, NodeClass::ConditionalBranch, arm.syntax()),
condition,
binding: None,
body,
container_id: None,
}
}
fn lower_arm_items(
file_id: FileId,
arm_syntax: &SyntaxNode,
elements: &mut Elements,
diags: &mut Vec<Diagnostic>,
) -> Block {
if let Some(block_node) = arm_syntax.children().find(|n| n.kind() == N::BLOCK) {
let items: Vec<SyntaxNode> = block_node.children().collect();
let stmts = lower_items(file_id, &items, 0, elements, diags);
let tail = crate::tail_from_stmts(&stmts);
Block {
label: None,
stmts,
container_id: None,
tail,
}
} else {
let items: Vec<SyntaxNode> = arm_syntax.children().collect();
let stmts = lower_items(file_id, &items, 0, elements, diags);
let tail = crate::tail_from_stmts(&stmts);
Block {
label: None,
stmts,
container_id: None,
tail,
}
}
}
pub(super) fn lower_alternation(
file_id: FileId,
ab: &ast::AlternationBlock,
elements: &mut Elements,
diags: &mut Vec<Diagnostic>,
is_block_level: bool,
) -> Sequence {
let ptr = native_provenance(file_id, NodeClass::Sequence, ab.syntax());
let kind = sequence_type(ab);
let entries: Vec<ast::Entry> = ab.entries().collect();
let branches: Vec<SequenceBranch> = if entries.is_empty() {
lower_inline_alternation_branches(file_id, ab.syntax(), elements, diags, is_block_level)
} else {
entries
.iter()
.map(|e| {
let branch_ptr = native_provenance(file_id, NodeClass::SequenceBranch, e.syntax());
let items: Vec<SyntaxNode> = e.items().collect();
let mut stmts = lower_items(file_id, &items, 0, elements, diags);
if is_block_level {
stmts.insert(0, Stmt::EndOfLine);
}
let tail = crate::tail_from_stmts(&stmts);
SequenceBranch {
ptr: branch_ptr,
body: Block {
label: None,
stmts,
container_id: None,
tail,
},
}
})
.collect()
};
Sequence {
ptr,
kind,
branches,
container_id: None,
counter_id: None,
}
}
fn sequence_type(ab: &ast::AlternationBlock) -> SequenceType {
let marker: Option<SyntaxToken> = ab.marker_token();
match marker.map(|t| t.kind()) {
Some(N::TILDE) => SequenceType::SHUFFLE,
Some(N::AMP) => SequenceType::CYCLE,
Some(N::BANG) => SequenceType::ONCE,
_ => SequenceType::STOPPING,
}
}
fn lower_inline_alternation_branches(
file_id: FileId,
ab_syntax: &SyntaxNode,
elements: &mut Elements,
diags: &mut Vec<Diagnostic>,
is_block_level: bool,
) -> Vec<SequenceBranch> {
let mut branches = Vec::new();
let mut current: Vec<SyntaxNode> = Vec::new();
let mut past_marker = false;
for el in ab_syntax.children_with_tokens() {
match el {
rowan::NodeOrToken::Node(n) if n.kind() == N::ALTERNATION_MARKER => {
past_marker = true;
}
rowan::NodeOrToken::Node(n) if past_marker => current.push(n),
rowan::NodeOrToken::Token(t) if past_marker && t.kind() == N::PIPE => {
branches.push(finish_inline_branch(
file_id,
ab_syntax,
¤t,
elements,
diags,
is_block_level,
));
current.clear();
}
_ => {}
}
}
branches.push(finish_inline_branch(
file_id,
ab_syntax,
¤t,
elements,
diags,
is_block_level,
));
branches
}
fn branch_span(file_id: FileId, items: &[SyntaxNode], ab_syntax: &SyntaxNode) -> Provenance {
let range = items
.iter()
.map(SyntaxNode::text_range)
.fold(None::<rowan::TextRange>, |acc, r| {
Some(acc.map_or(r, |a| a.cover(r)))
});
Provenance::new(
file_id,
range.unwrap_or_else(|| ab_syntax.text_range()),
KindToken::synthetic(NodeClass::SequenceBranch),
)
}
fn finish_inline_branch(
file_id: FileId,
ab_syntax: &SyntaxNode,
items: &[SyntaxNode],
elements: &mut Elements,
diags: &mut Vec<Diagnostic>,
is_block_level: bool,
) -> SequenceBranch {
let ptr = branch_span(file_id, items, ab_syntax);
let mut stmts = super::body::lower_content_run(file_id, items, None, elements, diags, false);
if is_block_level {
stmts.insert(0, Stmt::EndOfLine);
}
let tail = crate::tail_from_stmts(&stmts);
SequenceBranch {
ptr,
body: Block {
label: None,
stmts,
container_id: None,
tail,
},
}
}