use std::sync::Arc;
use rustc_hash::{FxHashMap, FxHashSet};
use typst::{syntax::Source, utils::LazyHash};
use super::SharedContext;
use crate::syntax::{
Decl, Expr, ExprInfo, LexicalScope, SelectExpr,
expr::{ExportLookup, declaration_of, expr_of},
};
type DeclarationInterfaces = Arc<FxHashMap<typst::syntax::FileId, Arc<LazyHash<LexicalScope>>>>;
struct DeclarationInputs {
interfaces: DeclarationInterfaces,
}
impl ExportLookup for DeclarationInputs {
fn exports_of(
&mut self,
ctx: &Arc<SharedContext>,
importer: typst::syntax::FileId,
source: &Source,
) -> Option<Arc<LazyHash<LexicalScope>>> {
self.interfaces
.get(&source.id())
.cloned()
.or_else(|| ctx.external_exports_of(importer, source))
}
}
struct DeclarationBuilder {
sources: Vec<Source>,
inputs: DeclarationInputs,
}
impl DeclarationBuilder {
fn new(sources: impl IntoIterator<Item = Source>) -> Self {
let sources: Vec<_> = sources.into_iter().collect();
let component: FxHashSet<_> = sources.iter().map(Source::id).collect();
assert_eq!(
component.len(),
sources.len(),
"an expression component cannot contain duplicate sources"
);
let empty_inputs = component
.into_iter()
.map(|fid| (fid, Arc::new(LazyHash::new(LexicalScope::default()))))
.collect();
Self {
sources,
inputs: DeclarationInputs {
interfaces: Arc::new(empty_inputs),
},
}
}
fn finish(mut self, ctx: Arc<SharedContext>) -> DeclarationInterfaces {
loop {
let mut outputs = FxHashMap::default();
for source in &self.sources {
let interface = declaration_of(ctx.clone(), source.clone(), &mut self.inputs);
outputs.insert(source.id(), interface);
}
if self.sources.len() == 1 {
return Arc::new(outputs);
}
let mut stable = true;
for source in &self.sources {
let fid = source.id();
let input = self
.inputs
.interfaces
.get(&fid)
.expect("every declaration round must contain every component member");
let output = outputs
.get(&fid)
.expect("every declaration round must produce every component member");
assert!(
input.keys().all(|name| output.contains_key(name)),
"component declaration names must grow monotonically for {fid:?}"
);
stable &= input.keys().count() == output.keys().count();
}
if stable {
return Arc::new(outputs);
}
let symbolic_inputs = outputs
.iter()
.map(|(&fid, interface)| {
let module = Expr::Decl(Decl::module(fid).into());
let mut symbolic = LexicalScope::default();
for name in interface.keys() {
let field = Decl::lit_(name.clone()).into();
symbolic.insert_mut(
name.clone(),
Expr::Select(SelectExpr::new(field, module.clone())),
);
}
(fid, Arc::new(LazyHash::new(symbolic)))
})
.collect();
self.inputs = DeclarationInputs {
interfaces: Arc::new(symbolic_inputs),
};
}
}
}
pub(super) struct ExprStage {
component: FxHashSet<typst::syntax::FileId>,
declarations: DeclarationInterfaces,
checking: FxHashSet<typst::syntax::FileId>,
completed: FxHashMap<typst::syntax::FileId, ExprInfo>,
}
impl ExprStage {
pub(super) fn new(ctx: Arc<SharedContext>, sources: impl IntoIterator<Item = Source>) -> Self {
let sources: Vec<_> = sources.into_iter().collect();
let component: FxHashSet<_> = sources.iter().map(Source::id).collect();
let declarations = DeclarationBuilder::new(sources).finish(ctx);
assert!(
declarations.len() == component.len()
&& declarations.keys().all(|fid| component.contains(fid)),
"sealed declarations must cover the complete expression component"
);
Self {
component,
declarations,
checking: Default::default(),
completed: Default::default(),
}
}
fn contains(&self, fid: &typst::syntax::FileId) -> bool {
self.component.contains(fid)
}
pub(super) fn analyze(&mut self, ctx: Arc<SharedContext>, source: Source) -> ExprInfo {
assert!(
self.contains(&source.id()),
"cross-component expression analysis must use the shared component entry point"
);
if let Some(cached) = self.completed.get(&source.id()) {
return cached.clone();
}
assert!(
self.checking.insert(source.id()),
"recursive expression analysis must resolve through declared exports"
);
let guard = ctx.expr_stage_stat(source.id());
guard.miss();
let info = expr_of(ctx, source, self);
self.checking.remove(&info.fid);
self.completed.insert(info.fid, info.clone());
info
}
}
impl ExportLookup for ExprStage {
fn exports_of(
&mut self,
ctx: &Arc<SharedContext>,
importer: typst::syntax::FileId,
source: &Source,
) -> Option<Arc<LazyHash<LexicalScope>>> {
if !self.contains(&source.id()) {
return ctx.external_exports_of(importer, source);
}
if let Some(info) = self.completed.get(&source.id()) {
return Some(info.exports.clone());
}
if self.checking.contains(&source.id()) {
return self.declarations.get(&source.id()).cloned();
}
Some(self.analyze(ctx.clone(), source.clone()).exports.clone())
}
}