use brink_syntax::ast::{self, AstNode, AstPtr, SyntaxNodePtr};
use crate::{
DiagnosticCode, Divert, DivertPath, DivertTarget, Expr, RefKind, Return, Stmt, ThreadStart,
TunnelCall,
};
use super::context::{LowerScope, LowerSink, Lowered};
use super::expr::LowerExpr;
use super::helpers::{lower_path, path_full_name};
pub trait LowerDivert {
fn lower_divert(&self, scope: &LowerScope, sink: &mut impl LowerSink) -> Lowered<Stmt>;
}
impl LowerDivert for ast::DivertNode {
fn lower_divert(&self, scope: &LowerScope, sink: &mut impl LowerSink) -> Lowered<Stmt> {
let range = self.syntax().text_range();
if let Some(thread) = self.thread_start() {
if let Some(ts) = lower_thread_target(&thread, scope, sink) {
return Ok(Stmt::ThreadStart(ts));
}
return Err(sink.diagnose(range, DiagnosticCode::E013));
}
if let Some(tunnel) = self.tunnel_call() {
let targets: Vec<DivertTarget> = tunnel
.targets()
.filter_map(|t| lower_divert_target_with_args(&t, scope, sink))
.collect();
if !targets.is_empty() {
return Ok(Stmt::TunnelCall(TunnelCall {
ptr: AstPtr::new(self),
targets,
}));
}
return Err(sink.diagnose(range, DiagnosticCode::E012));
}
if let Some(tunnel_onwards) = self.tunnel_onwards() {
let onwards_targets: Vec<DivertTarget> = tunnel_onwards
.targets()
.filter_map(|t| lower_divert_target_with_args(&t, scope, sink))
.collect();
if let Some(tc) = tunnel_onwards.tunnel_call() {
let mut targets = onwards_targets;
targets.extend(
tc.targets()
.filter_map(|t| lower_divert_target_with_args(&t, scope, sink)),
);
if !targets.is_empty() {
return Ok(Stmt::TunnelCall(TunnelCall {
ptr: AstPtr::new(self),
targets,
}));
}
} else if let Some(target) = onwards_targets.into_iter().next() {
match &target.path {
DivertPath::Path(path) => {
return Ok(Stmt::Return(Return {
ptr: None,
value: Some(Expr::DivertTarget(path.clone())),
onwards_args: target.args,
}));
}
DivertPath::Done => {
return Ok(Stmt::Divert(Divert {
ptr: Some(SyntaxNodePtr::from_node(self.syntax())),
target: DivertTarget {
path: DivertPath::Done,
args: Vec::new(),
},
}));
}
DivertPath::End => {
return Ok(Stmt::Divert(Divert {
ptr: Some(SyntaxNodePtr::from_node(self.syntax())),
target: DivertTarget {
path: DivertPath::End,
args: Vec::new(),
},
}));
}
}
}
return Ok(Stmt::Return(Return {
ptr: None,
value: None,
onwards_args: Vec::new(),
}));
}
if let Some(simple) = self.simple_divert() {
let targets: Vec<DivertTarget> = simple
.targets()
.filter_map(|t| lower_divert_target_with_args(&t, scope, sink))
.collect();
return match targets.len() {
0 => Err(sink.diagnose(range, DiagnosticCode::E012)),
1 => Ok(Stmt::Divert(Divert {
ptr: Some(SyntaxNodePtr::from_node(self.syntax())),
#[expect(clippy::unwrap_used, reason = "length checked to be 1")]
target: targets.into_iter().next().unwrap(),
})),
_ => Ok(Stmt::TunnelCall(TunnelCall {
ptr: AstPtr::new(self),
targets,
})),
};
}
Err(sink.diagnose(range, DiagnosticCode::E012))
}
}
fn lower_thread_target(
thread: &ast::ThreadStart,
scope: &LowerScope,
sink: &mut impl LowerSink,
) -> Option<ThreadStart> {
let ast_path = thread.target()?;
let path = lower_path(&ast_path);
let full = path_full_name(&path);
sink.add_unresolved(
&full,
ast_path.syntax().text_range(),
RefKind::Divert,
&scope.to_scope(),
None,
);
let args: Vec<Expr> = thread
.arg_list()
.map(|al| {
al.args()
.filter_map(|a| a.lower_expr(scope, sink).ok())
.collect()
})
.unwrap_or_default();
Some(ThreadStart {
ptr: AstPtr::new(thread),
target: DivertTarget {
path: DivertPath::Path(path),
args,
},
})
}
pub fn lower_divert_target_with_args(
t: &ast::DivertTargetWithArgs,
scope: &LowerScope,
sink: &mut impl LowerSink,
) -> Option<DivertTarget> {
let path = lower_divert_path(t, scope, sink)?;
let args: Vec<Expr> = t
.arg_list()
.map(|al| {
al.args()
.filter_map(|a| a.lower_expr(scope, sink).ok())
.collect()
})
.unwrap_or_default();
Some(DivertTarget { path, args })
}
fn lower_divert_path(
t: &ast::DivertTargetWithArgs,
scope: &LowerScope,
sink: &mut impl LowerSink,
) -> Option<DivertPath> {
if t.done_kw().is_some() {
return Some(DivertPath::Done);
}
if t.end_kw().is_some() {
return Some(DivertPath::End);
}
let ast_path = t.path()?;
let path = lower_path(&ast_path);
let full = path_full_name(&path);
sink.add_unresolved(
&full,
ast_path.syntax().text_range(),
RefKind::Divert,
&scope.to_scope(),
None,
);
Some(DivertPath::Path(path))
}