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::Extension(_) => (BoundKind::Nop, Purity::Impure),
|
||||||
|
BoundKind::Error => (BoundKind::Error, Purity::Impure),
|
||||||
};
|
};
|
||||||
|
|
||||||
crate::ast::nodes::Node {
|
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>>,
|
bound_expanded: Box<BoundNode<T>>,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// A diagnostic poison node, allowing compilation to continue after an error.
|
||||||
|
Error,
|
||||||
|
|
||||||
/// DSL-specific extension slot
|
/// DSL-specific extension slot
|
||||||
Extension(Box<dyn BoundExtension<T>>),
|
Extension(Box<dyn BoundExtension<T>>),
|
||||||
}
|
}
|
||||||
@@ -260,14 +263,15 @@ where
|
|||||||
}
|
}
|
||||||
(
|
(
|
||||||
BoundKind::Expansion {
|
BoundKind::Expansion {
|
||||||
original_call: oa,
|
original_call: ca,
|
||||||
bound_expanded: ba,
|
bound_expanded: ea,
|
||||||
},
|
},
|
||||||
BoundKind::Expansion {
|
BoundKind::Expansion {
|
||||||
original_call: ob,
|
original_call: cb,
|
||||||
bound_expanded: bb,
|
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
|
(BoundKind::Extension(_), BoundKind::Extension(_)) => false, // Currently no equality for extensions
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
@@ -314,6 +318,7 @@ impl<T> BoundKind<T> {
|
|||||||
BoundKind::Record { values, .. } => format!("RECORD({})", values.len()),
|
BoundKind::Record { values, .. } => format!("RECORD({})", values.len()),
|
||||||
BoundKind::Expansion { .. } => "EXPANSION".to_string(),
|
BoundKind::Expansion { .. } => "EXPANSION".to_string(),
|
||||||
BoundKind::Extension(ext) => ext.display_name(),
|
BoundKind::Extension(ext) => ext.display_name(),
|
||||||
|
BoundKind::Error => "ERROR".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -141,7 +141,8 @@ impl CapturePass {
|
|||||||
BoundKind::Nop
|
BoundKind::Nop
|
||||||
| BoundKind::Constant(_)
|
| BoundKind::Constant(_)
|
||||||
| BoundKind::Get { .. }
|
| BoundKind::Get { .. }
|
||||||
| BoundKind::Extension(_) => {}
|
| BoundKind::Extension(_)
|
||||||
|
| BoundKind::Error => {}
|
||||||
}
|
}
|
||||||
node
|
node
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -256,6 +256,9 @@ impl Dumper {
|
|||||||
BoundKind::Extension(ext) => {
|
BoundKind::Extension(ext) => {
|
||||||
self.log(&ext.display_name(), node);
|
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);
|
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::Extension(_) => BoundKind::Nop,
|
||||||
|
BoundKind::Error => BoundKind::Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
Node {
|
Node {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::ast::compiler::bound_nodes::{Address, BoundKind, BoundNode, GlobalIdx, TypedNode};
|
use crate::ast::compiler::bound_nodes::{Address, BoundKind, BoundNode, GlobalIdx, TypedNode};
|
||||||
|
use crate::ast::diagnostics::Diagnostics;
|
||||||
use crate::ast::nodes::Node;
|
use crate::ast::nodes::Node;
|
||||||
use crate::ast::types::StaticType;
|
use crate::ast::types::StaticType;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -78,7 +79,7 @@ impl TypeChecker {
|
|||||||
Self { global_types }
|
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 {
|
match node.kind {
|
||||||
BoundKind::Lambda {
|
BoundKind::Lambda {
|
||||||
params,
|
params,
|
||||||
@@ -104,11 +105,10 @@ impl TypeChecker {
|
|||||||
StaticType::Tuple(arg_types.to_vec())
|
StaticType::Tuple(arg_types.to_vec())
|
||||||
};
|
};
|
||||||
|
|
||||||
let params_typed =
|
let params_typed = self.check_params(params.as_ref().clone(), &arg_tuple_ty, &mut lambda_ctx, diag);
|
||||||
self.check_params(params.as_ref().clone(), &arg_tuple_ty, &mut lambda_ctx)?;
|
|
||||||
|
|
||||||
// 4. Check body with the new types
|
// 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();
|
let ret_ty = body_typed.ty.clone();
|
||||||
|
|
||||||
// 5. Construct specialized function type
|
// 5. Construct specialized function type
|
||||||
@@ -119,7 +119,7 @@ impl TypeChecker {
|
|||||||
ret: ret_ty,
|
ret: ret_ty,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
Ok(Node {
|
Node {
|
||||||
identity: node.identity,
|
identity: node.identity,
|
||||||
kind: BoundKind::Lambda {
|
kind: BoundKind::Lambda {
|
||||||
params: Rc::new(params_typed),
|
params: Rc::new(params_typed),
|
||||||
@@ -128,7 +128,7 @@ impl TypeChecker {
|
|||||||
positional_count,
|
positional_count,
|
||||||
},
|
},
|
||||||
ty: fn_ty,
|
ty: fn_ty,
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Fallback: Wrap in implicit lambda
|
// Fallback: Wrap in implicit lambda
|
||||||
@@ -146,7 +146,7 @@ impl TypeChecker {
|
|||||||
},
|
},
|
||||||
ty: (),
|
ty: (),
|
||||||
};
|
};
|
||||||
self.check(virtual_lambda, &[])
|
self.check(virtual_lambda, &[], diag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,7 +156,8 @@ impl TypeChecker {
|
|||||||
node: BoundNode,
|
node: BoundNode,
|
||||||
specialized_ty: &StaticType,
|
specialized_ty: &StaticType,
|
||||||
ctx: &mut TypeContext,
|
ctx: &mut TypeContext,
|
||||||
) -> Result<TypedNode, String> {
|
diag: &mut Diagnostics,
|
||||||
|
) -> TypedNode {
|
||||||
let (kind, ty) = match node.kind {
|
let (kind, ty) = match node.kind {
|
||||||
BoundKind::Define {
|
BoundKind::Define {
|
||||||
name,
|
name,
|
||||||
@@ -208,12 +209,15 @@ impl TypeChecker {
|
|||||||
| StaticType::Vector(_, _)
|
| StaticType::Vector(_, _)
|
||||||
| StaticType::Matrix(_, _)
|
| StaticType::Matrix(_, _)
|
||||||
| StaticType::List(_)
|
| StaticType::List(_)
|
||||||
| StaticType::Record(_) => {}
|
| StaticType::Record(_)
|
||||||
|
| StaticType::Error => {}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(format!(
|
diag.push_error(format!("Cannot destructure type {} as a tuple/vector", specialized_ty), Some(node.identity.clone()));
|
||||||
"Cannot destructure type {} as a tuple/vector",
|
return Node {
|
||||||
specialized_ty
|
identity: node.identity,
|
||||||
));
|
kind: BoundKind::Error,
|
||||||
|
ty: StaticType::Error,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,9 +235,10 @@ impl TypeChecker {
|
|||||||
.get(i)
|
.get(i)
|
||||||
.map(|(_, ty): &(crate::ast::types::Keyword, StaticType)| ty.clone())
|
.map(|(_, ty): &(crate::ast::types::Keyword, StaticType)| ty.clone())
|
||||||
.unwrap_or(StaticType::Any),
|
.unwrap_or(StaticType::Any),
|
||||||
|
StaticType::Error => StaticType::Error,
|
||||||
_ => StaticType::Any,
|
_ => 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());
|
elem_types.push(t.ty.clone());
|
||||||
typed_elements.push(t);
|
typed_elements.push(t);
|
||||||
}
|
}
|
||||||
@@ -244,17 +249,21 @@ impl TypeChecker {
|
|||||||
StaticType::Tuple(elem_types),
|
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,
|
identity: node.identity,
|
||||||
kind,
|
kind,
|
||||||
ty,
|
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 {
|
let (kind, ty) = match node.kind {
|
||||||
BoundKind::Nop => (BoundKind::Nop, StaticType::Void),
|
BoundKind::Nop => (BoundKind::Nop, StaticType::Void),
|
||||||
|
|
||||||
@@ -270,7 +279,7 @@ impl TypeChecker {
|
|||||||
value,
|
value,
|
||||||
captured_by,
|
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();
|
let ty = val_typed.ty.clone();
|
||||||
ctx.set_type(addr, ty.clone());
|
ctx.set_type(addr, ty.clone());
|
||||||
|
|
||||||
@@ -300,22 +309,21 @@ impl TypeChecker {
|
|||||||
BoundKind::FieldAccessor(k) => (BoundKind::FieldAccessor(k), StaticType::FieldAccessor(k)),
|
BoundKind::FieldAccessor(k) => (BoundKind::FieldAccessor(k), StaticType::FieldAccessor(k)),
|
||||||
|
|
||||||
BoundKind::GetField { rec, field } => {
|
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 {
|
let field_ty = match &rec_typed.ty {
|
||||||
StaticType::Record(layout) => {
|
StaticType::Record(layout) => {
|
||||||
if let Some(idx) = layout.index_of(field) {
|
if let Some(idx) = layout.index_of(field) {
|
||||||
layout.fields[idx].1.clone()
|
layout.fields[idx].1.clone()
|
||||||
} else {
|
} 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::Any => StaticType::Any,
|
||||||
|
StaticType::Error => StaticType::Error,
|
||||||
_ => {
|
_ => {
|
||||||
return Err(format!(
|
diag.push_error(format!("Cannot access field :{} on non-record type {}", field.name(), rec_typed.ty), Some(rec_typed.identity.clone()));
|
||||||
"Cannot access field :{} on non-record type {}",
|
StaticType::Error
|
||||||
field.name(),
|
|
||||||
rec_typed.ty
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
(
|
(
|
||||||
@@ -328,7 +336,7 @@ impl TypeChecker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
BoundKind::Set { addr, value } => {
|
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();
|
let ty = val_typed.ty.clone();
|
||||||
ctx.set_type(addr, ty.clone());
|
ctx.set_type(addr, ty.clone());
|
||||||
|
|
||||||
@@ -342,8 +350,8 @@ impl TypeChecker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
BoundKind::Destructure { pattern, value } => {
|
BoundKind::Destructure { pattern, value } => {
|
||||||
let val_typed = self.check_node(*value, ctx)?;
|
let val_typed = self.check_node(*value, ctx, diag);
|
||||||
let pat_typed = self.check_params(*pattern, &val_typed.ty, ctx)?;
|
let pat_typed = self.check_params(*pattern, &val_typed.ty, ctx, diag);
|
||||||
let ty = val_typed.ty.clone();
|
let ty = val_typed.ty.clone();
|
||||||
(
|
(
|
||||||
BoundKind::Destructure {
|
BoundKind::Destructure {
|
||||||
@@ -359,14 +367,14 @@ impl TypeChecker {
|
|||||||
then_br,
|
then_br,
|
||||||
else_br,
|
else_br,
|
||||||
} => {
|
} => {
|
||||||
let cond_typed = self.check_node(*cond, ctx)?;
|
let cond_typed = self.check_node(*cond, ctx, diag);
|
||||||
let then_typed = self.check_node(*then_br, ctx)?;
|
let then_typed = self.check_node(*then_br, ctx, diag);
|
||||||
|
|
||||||
let mut else_typed = None;
|
let mut else_typed = None;
|
||||||
let mut final_ty = then_typed.ty.clone();
|
let mut final_ty = then_typed.ty.clone();
|
||||||
|
|
||||||
if let Some(e) = else_br {
|
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
|
// Basic type promotion: if types differ, fall back to Any
|
||||||
if et.ty != final_ty {
|
if et.ty != final_ty {
|
||||||
final_ty = StaticType::Any;
|
final_ty = StaticType::Any;
|
||||||
@@ -391,7 +399,7 @@ impl TypeChecker {
|
|||||||
let mut typed_inputs = Vec::with_capacity(inputs.len());
|
let mut typed_inputs = Vec::with_capacity(inputs.len());
|
||||||
let mut arg_types = Vec::with_capacity(inputs.len());
|
let mut arg_types = Vec::with_capacity(inputs.len());
|
||||||
for input in inputs {
|
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
|
// Unwrap Series(T) to T for the lambda argument
|
||||||
let arg_ty = if let StaticType::Series(inner) = &typed_input.ty {
|
let arg_ty = if let StaticType::Series(inner) = &typed_input.ty {
|
||||||
*inner.clone()
|
*inner.clone()
|
||||||
@@ -403,7 +411,7 @@ impl TypeChecker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Specialized check for the lambda using the input types!
|
// 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 {
|
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!
|
// 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;
|
let mut last_ty = StaticType::Void;
|
||||||
|
|
||||||
for e in exprs {
|
for e in exprs {
|
||||||
let t = self.check_node(e, ctx)?;
|
let t = self.check_node(e, ctx, diag);
|
||||||
last_ty = t.ty.clone();
|
last_ty = t.ty.clone();
|
||||||
typed_exprs.push(t);
|
typed_exprs.push(t);
|
||||||
}
|
}
|
||||||
@@ -455,13 +463,12 @@ impl TypeChecker {
|
|||||||
TypeContext::new(64, upvalue_types, ctx.global_types, Some(ctx));
|
TypeContext::new(64, upvalue_types, ctx.global_types, Some(ctx));
|
||||||
|
|
||||||
// 3. Check parameters and body
|
// 3. Check parameters and body
|
||||||
let params_typed =
|
let params_typed = self.check_params(params.as_ref().clone(), &StaticType::Any, &mut lambda_ctx, diag);
|
||||||
self.check_params(params.as_ref().clone(), &StaticType::Any, &mut lambda_ctx)?;
|
|
||||||
|
|
||||||
// Set current params type for 'again' validation
|
// Set current params type for 'again' validation
|
||||||
lambda_ctx.current_params_ty = Some(params_typed.ty.clone());
|
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();
|
let ret_ty = body_typed.ty.clone();
|
||||||
|
|
||||||
// 4. Construct function type
|
// 4. Construct function type
|
||||||
@@ -482,14 +489,14 @@ impl TypeChecker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
BoundKind::Call { callee, args } => {
|
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
|
// Manually check args (Tuple) to prevent Vector/Matrix promotion
|
||||||
let args_typed = if let BoundKind::Tuple { elements } = args.kind {
|
let args_typed = if let BoundKind::Tuple { elements } = args.kind {
|
||||||
let mut typed_elements = Vec::new();
|
let mut typed_elements = Vec::new();
|
||||||
let mut elem_types = Vec::new();
|
let mut elem_types = Vec::new();
|
||||||
for e in elements {
|
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());
|
elem_types.push(t.ty.clone());
|
||||||
typed_elements.push(t);
|
typed_elements.push(t);
|
||||||
}
|
}
|
||||||
@@ -502,16 +509,14 @@ impl TypeChecker {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Should not happen as parser wraps args in Tuple, but fallback safely
|
// 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) {
|
let ret_ty = match callee_typed.ty.resolve_call(&args_typed.ty) {
|
||||||
Some(ty) => ty,
|
Some(ty) => ty,
|
||||||
None => {
|
None => {
|
||||||
return Err(format!(
|
diag.push_error(format!("Invalid arguments for function call. Expected {}, got {}", callee_typed.ty, args_typed.ty), Some(node.identity.clone()));
|
||||||
"Invalid arguments for function call. Expected {}, got {}",
|
StaticType::Error
|
||||||
callee_typed.ty, args_typed.ty
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -530,7 +535,7 @@ impl TypeChecker {
|
|||||||
let mut typed_elements = Vec::new();
|
let mut typed_elements = Vec::new();
|
||||||
let mut elem_types = Vec::new();
|
let mut elem_types = Vec::new();
|
||||||
for e in elements {
|
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());
|
elem_types.push(t.ty.clone());
|
||||||
typed_elements.push(t);
|
typed_elements.push(t);
|
||||||
}
|
}
|
||||||
@@ -542,16 +547,13 @@ impl TypeChecker {
|
|||||||
ty: StaticType::Tuple(elem_types),
|
ty: StaticType::Tuple(elem_types),
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.check_node(*args, ctx)?
|
self.check_node(*args, ctx, diag)
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(expected_ty) = &ctx.current_params_ty
|
if let Some(expected_ty) = &ctx.current_params_ty
|
||||||
&& !expected_ty.is_assignable_from(&args_typed.ty)
|
&& !expected_ty.is_assignable_from(&args_typed.ty)
|
||||||
{
|
{
|
||||||
return Err(format!(
|
diag.push_error(format!("Type mismatch in 'again' call: expected {}, but got {}", expected_ty, args_typed.ty), Some(node.identity.clone()));
|
||||||
"Type mismatch in 'again' call: expected {}, but got {}",
|
|
||||||
expected_ty, args_typed.ty
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
(
|
(
|
||||||
@@ -565,7 +567,7 @@ impl TypeChecker {
|
|||||||
BoundKind::Tuple { elements } => {
|
BoundKind::Tuple { elements } => {
|
||||||
let mut typed_elements = Vec::new();
|
let mut typed_elements = Vec::new();
|
||||||
for e in elements {
|
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() {
|
let ty = if typed_elements.is_empty() {
|
||||||
@@ -606,7 +608,7 @@ impl TypeChecker {
|
|||||||
let mut fields_ty = Vec::with_capacity(values.len());
|
let mut fields_ty = Vec::with_capacity(values.len());
|
||||||
|
|
||||||
for (i, v) in values.into_iter().enumerate() {
|
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()));
|
fields_ty.push((layout.fields[i].0, vt.ty.clone()));
|
||||||
typed_values.push(vt);
|
typed_values.push(vt);
|
||||||
}
|
}
|
||||||
@@ -625,7 +627,7 @@ impl TypeChecker {
|
|||||||
original_call,
|
original_call,
|
||||||
bound_expanded,
|
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();
|
let ty = expanded_typed.ty.clone();
|
||||||
(
|
(
|
||||||
BoundKind::Expansion {
|
BoundKind::Expansion {
|
||||||
@@ -637,18 +639,17 @@ impl TypeChecker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
BoundKind::Extension(_ext) => {
|
BoundKind::Extension(_ext) => {
|
||||||
return Err(format!(
|
diag.push_error(format!("TypeChecking for extension '{}' not implemented", _ext.display_name()), Some(node.identity.clone()));
|
||||||
"TypeChecking for extension '{}' not implemented",
|
(BoundKind::Error, StaticType::Error)
|
||||||
_ext.display_name()
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
BoundKind::Error => (BoundKind::Error, StaticType::Error),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Node {
|
Node {
|
||||||
identity: node.identity,
|
identity: node.identity,
|
||||||
kind,
|
kind,
|
||||||
ty,
|
ty,
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -659,13 +660,13 @@ mod tests {
|
|||||||
|
|
||||||
fn check_source(source: &str) -> TypedNode {
|
fn check_source(source: &str) -> TypedNode {
|
||||||
let env = crate::ast::environment::Environment::new();
|
let env = crate::ast::environment::Environment::new();
|
||||||
env.compile(source).unwrap()
|
env.compile(source).into_result().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_destructuring_scalar_error() {
|
fn test_destructuring_scalar_error() {
|
||||||
let env = crate::ast::environment::Environment::new();
|
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!(result.is_err());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
result.unwrap_err(),
|
result.unwrap_err(),
|
||||||
@@ -677,7 +678,7 @@ mod tests {
|
|||||||
fn test_call_argument_mismatch() {
|
fn test_call_argument_mismatch() {
|
||||||
let env = crate::ast::environment::Environment::new();
|
let env = crate::ast::environment::Environment::new();
|
||||||
// fn takes 1 arg, called with 0
|
// 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.is_err());
|
||||||
assert!(
|
assert!(
|
||||||
result
|
result
|
||||||
@@ -690,7 +691,7 @@ mod tests {
|
|||||||
fn test_destructuring_vector_args() {
|
fn test_destructuring_vector_args() {
|
||||||
let env = crate::ast::environment::Environment::new();
|
let env = crate::ast::environment::Environment::new();
|
||||||
// ((fn [[x y]] (+ x y)) [10 20]) -> 30
|
// ((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!(result.is_ok());
|
||||||
assert_eq!(get_ret_type(&result.unwrap()), StaticType::Int);
|
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,
|
Identity, NodeIdentity, Object, Purity, SourceLocation, StaticType, Value,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::ast::diagnostics::{DiagnosticLevel, Diagnostics};
|
||||||
pub type PipelineGenerator = Box<dyn FnMut() -> bool>;
|
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 struct Environment {
|
||||||
pub global_names: Rc<RefCell<HashMap<Symbol, (GlobalIdx, Identity)>>>,
|
pub global_names: Rc<RefCell<HashMap<Symbol, (GlobalIdx, Identity)>>>,
|
||||||
pub global_types: Rc<RefCell<HashMap<GlobalIdx, StaticType>>>,
|
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>));
|
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 bound_ast = crate::ast::compiler::captures::CapturePass::apply(bound_ast, &captures);
|
||||||
let checker = TypeChecker::new(self.global_types.clone());
|
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 exec_ast = TCO::optimize(Analyzer::analyze(&typed_ast, &HashMap::new())); // Minimal analysis for macro eval
|
||||||
|
|
||||||
let mut vm = VM::new(self.global_values.clone());
|
let mut vm = VM::new(self.global_values.clone());
|
||||||
@@ -141,8 +188,8 @@ impl Environment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn eval_prelude(&self, source: &str) {
|
fn eval_prelude(&self, source: &str) {
|
||||||
let mut parser = crate::ast::parser::Parser::new(source).expect("Failed to parse prelude");
|
let mut parser = crate::ast::parser::Parser::new(source);
|
||||||
let untyped_ast = parser.parse_expression().expect("Failed to parse prelude expression");
|
let untyped_ast = parser.parse_expression();
|
||||||
let mut expander = self.get_expander();
|
let mut expander = self.get_expander();
|
||||||
let _ = expander.expand(untyped_ast).expect("Failed to expand prelude");
|
let _ = expander.expand(untyped_ast).expect("Failed to expand prelude");
|
||||||
*self.macro_registry.borrow_mut() = expander.into_registry();
|
*self.macro_registry.borrow_mut() = expander.into_registry();
|
||||||
@@ -204,21 +251,40 @@ impl Environment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn dump_ast(&self, source: &str) -> Result<String, String> {
|
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);
|
let linked = self.link(compiled);
|
||||||
Ok(Dumper::dump(&linked))
|
Ok(Dumper::dump(&linked))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn compile(&self, source: &str) -> Result<TypedNode, String> {
|
pub fn compile(&self, source: &str) -> CompilationResult {
|
||||||
let mut parser = Parser::new(source)?;
|
let mut parser = Parser::new(source);
|
||||||
let untyped_ast = parser.parse_expression()?;
|
let untyped_ast = parser.parse_expression();
|
||||||
|
|
||||||
if !parser.at_eof() {
|
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);
|
let bound_ast = crate::ast::compiler::captures::CapturePass::apply(bound_ast, &captures);
|
||||||
|
|
||||||
// Pre-allocate global slots to prevent out-of-bounds during specialization/optimization
|
// 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());
|
LambdaCollector::collect(&bound_ast, &mut self.function_registry.borrow_mut());
|
||||||
|
|
||||||
let checker = TypeChecker::new(self.global_types.clone());
|
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 {
|
pub fn link(&self, node: TypedNode) -> ExecNode {
|
||||||
@@ -335,8 +404,13 @@ impl Environment {
|
|||||||
move |func_template: BoundNode,
|
move |func_template: BoundNode,
|
||||||
arg_types: &[StaticType]|
|
arg_types: &[StaticType]|
|
||||||
-> Result<(Value, StaticType), String> {
|
-> Result<(Value, StaticType), String> {
|
||||||
|
let mut diag = Diagnostics::new();
|
||||||
let checker = TypeChecker::new(global_types.clone());
|
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());
|
let analyzed = Analyzer::analyze(&retyped_ast, &global_purity.borrow());
|
||||||
|
|
||||||
@@ -391,17 +465,20 @@ impl Environment {
|
|||||||
}
|
}
|
||||||
res
|
res
|
||||||
} else {
|
} else {
|
||||||
let compiled = self.compile(source)?;
|
self.compile(source).into_result().and_then(|ast| self.run_script_compiled(ast))
|
||||||
let linked = self.link(compiled);
|
|
||||||
let func = self.instantiate(linked);
|
|
||||||
let res = (func.func)(vec![]);
|
|
||||||
self.run_pipeline();
|
|
||||||
Ok(res)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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> {
|
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 linked = self.link(compiled);
|
||||||
let mut vm = VM::new(self.global_values.clone());
|
let mut vm = VM::new(self.global_values.clone());
|
||||||
let mut observer = TracingObserver::new();
|
let mut observer = TracingObserver::new();
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
pub mod compiler;
|
pub mod compiler;
|
||||||
|
pub mod diagnostics;
|
||||||
pub mod environment;
|
pub mod environment;
|
||||||
pub mod lexer;
|
pub mod lexer;
|
||||||
pub mod nodes;
|
pub mod nodes;
|
||||||
|
|||||||
@@ -123,5 +123,7 @@ pub enum UntypedKind {
|
|||||||
/// The resulting AST after macro expansion.
|
/// The resulting AST after macro expansion.
|
||||||
expanded: Box<Node<UntypedKind>>,
|
expanded: Box<Node<UntypedKind>>,
|
||||||
},
|
},
|
||||||
|
/// A diagnostic poison node, allowing compilation to continue after an error.
|
||||||
|
Error,
|
||||||
Extension(Box<dyn CustomNode>),
|
Extension(Box<dyn CustomNode>),
|
||||||
}
|
}
|
||||||
|
|||||||
+207
-135
@@ -1,33 +1,55 @@
|
|||||||
use crate::ast::lexer::{Lexer, Token, TokenKind};
|
use crate::ast::lexer::{Lexer, Token, TokenKind};
|
||||||
use crate::ast::nodes::{Node, Symbol, UntypedKind};
|
use crate::ast::nodes::{Node, Symbol, UntypedKind};
|
||||||
use crate::ast::types::{Identity, Keyword, NodeIdentity, Value};
|
use crate::ast::types::{Identity, Keyword, NodeIdentity, Value};
|
||||||
|
use crate::ast::diagnostics::Diagnostics;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
pub struct Parser<'a> {
|
pub struct Parser<'a> {
|
||||||
lexer: Lexer<'a>,
|
lexer: Lexer<'a>,
|
||||||
current_token: Token,
|
current_token: Token,
|
||||||
|
pub diagnostics: Diagnostics,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Parser<'a> {
|
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 mut lexer = Lexer::new(input);
|
||||||
let current_token = lexer.next_token()?;
|
let mut diagnostics = Diagnostics::new();
|
||||||
Ok(Self {
|
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,
|
lexer,
|
||||||
current_token,
|
current_token,
|
||||||
})
|
diagnostics,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn advance(&mut self) -> Result<Token, String> {
|
fn advance(&mut self) -> Token {
|
||||||
let prev = std::mem::replace(&mut self.current_token, self.lexer.next_token()?);
|
let next = match self.lexer.next_token() {
|
||||||
Ok(prev)
|
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 {
|
fn peek(&self) -> &TokenKind {
|
||||||
&self.current_token.kind
|
&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 token_loc = self.current_token.location;
|
||||||
let identity = NodeIdentity::new(token_loc);
|
let identity = NodeIdentity::new(token_loc);
|
||||||
|
|
||||||
@@ -36,9 +58,9 @@ impl<'a> Parser<'a> {
|
|||||||
TokenKind::LeftBracket => self.parse_vector_literal(),
|
TokenKind::LeftBracket => self.parse_vector_literal(),
|
||||||
TokenKind::LeftBrace => self.parse_record_literal(),
|
TokenKind::LeftBrace => self.parse_record_literal(),
|
||||||
TokenKind::Quote => {
|
TokenKind::Quote => {
|
||||||
self.advance()?; // consume '
|
self.advance(); // consume '
|
||||||
let expr = self.parse_expression()?;
|
let expr = self.parse_expression();
|
||||||
Ok(Node {
|
Node {
|
||||||
identity: identity.clone(),
|
identity: identity.clone(),
|
||||||
kind: UntypedKind::Call {
|
kind: UntypedKind::Call {
|
||||||
callee: Box::new(self.make_id_node("quote", identity.clone())),
|
callee: Box::new(self.make_id_node("quote", identity.clone())),
|
||||||
@@ -51,34 +73,34 @@ impl<'a> Parser<'a> {
|
|||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
ty: (),
|
ty: (),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
TokenKind::Backtick => {
|
TokenKind::Backtick => {
|
||||||
self.advance()?; // consume `
|
self.advance(); // consume `
|
||||||
let expr = self.parse_expression()?;
|
let expr = self.parse_expression();
|
||||||
Ok(Node {
|
Node {
|
||||||
identity,
|
identity,
|
||||||
kind: UntypedKind::Template(Box::new(expr)),
|
kind: UntypedKind::Template(Box::new(expr)),
|
||||||
ty: (),
|
ty: (),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
TokenKind::Tilde => {
|
TokenKind::Tilde => {
|
||||||
self.advance()?; // consume ~
|
self.advance(); // consume ~
|
||||||
if *self.peek() == TokenKind::At {
|
if *self.peek() == TokenKind::At {
|
||||||
self.advance()?; // consume @
|
self.advance(); // consume @
|
||||||
let expr = self.parse_expression()?;
|
let expr = self.parse_expression();
|
||||||
Ok(Node {
|
Node {
|
||||||
identity,
|
identity,
|
||||||
kind: UntypedKind::Splice(Box::new(expr)),
|
kind: UntypedKind::Splice(Box::new(expr)),
|
||||||
ty: (),
|
ty: (),
|
||||||
})
|
}
|
||||||
} else {
|
} else {
|
||||||
let expr = self.parse_expression()?;
|
let expr = self.parse_expression();
|
||||||
Ok(Node {
|
Node {
|
||||||
identity,
|
identity,
|
||||||
kind: UntypedKind::Placeholder(Box::new(expr)),
|
kind: UntypedKind::Placeholder(Box::new(expr)),
|
||||||
ty: (),
|
ty: (),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => self.parse_atom(),
|
_ => self.parse_atom(),
|
||||||
@@ -89,8 +111,22 @@ impl<'a> Parser<'a> {
|
|||||||
matches!(self.current_token.kind, TokenKind::EOF)
|
matches!(self.current_token.kind, TokenKind::EOF)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_atom(&mut self) -> Result<Node<UntypedKind>, String> {
|
fn synchronize(&mut self) {
|
||||||
let token = self.advance()?;
|
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 identity = NodeIdentity::new(token.location);
|
||||||
|
|
||||||
let kind = match token.kind {
|
let kind = match token.kind {
|
||||||
@@ -105,35 +141,43 @@ impl<'a> Parser<'a> {
|
|||||||
}
|
}
|
||||||
_ => UntypedKind::Identifier(id.into()),
|
_ => UntypedKind::Identifier(id.into()),
|
||||||
},
|
},
|
||||||
|
TokenKind::EOF => UntypedKind::Error, // Error already logged by advance
|
||||||
_ => {
|
_ => {
|
||||||
return Err(format!(
|
self.diagnostics.push_error(
|
||||||
"Unexpected token in atom: {:?} at {:?}",
|
format!("Unexpected token in atom: {:?}", token.kind),
|
||||||
token.kind, token.location
|
Some(identity.clone()),
|
||||||
));
|
);
|
||||||
|
UntypedKind::Error
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Node {
|
Node {
|
||||||
identity,
|
identity,
|
||||||
kind,
|
kind,
|
||||||
ty: (),
|
ty: (),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_list(&mut self) -> Result<Node<UntypedKind>, String> {
|
fn parse_list(&mut self) -> Node<UntypedKind> {
|
||||||
let start_loc = self.advance()?.location; // consume '('
|
let start_loc = self.advance().location; // consume '('
|
||||||
let identity = NodeIdentity::new(start_loc);
|
let identity = NodeIdentity::new(start_loc);
|
||||||
|
|
||||||
if *self.peek() == TokenKind::RightParen {
|
if *self.peek() == TokenKind::RightParen {
|
||||||
return Err(format!(
|
self.diagnostics.push_error(
|
||||||
"Empty list () is not a valid expression at {:?}",
|
"Empty list () is not a valid expression",
|
||||||
start_loc
|
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() {
|
match sym.name.as_ref() {
|
||||||
"if" => self.parse_if(identity),
|
"if" => self.parse_if(identity),
|
||||||
"fn" => self.parse_fn(identity),
|
"fn" => self.parse_fn(identity),
|
||||||
@@ -149,15 +193,15 @@ impl<'a> Parser<'a> {
|
|||||||
self.parse_call(head, identity)
|
self.parse_call(head, identity)
|
||||||
};
|
};
|
||||||
|
|
||||||
self.expect(TokenKind::RightParen)?;
|
self.expect(TokenKind::RightParen);
|
||||||
result
|
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();
|
let mut elements = Vec::new();
|
||||||
|
|
||||||
while *self.peek() != TokenKind::RightParen && *self.peek() != TokenKind::EOF {
|
while *self.peek() != TokenKind::RightParen && *self.peek() != TokenKind::EOF {
|
||||||
elements.push(self.parse_expression()?);
|
elements.push(self.parse_expression());
|
||||||
}
|
}
|
||||||
|
|
||||||
let args_node = Node {
|
let args_node = Node {
|
||||||
@@ -166,25 +210,25 @@ impl<'a> Parser<'a> {
|
|||||||
ty: (),
|
ty: (),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Node {
|
Node {
|
||||||
identity,
|
identity,
|
||||||
kind: UntypedKind::Again {
|
kind: UntypedKind::Again {
|
||||||
args: Box::new(args_node),
|
args: Box::new(args_node),
|
||||||
},
|
},
|
||||||
ty: (),
|
ty: (),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_if(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> {
|
fn parse_if(&mut self, identity: Identity) -> Node<UntypedKind> {
|
||||||
let cond = Box::new(self.parse_expression()?);
|
let cond = Box::new(self.parse_expression());
|
||||||
let then_br = Box::new(self.parse_expression()?);
|
let then_br = Box::new(self.parse_expression());
|
||||||
let mut else_br = None;
|
let mut else_br = None;
|
||||||
|
|
||||||
if *self.peek() != TokenKind::RightParen {
|
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,
|
identity,
|
||||||
kind: UntypedKind::If {
|
kind: UntypedKind::If {
|
||||||
cond,
|
cond,
|
||||||
@@ -192,82 +236,88 @@ impl<'a> Parser<'a> {
|
|||||||
else_br,
|
else_br,
|
||||||
},
|
},
|
||||||
ty: (),
|
ty: (),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_def(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> {
|
fn parse_def(&mut self, identity: Identity) -> Node<UntypedKind> {
|
||||||
let target = Box::new(self.parse_pattern()?);
|
let target = Box::new(self.parse_pattern());
|
||||||
let value = Box::new(self.parse_expression()?);
|
let value = Box::new(self.parse_expression());
|
||||||
|
|
||||||
Ok(Node {
|
Node {
|
||||||
identity,
|
identity,
|
||||||
kind: UntypedKind::Def { target, value },
|
kind: UntypedKind::Def { target, value },
|
||||||
ty: (),
|
ty: (),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_assign(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> {
|
fn parse_assign(&mut self, identity: Identity) -> Node<UntypedKind> {
|
||||||
// (assign target value)
|
// (assign target value)
|
||||||
let target = Box::new(self.parse_expression()?);
|
let target = Box::new(self.parse_expression());
|
||||||
let value = Box::new(self.parse_expression()?);
|
let value = Box::new(self.parse_expression());
|
||||||
|
|
||||||
Ok(Node {
|
Node {
|
||||||
identity,
|
identity,
|
||||||
kind: UntypedKind::Assign { target, value },
|
kind: UntypedKind::Assign { target, value },
|
||||||
ty: (),
|
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();
|
let mut exprs = Vec::new();
|
||||||
while *self.peek() != TokenKind::RightParen && *self.peek() != TokenKind::EOF {
|
while *self.peek() != TokenKind::RightParen && *self.peek() != TokenKind::EOF {
|
||||||
exprs.push(self.parse_expression()?);
|
exprs.push(self.parse_expression());
|
||||||
}
|
}
|
||||||
Ok(Node {
|
Node {
|
||||||
identity,
|
identity,
|
||||||
kind: UntypedKind::Block { exprs },
|
kind: UntypedKind::Block { exprs },
|
||||||
ty: (),
|
ty: (),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_pipe(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> {
|
fn parse_pipe(&mut self, identity: Identity) -> Node<UntypedKind> {
|
||||||
let inputs_node = self.parse_expression()?;
|
let inputs_node = self.parse_expression();
|
||||||
let inputs = match inputs_node.kind {
|
let inputs = match inputs_node.kind {
|
||||||
UntypedKind::Tuple { elements } => elements,
|
UntypedKind::Tuple { elements } => elements,
|
||||||
_ => vec![inputs_node],
|
_ => vec![inputs_node],
|
||||||
};
|
};
|
||||||
let lambda = Box::new(self.parse_expression()?);
|
let lambda = Box::new(self.parse_expression());
|
||||||
|
|
||||||
Ok(Node {
|
Node {
|
||||||
identity,
|
identity,
|
||||||
kind: UntypedKind::Pipe { inputs, lambda },
|
kind: UntypedKind::Pipe { inputs, lambda },
|
||||||
ty: (),
|
ty: (),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_fn(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> {
|
fn parse_fn(&mut self, identity: Identity) -> Node<UntypedKind> {
|
||||||
let params = Box::new(self.parse_param_vector()?);
|
let params = Box::new(self.parse_param_vector());
|
||||||
let body = self.parse_expression()?;
|
let body = self.parse_expression();
|
||||||
|
|
||||||
Ok(Node {
|
Node {
|
||||||
identity,
|
identity,
|
||||||
kind: UntypedKind::Lambda {
|
kind: UntypedKind::Lambda {
|
||||||
params,
|
params,
|
||||||
body: Rc::new(body),
|
body: Rc::new(body),
|
||||||
},
|
},
|
||||||
ty: (),
|
ty: (),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_macro_decl(&mut self, identity: Identity) -> Result<Node<UntypedKind>, String> {
|
fn parse_macro_decl(&mut self, identity: Identity) -> Node<UntypedKind> {
|
||||||
let name_node = self.parse_expression()?;
|
let name_node = self.parse_expression();
|
||||||
let name = match name_node.kind {
|
let name = match name_node.kind {
|
||||||
UntypedKind::Identifier(sym) => sym,
|
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 params = Box::new(self.parse_param_vector());
|
||||||
let body = self.parse_expression()?;
|
let body = self.parse_expression();
|
||||||
Ok(Node {
|
Node {
|
||||||
identity,
|
identity,
|
||||||
kind: UntypedKind::MacroDecl {
|
kind: UntypedKind::MacroDecl {
|
||||||
name,
|
name,
|
||||||
@@ -275,54 +325,66 @@ impl<'a> Parser<'a> {
|
|||||||
body: Box::new(body),
|
body: Box::new(body),
|
||||||
},
|
},
|
||||||
ty: (),
|
ty: (),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_param_vector(&mut self) -> Result<Node<UntypedKind>, String> {
|
fn parse_param_vector(&mut self) -> Node<UntypedKind> {
|
||||||
if *self.peek() != TokenKind::LeftBracket {
|
if *self.peek() != TokenKind::LeftBracket {
|
||||||
return Err(format!(
|
self.diagnostics.push_error(
|
||||||
"Expected parameter vector [...] for fn, found {:?}",
|
format!("Expected parameter vector [...] for fn, found {:?}", self.peek()),
|
||||||
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()
|
self.parse_pattern()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_pattern(&mut self) -> Result<Node<UntypedKind>, String> {
|
fn parse_pattern(&mut self) -> Node<UntypedKind> {
|
||||||
let next = self.peek();
|
let next = self.peek();
|
||||||
match next {
|
match next {
|
||||||
TokenKind::Identifier(_) => {
|
TokenKind::Identifier(_) => {
|
||||||
let token = self.advance()?;
|
let token = self.advance();
|
||||||
let sym: Symbol = match token.kind {
|
let sym: Symbol = match token.kind {
|
||||||
TokenKind::Identifier(s) => s.into(),
|
TokenKind::Identifier(s) => s.into(),
|
||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
};
|
};
|
||||||
Ok(Node {
|
Node {
|
||||||
identity: NodeIdentity::new(token.location),
|
identity: NodeIdentity::new(token.location),
|
||||||
kind: UntypedKind::Parameter(sym),
|
kind: UntypedKind::Parameter(sym),
|
||||||
ty: (),
|
ty: (),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
TokenKind::LeftBracket => {
|
TokenKind::LeftBracket => {
|
||||||
let token = self.advance()?;
|
let token = self.advance();
|
||||||
let identity = NodeIdentity::new(token.location);
|
let identity = NodeIdentity::new(token.location);
|
||||||
|
|
||||||
let mut elements = Vec::new();
|
let mut elements = Vec::new();
|
||||||
while *self.peek() != TokenKind::RightBracket {
|
while *self.peek() != TokenKind::RightBracket && *self.peek() != TokenKind::EOF {
|
||||||
elements.push(self.parse_pattern()?);
|
elements.push(self.parse_pattern());
|
||||||
}
|
}
|
||||||
self.expect(TokenKind::RightBracket)?;
|
self.expect(TokenKind::RightBracket);
|
||||||
|
|
||||||
Ok(Node {
|
Node {
|
||||||
identity,
|
identity,
|
||||||
kind: UntypedKind::Tuple { elements },
|
kind: UntypedKind::Tuple { elements },
|
||||||
ty: (),
|
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,
|
&mut self,
|
||||||
callee: Node<UntypedKind>,
|
callee: Node<UntypedKind>,
|
||||||
identity: Identity,
|
identity: Identity,
|
||||||
) -> Result<Node<UntypedKind>, String> {
|
) -> Node<UntypedKind> {
|
||||||
let mut elements = Vec::new();
|
let mut elements = Vec::new();
|
||||||
|
|
||||||
while *self.peek() != TokenKind::RightParen && *self.peek() != TokenKind::EOF {
|
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.
|
// The arguments are wrapped in a Tuple node, reusing the call's identity/location.
|
||||||
@@ -344,77 +406,87 @@ impl<'a> Parser<'a> {
|
|||||||
ty: (),
|
ty: (),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(Node {
|
Node {
|
||||||
identity,
|
identity,
|
||||||
kind: UntypedKind::Call {
|
kind: UntypedKind::Call {
|
||||||
callee: Box::new(callee),
|
callee: Box::new(callee),
|
||||||
args: Box::new(args_node),
|
args: Box::new(args_node),
|
||||||
},
|
},
|
||||||
ty: (),
|
ty: (),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_vector_literal(&mut self) -> Result<Node<UntypedKind>, String> {
|
fn parse_vector_literal(&mut self) -> Node<UntypedKind> {
|
||||||
let token = self.advance()?;
|
let token = self.advance();
|
||||||
let mut elements = Vec::new();
|
let mut elements = Vec::new();
|
||||||
|
|
||||||
while *self.peek() != TokenKind::RightBracket && *self.peek() != TokenKind::EOF {
|
while *self.peek() != TokenKind::RightBracket && *self.peek() != TokenKind::EOF {
|
||||||
let expr = self.parse_expression()?;
|
let expr = self.parse_expression();
|
||||||
elements.push(expr);
|
elements.push(expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.expect(TokenKind::RightBracket)?;
|
self.expect(TokenKind::RightBracket);
|
||||||
|
|
||||||
Ok(Node {
|
Node {
|
||||||
identity: NodeIdentity::new(token.location),
|
identity: NodeIdentity::new(token.location),
|
||||||
kind: UntypedKind::Tuple { elements },
|
kind: UntypedKind::Tuple { elements },
|
||||||
ty: (),
|
ty: (),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_record_literal(&mut self) -> Result<Node<UntypedKind>, String> {
|
fn parse_record_literal(&mut self) -> Node<UntypedKind> {
|
||||||
let token = self.advance()?;
|
let token = self.advance();
|
||||||
let mut fields = Vec::new();
|
let mut fields = Vec::new();
|
||||||
|
|
||||||
while *self.peek() != TokenKind::RightBrace {
|
while *self.peek() != TokenKind::RightBrace && *self.peek() != TokenKind::EOF {
|
||||||
if *self.peek() == TokenKind::EOF {
|
let key_node = self.parse_expression();
|
||||||
return Err("Unexpected EOF in record literal".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
let key_node = self.parse_expression()?;
|
|
||||||
// We check for keyword kind here (syntactically) to avoid ambiguity, but
|
// We check for keyword kind here (syntactically) to avoid ambiguity, but
|
||||||
// strictly we could allow any expression and check at runtime.
|
// strictly we could allow any expression and check at runtime.
|
||||||
// Delphi enforces keywords. We can do minimal check here.
|
// Delphi enforces keywords. We can do minimal check here.
|
||||||
match &key_node.kind {
|
match &key_node.kind {
|
||||||
UntypedKind::Constant(Value::Keyword(_)) => {}
|
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 {
|
if *self.peek() == TokenKind::RightBrace || *self.peek() == TokenKind::EOF {
|
||||||
return Err("Record literal must have even number of forms".to_string());
|
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));
|
fields.push((key_node, val_node));
|
||||||
}
|
}
|
||||||
self.expect(TokenKind::RightBrace)?;
|
self.expect(TokenKind::RightBrace);
|
||||||
|
|
||||||
Ok(Node {
|
Node {
|
||||||
identity: NodeIdentity::new(token.location),
|
identity: NodeIdentity::new(token.location),
|
||||||
kind: UntypedKind::Record { fields },
|
kind: UntypedKind::Record { fields },
|
||||||
ty: (),
|
ty: (),
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expect(&mut self, kind: TokenKind) -> Result<(), String> {
|
fn expect(&mut self, kind: TokenKind) -> Token {
|
||||||
let token = self.advance()?;
|
if self.peek() == &kind {
|
||||||
if token.kind == kind {
|
self.advance()
|
||||||
Ok(())
|
|
||||||
} else {
|
} else {
|
||||||
Err(format!(
|
self.diagnostics.push_error(
|
||||||
"Expected {:?}, but found {:?} at {:?}",
|
format!("Expected {:?}, but found {:?}", kind, self.peek()),
|
||||||
kind, token.kind, token.location
|
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>),
|
Function(Box<Signature>),
|
||||||
FunctionOverloads(Vec<Signature>),
|
FunctionOverloads(Vec<Signature>),
|
||||||
Object(&'static str),
|
Object(&'static str),
|
||||||
|
/// A diagnostic poison type, allowing type-checking to continue after an error.
|
||||||
|
Error,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for StaticType {
|
impl fmt::Display for StaticType {
|
||||||
@@ -361,6 +363,7 @@ impl fmt::Display for StaticType {
|
|||||||
write!(f, "overloads({} variants)", sigs.len())
|
write!(f, "overloads({} variants)", sigs.len())
|
||||||
}
|
}
|
||||||
StaticType::Object(name) => write!(f, "{}", name),
|
StaticType::Object(name) => write!(f, "{}", name),
|
||||||
|
StaticType::Error => write!(f, "<error>"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -368,7 +371,7 @@ impl fmt::Display for StaticType {
|
|||||||
impl StaticType {
|
impl StaticType {
|
||||||
/// Returns true if `other` can be assigned to a location of type `self`.
|
/// Returns true if `other` can be assigned to a location of type `self`.
|
||||||
pub fn is_assignable_from(&self, other: &StaticType) -> bool {
|
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -658,6 +658,7 @@ impl VM {
|
|||||||
"Execution of extension '{}' not implemented yet",
|
"Execution of extension '{}' not implemented yet",
|
||||||
ext.display_name()
|
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) {
|
fn execute(env: &Environment, source: &str) {
|
||||||
match env.run_script(source) {
|
let result = env.compile(source);
|
||||||
Ok(result) => println!("{}", result),
|
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) => {
|
Err(e) => {
|
||||||
eprintln!("Error: {}", e);
|
eprintln!("Runtime Error: {}", e);
|
||||||
std::process::exit(1);
|
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)
|
// Compile once for this file (symbols/types are compatible with all fresh environments)
|
||||||
let initial_env = Environment::new();
|
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,
|
Ok(c) => c,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
results.push(BenchmarkResult {
|
results.push(BenchmarkResult {
|
||||||
|
|||||||
Reference in New Issue
Block a user