use brink_syntax_native::SyntaxKind as N;
use brink_syntax_native::SyntaxNode;
use brink_syntax_native::ast::{self, AstNode as _};
use crate::hir::FileId;
use crate::provenance::NodeClass;
use crate::{
Block, Choice, ChoiceSet, ChoiceSetContext, Content, ContentPart, Diagnostic, DiagnosticCode,
DivertPath, Expr, Stmt, ThreadStart,
};
use super::body::{lower_divert_like, lower_items, lower_span, push_escape, push_text};
use super::cond::{lower_alternation, lower_conditional};
use super::control_flow::lower_as_binding;
use super::element::Elements;
use super::expr::lower_path;
use super::provenance::native_provenance;
fn diag(file: FileId, range: rowan::TextRange, code: DiagnosticCode) -> Diagnostic {
Diagnostic {
file,
range,
message: code.title().to_string(),
code,
}
}
fn name_from(tok: Option<brink_syntax_native::SyntaxToken>) -> Option<crate::Name> {
tok.map(|t| crate::Name {
text: t.text().to_string(),
range: t.text_range(),
})
}
pub(super) fn lower_choice_point(
file_id: FileId,
cp: &ast::ChoicePoint,
continuation: Block,
elements: &mut Elements,
diags: &mut Vec<Diagnostic>,
) -> Vec<Stmt> {
let mut preamble: Vec<Stmt> = Vec::new();
let mut choices: Vec<Choice> = Vec::new();
for child in cp.syntax().children() {
match child.kind() {
N::CHOICE => {
if let Some(c) = ast::Choice::cast(child) {
choices.push(lower_choice(file_id, &c, elements, diags));
}
}
N::ELSE_BRANCH => {
if let Some(eb) = ast::ElseBranch::cast(child) {
choices.push(lower_fallback_choice(file_id, &eb, elements, diags));
}
}
N::SPLICE => {
if let Some(sp) = ast::Splice::cast(child)
&& let Some(ts) = lower_splice(file_id, &sp, diags)
{
if let Some(last) = choices.last_mut() {
last.body.stmts.push(Stmt::ThreadStart(ts));
last.body.recompute_tail();
} else {
preamble.push(Stmt::ThreadStart(ts));
}
}
}
_ => {}
}
}
let mut out = preamble;
out.push(Stmt::ChoiceSet(Box::new(ChoiceSet {
choices,
continuation,
context: ChoiceSetContext::Inline,
depth: 0,
gather_id: None,
})));
out
}
fn lower_choice(
file_id: FileId,
c: &ast::Choice,
elements: &mut Elements,
diags: &mut Vec<Diagnostic>,
) -> Choice {
let is_sticky = c.is_sticky();
let label = c.label().and_then(|l| name_from(l.name_token()));
let condition = c
.guard()
.and_then(|g| g.expr())
.map(|e| super::expr::lower_expr(file_id, &e, diags));
let binding = condition.as_ref().and_then(|cond| {
lower_as_binding(
file_id,
c.guard().and_then(|g| g.as_binding()).as_ref(),
cond,
diags,
)
});
let (start_content, start_divert) = lower_choice_region(
file_id,
c.start_content().map(|n| n.syntax().clone()),
elements,
diags,
);
let (bracket_content, bracket_divert) = lower_choice_region(
file_id,
c.bracket_content().map(|n| n.syntax().clone()),
elements,
diags,
);
let (inner_content, inner_divert) = lower_choice_region(
file_id,
c.inner_content().map(|n| n.syntax().clone()),
elements,
diags,
);
let region_diverts = [start_divert, bracket_divert, inner_divert];
if region_diverts.iter().filter(|d| d.is_some()).count() > 1 {
diags.push(diag(file_id, c.syntax().text_range(), DiagnosticCode::E129));
}
let divert = region_diverts.into_iter().flatten().next();
let mut stmts = Vec::new();
if let Some(d) = divert {
stmts.push(d);
}
stmts.push(Stmt::EndOfLine);
if let Some(body) = c.body() {
let items: Vec<SyntaxNode> = body.items().collect();
stmts.extend(lower_items(file_id, &items, 0, elements, diags));
}
Choice {
ptr: native_provenance(file_id, NodeClass::Choice, c.syntax()),
is_sticky,
is_fallback: false,
label,
condition,
binding,
start_content,
bracket_content,
inner_content,
tags: Vec::new(),
body: {
let tail = crate::tail_from_stmts(&stmts);
Block {
label: None,
stmts,
container_id: None,
tail,
}
},
container_id: None,
}
}
fn lower_fallback_choice(
file_id: FileId,
eb: &ast::ElseBranch,
elements: &mut Elements,
diags: &mut Vec<Diagnostic>,
) -> Choice {
let mut stmts = vec![Stmt::EndOfLine];
if let Some(body) = eb.choice_body() {
let items: Vec<SyntaxNode> = body.items().collect();
stmts.extend(lower_items(file_id, &items, 0, elements, diags));
}
Choice {
ptr: native_provenance(file_id, NodeClass::Choice, eb.syntax()),
is_sticky: false,
is_fallback: true,
label: None,
condition: None,
binding: None,
start_content: None,
bracket_content: None,
inner_content: None,
tags: Vec::new(),
body: {
let tail = crate::tail_from_stmts(&stmts);
Block {
label: None,
stmts,
container_id: None,
tail,
}
},
container_id: None,
}
}
fn lower_choice_region(
file_id: FileId,
node: Option<SyntaxNode>,
elements: &mut Elements,
diags: &mut Vec<Diagnostic>,
) -> (Option<Content>, Option<Stmt>) {
let Some(node) = node else {
return (None, None);
};
let ptr = if node.text_range().is_empty() {
None
} else {
Some(native_provenance(file_id, NodeClass::Content, &node))
};
let mut parts = Vec::new();
let mut divert = None;
for child in node.children() {
match child.kind() {
N::TEXT => push_text(&mut parts, &child),
N::ESCAPE => push_escape(&mut parts, &child),
N::INTERPOLATION => {
parts.push(super::body::lower_interpolation(file_id, &child, diags));
}
N::SPAN => parts.push(lower_span(file_id, &child, elements, diags)),
N::GLUE_NODE => parts.push(ContentPart::Glue),
N::DIVERT_STMT | N::TUNNEL_CALL => {
if divert.is_none() {
divert = lower_divert_like(file_id, &child, diags);
} else {
diags.push(diag(file_id, child.text_range(), DiagnosticCode::E129));
}
}
N::CONDITIONAL_BLOCK => {
if let Some(cb) = ast::ConditionalBlock::cast(child) {
parts.push(ContentPart::InlineConditional(lower_conditional(
file_id, &cb, elements, diags,
)));
}
}
N::ALTERNATION_BLOCK => {
if let Some(ab) = ast::AlternationBlock::cast(child) {
parts.push(ContentPart::InlineSequence(lower_alternation(
file_id, &ab, elements, diags, false,
)));
}
}
N::ERROR => {}
_ => diags.push(diag(file_id, child.text_range(), DiagnosticCode::E129)),
}
}
(
Some(Content {
ptr,
parts,
tags: Vec::new(),
}),
divert,
)
}
fn lower_splice(
file_id: FileId,
sp: &ast::Splice,
diags: &mut Vec<Diagnostic>,
) -> Option<ThreadStart> {
let Some(p) = sp.path() else {
diags.push(diag(
file_id,
sp.syntax().text_range(),
DiagnosticCode::E012,
));
return None;
};
let path = lower_path(&p);
let args: Vec<Expr> = sp
.arg_list()
.into_iter()
.flat_map(|al| al.syntax().children().collect::<Vec<_>>())
.map(|n| super::expr::lower_expr(file_id, &n, diags))
.collect();
Some(ThreadStart {
ptr: native_provenance(file_id, NodeClass::ThreadStart, sp.syntax()),
target: crate::DivertTarget {
path: DivertPath::Path(path),
args,
},
})
}