Phase 5: Editor und inkrementellen Compiler implementieren und archivieren
This commit is contained in:
@@ -22,135 +22,237 @@ fn diagnostic(message: impl Into<String>) -> Diagnostic {
|
||||
}
|
||||
}
|
||||
|
||||
/// Prozesslokaler Cache: unverknüpfte Produkte, niemals ein bereits gelinktes Programm.
|
||||
#[derive(Default)]
|
||||
pub struct ProjectCompiler {
|
||||
parsed: Vec<Parsed>,
|
||||
products: Vec<Product>,
|
||||
pub stats: CompileStats,
|
||||
}
|
||||
#[derive(Debug, Default, Clone, Copy)]
|
||||
pub struct CompileStats {
|
||||
pub parsed: usize,
|
||||
pub compiled: usize,
|
||||
pub reused: usize,
|
||||
}
|
||||
struct Parsed {
|
||||
unit: SourceUnit,
|
||||
prefix: Vec<tb_frontend::source::SourceFile>,
|
||||
sources: Vec<tb_frontend::source::SourceFile>,
|
||||
module: Module,
|
||||
diagnostics: Vec<Diagnostic>,
|
||||
names: HashSet<String>,
|
||||
}
|
||||
struct Product {
|
||||
module: Module,
|
||||
catalog: FormCatalog,
|
||||
code: CompiledModule,
|
||||
commons: Vec<tb_frontend::hir::HCommon>,
|
||||
}
|
||||
|
||||
pub fn compile_project(
|
||||
name: &str,
|
||||
units: &[SourceUnit],
|
||||
catalog: &FormCatalog,
|
||||
forms: &[FormFile],
|
||||
) -> Result<CompiledModule, Vec<Diagnostic>> {
|
||||
if units.is_empty() || units.len() > u16::MAX as usize {
|
||||
return Err(vec![diagnostic("Projekt ohne Module oder zu viele Module")]);
|
||||
}
|
||||
let mut sources = Vec::new();
|
||||
let mut diagnostics = Vec::new();
|
||||
let mut names = HashSet::new();
|
||||
let parsed: Vec<_> = units
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(id, unit)| {
|
||||
let (module, errors) = unit.parse(id as u16, &mut sources);
|
||||
if !names.insert(unit.name.to_uppercase()) {
|
||||
let mut error = diagnostic(format!("Duplicate definition: module {}", unit.name));
|
||||
error.pos = module_pos(&module);
|
||||
error.file = unit.segments.first().map(|s| s.file.clone());
|
||||
diagnostics.push(error);
|
||||
}
|
||||
diagnostics.extend(errors);
|
||||
module
|
||||
})
|
||||
.collect();
|
||||
locate_diagnostics(&mut diagnostics, &sources);
|
||||
if !diagnostics.is_empty() {
|
||||
return Err(diagnostics);
|
||||
}
|
||||
let mut catalog = catalog.clone();
|
||||
if catalog.find("SCREEN").is_none() {
|
||||
catalog.add(
|
||||
"SCREEN",
|
||||
tb_frontend::forms::ObjectClass::Screen,
|
||||
None,
|
||||
false,
|
||||
);
|
||||
}
|
||||
for module in &parsed {
|
||||
if module
|
||||
.body
|
||||
ProjectCompiler::default().compile(name, units, catalog, forms)
|
||||
}
|
||||
|
||||
impl ProjectCompiler {
|
||||
pub fn compile(
|
||||
&mut self,
|
||||
name: &str,
|
||||
units: &[SourceUnit],
|
||||
catalog: &FormCatalog,
|
||||
forms: &[FormFile],
|
||||
) -> Result<CompiledModule, Vec<Diagnostic>> {
|
||||
self.stats = CompileStats::default();
|
||||
if units.is_empty() || units.len() > u16::MAX as usize {
|
||||
return Err(vec![diagnostic("Projekt ohne Module oder zu viele Module")]);
|
||||
}
|
||||
let mut sources = Vec::new();
|
||||
let mut diagnostics = Vec::new();
|
||||
let mut names = HashSet::new();
|
||||
let parsed: Vec<_> = units
|
||||
.iter()
|
||||
.any(|s| matches!(s, Stmt::MetaForm { .. }))
|
||||
&& catalog.find(&module.name).is_none()
|
||||
{
|
||||
.enumerate()
|
||||
.map(|(id, unit)| {
|
||||
let cached = self
|
||||
.parsed
|
||||
.get(id)
|
||||
.filter(|p| p.unit == *unit && p.prefix == sources);
|
||||
let (module, errors) = if let Some(p) = cached {
|
||||
sources = p.sources.clone();
|
||||
(p.module.clone(), p.diagnostics.clone())
|
||||
} else {
|
||||
let prefix = sources.clone();
|
||||
let (module, errors) = unit.parse(id as u16, &mut sources);
|
||||
let entry = Parsed {
|
||||
unit: unit.clone(),
|
||||
prefix,
|
||||
sources: sources.clone(),
|
||||
module: module.clone(),
|
||||
diagnostics: errors.clone(),
|
||||
names: unit
|
||||
.segments
|
||||
.iter()
|
||||
.flat_map(|s| tb_frontend::lexer::lex(&s.text).tokens)
|
||||
.filter_map(|t| match t.kind {
|
||||
tb_frontend::lexer::TokenKind::Ident { name, .. } => Some(name),
|
||||
_ => None,
|
||||
})
|
||||
.collect(),
|
||||
};
|
||||
if id < self.parsed.len() {
|
||||
self.parsed[id] = entry;
|
||||
} else {
|
||||
self.parsed.push(entry);
|
||||
}
|
||||
self.stats.parsed += 1;
|
||||
(module, errors)
|
||||
};
|
||||
if !names.insert(unit.name.to_uppercase()) {
|
||||
let mut error =
|
||||
diagnostic(format!("Duplicate definition: module {}", unit.name));
|
||||
error.pos = module_pos(&module);
|
||||
error.file = unit.segments.first().map(|s| s.file.clone());
|
||||
diagnostics.push(error);
|
||||
}
|
||||
diagnostics.extend(errors);
|
||||
module
|
||||
})
|
||||
.collect();
|
||||
locate_diagnostics(&mut diagnostics, &sources);
|
||||
if !diagnostics.is_empty() {
|
||||
return Err(diagnostics);
|
||||
}
|
||||
let mut catalog = catalog.clone();
|
||||
if catalog.find("SCREEN").is_none() {
|
||||
catalog.add(
|
||||
&module.name,
|
||||
tb_frontend::forms::ObjectClass::Form,
|
||||
"SCREEN",
|
||||
tb_frontend::forms::ObjectClass::Screen,
|
||||
None,
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
let mut exports: Vec<_> = parsed
|
||||
.iter()
|
||||
.map(|m| tb_frontend::sema::export_declarations(m, &[]))
|
||||
.collect();
|
||||
// Konstantenabhängigkeiten können beliebig über Module verteilt sein.
|
||||
// Jeder erfolgreiche Durchlauf löst mindestens eine weitere Deklaration auf.
|
||||
let constant_count: usize = exports
|
||||
.iter()
|
||||
.flatten()
|
||||
.filter(|s| matches!(s, Stmt::ConstDecl { .. }))
|
||||
.count();
|
||||
for _ in 0..constant_count {
|
||||
let mut constants: HashMap<_, Vec<_>> = HashMap::new();
|
||||
for stmt in exports.iter().flatten() {
|
||||
if let Stmt::ConstDecl { items, .. } = stmt {
|
||||
constants.entry(&items[0].0).or_default().push(stmt.clone());
|
||||
for module in &parsed {
|
||||
if module
|
||||
.body
|
||||
.iter()
|
||||
.any(|s| matches!(s, Stmt::MetaForm { .. }))
|
||||
&& catalog.find(&module.name).is_none()
|
||||
{
|
||||
catalog.add(
|
||||
&module.name,
|
||||
tb_frontend::forms::ObjectClass::Form,
|
||||
None,
|
||||
false,
|
||||
);
|
||||
}
|
||||
}
|
||||
let constants: Vec<_> = constants
|
||||
.into_values()
|
||||
.filter(|s| s.len() == 1)
|
||||
.flatten()
|
||||
.collect();
|
||||
let next: Vec<_> = parsed
|
||||
let mut exports: Vec<_> = parsed
|
||||
.iter()
|
||||
.map(|m| tb_frontend::sema::export_declarations(m, &constants))
|
||||
.map(|m| tb_frontend::sema::export_declarations(m, &[]))
|
||||
.collect();
|
||||
if next == exports {
|
||||
break;
|
||||
// Konstantenabhängigkeiten können beliebig über Module verteilt sein.
|
||||
// Jeder erfolgreiche Durchlauf löst mindestens eine weitere Deklaration auf.
|
||||
let constant_count: usize = exports
|
||||
.iter()
|
||||
.flatten()
|
||||
.filter(|s| matches!(s, Stmt::ConstDecl { .. }))
|
||||
.count();
|
||||
for _ in 0..constant_count {
|
||||
let mut constants: HashMap<_, Vec<_>> = HashMap::new();
|
||||
for stmt in exports.iter().flatten() {
|
||||
if let Stmt::ConstDecl { items, .. } = stmt {
|
||||
constants.entry(&items[0].0).or_default().push(stmt.clone());
|
||||
}
|
||||
}
|
||||
let constants: Vec<_> = constants
|
||||
.into_values()
|
||||
.filter(|s| s.len() == 1)
|
||||
.flatten()
|
||||
.collect();
|
||||
let next: Vec<_> = parsed
|
||||
.iter()
|
||||
.map(|m| tb_frontend::sema::export_declarations(m, &constants))
|
||||
.collect();
|
||||
if next == exports {
|
||||
break;
|
||||
}
|
||||
exports = next;
|
||||
}
|
||||
exports = next;
|
||||
}
|
||||
let mut parts = Vec::new();
|
||||
let mut commons = Vec::new();
|
||||
for module in &parsed {
|
||||
let mut module = module.clone();
|
||||
import_declarations(&mut module, &parsed, &exports);
|
||||
let (hir, errors) = tb_frontend::sema::lower_with_forms(&module, &catalog);
|
||||
diagnostics.extend(errors);
|
||||
if let Some(hir) = hir {
|
||||
parts.push(codegen::compile(&hir));
|
||||
commons.push(hir.commons);
|
||||
let mut parts = Vec::new();
|
||||
let mut commons = Vec::new();
|
||||
let mut products = Vec::new();
|
||||
for (index, module) in parsed.iter().enumerate() {
|
||||
let mut key = module.clone();
|
||||
import_declarations(&mut key, &parsed, &exports, Some(&self.parsed[index].names));
|
||||
let mut module = module.clone();
|
||||
import_declarations(&mut module, &parsed, &exports, None);
|
||||
if let Some(product) = self
|
||||
.products
|
||||
.iter()
|
||||
.find(|p| p.module == key && p.catalog == catalog)
|
||||
{
|
||||
parts.push(product.code.clone());
|
||||
commons.push(product.commons.clone());
|
||||
self.stats.reused += 1;
|
||||
continue;
|
||||
}
|
||||
self.stats.compiled += 1;
|
||||
let (hir, errors) = tb_frontend::sema::lower_with_forms(&module, &catalog);
|
||||
diagnostics.extend(errors);
|
||||
if let Some(hir) = hir {
|
||||
let code = codegen::compile(&hir);
|
||||
parts.push(code.clone());
|
||||
commons.push(hir.commons.clone());
|
||||
products.push(Product {
|
||||
module: key,
|
||||
catalog: catalog.clone(),
|
||||
code,
|
||||
commons: hir.commons,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
locate_diagnostics(&mut diagnostics, &sources);
|
||||
if !diagnostics.is_empty() {
|
||||
return Err(diagnostics);
|
||||
}
|
||||
let mut result = link(name, parts, &parsed, &commons).map_err(|error| {
|
||||
let mut errors = vec![error];
|
||||
locate_diagnostics(&mut errors, &sources);
|
||||
errors
|
||||
})?;
|
||||
result.sources = sources;
|
||||
let objects = FormCatalog {
|
||||
objects: result.objects.clone(),
|
||||
};
|
||||
for form in forms {
|
||||
locate_diagnostics(&mut diagnostics, &sources);
|
||||
if !diagnostics.is_empty() {
|
||||
return Err(diagnostics);
|
||||
}
|
||||
self.parsed.truncate(units.len());
|
||||
self.products.retain(|p| {
|
||||
parsed.iter().any(|m| m.name == p.module.name)
|
||||
&& !products.iter().any(|n| n.module.name == p.module.name)
|
||||
});
|
||||
self.products.extend(products);
|
||||
let mut result = link(name, parts, &parsed, &commons).map_err(|error| {
|
||||
let mut errors = vec![error];
|
||||
locate_diagnostics(&mut errors, &sources);
|
||||
errors
|
||||
})?;
|
||||
result.sources = sources;
|
||||
let objects = FormCatalog {
|
||||
objects: result.objects.clone(),
|
||||
};
|
||||
for form in forms {
|
||||
result
|
||||
.form_initial
|
||||
.extend(form.initial_values(&objects).map_err(|e| {
|
||||
vec![diagnostic(format!(
|
||||
"{}: ungültige Forms-Anfangsdaten ({e})",
|
||||
form.root.name
|
||||
))]
|
||||
})?);
|
||||
}
|
||||
result.startup_form = forms
|
||||
.first()
|
||||
.and_then(|form| objects.find(&form.root.name).map(|(id, _)| id));
|
||||
result
|
||||
.form_initial
|
||||
.extend(form.initial_values(&objects).map_err(|e| {
|
||||
vec![diagnostic(format!(
|
||||
"{}: ungültige Forms-Anfangsdaten ({e})",
|
||||
form.root.name
|
||||
))]
|
||||
})?);
|
||||
.validate()
|
||||
.map_err(|e| vec![diagnostic(e.to_string())])?;
|
||||
Ok(result)
|
||||
}
|
||||
result.startup_form = forms
|
||||
.first()
|
||||
.and_then(|form| objects.find(&form.root.name).map(|(id, _)| id));
|
||||
result
|
||||
.validate()
|
||||
.map_err(|e| vec![diagnostic(e.to_string())])?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Lokale Definition gewinnt; außerhalb ihres Moduls muss ein Name eindeutig sein.
|
||||
@@ -251,7 +353,12 @@ fn import_type(
|
||||
alias
|
||||
}
|
||||
|
||||
fn import_declarations(module: &mut Module, all: &[Module], exports: &[Vec<Stmt>]) {
|
||||
fn import_declarations(
|
||||
module: &mut Module,
|
||||
all: &[Module],
|
||||
exports: &[Vec<Stmt>],
|
||||
signature: Option<&HashSet<String>>,
|
||||
) {
|
||||
let target = all.iter().position(|m| m.name == module.name).unwrap();
|
||||
let mut procedures: HashSet<_> = module.procs.iter().map(|p| p.sig.name.clone()).collect();
|
||||
let mut local_constants = HashSet::new();
|
||||
@@ -318,6 +425,32 @@ fn import_declarations(module: &mut Module, all: &[Module], exports: &[Vec<Stmt>
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if let Some(names) = signature {
|
||||
// Nicht verwendete importierte Konstanten erzeugen weder Slots noch Tabellen.
|
||||
// TYPEs und Prozeduren dagegen gehören zu den unverketteten Tabellen.
|
||||
imported
|
||||
.retain(|s| !matches!(s, Stmt::ConstDecl {items,..} if !names.contains(&items[0].0)));
|
||||
// Importverträge enthalten Namen/Typen/Werte, keine verschobenen Rumpfzeilen
|
||||
// ihres Ursprungs. Eigene Quellorte bleiben Teil des Modulschlüssels.
|
||||
for stmt in &mut imported {
|
||||
match stmt {
|
||||
Stmt::Declare { pos, .. } | Stmt::TypeDecl { pos, .. } => {
|
||||
*pos = SourcePos::default()
|
||||
}
|
||||
Stmt::ConstDecl { items, pos } => {
|
||||
*pos = SourcePos::default();
|
||||
for (_, _, expr) in items {
|
||||
match expr {
|
||||
tb_frontend::ast::Expr::DoubleLit(_, p)
|
||||
| tb_frontend::ast::Expr::StrLit(_, p) => *p = SourcePos::default(),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
imported.append(&mut module.body);
|
||||
let mut types = Vec::new();
|
||||
imported.retain(|stmt| {
|
||||
|
||||
Reference in New Issue
Block a user