Formatting

This commit is contained in:
Michael Schimmel
2026-03-02 23:13:36 +01:00
parent 5bc69c267b
commit 2eb7a2e136
22 changed files with 3212 additions and 2813 deletions
+12 -2
View File
@@ -237,7 +237,11 @@ impl<'a> Analyzer<'a> {
Purity::Impure, Purity::Impure,
) )
} }
BoundKind::Pipe { inputs, lambda, out_type } => { BoundKind::Pipe {
inputs,
lambda,
out_type,
} => {
let mut analyzed_inputs = Vec::with_capacity(inputs.len()); let mut analyzed_inputs = Vec::with_capacity(inputs.len());
for input in inputs { for input in inputs {
analyzed_inputs.push(self.visit(Rc::new(input.clone()))); analyzed_inputs.push(self.visit(Rc::new(input.clone())));
@@ -288,7 +292,13 @@ impl<'a> Analyzer<'a> {
p = p.min(vm.ty.purity); p = p.min(vm.ty.purity);
new_values.push(vm); new_values.push(vm);
} }
(BoundKind::Record { layout: layout.clone(), values: new_values }, p) (
BoundKind::Record {
layout: layout.clone(),
values: new_values,
},
p,
)
} }
BoundKind::Expansion { BoundKind::Expansion {
+45 -15
View File
@@ -1,4 +1,4 @@
use crate::ast::compiler::bound_nodes::{ use crate::ast::compiler::bound_nodes::{
Address, BoundKind, BoundNode, DeclarationKind, GlobalIdx, LocalSlot, UpvalueIdx, Address, BoundKind, BoundNode, DeclarationKind, GlobalIdx, LocalSlot, UpvalueIdx,
}; };
use crate::ast::diagnostics::Diagnostics; use crate::ast::diagnostics::Diagnostics;
@@ -315,10 +315,8 @@ impl Binder {
UntypedKind::Lambda { params, body } => { UntypedKind::Lambda { params, body } => {
let identity = node.identity.clone(); let identity = node.identity.clone();
self.functions.push(FunctionCompiler::new( self.functions
ScopeKind::Local, .push(FunctionCompiler::new(ScopeKind::Local, identity.clone()));
identity.clone(),
));
// 1. Bind the parameter pattern/tuple // 1. Bind the parameter pattern/tuple
let params_bound = self.bind_pattern(params, DeclarationKind::Parameter, diag); let params_bound = self.bind_pattern(params, DeclarationKind::Parameter, diag);
@@ -375,7 +373,10 @@ impl Binder {
UntypedKind::Again { args } => { UntypedKind::Again { args } => {
if self.functions.len() <= 1 { if self.functions.len() <= 1 {
diag.push_error("'again' is only allowed inside a function or lambda.", Some(node.identity.clone())); diag.push_error(
"'again' is only allowed inside a function or lambda.",
Some(node.identity.clone()),
);
return self.make_node(node.identity.clone(), BoundKind::Error); return self.make_node(node.identity.clone(), BoundKind::Error);
} }
let args = self.bind(args, diag); let args = self.bind(args, diag);
@@ -419,10 +420,18 @@ impl Binder {
let key_node = self.bind(k, diag); let key_node = self.bind(k, diag);
let val_node = self.bind(v, diag); let val_node = self.bind(v, diag);
if let BoundKind::Constant(crate::ast::types::Value::Keyword(kw)) = key_node.kind { if let BoundKind::Constant(crate::ast::types::Value::Keyword(kw)) =
key_node.kind
{
layout_fields.push((kw, crate::ast::types::StaticType::Any)); layout_fields.push((kw, crate::ast::types::StaticType::Any));
} else { } else {
diag.push_error(format!("Record keys must be keywords, found at {:?}", key_node.identity.location), Some(key_node.identity.clone())); diag.push_error(
format!(
"Record keys must be keywords, found at {:?}",
key_node.identity.location
),
Some(key_node.identity.clone()),
);
} }
bound_values.push(val_node); bound_values.push(val_node);
} }
@@ -458,7 +467,10 @@ impl Binder {
} }
UntypedKind::Extension(_) => { UntypedKind::Extension(_) => {
diag.push_error("Custom extensions not supported in Binder yet", Some(node.identity.clone())); diag.push_error(
"Custom extensions not supported in Binder yet",
Some(node.identity.clone()),
);
self.make_node(node.identity.clone(), BoundKind::Error) self.make_node(node.identity.clone(), BoundKind::Error)
} }
UntypedKind::Error => crate::ast::compiler::bound_nodes::BoundNode { UntypedKind::Error => crate::ast::compiler::bound_nodes::BoundNode {
@@ -469,7 +481,12 @@ impl Binder {
} }
} }
fn resolve_variable(&mut self, sym: &Symbol, diag: &mut Diagnostics, identity: &Identity) -> Option<Address> { fn resolve_variable(
&mut self,
sym: &Symbol,
diag: &mut Diagnostics,
identity: &Identity,
) -> Option<Address> {
let current_fn_idx = self.functions.len() - 1; let current_fn_idx = self.functions.len() - 1;
// 1. Try local in current function // 1. Try local in current function
@@ -512,7 +529,10 @@ impl Binder {
} }
} }
diag.push_error(format!("Undefined variable '{}'", sym.name), Some(identity.clone())); diag.push_error(
format!("Undefined variable '{}'", sym.name),
Some(identity.clone()),
);
None None
} }
@@ -552,13 +572,20 @@ impl Binder {
) )
} }
_ => { _ => {
diag.push_error(format!("Invalid node in pattern: {:?}", node.kind), Some(node.identity.clone())); diag.push_error(
format!("Invalid node in pattern: {:?}", node.kind),
Some(node.identity.clone()),
);
self.make_node(node.identity.clone(), BoundKind::Error) self.make_node(node.identity.clone(), BoundKind::Error)
} }
} }
} }
fn bind_assign_pattern(&mut self, node: &Node<UntypedKind>, diag: &mut Diagnostics) -> BoundNode { fn bind_assign_pattern(
&mut self,
node: &Node<UntypedKind>,
diag: &mut Diagnostics,
) -> BoundNode {
match &node.kind { match &node.kind {
UntypedKind::Identifier(sym) => { UntypedKind::Identifier(sym) => {
if let Some(addr) = self.resolve_variable(sym, diag, &node.identity) { if let Some(addr) = self.resolve_variable(sym, diag, &node.identity) {
@@ -586,7 +613,10 @@ impl Binder {
) )
} }
_ => { _ => {
diag.push_error(format!("Invalid node in assignment pattern: {:?}", node.kind), Some(node.identity.clone())); diag.push_error(
format!("Invalid node in assignment pattern: {:?}", node.kind),
Some(node.identity.clone()),
);
self.make_node(node.identity.clone(), BoundKind::Error) self.make_node(node.identity.clone(), BoundKind::Error)
} }
} }
@@ -604,8 +634,8 @@ impl Binder {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::ast::parser::Parser;
use crate::ast::diagnostics::Diagnostics; use crate::ast::diagnostics::Diagnostics;
use crate::ast::parser::Parser;
#[test] #[test]
fn test_upvalue_capture_sets_is_boxed() { fn test_upvalue_capture_sets_is_boxed() {
+14 -6
View File
@@ -206,9 +206,10 @@ where
}, },
) => na == nb && aa == ab && ka == kb && va == vb && ca == cb, ) => na == nb && aa == ab && ka == kb && va == vb && ca == cb,
(BoundKind::FieldAccessor(a), BoundKind::FieldAccessor(b)) => a == b, (BoundKind::FieldAccessor(a), BoundKind::FieldAccessor(b)) => a == b,
(BoundKind::GetField { rec: ra, field: fa }, BoundKind::GetField { rec: rb, field: fb }) => { (
ra == rb && fa == fb BoundKind::GetField { rec: ra, field: fa },
} BoundKind::GetField { rec: rb, field: fb },
) => ra == rb && fa == fb,
( (
BoundKind::If { BoundKind::If {
cond: ca, cond: ca,
@@ -258,9 +259,16 @@ where
(BoundKind::Again { args: aa }, BoundKind::Again { args: ab }) => aa == ab, (BoundKind::Again { args: aa }, BoundKind::Again { args: ab }) => aa == ab,
(BoundKind::Block { exprs: ea }, BoundKind::Block { exprs: eb }) => ea == eb, (BoundKind::Block { exprs: ea }, BoundKind::Block { exprs: eb }) => ea == eb,
(BoundKind::Tuple { elements: ea }, BoundKind::Tuple { elements: eb }) => ea == eb, (BoundKind::Tuple { elements: ea }, BoundKind::Tuple { elements: eb }) => ea == eb,
(BoundKind::Record { layout: la, values: va }, BoundKind::Record { layout: lb, values: vb }) => { (
std::sync::Arc::ptr_eq(la, lb) && va == vb BoundKind::Record {
} layout: la,
values: va,
},
BoundKind::Record {
layout: lb,
values: vb,
},
) => std::sync::Arc::ptr_eq(la, lb) && va == vb,
( (
BoundKind::Expansion { BoundKind::Expansion {
original_call: ca, original_call: ca,
+5 -1
View File
@@ -89,7 +89,11 @@ impl CapturePass {
args: Box::new(Self::transform(*args, capture_map)), args: Box::new(Self::transform(*args, capture_map)),
}; };
} }
BoundKind::Pipe { inputs, lambda, out_type } => { BoundKind::Pipe {
inputs,
lambda,
out_type,
} => {
let mut t_inputs = Vec::with_capacity(inputs.len()); let mut t_inputs = Vec::with_capacity(inputs.len());
for input in inputs { for input in inputs {
t_inputs.push(Self::transform(input, capture_map)); t_inputs.push(Self::transform(input, capture_map));
+5 -4
View File
@@ -66,9 +66,7 @@ impl Dumper {
self.log(&format!("Get: {} ({:?})", name.name, addr), node) self.log(&format!("Get: {} ({:?})", name.name, addr), node)
} }
BoundKind::FieldAccessor(k) => { BoundKind::FieldAccessor(k) => self.log(&format!("FieldAccessor: .{}", k.name()), node),
self.log(&format!("FieldAccessor: .{}", k.name()), node)
}
BoundKind::GetField { rec, field } => { BoundKind::GetField { rec, field } => {
self.log(&format!("GetField: .{}", field.name()), node); self.log(&format!("GetField: .{}", field.name()), node);
@@ -232,7 +230,10 @@ impl Dumper {
} }
BoundKind::Record { layout, values } => { BoundKind::Record { layout, values } => {
self.log(&format!("Record (Layout: {} fields)", layout.fields.len()), node); self.log(
&format!("Record (Layout: {} fields)", layout.fields.len()),
node,
);
self.indent += 1; self.indent += 1;
for v in values { for v in values {
self.visit(v); self.visit(v);
+19 -4
View File
@@ -386,7 +386,11 @@ impl Optimizer {
) )
} }
BoundKind::Pipe { inputs, lambda, out_type } => { BoundKind::Pipe {
inputs,
lambda,
out_type,
} => {
let mut o_inputs = Vec::with_capacity(inputs.len()); let mut o_inputs = Vec::with_capacity(inputs.len());
for input in inputs { for input in inputs {
o_inputs.push(self.visit_node(input, sub, path)); o_inputs.push(self.visit_node(input, sub, path));
@@ -559,17 +563,28 @@ impl Optimizer {
.collect(); .collect();
(BoundKind::Tuple { elements }, node.ty.clone()) (BoundKind::Tuple { elements }, node.ty.clone())
} }
BoundKind::Record { ref layout, ref values } => { BoundKind::Record {
ref layout,
ref values,
} => {
let mapped_values: Vec<_> = values let mapped_values: Vec<_> = values
.iter() .iter()
.map(|v| self.visit_node(v.clone(), sub, path)) .map(|v| self.visit_node(v.clone(), sub, path))
.collect(); .collect();
if self.enabled && let Some(folded) = folder.try_fold_record(layout, &mapped_values, &node) { if self.enabled
&& let Some(folded) = folder.try_fold_record(layout, &mapped_values, &node)
{
return folded; return folded;
} }
(BoundKind::Record { layout: layout.clone(), values: mapped_values }, node.ty.clone()) (
BoundKind::Record {
layout: layout.clone(),
values: mapped_values,
},
node.ty.clone(),
)
} }
BoundKind::Expansion { BoundKind::Expansion {
ref original_call, ref original_call,
+1 -1
View File
@@ -1,6 +1,6 @@
use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind, NodeMetrics}; use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind, NodeMetrics};
use crate::ast::nodes::Node; use crate::ast::nodes::Node;
use crate::ast::types::{Purity, StaticType, Value, RecordLayout}; use crate::ast::types::{Purity, RecordLayout, StaticType, Value};
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
+4 -1
View File
@@ -158,7 +158,10 @@ impl UsageInfo {
} }
self.collect(lambda); self.collect(lambda);
} }
BoundKind::Nop | BoundKind::FieldAccessor(_) | BoundKind::Extension(_) | BoundKind::Error => {} BoundKind::Nop
| BoundKind::FieldAccessor(_)
| BoundKind::Extension(_)
| BoundKind::Error => {}
} }
} }
+1 -4
View File
@@ -132,10 +132,7 @@ impl Specializer {
(BoundKind::Tuple { elements }, node.ty.clone()) (BoundKind::Tuple { elements }, node.ty.clone())
} }
BoundKind::Record { layout, values } => { BoundKind::Record { layout, values } => {
let values = values let values = values.into_iter().map(|v| self.visit_node(v)).collect();
.into_iter()
.map(|v| self.visit_node(v))
.collect();
(BoundKind::Record { layout, values }, node.ty.clone()) (BoundKind::Record { layout, values }, node.ty.clone())
} }
BoundKind::Expansion { BoundKind::Expansion {
+5 -1
View File
@@ -48,7 +48,11 @@ impl TCO {
args: Box::new(Self::transform(Rc::new((**args).clone()), false)), args: Box::new(Self::transform(Rc::new((**args).clone()), false)),
} }
} }
BoundKind::Pipe { inputs, lambda, out_type } => { BoundKind::Pipe {
inputs,
lambda,
out_type,
} => {
let mut t_inputs = Vec::with_capacity(inputs.len()); let mut t_inputs = Vec::with_capacity(inputs.len());
for input in inputs { for input in inputs {
t_inputs.push(Self::transform(Rc::new(input.clone()), false)); t_inputs.push(Self::transform(Rc::new(input.clone()), false));
+71 -12
View File
@@ -79,7 +79,12 @@ impl TypeChecker {
Self { global_types } Self { global_types }
} }
pub fn check(&self, node: BoundNode, arg_types: &[StaticType], diag: &mut Diagnostics) -> TypedNode { 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,
@@ -105,7 +110,12 @@ impl TypeChecker {
StaticType::Tuple(arg_types.to_vec()) StaticType::Tuple(arg_types.to_vec())
}; };
let params_typed = self.check_params(params.as_ref().clone(), &arg_tuple_ty, &mut lambda_ctx, diag); let params_typed = self.check_params(
params.as_ref().clone(),
&arg_tuple_ty,
&mut lambda_ctx,
diag,
);
// 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, diag); let body_typed = self.check_node((*body).clone(), &mut lambda_ctx, diag);
@@ -212,7 +222,13 @@ impl TypeChecker {
| StaticType::Record(_) | StaticType::Record(_)
| StaticType::Error => {} | StaticType::Error => {}
_ => { _ => {
diag.push_error(format!("Cannot destructure type {} as a tuple/vector", specialized_ty), Some(node.identity.clone())); diag.push_error(
format!(
"Cannot destructure type {} as a tuple/vector",
specialized_ty
),
Some(node.identity.clone()),
);
return Node { return Node {
identity: node.identity, identity: node.identity,
kind: BoundKind::Error, kind: BoundKind::Error,
@@ -251,7 +267,10 @@ impl TypeChecker {
} }
BoundKind::Error => (BoundKind::Error, StaticType::Error), BoundKind::Error => (BoundKind::Error, StaticType::Error),
_ => { _ => {
diag.push_error("Invalid node in parameter list", Some(node.identity.clone())); diag.push_error(
"Invalid node in parameter list",
Some(node.identity.clone()),
);
(BoundKind::Error, StaticType::Error) (BoundKind::Error, StaticType::Error)
} }
}; };
@@ -263,7 +282,12 @@ impl TypeChecker {
} }
} }
fn check_node(&self, node: BoundNode, ctx: &mut TypeContext, diag: &mut Diagnostics) -> TypedNode { 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),
@@ -306,7 +330,9 @@ 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, diag); let rec_typed = self.check_node(*rec, ctx, diag);
@@ -315,14 +341,24 @@ impl TypeChecker {
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 {
diag.push_error(format!("Record does not have field :{}", field.name()), Some(rec_typed.identity.clone())); diag.push_error(
format!("Record does not have field :{}", field.name()),
Some(rec_typed.identity.clone()),
);
StaticType::Error StaticType::Error
} }
} }
StaticType::Any => StaticType::Any, StaticType::Any => StaticType::Any,
StaticType::Error => StaticType::Error, StaticType::Error => StaticType::Error,
_ => { _ => {
diag.push_error(format!("Cannot access field :{} on non-record type {}", field.name(), rec_typed.ty), Some(rec_typed.identity.clone())); diag.push_error(
format!(
"Cannot access field :{} on non-record type {}",
field.name(),
rec_typed.ty
),
Some(rec_typed.identity.clone()),
);
StaticType::Error StaticType::Error
} }
}; };
@@ -463,7 +499,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 = self.check_params(params.as_ref().clone(), &StaticType::Any, &mut lambda_ctx, diag); let params_typed = self.check_params(
params.as_ref().clone(),
&StaticType::Any,
&mut lambda_ctx,
diag,
);
// 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());
@@ -515,7 +556,13 @@ impl TypeChecker {
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 => {
diag.push_error(format!("Invalid arguments for function call. Expected {}, got {}", callee_typed.ty, args_typed.ty), Some(node.identity.clone())); diag.push_error(
format!(
"Invalid arguments for function call. Expected {}, got {}",
callee_typed.ty, args_typed.ty
),
Some(node.identity.clone()),
);
StaticType::Error StaticType::Error
} }
}; };
@@ -553,7 +600,13 @@ impl TypeChecker {
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)
{ {
diag.push_error(format!("Type mismatch in 'again' call: expected {}, but got {}", expected_ty, args_typed.ty), Some(node.identity.clone())); diag.push_error(
format!(
"Type mismatch in 'again' call: expected {}, but got {}",
expected_ty, args_typed.ty
),
Some(node.identity.clone()),
);
} }
( (
@@ -639,7 +692,13 @@ impl TypeChecker {
} }
BoundKind::Extension(_ext) => { BoundKind::Extension(_ext) => {
diag.push_error(format!("TypeChecking for extension '{}' not implemented", _ext.display_name()), Some(node.identity.clone())); diag.push_error(
format!(
"TypeChecking for extension '{}' not implemented",
_ext.display_name()
),
Some(node.identity.clone()),
);
(BoundKind::Error, StaticType::Error) (BoundKind::Error, StaticType::Error)
} }
BoundKind::Error => (BoundKind::Error, StaticType::Error), BoundKind::Error => (BoundKind::Error, StaticType::Error),
+45 -15
View File
@@ -125,7 +125,12 @@ impl MacroEvaluator for RuntimeMacroEvaluator {
let typed_ast = checker.check(bound_ast, &[], &mut diag); let typed_ast = checker.check(bound_ast, &[], &mut diag);
if diag.has_errors() { if diag.has_errors() {
return Err(diag.items.into_iter().map(|d| d.message).collect::<Vec<_>>().join("\n")); 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
@@ -191,7 +196,9 @@ impl Environment {
let mut parser = crate::ast::parser::Parser::new(source); let mut parser = crate::ast::parser::Parser::new(source);
let untyped_ast = parser.parse_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();
} }
@@ -261,7 +268,9 @@ impl Environment {
let untyped_ast = parser.parse_expression(); let untyped_ast = parser.parse_expression();
if !parser.at_eof() { if !parser.at_eof() {
parser.diagnostics.push_error("Unexpected trailing expressions in script.", None); parser
.diagnostics
.push_error("Unexpected trailing expressions in script.", None);
return CompilationResult { return CompilationResult {
ast: None, ast: None,
diagnostics: parser.diagnostics, diagnostics: parser.diagnostics,
@@ -273,17 +282,24 @@ impl Environment {
Ok(ast) => ast, Ok(ast) => ast,
Err(e) => { Err(e) => {
diagnostics.push_error(e, None); diagnostics.push_error(e, None);
return CompilationResult { ast: None, diagnostics }; return CompilationResult {
ast: None,
diagnostics,
};
} }
}; };
let (bound_ast, captures) = match Binder::bind_root(self.global_names.clone(), &expanded_ast, &mut diagnostics) { let (bound_ast, captures) =
Ok(res) => res, match Binder::bind_root(self.global_names.clone(), &expanded_ast, &mut diagnostics) {
Err(e) => { Ok(res) => res,
diagnostics.push_error(e, None); Err(e) => {
return CompilationResult { ast: None, diagnostics }; diagnostics.push_error(e, None);
} return CompilationResult {
}; ast: None,
diagnostics,
};
}
};
let bound_ast = crate::ast::compiler::captures::CapturePass::apply(bound_ast, &captures); let bound_ast = crate::ast::compiler::captures::CapturePass::apply(bound_ast, &captures);
@@ -373,7 +389,10 @@ impl Environment {
let mut final_res = res; let mut final_res = res;
if let Value::Object(obj) = &final_res if let Value::Object(obj) = &final_res
&& obj.as_any().downcast_ref::<crate::ast::vm::Closure>().is_some() && obj
.as_any()
.downcast_ref::<crate::ast::vm::Closure>()
.is_some()
{ {
final_res = match vm.run_with_args(obj.clone(), args) { final_res = match vm.run_with_args(obj.clone(), args) {
Ok(v) => v, Ok(v) => v,
@@ -409,7 +428,12 @@ impl Environment {
let retyped_ast = checker.check(func_template, arg_types, &mut diag); let retyped_ast = checker.check(func_template, arg_types, &mut diag);
if diag.has_errors() { if diag.has_errors() {
return Err(diag.items.into_iter().map(|d| d.message).collect::<Vec<_>>().join("\n")); 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());
@@ -465,7 +489,9 @@ impl Environment {
} }
res res
} else { } else {
self.compile(source).into_result().and_then(|ast| self.run_script_compiled(ast)) self.compile(source)
.into_result()
.and_then(|ast| self.run_script_compiled(ast))
} }
} }
@@ -492,7 +518,11 @@ impl Environment {
while let Ok(Value::TailCallRequest(payload)) = result { while let Ok(Value::TailCallRequest(payload)) = result {
let (next_obj, next_args) = *payload; let (next_obj, next_args) = *payload;
if next_obj.as_any().downcast_ref::<crate::ast::vm::Closure>().is_some() { if next_obj
.as_any()
.downcast_ref::<crate::ast::vm::Closure>()
.is_some()
{
result = vm.run_with_args_observed(&mut observer, next_obj, next_args); result = vm.run_with_args_observed(&mut observer, next_obj, next_args);
} else { } else {
result = Err(format!( result = Err(format!(
+12 -9
View File
@@ -1,7 +1,7 @@
use crate::ast::diagnostics::Diagnostics;
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> {
@@ -35,7 +35,8 @@ impl<'a> Parser<'a> {
let next = match self.lexer.next_token() { let next = match self.lexer.next_token() {
Ok(t) => t, Ok(t) => t,
Err(e) => { Err(e) => {
self.diagnostics.push_error(e, Some(NodeIdentity::new(self.current_token.location))); self.diagnostics
.push_error(e, Some(NodeIdentity::new(self.current_token.location)));
Token { Token {
kind: TokenKind::EOF, kind: TokenKind::EOF,
location: self.current_token.location, location: self.current_token.location,
@@ -331,7 +332,10 @@ impl<'a> Parser<'a> {
fn parse_param_vector(&mut self) -> Node<UntypedKind> { fn parse_param_vector(&mut self) -> Node<UntypedKind> {
if *self.peek() != TokenKind::LeftBracket { if *self.peek() != TokenKind::LeftBracket {
self.diagnostics.push_error( self.diagnostics.push_error(
format!("Expected parameter vector [...] for fn, found {:?}", self.peek()), format!(
"Expected parameter vector [...] for fn, found {:?}",
self.peek()
),
Some(NodeIdentity::new(self.current_token.location)), Some(NodeIdentity::new(self.current_token.location)),
); );
return Node { return Node {
@@ -376,7 +380,10 @@ impl<'a> Parser<'a> {
} }
_ => { _ => {
self.diagnostics.push_error( self.diagnostics.push_error(
format!("Expected identifier or pattern vector [...] for definition, found {:?}", next), format!(
"Expected identifier or pattern vector [...] for definition, found {:?}",
next
),
Some(NodeIdentity::new(self.current_token.location)), Some(NodeIdentity::new(self.current_token.location)),
); );
Node { Node {
@@ -388,11 +395,7 @@ impl<'a> Parser<'a> {
} }
} }
fn parse_call( fn parse_call(&mut self, callee: Node<UntypedKind>, identity: Identity) -> Node<UntypedKind> {
&mut self,
callee: Node<UntypedKind>,
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 {
+19 -16
View File
@@ -152,22 +152,25 @@ pub fn register(env: &Environment) {
}, },
]); ]);
env.register_native_fn("make-random", make_random_ty, Purity::SideEffectFree, |args| { env.register_native_fn(
let rng = if args.is_empty() { "make-random",
fastrand::Rng::new() make_random_ty,
} else if let Value::Int(s) = args[0] { Purity::SideEffectFree,
fastrand::Rng::with_seed(s as u64) |args| {
} else { let rng = if args.is_empty() {
fastrand::Rng::new() fastrand::Rng::new()
}; } else if let Value::Int(s) = args[0] {
fastrand::Rng::with_seed(s as u64)
} else {
fastrand::Rng::new()
};
let prng = std::rc::Rc::new(std::cell::RefCell::new(rng)); let prng = std::rc::Rc::new(std::cell::RefCell::new(rng));
Value::Function(std::rc::Rc::new(crate::ast::types::NativeFunction { Value::Function(std::rc::Rc::new(crate::ast::types::NativeFunction {
func: std::rc::Rc::new(move |_| { func: std::rc::Rc::new(move |_| Value::Float(prng.borrow_mut().f64())),
Value::Float(prng.borrow_mut().f64()) purity: Purity::Impure,
}), }))
purity: Purity::Impure, },
})) );
});
} }
+39 -11
View File
@@ -101,7 +101,12 @@ impl<T: ScalarValue> ScalarSeries<T> {
impl<T: ScalarValue> Debug for ScalarSeries<T> { impl<T: ScalarValue> Debug for ScalarSeries<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "ScalarSeries<{}>[len: {}]", self.type_name, self.data.len()) write!(
f,
"ScalarSeries<{}>[len: {}]",
self.type_name,
self.data.len()
)
} }
} }
@@ -227,14 +232,21 @@ pub struct RecordSeries {
impl RecordSeries { impl RecordSeries {
/// Creates a new RecordSeries matching the given layout. /// Creates a new RecordSeries matching the given layout.
pub fn new(layout: Arc<RecordLayout>) -> Self { pub fn new(layout: Arc<RecordLayout>) -> Self {
let mut fields: Vec<Rc<std::cell::RefCell<dyn SeriesMember>>> = Vec::with_capacity(layout.fields.len()); let mut fields: Vec<Rc<std::cell::RefCell<dyn SeriesMember>>> =
Vec::with_capacity(layout.fields.len());
for (_, static_type) in &layout.fields { for (_, static_type) in &layout.fields {
// Automatically select the optimal storage representation based on the field's static type. // Automatically select the optimal storage representation based on the field's static type.
let member: Rc<std::cell::RefCell<dyn SeriesMember>> = match static_type { let member: Rc<std::cell::RefCell<dyn SeriesMember>> = match static_type {
StaticType::Float => Rc::new(std::cell::RefCell::new(ScalarSeries::<f64>::new("FloatSeries"))), StaticType::Float => Rc::new(std::cell::RefCell::new(ScalarSeries::<f64>::new(
StaticType::Int | StaticType::DateTime => Rc::new(std::cell::RefCell::new(ScalarSeries::<i64>::new("IntSeries"))), "FloatSeries",
StaticType::Bool => Rc::new(std::cell::RefCell::new(ScalarSeries::<bool>::new("BoolSeries"))), ))),
StaticType::Int | StaticType::DateTime => Rc::new(std::cell::RefCell::new(
ScalarSeries::<i64>::new("IntSeries"),
)),
StaticType::Bool => Rc::new(std::cell::RefCell::new(ScalarSeries::<bool>::new(
"BoolSeries",
))),
// Fallback for everything else (Text, Lists, nested Records without SoA, etc.) // Fallback for everything else (Text, Lists, nested Records without SoA, etc.)
_ => Rc::new(std::cell::RefCell::new(ValueSeries::new())), _ => Rc::new(std::cell::RefCell::new(ValueSeries::new())),
}; };
@@ -249,7 +261,9 @@ impl RecordSeries {
/// Example: `my_ticks.field(Keyword::intern("close"))` /// Example: `my_ticks.field(Keyword::intern("close"))`
/// returns an `Rc<RefCell<ScalarSeries<f64>>>`. No copy involved! /// returns an `Rc<RefCell<ScalarSeries<f64>>>`. No copy involved!
pub fn field(&self, key: Keyword) -> Option<Rc<std::cell::RefCell<dyn SeriesMember>>> { pub fn field(&self, key: Keyword) -> Option<Rc<std::cell::RefCell<dyn SeriesMember>>> {
self.layout.index_of(key).map(|idx| self.fields[idx].clone()) self.layout
.index_of(key)
.map(|idx| self.fields[idx].clone())
} }
/// Dynamically reconstructs the full Record at the given lookback index. /// Dynamically reconstructs the full Record at the given lookback index.
@@ -364,7 +378,12 @@ pub struct SharedSeries<T: ScalarValue> {
impl<T: ScalarValue> Debug for SharedSeries<T> { impl<T: ScalarValue> Debug for SharedSeries<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SharedSeries<{}>[len: {}]", self.type_name, self.buffer.borrow().len()) write!(
f,
"SharedSeries<{}>[len: {}]",
self.type_name,
self.buffer.borrow().len()
)
} }
} }
@@ -382,7 +401,10 @@ impl<T: ScalarValue> Object for SharedSeries<T> {
impl<T: ScalarValue> crate::ast::types::Series for SharedSeries<T> { impl<T: ScalarValue> crate::ast::types::Series for SharedSeries<T> {
fn get_item(&self, index: usize) -> Option<Value> { fn get_item(&self, index: usize) -> Option<Value> {
self.buffer.borrow().get(index).map(|&v| (self.converter)(v)) self.buffer
.borrow()
.get(index)
.map(|&v| (self.converter)(v))
} }
} }
@@ -426,7 +448,11 @@ pub struct SharedRecordSeries {
impl Debug for SharedRecordSeries { impl Debug for SharedRecordSeries {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "SharedRecordSeries[fields: {}]", self.field_buffers.len()) write!(
f,
"SharedRecordSeries[fields: {}]",
self.field_buffers.len()
)
} }
} }
@@ -474,7 +500,7 @@ pub fn register(env: &Environment) {
"create-series", "create-series",
StaticType::Function(Box::new(Signature { StaticType::Function(Box::new(Signature {
params: StaticType::Tuple(vec![StaticType::Any]), // In reality: Record params: StaticType::Tuple(vec![StaticType::Any]), // In reality: Record
ret: StaticType::Any, // In reality: Object(RecordSeries) ret: StaticType::Any, // In reality: Object(RecordSeries)
})), })),
Purity::Impure, Purity::Impure,
|args: std::vec::Vec<Value>| { |args: std::vec::Vec<Value>| {
@@ -516,7 +542,9 @@ pub fn register(env: &Environment) {
// Push each value into its corresponding column (SoA push) // Push each value into its corresponding column (SoA push)
for (i, field_val) in values.iter().enumerate() { for (i, field_val) in values.iter().enumerate() {
if let Some(field_series) = record_series.fields.get(i) { if let Some(field_series) = record_series.fields.get(i) {
field_series.borrow_mut().push_value(field_val.clone(), None); field_series
.borrow_mut()
.push_value(field_val.clone(), None);
} }
} }
return Value::Void; return Value::Void;
+70 -22
View File
@@ -1,6 +1,6 @@
use std::rc::Rc;
use std::cell::RefCell;
use crate::ast::types::Value; use crate::ast::types::Value;
use std::cell::RefCell;
use std::rc::Rc;
/// A Signal is the "packet" flowing through the reactive pipeline. /// A Signal is the "packet" flowing through the reactive pipeline.
/// It represents a value produced at a specific logical time (cycle_id). /// It represents a value produced at a specific logical time (cycle_id).
@@ -33,7 +33,9 @@ pub struct SourceAdapter {
impl Observer for SourceAdapter { impl Observer for SourceAdapter {
fn notify(&mut self, _ignored_source: usize, cycle_id: u64, value: Value) { fn notify(&mut self, _ignored_source: usize, cycle_id: u64, value: Value) {
self.target.borrow_mut().notify(self.target_index, cycle_id, value); self.target
.borrow_mut()
.notify(self.target_index, cycle_id, value);
} }
} }
@@ -170,7 +172,7 @@ impl PipeStream {
pub fn new( pub fn new(
name: String, name: String,
input_count: usize, input_count: usize,
executor: Option<Box<dyn FnMut(Vec<Value>) -> Value>> executor: Option<Box<dyn FnMut(Vec<Value>) -> Value>>,
) -> Self { ) -> Self {
Self { Self {
name, name,
@@ -224,13 +226,17 @@ impl Observer for PipeStream {
} }
// 4. Update Current Signal // 4. Update Current Signal
let new_signal = Signal { cycle_id, value: result }; let new_signal = Signal {
cycle_id,
value: result,
};
*self.current_signal.borrow_mut() = Some(new_signal.clone()); *self.current_signal.borrow_mut() = Some(new_signal.clone());
// 5. Notify Observers (Always at source_index 0 of the NEXT pipe) // 5. Notify Observers (Always at source_index 0 of the NEXT pipe)
let obs_list = self.observers.borrow(); let obs_list = self.observers.borrow();
for obs in obs_list.iter() { for obs in obs_list.iter() {
obs.borrow_mut().notify(0, cycle_id, new_signal.value.clone()); obs.borrow_mut()
.notify(0, cycle_id, new_signal.value.clone());
} }
} }
} }
@@ -311,7 +317,13 @@ pub fn build_pipeline_node(
let pusher = Rc::new(RefCell::new(SeriesPusher { let pusher = Rc::new(RefCell::new(SeriesPusher {
buffer: buffer.clone(), buffer: buffer.clone(),
lookback: Some(100), // TODO: Configuration lookback: Some(100), // TODO: Configuration
extractor: |v| if let Value::Float(f) = v { Some(f) } else { None }, extractor: |v| {
if let Value::Float(f) = v {
Some(f)
} else {
None
}
},
})); }));
pipe.borrow().add_observer(pusher); pipe.borrow().add_observer(pusher);
Rc::new(SharedSeries { Rc::new(SharedSeries {
@@ -343,7 +355,13 @@ pub fn build_pipeline_node(
let pusher = Rc::new(RefCell::new(SeriesPusher { let pusher = Rc::new(RefCell::new(SeriesPusher {
buffer: buffer.clone(), buffer: buffer.clone(),
lookback: Some(100), lookback: Some(100),
extractor: |v| if let Value::Bool(b) = v { Some(b) } else { None }, extractor: |v| {
if let Value::Bool(b) = v {
Some(b)
} else {
None
}
},
})); }));
pipe.borrow().add_observer(pusher); pipe.borrow().add_observer(pusher);
Rc::new(SharedSeries { Rc::new(SharedSeries {
@@ -356,11 +374,15 @@ pub fn build_pipeline_node(
let mut field_buffers = Vec::with_capacity(layout.fields.len()); let mut field_buffers = Vec::with_capacity(layout.fields.len());
for (_, ty) in &layout.fields { for (_, ty) in &layout.fields {
let member: Rc<RefCell<dyn SeriesMember>> = match ty { let member: Rc<RefCell<dyn SeriesMember>> = match ty {
StaticType::Float => Rc::new(RefCell::new(ScalarSeries::<f64>::new("FloatSeries"))), StaticType::Float => {
Rc::new(RefCell::new(ScalarSeries::<f64>::new("FloatSeries")))
}
StaticType::Int | StaticType::DateTime => { StaticType::Int | StaticType::DateTime => {
Rc::new(RefCell::new(ScalarSeries::<i64>::new("IntSeries"))) Rc::new(RefCell::new(ScalarSeries::<i64>::new("IntSeries")))
} }
StaticType::Bool => Rc::new(RefCell::new(ScalarSeries::<bool>::new("BoolSeries"))), StaticType::Bool => {
Rc::new(RefCell::new(ScalarSeries::<bool>::new("BoolSeries")))
}
_ => Rc::new(RefCell::new(ValueSeries::new())), _ => Rc::new(RefCell::new(ValueSeries::new())),
}; };
field_buffers.push(member); field_buffers.push(member);
@@ -399,7 +421,7 @@ pub fn build_pipeline_node(
// ============================================================================ // ============================================================================
use crate::ast::environment::Environment; use crate::ast::environment::Environment;
use crate::ast::types::{Purity, Signature, StaticType, Keyword, RecordLayout}; use crate::ast::types::{Keyword, Purity, RecordLayout, Signature, StaticType};
pub fn register(env: &Environment) { pub fn register(env: &Environment) {
// (create-random-ohlc seed limit) -> StreamNode // (create-random-ohlc seed limit) -> StreamNode
@@ -424,12 +446,22 @@ pub fn register(env: &Environment) {
if args.len() != 2 { if args.len() != 2 {
panic!("create-random-ohlc expects exactly 2 arguments (seed, limit)"); panic!("create-random-ohlc expects exactly 2 arguments (seed, limit)");
} }
let seed = if let Value::Int(s) = args[0] { s as u64 } else { 0 }; let seed = if let Value::Int(s) = args[0] {
let limit = if let Value::Int(l) = args[1] { l as usize } else { 0 }; s as u64
} else {
0
};
let limit = if let Value::Int(l) = args[1] {
l as usize
} else {
0
};
// 1. Create the RootStream // 1. Create the RootStream
let root_stream = Rc::new(RootStream::new()); let root_stream = Rc::new(RootStream::new());
let stream_node = StreamNode { inner: root_stream.clone() }; let stream_node = StreamNode {
inner: root_stream.clone(),
};
// 2. Setup the Layout for OHLC records // 2. Setup the Layout for OHLC records
let layout = RecordLayout::get_or_create(vec![ let layout = RecordLayout::get_or_create(vec![
@@ -466,7 +498,7 @@ pub fn register(env: &Environment) {
Value::Float(high), Value::Float(high),
Value::Float(low), Value::Float(low),
Value::Float(close), Value::Float(close),
]) ]),
); );
// Pump the signal into the RootStream // Pump the signal into the RootStream
@@ -492,7 +524,7 @@ pub fn register(env: &Environment) {
"create-ticker", "create-ticker",
StaticType::Function(Box::new(Signature { StaticType::Function(Box::new(Signature {
params: StaticType::Tuple(vec![StaticType::Any]), // Expects a closure params: StaticType::Tuple(vec![StaticType::Any]), // Expects a closure
ret: StaticType::Any, // Returns StreamNode ret: StaticType::Any, // Returns StreamNode
})), })),
Purity::Impure, Purity::Impure,
move |args: std::vec::Vec<Value>| { move |args: std::vec::Vec<Value>| {
@@ -508,7 +540,9 @@ pub fn register(env: &Environment) {
// 1. Create the RootStream // 1. Create the RootStream
let root_stream = Rc::new(RootStream::new()); let root_stream = Rc::new(RootStream::new());
let stream_node = StreamNode { inner: root_stream.clone() }; let stream_node = StreamNode {
inner: root_stream.clone(),
};
// 2. Setup isolated VM // 2. Setup isolated VM
let mut ticker_vm = crate::ast::vm::VM::new(globals.clone()); let mut ticker_vm = crate::ast::vm::VM::new(globals.clone());
@@ -537,7 +571,7 @@ pub fn register(env: &Environment) {
// 5. Return stream // 5. Return stream
Value::Object(Rc::new(stream_node)) Value::Object(Rc::new(stream_node))
} },
); );
} }
@@ -549,7 +583,11 @@ mod tests {
#[test] #[test]
fn test_root_to_pipe_flow() { fn test_root_to_pipe_flow() {
let root = RootStream::new(); let root = RootStream::new();
let pipe = Rc::new(RefCell::new(PipeStream::new("test-pipe".to_string(), 1, None))); let pipe = Rc::new(RefCell::new(PipeStream::new(
"test-pipe".to_string(),
1,
None,
)));
root.add_observer(pipe.clone()); root.add_observer(pipe.clone());
@@ -578,14 +616,24 @@ mod tests {
#[test] #[test]
fn test_barrier_sync() { fn test_barrier_sync() {
// Pipe with 2 inputs // Pipe with 2 inputs
let pipe = Rc::new(RefCell::new(PipeStream::new("barrier-pipe".to_string(), 2, None))); let pipe = Rc::new(RefCell::new(PipeStream::new(
"barrier-pipe".to_string(),
2,
None,
)));
// Manual notifications simulate different input streams // Manual notifications simulate different input streams
pipe.borrow_mut().notify(0, 1, Value::Float(10.0)); pipe.borrow_mut().notify(0, 1, Value::Float(10.0));
assert!(pipe.borrow().current_signal().is_none(), "Barrier should NOT be reached after 1st input"); assert!(
pipe.borrow().current_signal().is_none(),
"Barrier should NOT be reached after 1st input"
);
pipe.borrow_mut().notify(1, 1, Value::Float(20.0)); pipe.borrow_mut().notify(1, 1, Value::Float(20.0));
assert!(pipe.borrow().current_signal().is_some(), "Barrier SHOULD be reached after 2nd input"); assert!(
pipe.borrow().current_signal().is_some(),
"Barrier SHOULD be reached after 2nd input"
);
let sig = pipe.borrow().current_signal().unwrap(); let sig = pipe.borrow().current_signal().unwrap();
assert_eq!(sig.cycle_id, 1); assert_eq!(sig.cycle_id, 1);
+6 -1
View File
@@ -202,7 +202,12 @@ mod tests {
// Check static type // Check static type
let st = TypeRegistry::resolve_type::<Person>(&registry); let st = TypeRegistry::resolve_type::<Person>(&registry);
if let StaticType::Record(layout) = st { if let StaticType::Record(layout) = st {
assert!(layout.fields.iter().any(|(k, _)| *k == Keyword::intern("greet"))); assert!(
layout
.fields
.iter()
.any(|(k, _)| *k == Keyword::intern("greet"))
);
} else { } else {
panic!("Expected Record type"); panic!("Expected Record type");
} }
+27 -18
View File
@@ -168,11 +168,7 @@ impl RecordLayout {
return None; return None;
} }
let idx = self.fmap[offset]; let idx = self.fmap[offset];
if idx < 0 { if idx < 0 { None } else { Some(idx as usize) }
None
} else {
Some(idx as usize)
}
} }
} }
@@ -295,11 +291,11 @@ pub enum StaticType {
DateTime, DateTime,
Text, Text,
Keyword, Keyword,
Optional(Box<StaticType>), // Represents T | Void (e.g. for filter pipes) Optional(Box<StaticType>), // Represents T | Void (e.g. for filter pipes)
List(Box<StaticType>), // Legacy / Dynamic list List(Box<StaticType>), // Legacy / Dynamic list
Series(Box<StaticType>), // Time series of a specific type Series(Box<StaticType>), // Time series of a specific type
Tuple(Vec<StaticType>), // Heterogeneous fixed-size Tuple(Vec<StaticType>), // Heterogeneous fixed-size
Vector(Box<StaticType>, usize), // Homogeneous fixed-size Vector(Box<StaticType>, usize), // Homogeneous fixed-size
Matrix(Box<StaticType>, Vec<usize>), // Multi-dimensional homogeneous Matrix(Box<StaticType>, Vec<usize>), // Multi-dimensional homogeneous
Record(std::sync::Arc<RecordLayout>), Record(std::sync::Arc<RecordLayout>),
FieldAccessor(Keyword), FieldAccessor(Keyword),
@@ -371,7 +367,12 @@ 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) || matches!(self, StaticType::Error) || matches!(other, StaticType::Error) { if self == other
|| matches!(self, StaticType::Any)
|| matches!(other, StaticType::Any)
|| matches!(self, StaticType::Error)
|| matches!(other, StaticType::Error)
{
return true; return true;
} }
@@ -411,9 +412,7 @@ impl StaticType {
} }
} }
// Records are assignable if their layouts match (Structural identity via interning) // Records are assignable if their layouts match (Structural identity via interning)
(StaticType::Record(a), StaticType::Record(b)) => { (StaticType::Record(a), StaticType::Record(b)) => std::sync::Arc::ptr_eq(a, b),
std::sync::Arc::ptr_eq(a, b)
}
// Series are assignable if their inner types are assignable // Series are assignable if their inner types are assignable
(StaticType::Series(inner_a), StaticType::Series(inner_b)) => { (StaticType::Series(inner_a), StaticType::Series(inner_b)) => {
inner_a.is_assignable_from(inner_b) inner_a.is_assignable_from(inner_b)
@@ -550,7 +549,7 @@ impl Value {
} }
Value::Record(layout, _) => StaticType::Record(layout.clone()), Value::Record(layout, _) => StaticType::Record(layout.clone()),
Value::FieldAccessor(k) => StaticType::FieldAccessor(*k), Value::FieldAccessor(k) => StaticType::FieldAccessor(*k),
Value::Function(_) => StaticType::Any, // Dynamic function Value::Function(_) => StaticType::Any, // Dynamic function
Value::Object(o) => StaticType::Object(o.type_name()), Value::Object(o) => StaticType::Object(o.type_name()),
Value::Cell(c) => c.borrow().static_type(), Value::Cell(c) => c.borrow().static_type(),
Value::TailCallRequest(_) => StaticType::Any, // Internal state, but typable as Any Value::TailCallRequest(_) => StaticType::Any, // Internal state, but typable as Any
@@ -596,10 +595,20 @@ impl fmt::Display for Value {
Value::FieldAccessor(k) => write!(f, ".{}", k.name()), Value::FieldAccessor(k) => write!(f, ".{}", k.name()),
Value::Function(f_meta) => write!(f, "<native fn, {:?}>", f_meta.purity), Value::Function(f_meta) => write!(f, "<native fn, {:?}>", f_meta.purity),
Value::Object(o) => { Value::Object(o) => {
if let Some(pipe) = o.as_any().downcast_ref::<crate::ast::rtl::streams::PipelineNode>() { if let Some(pipe) = o
.as_any()
.downcast_ref::<crate::ast::rtl::streams::PipelineNode>()
{
write!(f, "PipelineNode[last: {:?}]", pipe.series.get_item(0)) write!(f, "PipelineNode[last: {:?}]", pipe.series.get_item(0))
} else if let Some(val_series) = o.as_any().downcast_ref::<crate::ast::rtl::series::SharedValueSeries>() { } else if let Some(val_series) =
write!(f, "SharedValueSeries[len: {}]", val_series.buffer.borrow().len()) o.as_any()
.downcast_ref::<crate::ast::rtl::series::SharedValueSeries>()
{
write!(
f,
"SharedValueSeries[len: {}]",
val_series.buffer.borrow().len()
)
} else { } else {
write!(f, "<{}>", o.type_name()) write!(f, "<{}>", o.type_name())
} }
+94 -30
View File
@@ -177,7 +177,11 @@ impl VM {
} }
} }
pub fn run_with_args(&mut self, closure_obj: Rc<dyn Object>, args: Vec<Value>) -> Result<Value, String> { pub fn run_with_args(
&mut self,
closure_obj: Rc<dyn Object>,
args: Vec<Value>,
) -> Result<Value, String> {
let closure = closure_obj.as_any().downcast_ref::<Closure>().unwrap(); let closure = closure_obj.as_any().downcast_ref::<Closure>().unwrap();
self.stack.clear(); self.stack.clear();
self.frames.clear(); self.frames.clear();
@@ -323,8 +327,9 @@ impl VM {
// 2. Downcast! We check at runtime if the pointer actually points to a `RecordSeries`. // 2. Downcast! We check at runtime if the pointer actually points to a `RecordSeries`.
// `downcast_ref` is very fast (essentially an O(1) type ID comparison under the hood). // `downcast_ref` is very fast (essentially an O(1) type ID comparison under the hood).
if let Some(record_series) = any_ptr.downcast_ref::<crate::ast::rtl::series::RecordSeries>() { if let Some(record_series) =
any_ptr.downcast_ref::<crate::ast::rtl::series::RecordSeries>()
{
// 3. We call our highly performant 0-copy method on the series. // 3. We call our highly performant 0-copy method on the series.
// It returns an `Rc<RefCell<dyn SeriesMember>>`, which is a shared pointer // It returns an `Rc<RefCell<dyn SeriesMember>>`, which is a shared pointer
// to the concrete column array (e.g., a `ScalarSeries<f64>`). // to the concrete column array (e.g., a `ScalarSeries<f64>`).
@@ -333,19 +338,29 @@ impl VM {
// The `SeriesView` acts as a pure `Object` for the VM, holding the reference. // The `SeriesView` acts as a pure `Object` for the VM, holding the reference.
// CRITICAL: No array elements are copied here! This is pure, fast pointer juggling. // CRITICAL: No array elements are copied here! This is pure, fast pointer juggling.
// This single operation turns a SoA `RecordSeries` into a high-speed `FloatSeries` view. // This single operation turns a SoA `RecordSeries` into a high-speed `FloatSeries` view.
let view = crate::ast::rtl::series::SeriesView::new(field_series, *field); let view =
crate::ast::rtl::series::SeriesView::new(field_series, *field);
return Ok(Value::Object(std::rc::Rc::new(view))); return Ok(Value::Object(std::rc::Rc::new(view)));
} else { } else {
return Err(format!("RecordSeries does not have field :{}", field.name())); return Err(format!(
"RecordSeries does not have field :{}",
field.name()
));
} }
} }
// Fallback if it's another type of object that is not a RecordSeries. // Fallback if it's another type of object that is not a RecordSeries.
Err(format!("Attempt to access field on non-record object: {}", obj.type_name())) Err(format!(
"Attempt to access field on non-record object: {}",
obj.type_name()
))
} }
// Error handling for primitives (Int, Float, etc.). // Error handling for primitives (Int, Float, etc.).
_ => Err(format!("Attempt to access field on non-record: {}", rec_val)), _ => Err(format!(
"Attempt to access field on non-record: {}",
rec_val
)),
} }
} }
@@ -413,11 +428,8 @@ impl VM {
}); });
// Delegate to the RTL Factory for specialized buffer instantiation // Delegate to the RTL Factory for specialized buffer instantiation
let node = crate::ast::rtl::streams::build_pipeline_node( let node =
obs_streams, crate::ast::rtl::streams::build_pipeline_node(obs_streams, executor, out_type);
executor,
out_type,
);
Ok(Value::Object(node)) Ok(Value::Object(node))
} }
@@ -489,23 +501,44 @@ impl VM {
if let Some(idx) = layout.index_of(k) { if let Some(idx) = layout.index_of(k) {
return Ok(values[idx].clone()); return Ok(values[idx].clone());
} else { } else {
return Err(format!("Record does not have field :{}", k.name())); return Err(format!(
"Record does not have field :{}",
k.name()
));
} }
} else if let Value::Object(obj) = rec { } else if let Value::Object(obj) = rec {
// Polymorphic Field Access: Allow `.field` on a RecordSeries // Polymorphic Field Access: Allow `.field` on a RecordSeries
if let Some(rs) = obj.as_any().downcast_ref::<crate::ast::rtl::series::RecordSeries>() { if let Some(rs) = obj
.as_any()
.downcast_ref::<crate::ast::rtl::series::RecordSeries>()
{
if let Some(field_series) = rs.field(k) { if let Some(field_series) = rs.field(k) {
let view = crate::ast::rtl::series::SeriesView::new(field_series, k); let view = crate::ast::rtl::series::SeriesView::new(
field_series,
k,
);
return Ok(Value::Object(std::rc::Rc::new(view))); return Ok(Value::Object(std::rc::Rc::new(view)));
} else { } else {
return Err(format!("RecordSeries does not have field :{}", k.name())); return Err(format!(
"RecordSeries does not have field :{}",
k.name()
));
} }
} }
return Err(format!("Field accessor .{} expects a record or RecordSeries, got {}", k.name(), obj.type_name())); return Err(format!(
"Field accessor .{} expects a record or RecordSeries, got {}",
k.name(),
obj.type_name()
));
} else { } else {
return Err(format!("Field accessor .{} expects a record or RecordSeries, got {}", k.name(), rec)); return Err(format!(
"Field accessor .{} expects a record or RecordSeries, got {}",
k.name(),
rec
));
} }
} _ => { }
_ => {
return Err(format!( return Err(format!(
"Tail call target is not a function: {}", "Tail call target is not a function: {}",
func_val func_val
@@ -534,19 +567,37 @@ impl VM {
} }
} else if let Value::Object(obj) = rec { } else if let Value::Object(obj) = rec {
// Polymorphic Field Access: Allow `.field` on a RecordSeries // Polymorphic Field Access: Allow `.field` on a RecordSeries
if let Some(rs) = obj.as_any().downcast_ref::<crate::ast::rtl::series::RecordSeries>() { if let Some(rs) = obj
.as_any()
.downcast_ref::<crate::ast::rtl::series::RecordSeries>()
{
if let Some(field_series) = rs.field(k) { if let Some(field_series) = rs.field(k) {
let view = crate::ast::rtl::series::SeriesView::new(field_series, k); let view = crate::ast::rtl::series::SeriesView::new(
field_series,
k,
);
break Ok(Value::Object(std::rc::Rc::new(view))); break Ok(Value::Object(std::rc::Rc::new(view)));
} else { } else {
break Err(format!("RecordSeries does not have field :{}", k.name())); break Err(format!(
"RecordSeries does not have field :{}",
k.name()
));
} }
} }
break Err(format!("Field accessor .{} expects a record or RecordSeries, got {}", k.name(), obj.type_name())); break Err(format!(
"Field accessor .{} expects a record or RecordSeries, got {}",
k.name(),
obj.type_name()
));
} else { } else {
break Err(format!("Field accessor .{} expects a record or RecordSeries, got {}", k.name(), rec)); break Err(format!(
"Field accessor .{} expects a record or RecordSeries, got {}",
k.name(),
rec
));
} }
} Value::Object(obj) => { }
Value::Object(obj) => {
if let Some(closure) = obj.as_any().downcast_ref::<Closure>() { if let Some(closure) = obj.as_any().downcast_ref::<Closure>() {
let old_stack_top = self.stack.len(); let old_stack_top = self.stack.len();
self.frames.push(CallFrame { self.frames.push(CallFrame {
@@ -586,11 +637,17 @@ impl VM {
// Unified Series Access (Step 1 of Dual Series Architecture) // Unified Series Access (Step 1 of Dual Series Architecture)
// This handles RecordSeries, SeriesView, and future SharedSeries polymorphically. // This handles RecordSeries, SeriesView, and future SharedSeries polymorphically.
if arg_vals.len() != 1 { if arg_vals.len() != 1 {
break Err(format!("{} indexer expects exactly 1 argument (the lookback index)", obj.type_name())); break Err(format!(
"{} indexer expects exactly 1 argument (the lookback index)",
obj.type_name()
));
} }
if let Value::Int(idx) = arg_vals[0] { if let Value::Int(idx) = arg_vals[0] {
if idx < 0 { if idx < 0 {
break Err(format!("{} lookback index cannot be negative", obj.type_name())); break Err(format!(
"{} lookback index cannot be negative",
obj.type_name()
));
} }
if let Some(val) = series.get_item(idx as usize) { if let Some(val) = series.get_item(idx as usize) {
break Ok(val); break Ok(val);
@@ -599,12 +656,16 @@ impl VM {
break Ok(Value::Void); break Ok(Value::Void);
} }
} else { } else {
break Err(format!("{} index must be an integer", obj.type_name())); break Err(format!(
"{} index must be an integer",
obj.type_name()
));
} }
} else { } else {
break Err(format!("Object is not callable: {}", obj.type_name())); break Err(format!("Object is not callable: {}", obj.type_name()));
} }
} _ => break Err(format!("Attempt to call non-function: {}", func_val)), }
_ => break Err(format!("Attempt to call non-function: {}", func_val)),
} }
} }
} }
@@ -651,7 +712,10 @@ impl VM {
for v in values { for v in values {
evaluated_values.push(self.eval_internal(obs, v)?); evaluated_values.push(self.eval_internal(obs, v)?);
} }
Ok(Value::Record(layout.clone(), std::rc::Rc::new(evaluated_values))) Ok(Value::Record(
layout.clone(),
std::rc::Rc::new(evaluated_values),
))
} }
BoundKind::Expansion { bound_expanded, .. } => self.eval_internal(obs, bound_expanded), BoundKind::Expansion { bound_expanded, .. } => self.eval_internal(obs, bound_expanded),
BoundKind::Extension(ext) => Err(format!( BoundKind::Extension(ext) => Err(format!(
+6 -1
View File
@@ -103,7 +103,12 @@ fn execute(env: &Environment, source: &str) {
let result = env.compile(source); let result = env.compile(source);
for diag in &result.diagnostics.items { for diag in &result.diagnostics.items {
let level = format!("{:?}", diag.level).to_uppercase(); 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(); 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); eprintln!("{}{} : {}", level, loc, diag.message);
} }
+94 -21
View File
@@ -1,4 +1,4 @@
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::ast::environment::Environment; use crate::ast::environment::Environment;
use crate::ast::nodes::UntypedKind; use crate::ast::nodes::UntypedKind;
@@ -69,7 +69,10 @@ mod tests {
"#; "#;
let env = Environment::new(); let env = Environment::new();
let compiled = env.compile(source).into_result().expect("Failed to compile"); let compiled = env
.compile(source)
.into_result()
.expect("Failed to compile");
let linked = env.link(compiled); let linked = env.link(compiled);
let func = env.instantiate(linked); let func = env.instantiate(linked);
let result: Result<Value, String> = Ok((func.func)(vec![])); let result: Result<Value, String> = Ok((func.func)(vec![]));
@@ -191,7 +194,8 @@ mod tests {
); );
// 3. Create another generator in env2 with the same seed // 3. Create another generator in env2 with the same seed
env2.run_script("(def rand-same (make-random 123))").unwrap(); env2.run_script("(def rand-same (make-random 123))")
.unwrap();
let val2_b = env2.run_script("(rand-same)").unwrap(); let val2_b = env2.run_script("(rand-same)").unwrap();
// After same seeding, they should match (isolated but identical seed) // After same seeding, they should match (isolated but identical seed)
@@ -524,7 +528,11 @@ mod tests {
// 2. Verify optimization to GET_FIELD when the record contains non-constants // 2. Verify optimization to GET_FIELD when the record contains non-constants
let source_ast = "(fn [id] (.price {:id id :price 99.5}))"; let source_ast = "(fn [id] (.price {:id id :price 99.5}))";
let dump = env.dump_ast(source_ast).unwrap(); let dump = env.dump_ast(source_ast).unwrap();
assert!(dump.contains("GetField: .price"), "Should be optimized to GetField. Dump:\n{}", dump); assert!(
dump.contains("GetField: .price"),
"Should be optimized to GetField. Dump:\n{}",
dump
);
} }
#[test] #[test]
fn test_first_class_field_accessor() { fn test_first_class_field_accessor() {
@@ -545,7 +553,10 @@ mod tests {
// Optimizer should fold (.x {:x 10}) into 10 // Optimizer should fold (.x {:x 10}) into 10
let source = "(.x {:x 10 :y 20})"; let source = "(.x {:x 10 :y 20})";
let dump = env.dump_ast(source).unwrap(); let dump = env.dump_ast(source).unwrap();
assert!(dump.contains("Constant: 10"), "Should evaluate GetField at compile time."); assert!(
dump.contains("Constant: 10"),
"Should evaluate GetField at compile time."
);
} }
#[test] #[test]
@@ -555,8 +566,14 @@ mod tests {
let dump = env.dump_ast(source).unwrap(); let dump = env.dump_ast(source).unwrap();
// Ensure the record definition itself is folded into a Constant. // Ensure the record definition itself is folded into a Constant.
assert!(dump.contains("Constant: {:a 1, :b 2}"), "Should transform a pure record literal into a constant value."); assert!(
assert!(!dump.contains("Record {"), "Should not leave a runtime Record node in the AST."); dump.contains("Constant: {:a 1, :b 2}"),
"Should transform a pure record literal into a constant value."
);
assert!(
!dump.contains("Record {"),
"Should not leave a runtime Record node in the AST."
);
} }
#[test] #[test]
@@ -576,8 +593,14 @@ mod tests {
// The optimizer should inline `loop-config`, resolving `(.limit loop-config)` to `10`. // The optimizer should inline `loop-config`, resolving `(.limit loop-config)` to `10`.
// The GetField and the Get for 'loop-config' should vanish inside the condition. // The GetField and the Get for 'loop-config' should vanish inside the condition.
assert!(!dump.contains("GetField: .limit"), "The record field should be completely inlined."); assert!(
assert!(dump.contains("Constant: 10"), "The limit should be resolved to a constant 10."); !dump.contains("GetField: .limit"),
"The record field should be completely inlined."
);
assert!(
dump.contains("Constant: 10"),
"The limit should be resolved to a constant 10."
);
// The result of running it should obviously still be correct. // The result of running it should obviously still be correct.
let env_run = Environment::new(); let env_run = Environment::new();
@@ -627,13 +650,24 @@ mod tests {
let result = env.compile(source); let result = env.compile(source);
assert!(result.diagnostics.has_errors(), "Expected parser errors"); assert!(result.diagnostics.has_errors(), "Expected parser errors");
let error_msgs: Vec<_> = result.diagnostics.items.iter().map(|d| d.message.as_str()).collect(); let error_msgs: Vec<_> = result
.diagnostics
.items
.iter()
.map(|d| d.message.as_str())
.collect();
// It should complain about finding ) instead of ] // It should complain about finding ) instead of ]
assert!(error_msgs.iter().any(|m| m.contains("RightParen")), "Expected error about RightParen"); assert!(
error_msgs.iter().any(|m| m.contains("RightParen")),
"Expected error about RightParen"
);
// AST should still be partially built (not None) // AST should still be partially built (not None)
assert!(result.ast.is_some(), "AST should be partially built despite parser errors"); assert!(
result.ast.is_some(),
"AST should be partially built despite parser errors"
);
} }
#[test] #[test]
@@ -644,9 +678,19 @@ mod tests {
let result = env.compile(source); let result = env.compile(source);
assert!(result.diagnostics.has_errors(), "Expected binder errors"); assert!(result.diagnostics.has_errors(), "Expected binder errors");
let error_msgs: Vec<_> = result.diagnostics.items.iter().map(|d| d.message.as_str()).collect(); let error_msgs: Vec<_> = result
.diagnostics
.items
.iter()
.map(|d| d.message.as_str())
.collect();
assert!(error_msgs.iter().any(|m| m.contains("Undefined variable 'unknown_var'")), "Expected undefined variable error"); assert!(
error_msgs
.iter()
.any(|m| m.contains("Undefined variable 'unknown_var'")),
"Expected undefined variable error"
);
assert!(result.ast.is_some(), "AST should be built with Error nodes"); assert!(result.ast.is_some(), "AST should be built with Error nodes");
} }
@@ -657,10 +701,23 @@ mod tests {
let source = "(do (def a 10) (def b (not \"text\")) (- a 2))"; let source = "(do (def a 10) (def b (not \"text\")) (- a 2))";
let result = env.compile(source); let result = env.compile(source);
assert!(result.diagnostics.has_errors(), "Expected type checker errors"); assert!(
let error_msgs: Vec<_> = result.diagnostics.items.iter().map(|d| d.message.as_str()).collect(); result.diagnostics.has_errors(),
"Expected type checker errors"
);
let error_msgs: Vec<_> = result
.diagnostics
.items
.iter()
.map(|d| d.message.as_str())
.collect();
assert!(error_msgs.iter().any(|m| m.contains("Invalid arguments for function call")), "Expected invalid arguments error"); assert!(
error_msgs
.iter()
.any(|m| m.contains("Invalid arguments for function call")),
"Expected invalid arguments error"
);
assert!(result.ast.is_some(), "AST should be built with Error nodes"); assert!(result.ast.is_some(), "AST should be built with Error nodes");
} }
@@ -677,10 +734,26 @@ mod tests {
let result = env.compile(source); let result = env.compile(source);
assert!(result.diagnostics.has_errors()); assert!(result.diagnostics.has_errors());
assert!(result.diagnostics.items.len() >= 2, "Expected multiple errors to be collected"); assert!(
result.diagnostics.items.len() >= 2,
"Expected multiple errors to be collected"
);
let error_msgs: Vec<_> = result.diagnostics.items.iter().map(|d| d.message.as_str()).collect(); let error_msgs: Vec<_> = result
assert!(error_msgs.iter().any(|m| m.contains("Undefined variable 'undefined_var'"))); .diagnostics
assert!(error_msgs.iter().any(|m| m.contains("Invalid arguments for function call"))); .items
.iter()
.map(|d| d.message.as_str())
.collect();
assert!(
error_msgs
.iter()
.any(|m| m.contains("Undefined variable 'undefined_var'"))
);
assert!(
error_msgs
.iter()
.any(|m| m.contains("Invalid arguments for function call"))
);
} }
} }