use rowan::TextRange;
use crate::host_manifest::DocBlock;
use crate::symbols::{DeclaredSymbol, LocalSymbol, RefKind, UnresolvedRef};
use crate::{Diagnostic, DiagnosticCode, FileId, ParamInfo, Scope, SymbolKind, SymbolManifest};
pub struct LowerScope {
pub file_id: FileId,
pub current_knot: Option<String>,
pub current_stitch: Option<String>,
}
impl LowerScope {
pub fn new(file_id: FileId) -> Self {
Self {
file_id,
current_knot: None,
current_stitch: None,
}
}
pub fn to_scope(&self) -> Scope {
Scope {
knot: self.current_knot.clone(),
stitch: self.current_stitch.clone(),
}
}
pub fn qualify_label(&self, label: &str) -> String {
match (&self.current_knot, &self.current_stitch) {
(Some(knot), Some(stitch)) => format!("{knot}.{stitch}.{label}"),
(Some(knot), None) => format!("{knot}.{label}"),
_ => label.to_string(),
}
}
}
pub struct Diagnosed {
_private: (),
}
impl Diagnosed {
#[cfg(test)]
pub fn test_token() -> Self {
Self { _private: () }
}
}
pub type Lowered<T> = Result<T, Diagnosed>;
pub trait LowerSink {
fn diagnose(&mut self, range: TextRange, code: DiagnosticCode) -> Diagnosed;
fn declare(&mut self, kind: SymbolKind, name: &str, range: TextRange) {
self.declare_full(kind, name, range, Vec::new(), None, None);
}
fn declare_with(
&mut self,
kind: SymbolKind,
name: &str,
range: TextRange,
params: Vec<ParamInfo>,
detail: Option<String>,
) {
self.declare_full(kind, name, range, params, detail, None);
}
fn declare_full(
&mut self,
kind: SymbolKind,
name: &str,
range: TextRange,
params: Vec<ParamInfo>,
detail: Option<String>,
doc: Option<DocBlock>,
);
fn add_local(&mut self, local: LocalSymbol);
fn add_unresolved(
&mut self,
path: &str,
range: TextRange,
kind: RefKind,
scope: &Scope,
arg_count: Option<usize>,
);
}
pub struct EffectSink {
file_id: FileId,
pub diagnostics: Vec<Diagnostic>,
pub manifest: SymbolManifest,
}
impl EffectSink {
pub fn new(file_id: FileId) -> Self {
Self {
file_id,
diagnostics: Vec::new(),
manifest: SymbolManifest::default(),
}
}
pub fn finish(self) -> (SymbolManifest, Vec<Diagnostic>) {
(self.manifest, self.diagnostics)
}
}
impl LowerSink for EffectSink {
fn diagnose(&mut self, range: TextRange, code: DiagnosticCode) -> Diagnosed {
self.diagnostics.push(Diagnostic {
file: self.file_id,
range,
message: code.title().to_string(),
code,
});
Diagnosed { _private: () }
}
fn declare_full(
&mut self,
kind: SymbolKind,
name: &str,
range: TextRange,
params: Vec<ParamInfo>,
detail: Option<String>,
doc: Option<DocBlock>,
) {
let sym = DeclaredSymbol {
name: name.to_string(),
range,
params,
detail,
};
match kind {
SymbolKind::Knot => self.manifest.knots.push(sym),
SymbolKind::Stitch => self.manifest.stitches.push(sym),
SymbolKind::Variable => self.manifest.variables.push(sym),
SymbolKind::Constant => self.manifest.constants.push(sym),
SymbolKind::List => self.manifest.lists.push(sym),
SymbolKind::External => self.manifest.externals.push(sym),
SymbolKind::Label => self.manifest.labels.push(sym),
SymbolKind::ListItem => self.manifest.list_items.push(sym),
SymbolKind::Param | SymbolKind::Temp => {}
}
if let Some(doc) = doc {
self.manifest.docs.insert((kind, name.to_string()), doc);
}
}
fn add_local(&mut self, local: LocalSymbol) {
self.manifest.locals.push(local);
}
fn add_unresolved(
&mut self,
path: &str,
range: TextRange,
kind: RefKind,
scope: &Scope,
arg_count: Option<usize>,
) {
if path.is_empty() {
return;
}
self.manifest.unresolved.push(UnresolvedRef {
path: path.to_string(),
range,
kind,
scope: scope.clone(),
arg_count,
});
}
}