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:
Michael Schimmel
2026-03-02 23:12:51 +01:00
parent f7cb6655af
commit 5bc69c267b
19 changed files with 2678 additions and 2334 deletions
+1
View File
@@ -306,6 +306,7 @@ impl<'a> Analyzer<'a> {
}
BoundKind::Extension(_) => (BoundKind::Nop, Purity::Impure),
BoundKind::Error => (BoundKind::Error, Purity::Impure),
};
crate::ast::nodes::Node {
+708 -665
View File
File diff suppressed because it is too large Load Diff
+10 -5
View File
@@ -161,6 +161,9 @@ pub enum BoundKind<T = ()> {
bound_expanded: Box<BoundNode<T>>,
},
/// A diagnostic poison node, allowing compilation to continue after an error.
Error,
/// DSL-specific extension slot
Extension(Box<dyn BoundExtension<T>>),
}
@@ -260,14 +263,15 @@ where
}
(
BoundKind::Expansion {
original_call: oa,
bound_expanded: ba,
original_call: ca,
bound_expanded: ea,
},
BoundKind::Expansion {
original_call: ob,
bound_expanded: bb,
original_call: cb,
bound_expanded: eb,
},
) => Rc::ptr_eq(oa, ob) && ba == bb,
) => Rc::ptr_eq(ca, cb) && ea == eb,
(BoundKind::Error, BoundKind::Error) => true,
(BoundKind::Extension(_), BoundKind::Extension(_)) => false, // Currently no equality for extensions
_ => false,
}
@@ -314,6 +318,7 @@ impl<T> BoundKind<T> {
BoundKind::Record { values, .. } => format!("RECORD({})", values.len()),
BoundKind::Expansion { .. } => "EXPANSION".to_string(),
BoundKind::Extension(ext) => ext.display_name(),
BoundKind::Error => "ERROR".to_string(),
}
}
}
+2 -1
View File
@@ -141,7 +141,8 @@ impl CapturePass {
BoundKind::Nop
| BoundKind::Constant(_)
| BoundKind::Get { .. }
| BoundKind::Extension(_) => {}
| BoundKind::Extension(_)
| BoundKind::Error => {}
}
node
}
+3
View File
@@ -256,6 +256,9 @@ impl Dumper {
BoundKind::Extension(ext) => {
self.log(&ext.display_name(), node);
}
BoundKind::Error => {
self.log("ERROR_NODE", node);
}
}
}
}
+819 -816
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -158,7 +158,7 @@ impl UsageInfo {
}
self.collect(lambda);
}
BoundKind::Nop | BoundKind::FieldAccessor(_) | BoundKind::Extension(_) => {}
BoundKind::Nop | BoundKind::FieldAccessor(_) | BoundKind::Extension(_) | BoundKind::Error => {}
}
}
+1
View File
@@ -167,6 +167,7 @@ impl TCO {
)),
},
BoundKind::Extension(_) => BoundKind::Nop,
BoundKind::Error => BoundKind::Error,
};
Node {
+65 -64
View File
@@ -1,4 +1,5 @@
use crate::ast::compiler::bound_nodes::{Address, BoundKind, BoundNode, GlobalIdx, TypedNode};
use crate::ast::diagnostics::Diagnostics;
use crate::ast::nodes::Node;
use crate::ast::types::StaticType;
use std::collections::HashMap;
@@ -78,7 +79,7 @@ impl TypeChecker {
Self { global_types }
}
pub fn check(&self, node: BoundNode, arg_types: &[StaticType]) -> Result<TypedNode, String> {
pub fn check(&self, node: BoundNode, arg_types: &[StaticType], diag: &mut Diagnostics) -> TypedNode {
match node.kind {
BoundKind::Lambda {
params,
@@ -104,11 +105,10 @@ impl TypeChecker {
StaticType::Tuple(arg_types.to_vec())
};
let params_typed =
self.check_params(params.as_ref().clone(), &arg_tuple_ty, &mut lambda_ctx)?;
let params_typed = self.check_params(params.as_ref().clone(), &arg_tuple_ty, &mut lambda_ctx, diag);
// 4. Check body with the new types
let body_typed = self.check_node((*body).clone(), &mut lambda_ctx)?;
let body_typed = self.check_node((*body).clone(), &mut lambda_ctx, diag);
let ret_ty = body_typed.ty.clone();
// 5. Construct specialized function type
@@ -119,7 +119,7 @@ impl TypeChecker {
ret: ret_ty,
}));
Ok(Node {
Node {
identity: node.identity,
kind: BoundKind::Lambda {
params: Rc::new(params_typed),
@@ -128,7 +128,7 @@ impl TypeChecker {
positional_count,
},
ty: fn_ty,
})
}
}
_ => {
// Fallback: Wrap in implicit lambda
@@ -146,7 +146,7 @@ impl TypeChecker {
},
ty: (),
};
self.check(virtual_lambda, &[])
self.check(virtual_lambda, &[], diag)
}
}
}
@@ -156,7 +156,8 @@ impl TypeChecker {
node: BoundNode,
specialized_ty: &StaticType,
ctx: &mut TypeContext,
) -> Result<TypedNode, String> {
diag: &mut Diagnostics,
) -> TypedNode {
let (kind, ty) = match node.kind {
BoundKind::Define {
name,
@@ -208,12 +209,15 @@ impl TypeChecker {
| StaticType::Vector(_, _)
| StaticType::Matrix(_, _)
| StaticType::List(_)
| StaticType::Record(_) => {}
| StaticType::Record(_)
| StaticType::Error => {}
_ => {
return Err(format!(
"Cannot destructure type {} as a tuple/vector",
specialized_ty
));
diag.push_error(format!("Cannot destructure type {} as a tuple/vector", specialized_ty), Some(node.identity.clone()));
return Node {
identity: node.identity,
kind: BoundKind::Error,
ty: StaticType::Error,
};
}
}
@@ -231,9 +235,10 @@ impl TypeChecker {
.get(i)
.map(|(_, ty): &(crate::ast::types::Keyword, StaticType)| ty.clone())
.unwrap_or(StaticType::Any),
StaticType::Error => StaticType::Error,
_ => StaticType::Any,
};
let t = self.check_params(el, &sub_ty, ctx)?;
let t = self.check_params(el, &sub_ty, ctx, diag);
elem_types.push(t.ty.clone());
typed_elements.push(t);
}
@@ -244,17 +249,21 @@ impl TypeChecker {
StaticType::Tuple(elem_types),
)
}
_ => return Err("Invalid node in parameter list".to_string()),
BoundKind::Error => (BoundKind::Error, StaticType::Error),
_ => {
diag.push_error("Invalid node in parameter list", Some(node.identity.clone()));
(BoundKind::Error, StaticType::Error)
}
};
Ok(Node {
Node {
identity: node.identity,
kind,
ty,
})
}
}
fn check_node(&self, node: BoundNode, ctx: &mut TypeContext) -> Result<TypedNode, String> {
fn check_node(&self, node: BoundNode, ctx: &mut TypeContext, diag: &mut Diagnostics) -> TypedNode {
let (kind, ty) = match node.kind {
BoundKind::Nop => (BoundKind::Nop, StaticType::Void),
@@ -270,7 +279,7 @@ impl TypeChecker {
value,
captured_by,
} => {
let val_typed = self.check_node(*value, ctx)?;
let val_typed = self.check_node(*value, ctx, diag);
let ty = val_typed.ty.clone();
ctx.set_type(addr, ty.clone());
@@ -300,22 +309,21 @@ impl TypeChecker {
BoundKind::FieldAccessor(k) => (BoundKind::FieldAccessor(k), StaticType::FieldAccessor(k)),
BoundKind::GetField { rec, field } => {
let rec_typed = self.check_node(*rec, ctx)?;
let rec_typed = self.check_node(*rec, ctx, diag);
let field_ty = match &rec_typed.ty {
StaticType::Record(layout) => {
if let Some(idx) = layout.index_of(field) {
layout.fields[idx].1.clone()
} else {
return Err(format!("Record does not have field :{}", field.name()));
diag.push_error(format!("Record does not have field :{}", field.name()), Some(rec_typed.identity.clone()));
StaticType::Error
}
}
StaticType::Any => StaticType::Any,
StaticType::Error => StaticType::Error,
_ => {
return Err(format!(
"Cannot access field :{} on non-record type {}",
field.name(),
rec_typed.ty
));
diag.push_error(format!("Cannot access field :{} on non-record type {}", field.name(), rec_typed.ty), Some(rec_typed.identity.clone()));
StaticType::Error
}
};
(
@@ -328,7 +336,7 @@ impl TypeChecker {
}
BoundKind::Set { addr, value } => {
let val_typed = self.check_node(*value, ctx)?;
let val_typed = self.check_node(*value, ctx, diag);
let ty = val_typed.ty.clone();
ctx.set_type(addr, ty.clone());
@@ -342,8 +350,8 @@ impl TypeChecker {
}
BoundKind::Destructure { pattern, value } => {
let val_typed = self.check_node(*value, ctx)?;
let pat_typed = self.check_params(*pattern, &val_typed.ty, ctx)?;
let val_typed = self.check_node(*value, ctx, diag);
let pat_typed = self.check_params(*pattern, &val_typed.ty, ctx, diag);
let ty = val_typed.ty.clone();
(
BoundKind::Destructure {
@@ -359,14 +367,14 @@ impl TypeChecker {
then_br,
else_br,
} => {
let cond_typed = self.check_node(*cond, ctx)?;
let then_typed = self.check_node(*then_br, ctx)?;
let cond_typed = self.check_node(*cond, ctx, diag);
let then_typed = self.check_node(*then_br, ctx, diag);
let mut else_typed = None;
let mut final_ty = then_typed.ty.clone();
if let Some(e) = else_br {
let et = self.check_node(*e, ctx)?;
let et = self.check_node(*e, ctx, diag);
// Basic type promotion: if types differ, fall back to Any
if et.ty != final_ty {
final_ty = StaticType::Any;
@@ -391,7 +399,7 @@ impl TypeChecker {
let mut typed_inputs = Vec::with_capacity(inputs.len());
let mut arg_types = Vec::with_capacity(inputs.len());
for input in inputs {
let typed_input = self.check_node(input, ctx)?;
let typed_input = self.check_node(input, ctx, diag);
// Unwrap Series(T) to T for the lambda argument
let arg_ty = if let StaticType::Series(inner) = &typed_input.ty {
*inner.clone()
@@ -403,7 +411,7 @@ impl TypeChecker {
}
// Specialized check for the lambda using the input types!
let typed_lambda = self.check(*lambda, &arg_types)?;
let typed_lambda = self.check(*lambda, &arg_types, diag);
let ret_ty = if let StaticType::Function(sig) = &typed_lambda.ty {
// If the lambda returns an Optional(T), the pipeline filters Void and stores T!
@@ -430,7 +438,7 @@ impl TypeChecker {
let mut last_ty = StaticType::Void;
for e in exprs {
let t = self.check_node(e, ctx)?;
let t = self.check_node(e, ctx, diag);
last_ty = t.ty.clone();
typed_exprs.push(t);
}
@@ -455,13 +463,12 @@ impl TypeChecker {
TypeContext::new(64, upvalue_types, ctx.global_types, Some(ctx));
// 3. Check parameters and body
let params_typed =
self.check_params(params.as_ref().clone(), &StaticType::Any, &mut lambda_ctx)?;
let params_typed = self.check_params(params.as_ref().clone(), &StaticType::Any, &mut lambda_ctx, diag);
// Set current params type for 'again' validation
lambda_ctx.current_params_ty = Some(params_typed.ty.clone());
let body_typed = self.check_node((*body).clone(), &mut lambda_ctx)?;
let body_typed = self.check_node((*body).clone(), &mut lambda_ctx, diag);
let ret_ty = body_typed.ty.clone();
// 4. Construct function type
@@ -482,14 +489,14 @@ impl TypeChecker {
}
BoundKind::Call { callee, args } => {
let callee_typed = self.check_node(*callee, ctx)?;
let callee_typed = self.check_node(*callee, ctx, diag);
// Manually check args (Tuple) to prevent Vector/Matrix promotion
let args_typed = if let BoundKind::Tuple { elements } = args.kind {
let mut typed_elements = Vec::new();
let mut elem_types = Vec::new();
for e in elements {
let t = self.check_node(e, ctx)?;
let t = self.check_node(e, ctx, diag);
elem_types.push(t.ty.clone());
typed_elements.push(t);
}
@@ -502,16 +509,14 @@ impl TypeChecker {
}
} else {
// Should not happen as parser wraps args in Tuple, but fallback safely
self.check_node(*args, ctx)?
self.check_node(*args, ctx, diag)
};
let ret_ty = match callee_typed.ty.resolve_call(&args_typed.ty) {
Some(ty) => ty,
None => {
return Err(format!(
"Invalid arguments for function call. Expected {}, got {}",
callee_typed.ty, args_typed.ty
));
diag.push_error(format!("Invalid arguments for function call. Expected {}, got {}", callee_typed.ty, args_typed.ty), Some(node.identity.clone()));
StaticType::Error
}
};
@@ -530,7 +535,7 @@ impl TypeChecker {
let mut typed_elements = Vec::new();
let mut elem_types = Vec::new();
for e in elements {
let t = self.check_node(e, ctx)?;
let t = self.check_node(e, ctx, diag);
elem_types.push(t.ty.clone());
typed_elements.push(t);
}
@@ -542,16 +547,13 @@ impl TypeChecker {
ty: StaticType::Tuple(elem_types),
}
} else {
self.check_node(*args, ctx)?
self.check_node(*args, ctx, diag)
};
if let Some(expected_ty) = &ctx.current_params_ty
&& !expected_ty.is_assignable_from(&args_typed.ty)
{
return Err(format!(
"Type mismatch in 'again' call: expected {}, but got {}",
expected_ty, args_typed.ty
));
diag.push_error(format!("Type mismatch in 'again' call: expected {}, but got {}", expected_ty, args_typed.ty), Some(node.identity.clone()));
}
(
@@ -565,7 +567,7 @@ impl TypeChecker {
BoundKind::Tuple { elements } => {
let mut typed_elements = Vec::new();
for e in elements {
typed_elements.push(self.check_node(e, ctx)?);
typed_elements.push(self.check_node(e, ctx, diag));
}
let ty = if typed_elements.is_empty() {
@@ -606,7 +608,7 @@ impl TypeChecker {
let mut fields_ty = Vec::with_capacity(values.len());
for (i, v) in values.into_iter().enumerate() {
let vt = self.check_node(v, ctx)?;
let vt = self.check_node(v, ctx, diag);
fields_ty.push((layout.fields[i].0, vt.ty.clone()));
typed_values.push(vt);
}
@@ -625,7 +627,7 @@ impl TypeChecker {
original_call,
bound_expanded,
} => {
let expanded_typed = self.check_node(*bound_expanded, ctx)?;
let expanded_typed = self.check_node(*bound_expanded, ctx, diag);
let ty = expanded_typed.ty.clone();
(
BoundKind::Expansion {
@@ -637,18 +639,17 @@ impl TypeChecker {
}
BoundKind::Extension(_ext) => {
return Err(format!(
"TypeChecking for extension '{}' not implemented",
_ext.display_name()
));
diag.push_error(format!("TypeChecking for extension '{}' not implemented", _ext.display_name()), Some(node.identity.clone()));
(BoundKind::Error, StaticType::Error)
}
BoundKind::Error => (BoundKind::Error, StaticType::Error),
};
Ok(Node {
Node {
identity: node.identity,
kind,
ty,
})
}
}
}
@@ -659,13 +660,13 @@ mod tests {
fn check_source(source: &str) -> TypedNode {
let env = crate::ast::environment::Environment::new();
env.compile(source).unwrap()
env.compile(source).into_result().unwrap()
}
#[test]
fn test_destructuring_scalar_error() {
let env = crate::ast::environment::Environment::new();
let result = env.compile("(def [x] 5)");
let result = env.compile("(def [x] 5)").into_result();
assert!(result.is_err());
assert_eq!(
result.unwrap_err(),
@@ -677,7 +678,7 @@ mod tests {
fn test_call_argument_mismatch() {
let env = crate::ast::environment::Environment::new();
// fn takes 1 arg, called with 0
let result = env.compile("(do (def f (fn [x] x)) (f))");
let result = env.compile("(do (def f (fn [x] x)) (f))").into_result();
assert!(result.is_err());
assert!(
result
@@ -690,7 +691,7 @@ mod tests {
fn test_destructuring_vector_args() {
let env = crate::ast::environment::Environment::new();
// ((fn [[x y]] (+ x y)) [10 20]) -> 30
let result = env.compile("((fn [[x y]] (+ x y)) [10 20])");
let result = env.compile("((fn [[x y]] (+ x y)) [10 20])").into_result();
assert!(result.is_ok());
assert_eq!(get_ret_type(&result.unwrap()), StaticType::Int);
}
+54
View File
@@ -0,0 +1,54 @@
use crate::ast::types::Identity;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DiagnosticLevel {
Error,
Warning,
Hint,
}
#[derive(Debug, Clone)]
pub struct Diagnostic {
pub level: DiagnosticLevel,
pub message: String,
pub identity: Option<Identity>,
}
#[derive(Debug, Clone, Default)]
pub struct Diagnostics {
pub items: Vec<Diagnostic>,
}
impl Diagnostics {
pub fn new() -> Self {
Self { items: Vec::new() }
}
pub fn has_errors(&self) -> bool {
self.items.iter().any(|d| d.level == DiagnosticLevel::Error)
}
pub fn push_error(&mut self, msg: impl Into<String>, id: Option<Identity>) {
self.items.push(Diagnostic {
level: DiagnosticLevel::Error,
message: msg.into(),
identity: id,
});
}
pub fn push_warning(&mut self, msg: impl Into<String>, id: Option<Identity>) {
self.items.push(Diagnostic {
level: DiagnosticLevel::Warning,
message: msg.into(),
identity: id,
});
}
pub fn push_hint(&mut self, msg: impl Into<String>, id: Option<Identity>) {
self.items.push(Diagnostic {
level: DiagnosticLevel::Hint,
message: msg.into(),
identity: id,
});
}
}
+98 -21
View File
@@ -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();
+1
View File
@@ -1,4 +1,5 @@
pub mod compiler;
pub mod diagnostics;
pub mod environment;
pub mod lexer;
pub mod nodes;
+2
View File
@@ -123,5 +123,7 @@ pub enum UntypedKind {
/// The resulting AST after macro expansion.
expanded: Box<Node<UntypedKind>>,
},
/// A diagnostic poison node, allowing compilation to continue after an error.
Error,
Extension(Box<dyn CustomNode>),
}
+207 -135
View File
@@ -1,33 +1,55 @@
use crate::ast::lexer::{Lexer, Token, TokenKind};
use crate::ast::nodes::{Node, Symbol, UntypedKind};
use crate::ast::types::{Identity, Keyword, NodeIdentity, Value};
use crate::ast::diagnostics::Diagnostics;
use std::rc::Rc;
pub struct Parser<'a> {
lexer: Lexer<'a>,
current_token: Token,
pub diagnostics: Diagnostics,
}
impl<'a> Parser<'a> {
pub fn new(input: &'a str) -> Result<Self, String> {
pub fn new(input: &'a str) -> Self {
let mut lexer = Lexer::new(input);
let current_token = lexer.next_token()?;
Ok(Self {
let mut diagnostics = Diagnostics::new();
let current_token = match lexer.next_token() {
Ok(t) => t,
Err(e) => {
diagnostics.push_error(e, None);
Token {
kind: TokenKind::EOF,
location: crate::ast::types::SourceLocation { line: 1, col: 1 },
}
}
};
Self {
lexer,
current_token,
})
diagnostics,
}
}
fn advance(&mut self) -> Result<Token, String> {
let prev = std::mem::replace(&mut self.current_token, self.lexer.next_token()?);
Ok(prev)
fn advance(&mut self) -> Token {
let next = match self.lexer.next_token() {
Ok(t) => t,
Err(e) => {
self.diagnostics.push_error(e, Some(NodeIdentity::new(self.current_token.location)));
Token {
kind: TokenKind::EOF,
location: self.current_token.location,
}
}
};
std::mem::replace(&mut self.current_token, next)
}
fn peek(&self) -> &TokenKind {
&self.current_token.kind
}
pub fn parse_expression(&mut self) -> Result<Node<UntypedKind>, String> {
pub fn parse_expression(&mut self) -> Node<UntypedKind> {
let token_loc = self.current_token.location;
let identity = NodeIdentity::new(token_loc);
@@ -36,9 +58,9 @@ impl<'a> Parser<'a> {
TokenKind::LeftBracket => self.parse_vector_literal(),
TokenKind::LeftBrace => self.parse_record_literal(),
TokenKind::Quote => {
self.advance()?; // consume '
let expr = self.parse_expression()?;
Ok(Node {
self.advance(); // consume '
let expr = self.parse_expression();
Node {
identity: identity.clone(),
kind: UntypedKind::Call {
callee: Box::new(self.make_id_node("quote", identity.clone())),
@@ -51,34 +73,34 @@ impl<'a> Parser<'a> {
}),
},
ty: (),
})
}
}
TokenKind::Backtick => {
self.advance()?; // consume `
let expr = self.parse_expression()?;
Ok(Node {
self.advance(); // consume `
let expr = self.parse_expression();
Node {
identity,
kind: UntypedKind::Template(Box::new(expr)),
ty: (),
})
}
}
TokenKind::Tilde => {
self.advance()?; // consume ~
self.advance(); // consume ~
if *self.peek() == TokenKind::At {
self.advance()?; // consume @
let expr = self.parse_expression()?;
Ok(Node {
self.advance(); // consume @
let expr = self.parse_expression();
Node {
identity,
kind: UntypedKind::Splice(Box::new(expr)),
ty: (),
})
}
} else {
let expr = self.parse_expression()?;
Ok(Node {
let expr = self.parse_expression();
Node {
identity,
kind: UntypedKind::Placeholder(Box::new(expr)),
ty: (),
})
}
}
}
_ => self.parse_atom(),
@@ -89,8 +111,22 @@ impl<'a> Parser<'a> {
matches!(self.current_token.kind, TokenKind::EOF)
}
fn parse_atom(&mut self) -> Result<Node<UntypedKind>, String> {
let token = self.advance()?;
fn synchronize(&mut self) {
while !self.at_eof() {
match self.peek() {
TokenKind::RightParen | TokenKind::RightBracket | TokenKind::RightBrace => {
self.advance();
return;
}
_ => {
self.advance();
}
}
}
}
fn parse_atom(&mut self) -> Node<UntypedKind> {
let token = self.advance();
let identity = NodeIdentity::new(token.location);
let kind = match token.kind {
@@ -105,35 +141,43 @@ impl<'a> Parser<'a> {
}
_ => UntypedKind::Identifier(id.into()),
},
TokenKind::EOF => UntypedKind::Error, // Error already logged by advance
_ => {
return Err(format!(
"Unexpected token in atom: {:?} at {:?}",
token.kind, token.location
));
self.diagnostics.push_error(
format!("Unexpected token in atom: {:?}", token.kind),
Some(identity.clone()),
);
UntypedKind::Error
}
};
Ok(Node {
Node {
identity,
kind,
ty: (),
})
}
}
fn parse_list(&mut self) -> Result<Node<UntypedKind>, String> {
let start_loc = self.advance()?.location; // consume '('
fn parse_list(&mut self) -> Node<UntypedKind> {
let start_loc = self.advance().location; // consume '('
let identity = NodeIdentity::new(start_loc);
if *self.peek() == TokenKind::RightParen {
return Err(format!(
"Empty list () is not a valid expression at {:?}",
start_loc
));
self.diagnostics.push_error(
"Empty list () is not a valid expression",
Some(identity.clone()),
);
self.advance(); // consume )
return Node {
identity,
kind: UntypedKind::Error,
ty: (),
};
}
let head = self.parse_expression()?;
let head = self.parse_expression();
let result = if let UntypedKind::Identifier(ref sym) = head.kind {
let node = if let UntypedKind::Identifier(ref sym) = head.kind {
match sym.name.as_ref() {
"if" => self.parse_if(identity),
"fn" => self.parse_fn(identity),
@@ -149,15 +193,15 @@ impl<'a> Parser<'a> {
self.parse_call(head, identity)
};
self.expect(TokenKind::RightParen)?;
result
self.expect(TokenKind::RightParen);
node
}
fn parse_again(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> {
fn parse_again(&mut self, identity: Identity) -> Node<UntypedKind> {
let mut elements = Vec::new();
while *self.peek() != TokenKind::RightParen && *self.peek() != TokenKind::EOF {
elements.push(self.parse_expression()?);
elements.push(self.parse_expression());
}
let args_node = Node {
@@ -166,25 +210,25 @@ impl<'a> Parser<'a> {
ty: (),
};
Ok(Node {
Node {
identity,
kind: UntypedKind::Again {
args: Box::new(args_node),
},
ty: (),
})
}
}
fn parse_if(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> {
let cond = Box::new(self.parse_expression()?);
let then_br = Box::new(self.parse_expression()?);
fn parse_if(&mut self, identity: Identity) -> Node<UntypedKind> {
let cond = Box::new(self.parse_expression());
let then_br = Box::new(self.parse_expression());
let mut else_br = None;
if *self.peek() != TokenKind::RightParen {
else_br = Some(Box::new(self.parse_expression()?));
else_br = Some(Box::new(self.parse_expression()));
}
Ok(Node {
Node {
identity,
kind: UntypedKind::If {
cond,
@@ -192,82 +236,88 @@ impl<'a> Parser<'a> {
else_br,
},
ty: (),
})
}
}
fn parse_def(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> {
let target = Box::new(self.parse_pattern()?);
let value = Box::new(self.parse_expression()?);
fn parse_def(&mut self, identity: Identity) -> Node<UntypedKind> {
let target = Box::new(self.parse_pattern());
let value = Box::new(self.parse_expression());
Ok(Node {
Node {
identity,
kind: UntypedKind::Def { target, value },
ty: (),
})
}
}
fn parse_assign(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> {
fn parse_assign(&mut self, identity: Identity) -> Node<UntypedKind> {
// (assign target value)
let target = Box::new(self.parse_expression()?);
let value = Box::new(self.parse_expression()?);
let target = Box::new(self.parse_expression());
let value = Box::new(self.parse_expression());
Ok(Node {
Node {
identity,
kind: UntypedKind::Assign { target, value },
ty: (),
})
}
}
fn parse_do(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> {
fn parse_do(&mut self, identity: Identity) -> Node<UntypedKind> {
let mut exprs = Vec::new();
while *self.peek() != TokenKind::RightParen && *self.peek() != TokenKind::EOF {
exprs.push(self.parse_expression()?);
exprs.push(self.parse_expression());
}
Ok(Node {
Node {
identity,
kind: UntypedKind::Block { exprs },
ty: (),
})
}
}
fn parse_pipe(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> {
let inputs_node = self.parse_expression()?;
fn parse_pipe(&mut self, identity: Identity) -> Node<UntypedKind> {
let inputs_node = self.parse_expression();
let inputs = match inputs_node.kind {
UntypedKind::Tuple { elements } => elements,
_ => vec![inputs_node],
};
let lambda = Box::new(self.parse_expression()?);
let lambda = Box::new(self.parse_expression());
Ok(Node {
Node {
identity,
kind: UntypedKind::Pipe { inputs, lambda },
ty: (),
})
}
}
fn parse_fn(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> {
let params = Box::new(self.parse_param_vector()?);
let body = self.parse_expression()?;
fn parse_fn(&mut self, identity: Identity) -> Node<UntypedKind> {
let params = Box::new(self.parse_param_vector());
let body = self.parse_expression();
Ok(Node {
Node {
identity,
kind: UntypedKind::Lambda {
params,
body: Rc::new(body),
},
ty: (),
})
}
}
fn parse_macro_decl(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> {
let name_node = self.parse_expression()?;
fn parse_macro_decl(&mut self, identity: Identity) -> Node<UntypedKind> {
let name_node = self.parse_expression();
let name = match name_node.kind {
UntypedKind::Identifier(sym) => sym,
_ => return Err("Expected identifier for macro name".to_string()),
_ => {
self.diagnostics.push_error(
"Expected identifier for macro name",
Some(name_node.identity.clone()),
);
Symbol::from("error")
}
};
let params = Box::new(self.parse_param_vector()?);
let body = self.parse_expression()?;
Ok(Node {
let params = Box::new(self.parse_param_vector());
let body = self.parse_expression();
Node {
identity,
kind: UntypedKind::MacroDecl {
name,
@@ -275,54 +325,66 @@ impl<'a> Parser<'a> {
body: Box::new(body),
},
ty: (),
})
}
}
fn parse_param_vector(&mut self) -> Result<Node<UntypedKind>, String> {
fn parse_param_vector(&mut self) -> Node<UntypedKind> {
if *self.peek() != TokenKind::LeftBracket {
return Err(format!(
"Expected parameter vector [...] for fn, found {:?}",
self.peek()
));
self.diagnostics.push_error(
format!("Expected parameter vector [...] for fn, found {:?}", self.peek()),
Some(NodeIdentity::new(self.current_token.location)),
);
return Node {
identity: NodeIdentity::new(self.current_token.location),
kind: UntypedKind::Error,
ty: (),
};
}
self.parse_pattern()
}
fn parse_pattern(&mut self) -> Result<Node<UntypedKind>, String> {
fn parse_pattern(&mut self) -> Node<UntypedKind> {
let next = self.peek();
match next {
TokenKind::Identifier(_) => {
let token = self.advance()?;
let token = self.advance();
let sym: Symbol = match token.kind {
TokenKind::Identifier(s) => s.into(),
_ => unreachable!(),
};
Ok(Node {
Node {
identity: NodeIdentity::new(token.location),
kind: UntypedKind::Parameter(sym),
ty: (),
})
}
}
TokenKind::LeftBracket => {
let token = self.advance()?;
let token = self.advance();
let identity = NodeIdentity::new(token.location);
let mut elements = Vec::new();
while *self.peek() != TokenKind::RightBracket {
elements.push(self.parse_pattern()?);
while *self.peek() != TokenKind::RightBracket && *self.peek() != TokenKind::EOF {
elements.push(self.parse_pattern());
}
self.expect(TokenKind::RightBracket)?;
self.expect(TokenKind::RightBracket);
Ok(Node {
Node {
identity,
kind: UntypedKind::Tuple { elements },
ty: (),
})
}
}
_ => {
self.diagnostics.push_error(
format!("Expected identifier or pattern vector [...] for definition, found {:?}", next),
Some(NodeIdentity::new(self.current_token.location)),
);
Node {
identity: NodeIdentity::new(self.current_token.location),
kind: UntypedKind::Error,
ty: (),
}
}
_ => Err(format!(
"Expected identifier or pattern vector [...] for definition, found {:?}",
next
)),
}
}
@@ -330,11 +392,11 @@ impl<'a> Parser<'a> {
&mut self,
callee: Node<UntypedKind>,
identity: Identity,
) -> Result<Node<UntypedKind>, String> {
) -> Node<UntypedKind> {
let mut elements = Vec::new();
while *self.peek() != TokenKind::RightParen && *self.peek() != TokenKind::EOF {
elements.push(self.parse_expression()?);
elements.push(self.parse_expression());
}
// The arguments are wrapped in a Tuple node, reusing the call's identity/location.
@@ -344,77 +406,87 @@ impl<'a> Parser<'a> {
ty: (),
};
Ok(Node {
Node {
identity,
kind: UntypedKind::Call {
callee: Box::new(callee),
args: Box::new(args_node),
},
ty: (),
})
}
}
fn parse_vector_literal(&mut self) -> Result<Node<UntypedKind>, String> {
let token = self.advance()?;
fn parse_vector_literal(&mut self) -> Node<UntypedKind> {
let token = self.advance();
let mut elements = Vec::new();
while *self.peek() != TokenKind::RightBracket && *self.peek() != TokenKind::EOF {
let expr = self.parse_expression()?;
let expr = self.parse_expression();
elements.push(expr);
}
self.expect(TokenKind::RightBracket)?;
self.expect(TokenKind::RightBracket);
Ok(Node {
Node {
identity: NodeIdentity::new(token.location),
kind: UntypedKind::Tuple { elements },
ty: (),
})
}
}
fn parse_record_literal(&mut self) -> Result<Node<UntypedKind>, String> {
let token = self.advance()?;
fn parse_record_literal(&mut self) -> Node<UntypedKind> {
let token = self.advance();
let mut fields = Vec::new();
while *self.peek() != TokenKind::RightBrace {
if *self.peek() == TokenKind::EOF {
return Err("Unexpected EOF in record literal".to_string());
}
let key_node = self.parse_expression()?;
while *self.peek() != TokenKind::RightBrace && *self.peek() != TokenKind::EOF {
let key_node = self.parse_expression();
// We check for keyword kind here (syntactically) to avoid ambiguity, but
// strictly we could allow any expression and check at runtime.
// Delphi enforces keywords. We can do minimal check here.
match &key_node.kind {
UntypedKind::Constant(Value::Keyword(_)) => {}
_ => return Err("Record keys must be keywords (syntactically)".to_string()),
_ => {
self.diagnostics.push_error(
"Record keys must be keywords (syntactically)",
Some(key_node.identity.clone()),
);
}
}
if *self.peek() == TokenKind::RightBrace {
return Err("Record literal must have even number of forms".to_string());
if *self.peek() == TokenKind::RightBrace || *self.peek() == TokenKind::EOF {
self.diagnostics.push_error(
"Record literal must have even number of forms",
Some(NodeIdentity::new(self.current_token.location)),
);
break;
}
let val_node = self.parse_expression()?;
let val_node = self.parse_expression();
fields.push((key_node, val_node));
}
self.expect(TokenKind::RightBrace)?;
self.expect(TokenKind::RightBrace);
Ok(Node {
Node {
identity: NodeIdentity::new(token.location),
kind: UntypedKind::Record { fields },
ty: (),
})
}
}
fn expect(&mut self, kind: TokenKind) -> Result<(), String> {
let token = self.advance()?;
if token.kind == kind {
Ok(())
fn expect(&mut self, kind: TokenKind) -> Token {
if self.peek() == &kind {
self.advance()
} else {
Err(format!(
"Expected {:?}, but found {:?} at {:?}",
kind, token.kind, token.location
))
self.diagnostics.push_error(
format!("Expected {:?}, but found {:?}", kind, self.peek()),
Some(NodeIdentity::new(self.current_token.location)),
);
// Recovery: skip until we find what we expected or a synchronization point
self.synchronize();
Token {
kind,
location: self.current_token.location,
}
}
}
+4 -1
View File
@@ -306,6 +306,8 @@ pub enum StaticType {
Function(Box<Signature>),
FunctionOverloads(Vec<Signature>),
Object(&'static str),
/// A diagnostic poison type, allowing type-checking to continue after an error.
Error,
}
impl fmt::Display for StaticType {
@@ -361,6 +363,7 @@ impl fmt::Display for StaticType {
write!(f, "overloads({} variants)", sigs.len())
}
StaticType::Object(name) => write!(f, "{}", name),
StaticType::Error => write!(f, "<error>"),
}
}
}
@@ -368,7 +371,7 @@ impl fmt::Display for StaticType {
impl StaticType {
/// Returns true if `other` can be assigned to a location of type `self`.
pub fn is_assignable_from(&self, other: &StaticType) -> bool {
if self == other || matches!(self, StaticType::Any) || matches!(other, StaticType::Any) {
if self == other || matches!(self, StaticType::Any) || matches!(other, StaticType::Any) || matches!(self, StaticType::Error) || matches!(other, StaticType::Error) {
return true;
}
+1
View File
@@ -658,6 +658,7 @@ impl VM {
"Execution of extension '{}' not implemented yet",
ext.display_name()
)),
BoundKind::Error => Err("Cannot execute a poisoned AST node".to_string()),
}
}
+14 -3
View File
@@ -100,10 +100,21 @@ fn main() {
}
fn execute(env: &Environment, source: &str) {
match env.run_script(source) {
Ok(result) => println!("{}", result),
let result = env.compile(source);
for diag in &result.diagnostics.items {
let level = format!("{:?}", diag.level).to_uppercase();
let loc = diag.identity.as_ref().and_then(|id| id.location).map(|l| format!(" at {}:{}", l.line, l.col)).unwrap_or_default();
eprintln!("{}{} : {}", level, loc, diag.message);
}
if result.diagnostics.has_errors() {
std::process::exit(1);
}
match env.run_script_compiled(result.ast.unwrap()) {
Ok(res) => println!("{}", res),
Err(e) => {
eprintln!("Error: {}", e);
eprintln!("Runtime Error: {}", e);
std::process::exit(1);
}
}
+686 -621
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -109,7 +109,7 @@ pub fn run_benchmarks(update: bool, filter: Option<&str>) -> Vec<BenchmarkResult
// Compile once for this file (symbols/types are compatible with all fresh environments)
let initial_env = Environment::new();
let compiled_once = match initial_env.compile(&content) {
let compiled_once = match initial_env.compile(&content).into_result() {
Ok(c) => c,
Err(e) => {
results.push(BenchmarkResult {