use brink_syntax::ast::{self, AstNode, AstPtr};
use crate::{
Block, ContainerPtr, DiagnosticCode, Divert, DivertPath, DivertTarget, Knot, Name, Param,
ParamInfo, Path, Stitch, Stmt, SymbolKind,
};
use super::super::block::LowerBlock;
use super::super::context::{LowerScope, LowerSink, Lowered};
use super::super::doc_comment::{DocPolicy, parse_doc_comment};
use super::super::helpers::{make_name, name_from_ident};
use super::stitch::lower_stitch;
use crate::symbols::LocalSymbol;
pub(super) fn lower_knot(
scope: &mut LowerScope,
sink: &mut impl LowerSink,
knot: &ast::KnotDef,
) -> Lowered<Knot> {
let range = knot.syntax().text_range();
let header = knot
.header()
.ok_or_else(|| sink.diagnose(range, DiagnosticCode::E001))?;
let ident = header
.identifier()
.ok_or_else(|| sink.diagnose(range, DiagnosticCode::E001))?;
let name_text = header
.name()
.ok_or_else(|| sink.diagnose(range, DiagnosticCode::E001))?;
let name = make_name(name_text.clone(), ident.syntax().text_range());
let is_function = header.is_function();
let params = lower_knot_params(header.params(), sink);
let param_infos: Vec<ParamInfo> = params
.iter()
.map(|p| ParamInfo {
name: p.name.text.clone(),
is_ref: p.is_ref,
is_divert: p.is_divert,
})
.collect();
let detail = if is_function {
Some("function".to_owned())
} else {
None
};
let (doc, issues) = parse_doc_comment(knot.syntax(), DocPolicy::CALLABLE);
issues.diagnose(sink);
sink.declare_full(
SymbolKind::Knot,
&name_text,
ident.syntax().text_range(),
param_infos,
detail,
doc,
);
scope.current_knot = Some(name_text.clone());
for p in ¶ms {
sink.add_local(LocalSymbol {
name: p.name.text.clone(),
range: p.name.range,
scope: scope.to_scope(),
kind: crate::SymbolKind::Param,
param_detail: Some(ParamInfo {
name: p.name.text.clone(),
is_ref: p.is_ref,
is_divert: p.is_divert,
}),
});
}
let (body, stitches) = knot.body().map_or_else(
|| (Block::default(), Vec::new()),
|b| lower_knot_body(scope, sink, &b, &name_text),
);
scope.current_knot = None;
scope.current_stitch = None;
Ok(Knot {
ptr: ContainerPtr::Knot(AstPtr::new(knot)),
name,
is_function,
params,
body,
stitches,
})
}
pub(super) fn lower_knot_body(
scope: &mut LowerScope,
sink: &mut impl LowerSink,
body: &ast::KnotBody,
knot_name: &str,
) -> (Block, Vec<Stitch>) {
let stitches: Vec<Stitch> = body
.stitches()
.filter_map(|s| lower_stitch(scope, sink, &s, knot_name).ok())
.collect();
let mut block = body.lower_block(scope, sink).unwrap_or_default();
if block.stmts.is_empty()
&& let Some(first) = stitches.first()
&& first.params.is_empty()
{
sink.add_unresolved(
&first.name.text,
first.name.range,
crate::symbols::RefKind::Divert,
&scope.to_scope(),
None,
);
block.stmts.push(Stmt::Divert(Divert {
ptr: None,
target: DivertTarget {
path: DivertPath::Path(Path {
segments: vec![Name {
text: first.name.text.clone(),
range: first.name.range,
}],
range: first.name.range,
}),
args: Vec::new(),
},
}));
}
(block, stitches)
}
pub(super) fn lower_knot_params(
params: Option<ast::KnotParams>,
sink: &mut impl LowerSink,
) -> Vec<Param> {
params
.map(|p| {
p.params()
.filter_map(|pd| lower_param(&pd, sink).ok())
.collect()
})
.unwrap_or_default()
}
fn lower_param(p: &ast::KnotParamDecl, sink: &mut impl LowerSink) -> Lowered<Param> {
let range = p.syntax().text_range();
let ident = p
.identifier()
.ok_or_else(|| sink.diagnose(range, DiagnosticCode::E003))?;
let name = name_from_ident(&ident).ok_or_else(|| sink.diagnose(range, DiagnosticCode::E003))?;
Ok(Param {
name,
is_ref: p.is_ref(),
is_divert: p.is_divert(),
})
}