Refactor Binder to use Diagnostics
The Binder and its helper functions have been updated to accept and propagate a `Diagnostics` struct. This allows for better error reporting during the binding phase, enabling the compiler to continue processing even after encountering errors and collect all issues before halting. The `BoundKind::Error` node and `StaticType::Error` are introduced as "poison" nodes/types. These nodes indicate that an error occurred during compilation, preventing further valid processing of that specific AST fragment but allowing the compiler to continue with other parts of the code. The `Parser` has also been updated to return a `Diagnostics` struct, enabling it to report errors during the initial parsing stage while still attempting to build a partial AST. This adheres to the same error recovery strategy.
This commit is contained in:
+98
-21
@@ -21,8 +21,49 @@ use crate::ast::types::{
|
||||
Identity, NodeIdentity, Object, Purity, SourceLocation, StaticType, Value,
|
||||
};
|
||||
|
||||
use crate::ast::diagnostics::{DiagnosticLevel, Diagnostics};
|
||||
pub type PipelineGenerator = Box<dyn FnMut() -> bool>;
|
||||
|
||||
pub struct CompilationResult {
|
||||
pub ast: Option<TypedNode>,
|
||||
pub diagnostics: Diagnostics,
|
||||
}
|
||||
|
||||
impl CompilationResult {
|
||||
pub fn success(ast: TypedNode) -> Self {
|
||||
Self {
|
||||
ast: Some(ast),
|
||||
diagnostics: Diagnostics::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn error(msg: impl Into<String>) -> Self {
|
||||
let mut diag = Diagnostics::new();
|
||||
diag.push_error(msg, None);
|
||||
Self {
|
||||
ast: None,
|
||||
diagnostics: diag,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_result(self) -> Result<TypedNode, String> {
|
||||
if self.diagnostics.has_errors() {
|
||||
let errors: Vec<String> = self
|
||||
.diagnostics
|
||||
.items
|
||||
.into_iter()
|
||||
.filter(|d| d.level == DiagnosticLevel::Error)
|
||||
.map(|d| d.message)
|
||||
.collect();
|
||||
Err(errors.join("\n"))
|
||||
} else if let Some(ast) = self.ast {
|
||||
Ok(ast)
|
||||
} else {
|
||||
Err("Compilation failed without diagnostics".to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Environment {
|
||||
pub global_names: Rc<RefCell<HashMap<Symbol, (GlobalIdx, Identity)>>>,
|
||||
pub global_types: Rc<RefCell<HashMap<GlobalIdx, StaticType>>>,
|
||||
@@ -77,10 +118,16 @@ impl MacroEvaluator for RuntimeMacroEvaluator {
|
||||
return Ok(Value::Object(Rc::new(arg_node.clone()) as Rc<dyn Object>));
|
||||
}
|
||||
|
||||
let (bound_ast, captures) = Binder::bind_root(self.global_names.clone(), node)?;
|
||||
let mut diag = Diagnostics::new();
|
||||
let (bound_ast, captures) = Binder::bind_root(self.global_names.clone(), node, &mut diag)?;
|
||||
let bound_ast = crate::ast::compiler::captures::CapturePass::apply(bound_ast, &captures);
|
||||
let checker = TypeChecker::new(self.global_types.clone());
|
||||
let typed_ast = checker.check(bound_ast, &[])?;
|
||||
let typed_ast = checker.check(bound_ast, &[], &mut diag);
|
||||
|
||||
if diag.has_errors() {
|
||||
return Err(diag.items.into_iter().map(|d| d.message).collect::<Vec<_>>().join("\n"));
|
||||
}
|
||||
|
||||
let exec_ast = TCO::optimize(Analyzer::analyze(&typed_ast, &HashMap::new())); // Minimal analysis for macro eval
|
||||
|
||||
let mut vm = VM::new(self.global_values.clone());
|
||||
@@ -141,8 +188,8 @@ impl Environment {
|
||||
}
|
||||
|
||||
fn eval_prelude(&self, source: &str) {
|
||||
let mut parser = crate::ast::parser::Parser::new(source).expect("Failed to parse prelude");
|
||||
let untyped_ast = parser.parse_expression().expect("Failed to parse prelude expression");
|
||||
let mut parser = crate::ast::parser::Parser::new(source);
|
||||
let untyped_ast = parser.parse_expression();
|
||||
let mut expander = self.get_expander();
|
||||
let _ = expander.expand(untyped_ast).expect("Failed to expand prelude");
|
||||
*self.macro_registry.borrow_mut() = expander.into_registry();
|
||||
@@ -204,21 +251,40 @@ impl Environment {
|
||||
}
|
||||
|
||||
pub fn dump_ast(&self, source: &str) -> Result<String, String> {
|
||||
let compiled = self.compile(source)?;
|
||||
let compiled = self.compile(source).into_result()?;
|
||||
let linked = self.link(compiled);
|
||||
Ok(Dumper::dump(&linked))
|
||||
}
|
||||
|
||||
pub fn compile(&self, source: &str) -> Result<TypedNode, String> {
|
||||
let mut parser = Parser::new(source)?;
|
||||
let untyped_ast = parser.parse_expression()?;
|
||||
pub fn compile(&self, source: &str) -> CompilationResult {
|
||||
let mut parser = Parser::new(source);
|
||||
let untyped_ast = parser.parse_expression();
|
||||
|
||||
if !parser.at_eof() {
|
||||
return Err("Unexpected trailing expressions in script.".to_string());
|
||||
parser.diagnostics.push_error("Unexpected trailing expressions in script.", None);
|
||||
return CompilationResult {
|
||||
ast: None,
|
||||
diagnostics: parser.diagnostics,
|
||||
};
|
||||
}
|
||||
let mut diagnostics = parser.diagnostics;
|
||||
|
||||
let expanded_ast = match self.get_expander().expand(untyped_ast) {
|
||||
Ok(ast) => ast,
|
||||
Err(e) => {
|
||||
diagnostics.push_error(e, None);
|
||||
return CompilationResult { ast: None, diagnostics };
|
||||
}
|
||||
};
|
||||
|
||||
let (bound_ast, captures) = match Binder::bind_root(self.global_names.clone(), &expanded_ast, &mut diagnostics) {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
diagnostics.push_error(e, None);
|
||||
return CompilationResult { ast: None, diagnostics };
|
||||
}
|
||||
};
|
||||
|
||||
let expanded_ast = self.get_expander().expand(untyped_ast)?;
|
||||
let (bound_ast, captures) = Binder::bind_root(self.global_names.clone(), &expanded_ast)?;
|
||||
let bound_ast = crate::ast::compiler::captures::CapturePass::apply(bound_ast, &captures);
|
||||
|
||||
// Pre-allocate global slots to prevent out-of-bounds during specialization/optimization
|
||||
@@ -234,9 +300,12 @@ impl Environment {
|
||||
LambdaCollector::collect(&bound_ast, &mut self.function_registry.borrow_mut());
|
||||
|
||||
let checker = TypeChecker::new(self.global_types.clone());
|
||||
let typed_ast = checker.check(bound_ast, &[])?;
|
||||
let typed_ast = checker.check(bound_ast, &[], &mut diagnostics);
|
||||
|
||||
Ok(typed_ast)
|
||||
CompilationResult {
|
||||
ast: Some(typed_ast),
|
||||
diagnostics,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn link(&self, node: TypedNode) -> ExecNode {
|
||||
@@ -335,8 +404,13 @@ impl Environment {
|
||||
move |func_template: BoundNode,
|
||||
arg_types: &[StaticType]|
|
||||
-> Result<(Value, StaticType), String> {
|
||||
let mut diag = Diagnostics::new();
|
||||
let checker = TypeChecker::new(global_types.clone());
|
||||
let retyped_ast = checker.check(func_template, arg_types)?;
|
||||
let retyped_ast = checker.check(func_template, arg_types, &mut diag);
|
||||
|
||||
if diag.has_errors() {
|
||||
return Err(diag.items.into_iter().map(|d| d.message).collect::<Vec<_>>().join("\n"));
|
||||
}
|
||||
|
||||
let analyzed = Analyzer::analyze(&retyped_ast, &global_purity.borrow());
|
||||
|
||||
@@ -391,17 +465,20 @@ impl Environment {
|
||||
}
|
||||
res
|
||||
} else {
|
||||
let compiled = self.compile(source)?;
|
||||
let linked = self.link(compiled);
|
||||
let func = self.instantiate(linked);
|
||||
let res = (func.func)(vec![]);
|
||||
self.run_pipeline();
|
||||
Ok(res)
|
||||
self.compile(source).into_result().and_then(|ast| self.run_script_compiled(ast))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_script_compiled(&self, compiled: TypedNode) -> Result<Value, String> {
|
||||
let linked = self.link(compiled);
|
||||
let func = self.instantiate(linked);
|
||||
let res = (func.func)(vec![]);
|
||||
self.run_pipeline();
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
pub fn run_debug(&self, source: &str) -> Result<(Result<Value, String>, Vec<String>), String> {
|
||||
let compiled = self.compile(source)?;
|
||||
let compiled = self.compile(source).into_result()?;
|
||||
let linked = self.link(compiled);
|
||||
let mut vm = VM::new(self.global_values.clone());
|
||||
let mut observer = TracingObserver::new();
|
||||
|
||||
Reference in New Issue
Block a user