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:
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,7 +141,8 @@ impl CapturePass {
|
||||
BoundKind::Nop
|
||||
| BoundKind::Constant(_)
|
||||
| BoundKind::Get { .. }
|
||||
| BoundKind::Extension(_) => {}
|
||||
| BoundKind::Extension(_)
|
||||
| BoundKind::Error => {}
|
||||
}
|
||||
node
|
||||
}
|
||||
|
||||
@@ -256,6 +256,9 @@ impl Dumper {
|
||||
BoundKind::Extension(ext) => {
|
||||
self.log(&ext.display_name(), node);
|
||||
}
|
||||
BoundKind::Error => {
|
||||
self.log("ERROR_NODE", node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+819
-816
File diff suppressed because it is too large
Load Diff
@@ -158,7 +158,7 @@ impl UsageInfo {
|
||||
}
|
||||
self.collect(lambda);
|
||||
}
|
||||
BoundKind::Nop | BoundKind::FieldAccessor(_) | BoundKind::Extension(_) => {}
|
||||
BoundKind::Nop | BoundKind::FieldAccessor(_) | BoundKind::Extension(_) | BoundKind::Error => {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -167,6 +167,7 @@ impl TCO {
|
||||
)),
|
||||
},
|
||||
BoundKind::Extension(_) => BoundKind::Nop,
|
||||
BoundKind::Error => BoundKind::Error,
|
||||
};
|
||||
|
||||
Node {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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,4 +1,5 @@
|
||||
pub mod compiler;
|
||||
pub mod diagnostics;
|
||||
pub mod environment;
|
||||
pub mod lexer;
|
||||
pub mod nodes;
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user