mod import;
mod include;
mod knot;
mod stitch;
use brink_syntax::ast::{self, AstNode};
use brink_syntax::{SyntaxKind, SyntaxNode};
use crate::symbols::project_manifest;
use crate::{Block, DiagnosticCode, FileId, HirFile, Import, IncludeSite, Knot, SymbolManifest};
use super::block::lower_weave_body;
use super::context::{EffectSink, LowerScope, LowerSink};
use super::decl::DeclareSymbols;
use import::lower_import;
use include::lower_include;
use knot::lower_knot;
use stitch::lower_top_level_stitch;
pub fn lower(
file_id: FileId,
file: &ast::SourceFile,
) -> (HirFile, SymbolManifest, Vec<crate::Diagnostic>) {
let mut scope = LowerScope::new(file_id);
let mut sink = EffectSink::new(file_id);
let hir = lower_source_file(&mut scope, &mut sink, file);
emit_author_warnings(&mut sink, file.syntax(), SkipInsideKnots::No);
let manifest = project_manifest(&hir);
let diagnostics = sink.finish();
(hir, manifest, diagnostics)
}
pub fn lower_single_knot(
file_id: FileId,
knot: &ast::KnotDef,
) -> (Option<Knot>, Vec<crate::Diagnostic>) {
let mut scope = LowerScope::new(file_id);
let mut sink = EffectSink::new(file_id);
let result = lower_knot(&mut scope, &mut sink, knot).ok();
emit_author_warnings(&mut sink, knot.syntax(), SkipInsideKnots::No);
let diagnostics = sink.finish();
(result, diagnostics)
}
pub fn lower_declarations(
file_id: FileId,
file: &ast::SourceFile,
) -> (HirFile, Vec<crate::Diagnostic>) {
let mut scope = LowerScope::new(file_id);
let mut sink = EffectSink::new(file_id);
let head = lower_decl_head(&mut scope, &mut sink, file);
let hir = assemble_hir_file(&mut sink, file, head, Vec::new(), Block::default());
let diagnostics = sink.finish();
(hir, diagnostics)
}
pub fn lower_top_level(
file_id: FileId,
file: &ast::SourceFile,
) -> (Block, Vec<Knot>, Vec<crate::Diagnostic>) {
let mut scope = LowerScope::new(file_id);
let mut sink = EffectSink::new(file_id);
let top_level_knots: Vec<_> = file
.stitches()
.filter_map(|stitch| lower_top_level_stitch(&mut scope, &mut sink, &stitch).ok())
.collect();
let root_content = lower_weave_body(file.syntax(), &scope, &mut sink);
emit_author_warnings(&mut sink, file.syntax(), SkipInsideKnots::Yes);
let diagnostics = sink.finish();
(root_content, top_level_knots, diagnostics)
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum SkipInsideKnots {
Yes,
No,
}
fn emit_author_warnings(sink: &mut EffectSink, node: &SyntaxNode, skip: SkipInsideKnots) {
for warning in node.descendants().filter_map(ast::AuthorWarning::cast) {
if skip == SkipInsideKnots::Yes
&& warning
.syntax()
.ancestors()
.any(|a| a.kind() == SyntaxKind::KNOT_DEF)
{
continue;
}
let text = warning.text();
let message = if text.is_empty() {
"TODO".to_owned()
} else {
format!("TODO: {text}")
};
sink.diagnose_with_message(warning.syntax().text_range(), message, DiagnosticCode::E189);
}
}
struct DeclHead {
variables: Vec<crate::VarDecl>,
constants: Vec<crate::ConstDecl>,
lists: Vec<crate::ListDecl>,
structs: Vec<crate::StructDecl>,
externals: Vec<crate::ExternalDecl>,
includes: Vec<IncludeSite>,
}
fn lower_decl_head(
scope: &mut LowerScope,
sink: &mut impl LowerSink,
file: &ast::SourceFile,
) -> DeclHead {
let variables = file
.syntax()
.descendants()
.filter_map(ast::VarDecl::cast)
.filter_map(|v| v.declare_and_lower(scope, sink).ok())
.collect();
let constants = file
.syntax()
.descendants()
.filter_map(ast::ConstDecl::cast)
.filter_map(|c| c.declare_and_lower(scope, sink).ok())
.collect();
let lists = file
.syntax()
.descendants()
.filter_map(ast::ListDecl::cast)
.filter_map(|l| l.declare_and_lower(scope, sink).ok())
.collect();
let structs = file
.struct_decls()
.filter_map(|s| s.declare_and_lower(scope, sink).ok())
.collect();
let externals = file
.externals()
.filter_map(|e| e.declare_and_lower(scope, sink).ok())
.collect();
let includes: Vec<IncludeSite> = file
.includes()
.filter_map(|i| lower_include(scope, &i, sink).ok())
.collect();
DeclHead {
variables,
constants,
lists,
structs,
externals,
includes,
}
}
fn lower_source_file(
scope: &mut LowerScope,
sink: &mut impl LowerSink,
file: &ast::SourceFile,
) -> HirFile {
let head = lower_decl_head(scope, sink, file);
let mut knots: Vec<Knot> = file
.knots()
.filter_map(|k| lower_knot(scope, sink, &k).ok())
.collect();
for stitch in file.stitches() {
if let Ok(knot) = lower_top_level_stitch(scope, sink, &stitch) {
knots.push(knot);
}
}
let root_content = lower_weave_body(file.syntax(), scope, sink);
assemble_hir_file(sink, file, head, knots, root_content)
}
fn assemble_hir_file(
sink: &mut impl LowerSink,
file: &ast::SourceFile,
head: DeclHead,
knots: Vec<Knot>,
root_content: Block,
) -> HirFile {
let module =
super::directive::file_module_declaration(file.syntax(), sink).map(|(name, range)| {
crate::ModuleDecl {
name,
range,
was: None,
}
});
let module_was = super::directive::file_module_was(file.syntax(), sink);
let module = match (module, module_was) {
(Some(mut m), Some((old_name, was_range, _))) => {
if old_name == m.name {
sink.diagnose(was_range, DiagnosticCode::E095);
} else {
m.was = Some((old_name, was_range));
}
Some(m)
}
(Some(m), None) => Some(m),
(None, Some((_, was_range, attaches_to_decl))) => {
if !attaches_to_decl {
sink.diagnose(was_range, DiagnosticCode::E049);
}
None
}
(None, None) => None,
};
let imports: Vec<Import> = file.imports().map(|i| lower_import(&i)).collect();
let visibility = super::directive::collect_visibility_directives(file.syntax());
let was_directives = super::directive::collect_was_directives(file.syntax());
HirFile {
root_content,
knots,
variables: head.variables,
constants: head.constants,
lists: head.lists,
structs: head.structs,
externals: head.externals,
includes: head.includes,
module,
imports,
visibility,
was_directives,
allow_scopes: Vec::new(),
element_matches: Vec::new(),
cue_names: Vec::new(),
native: false,
claim_handlers: Vec::new(),
dispatch_handlers: Vec::new(),
}
}