Refactor bound node types for clarity

Introduce generic `CompilerPhase` trait to unify different stages of the
bound AST. Rename `LocalSlot` to `VirtualId` and introduce `StackOffset`
for clearer distinction between compile-time and run-time addressing.
Update type aliases and implementations to reflect these changes.
This commit is contained in:
Michael Schimmel
2026-03-13 19:20:44 +01:00
parent d035da9d91
commit e5d82ee2b6
15 changed files with 403 additions and 458 deletions
+3 -3
View File
@@ -1,5 +1,5 @@
use crate::ast::compiler::bound_nodes::{
Address, AnalyzedNode, BoundKind, GlobalIdx, NodeMetrics, TypedNode,
Address, AnalyzedNode, BoundKind, GlobalIdx, Node, NodeMetrics, TypedNode, TypedPhase,
};
use crate::ast::types::Purity;
use std::collections::{HashMap, HashSet};
@@ -318,7 +318,7 @@ impl<'a> Analyzer<'a> {
BoundKind::Error => (BoundKind::Error, Purity::Impure),
};
crate::ast::compiler::bound_nodes::Node {
Node {
identity: node.identity.clone(),
kind: new_kind,
ty: NodeMetrics {
@@ -334,7 +334,7 @@ trait NodeExt {
fn for_each_child<F: FnMut(&TypedNode)>(&self, f: F);
}
impl NodeExt for BoundKind<crate::ast::types::StaticType> {
impl NodeExt for BoundKind<TypedPhase> {
fn for_each_child<F: FnMut(&TypedNode)>(&self, mut f: F) {
match self {
BoundKind::If {
+16 -16
View File
@@ -1,5 +1,5 @@
use crate::ast::compiler::bound_nodes::{
Address, BoundKind, DeclarationKind, GlobalIdx, LocalSlot, Node, UpvalueIdx,
Address, BoundKind, BoundPhase, DeclarationKind, GlobalIdx, Node, UpvalueIdx, VirtualId,
};
use crate::ast::diagnostics::Diagnostics;
use crate::ast::nodes::{Symbol, SyntaxKind, SyntaxNode};
@@ -9,7 +9,7 @@ use std::rc::Rc;
#[derive(Debug, Clone)]
pub struct LocalInfo {
pub addr: Address,
pub addr: Address<VirtualId>,
pub identity: Identity,
pub _ty: StaticType,
pub purity: Purity,
@@ -38,7 +38,7 @@ struct FunctionCompiler {
identity: Identity,
scopes: Vec<CompilerScope>,
slot_count: u32,
upvalues: Vec<Address>,
upvalues: Vec<Address<VirtualId>>,
}
impl FunctionCompiler {
@@ -65,7 +65,7 @@ impl FunctionCompiler {
}
}
fn define_variable(&mut self, name: &Symbol, identity: Identity) -> Result<Address, String> {
fn define_variable(&mut self, name: &Symbol, identity: Identity) -> Result<Address<VirtualId>, String> {
let current_scope = self.scopes.last_mut().unwrap();
if current_scope.locals.contains_key(name) {
return Err(format!(
@@ -74,7 +74,7 @@ impl FunctionCompiler {
));
}
let slot = LocalSlot(self.slot_count);
let slot = VirtualId(self.slot_count);
current_scope.locals.insert(
name.clone(),
LocalInfo {
@@ -97,7 +97,7 @@ impl FunctionCompiler {
None
}
fn add_upvalue(&mut self, addr: Address) -> UpvalueIdx {
fn add_upvalue(&mut self, addr: Address<VirtualId>) -> UpvalueIdx {
if let Some(idx) = self.upvalues.iter().position(|&a| a == addr) {
return UpvalueIdx(idx as u32);
}
@@ -114,7 +114,7 @@ pub enum ExprContext {
}
pub type BindingResult = (
Node,
Node<BoundPhase>,
HashMap<Identity, Vec<Identity>>,
Vec<CompilerScope>,
u32,
@@ -179,7 +179,7 @@ impl Binder {
identity: Identity,
_kind: crate::ast::compiler::bound_nodes::DeclarationKind,
diag: &mut Diagnostics,
) -> Option<Address> {
) -> Option<Address<VirtualId>> {
let current_fn_idx = self.functions.len() - 1;
if current_fn_idx == 0 {
@@ -211,7 +211,7 @@ impl Binder {
node: &SyntaxNode,
ctx: ExprContext,
diag: &mut Diagnostics,
) -> Node {
) -> Node<BoundPhase> {
match &node.kind {
SyntaxKind::Nop => self.make_node(node.identity.clone(), BoundKind::Nop),
SyntaxKind::Constant(v) => {
@@ -361,7 +361,7 @@ impl Binder {
let body_bound = self.bind(body.as_ref(), ExprContext::Expression, diag);
let compiled_fn = self.functions.pop().unwrap();
fn count_params(node: &Node) -> Option<u32> {
fn count_params(node: &Node<BoundPhase>) -> Option<u32> {
match &node.kind {
BoundKind::Define {
kind: DeclarationKind::Parameter,
@@ -520,9 +520,9 @@ impl Binder {
);
self.make_node(node.identity.clone(), BoundKind::Error)
}
SyntaxKind::Error => crate::ast::compiler::bound_nodes::Node {
SyntaxKind::Error => Node {
identity: node.identity.clone(),
kind: crate::ast::compiler::bound_nodes::BoundKind::Error,
kind: BoundKind::Error,
ty: (),
},
}
@@ -533,7 +533,7 @@ impl Binder {
sym: &Symbol,
diag: &mut Diagnostics,
identity: &Identity,
) -> Option<Address> {
) -> Option<Address<VirtualId>> {
let current_fn_idx = self.functions.len() - 1;
// 1. Try local in current function
@@ -603,7 +603,7 @@ impl Binder {
node: &SyntaxNode,
kind: DeclarationKind,
diag: &mut Diagnostics,
) -> Node {
) -> Node<BoundPhase> {
match &node.kind {
SyntaxKind::Identifier(sym) => {
if let Some(addr) = self.declare_variable(sym, node.identity.clone(), kind, diag) {
@@ -647,7 +647,7 @@ impl Binder {
&mut self,
node: &SyntaxNode,
diag: &mut Diagnostics,
) -> Node {
) -> Node<BoundPhase> {
match &node.kind {
SyntaxKind::Identifier(sym) => {
if let Some(addr) = self.resolve_variable(sym, diag, &node.identity) {
@@ -684,7 +684,7 @@ impl Binder {
}
}
fn make_node(&self, identity: Identity, kind: BoundKind<()>) -> Node {
fn make_node(&self, identity: Identity, kind: BoundKind<BoundPhase>) -> Node<BoundPhase> {
Node {
identity,
kind,
+199 -199
View File
@@ -3,11 +3,20 @@ use crate::ast::types::{Identity, StaticType, Value};
use std::rc::Rc;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct LocalSlot(pub u32);
pub struct VirtualId(pub u32);
impl std::fmt::Display for LocalSlot {
impl std::fmt::Display for VirtualId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "L{}", self.0)
write!(f, "V{}", self.0)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct StackOffset(pub u32);
impl std::fmt::Display for StackOffset {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "S{}", self.0)
}
}
@@ -29,46 +38,84 @@ impl std::fmt::Display for GlobalIdx {
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Address {
Local(LocalSlot), // Stack-Slot index (relative to frame base)
#[derive(Debug, PartialEq, Eq, Hash)]
pub enum Address<L> {
Local(L), // Local address (VirtualId or StackOffset)
Upvalue(UpvalueIdx), // Index in the closure's upvalue array
Global(GlobalIdx), // Index in the global environment vector
}
impl<L: Clone> Clone for Address<L> {
fn clone(&self) -> Self {
match self {
Address::Local(l) => Address::Local(l.clone()),
Address::Upvalue(u) => Address::Upvalue(*u),
Address::Global(g) => Address::Global(*g),
}
}
}
impl<L: Copy> Copy for Address<L> {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DeclarationKind {
Variable,
Parameter,
}
/// Trait for DSL-specific AST extensions (like Pipe, Series-Access, etc.)
pub trait BoundExtension<T>: std::fmt::Debug {
fn clone_box(&self) -> Box<dyn BoundExtension<T>>;
fn display_name(&self) -> String;
pub trait CompilerPhase: std::fmt::Debug + 'static {
type Metadata: std::fmt::Debug + Clone + PartialEq;
type LocalAddress: std::fmt::Debug + Clone + Copy + PartialEq + Eq + std::hash::Hash;
}
impl<T> Clone for Box<dyn BoundExtension<T>> {
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BoundPhase;
impl CompilerPhase for BoundPhase {
type Metadata = ();
type LocalAddress = VirtualId;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TypedPhase;
impl CompilerPhase for TypedPhase {
type Metadata = StaticType;
type LocalAddress = VirtualId;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AnalyzedPhase;
impl CompilerPhase for AnalyzedPhase {
type Metadata = NodeMetrics;
type LocalAddress = VirtualId;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RuntimePhase;
impl CompilerPhase for RuntimePhase {
type Metadata = RuntimeMetadata;
type LocalAddress = StackOffset;
}
/// A bound AST node, decorated with phase-specific information P.
#[derive(Debug, PartialEq)]
pub struct Node<P: CompilerPhase = BoundPhase> {
pub identity: Identity,
pub kind: BoundKind<P>,
pub ty: P::Metadata,
}
impl<P: CompilerPhase> Clone for Node<P> {
fn clone(&self) -> Self {
self.clone_box()
Self {
identity: self.identity.clone(),
kind: self.kind.clone(),
ty: self.ty.clone(),
}
}
}
/// A bound AST node, decorated with type or metric information T.
#[derive(Debug, Clone, PartialEq)]
pub struct Node<T = ()> {
pub identity: Identity,
pub kind: BoundKind<T>,
pub ty: T,
}
pub type TypedNode = Node<TypedPhase>;
/// Type alias for a node that has been fully type-checked.
pub type TypedNode = Node<StaticType>;
/// Type alias for the global function registry.
pub type GlobalFunctionRegistry = std::collections::HashMap<GlobalIdx, Rc<Node>>;
/// Metrics collected during the analysis phase.
#[derive(Debug, Clone, PartialEq)]
pub struct NodeMetrics {
pub original: Rc<TypedNode>,
@@ -76,114 +123,144 @@ pub struct NodeMetrics {
pub is_recursive: bool,
}
/// Type alias for a node that has been analyzed.
pub type AnalyzedNode = Node<NodeMetrics>;
pub type AnalyzedNode = Node<AnalyzedPhase>;
/// Type alias for the global analyzed function registry.
#[derive(Clone)]
pub struct RuntimeMetadata {
pub ty: StaticType,
pub is_tail: bool,
pub original: Rc<AnalyzedNode>,
pub stack_size: u32,
}
impl std::fmt::Debug for RuntimeMetadata {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Metadata")
.field("ty", &self.ty)
.field("is_tail", &self.is_tail)
.field("stack_size", &self.stack_size)
.finish()
}
}
impl PartialEq for RuntimeMetadata {
fn eq(&self, other: &Self) -> bool {
self.ty == other.ty && self.is_tail == other.is_tail && Rc::ptr_eq(&self.original, &other.original) && self.stack_size == other.stack_size
}
}
pub type ExecNode = Node<RuntimePhase>;
pub type GlobalFunctionRegistry = std::collections::HashMap<GlobalIdx, Rc<Node<BoundPhase>>>;
pub type GlobalAnalyzedRegistry = std::collections::HashMap<GlobalIdx, Rc<AnalyzedNode>>;
#[derive(Debug, Clone)]
pub enum BoundKind<T = ()> {
pub trait BoundExtension<P: CompilerPhase>: std::fmt::Debug {
fn clone_box(&self) -> Box<dyn BoundExtension<P>>;
fn display_name(&self) -> String;
}
impl<P: CompilerPhase> Clone for Box<dyn BoundExtension<P>> {
fn clone(&self) -> Self {
self.clone_box()
}
}
#[derive(Debug)]
pub enum BoundKind<P: CompilerPhase = BoundPhase> {
Nop,
Constant(Value),
// Variable Access (Resolved)
Get {
addr: Address,
addr: Address<P::LocalAddress>,
name: Symbol,
},
// Variable Update (Assignment)
Set {
addr: Address,
value: Rc<Node<T>>,
addr: Address<P::LocalAddress>,
value: Rc<Node<P>>,
},
// Variable Declaration (Unified for Local/Global/Parameter)
Define {
name: Symbol,
addr: Address,
addr: Address<P::LocalAddress>,
kind: DeclarationKind,
value: Rc<Node<T>>,
value: Rc<Node<P>>,
captured_by: Vec<Identity>,
},
/// A first-class field accessor (e.g. .name)
FieldAccessor(crate::ast::types::Keyword),
/// Specialized field access (O(1) via RecordLayout)
GetField {
rec: Rc<Node<T>>,
rec: Rc<Node<P>>,
field: crate::ast::types::Keyword,
},
If {
cond: Rc<Node<T>>,
then_br: Rc<Node<T>>,
else_br: Option<Rc<Node<T>>>,
cond: Rc<Node<P>>,
then_br: Rc<Node<P>>,
else_br: Option<Rc<Node<P>>>,
},
/// A destructuring operation (can be a definition or an assignment depending on the pattern)
Destructure {
pattern: Rc<Node<T>>,
value: Rc<Node<T>>,
pattern: Rc<Node<P>>,
value: Rc<Node<P>>,
},
Lambda {
params: Rc<Node<T>>,
// The list of variables captured from enclosing scopes
upvalues: Vec<Address>,
body: Rc<Node<T>>,
/// Static optimization: number of positional parameters if the pattern is flat.
params: Rc<Node<P>>,
upvalues: Vec<Address<P::LocalAddress>>,
body: Rc<Node<P>>,
positional_count: Option<u32>,
},
Call {
callee: Rc<Node<T>>,
args: Rc<Node<T>>,
callee: Rc<Node<P>>,
args: Rc<Node<P>>,
},
Again {
args: Rc<Node<T>>,
args: Rc<Node<P>>,
},
Pipe {
inputs: Vec<Rc<Node<T>>>,
lambda: Rc<Node<T>>,
inputs: Vec<Rc<Node<P>>>,
lambda: Rc<Node<P>>,
out_type: crate::ast::types::StaticType,
},
Block {
exprs: Vec<Rc<Node<T>>>,
exprs: Vec<Rc<Node<P>>>,
},
Tuple {
elements: Vec<Rc<Node<T>>>,
elements: Vec<Rc<Node<P>>>,
},
Record {
layout: std::sync::Arc<crate::ast::types::RecordLayout>,
values: Vec<Rc<Node<T>>>,
values: Vec<Rc<Node<P>>>,
},
/// An expanded macro call, preserving the original call for debugging and UI.
Expansion {
/// The original call from the syntax AST.
original_call: Rc<crate::ast::nodes::SyntaxNode>,
/// The result of binding the expanded AST.
bound_expanded: Rc<Node<T>>,
bound_expanded: Rc<Node<P>>,
},
/// A diagnostic poison node, allowing compilation to continue after an error.
Error,
/// DSL-specific extension slot
Extension(Box<dyn BoundExtension<T>>),
Extension(Box<dyn BoundExtension<P>>),
}
impl<T> PartialEq for BoundKind<T>
where
T: PartialEq,
{
impl<P: CompilerPhase> Clone for BoundKind<P> {
fn clone(&self) -> Self {
match self {
BoundKind::Nop => BoundKind::Nop,
BoundKind::Constant(v) => BoundKind::Constant(v.clone()),
BoundKind::Get { addr, name } => BoundKind::Get { addr: addr.clone(), name: name.clone() },
BoundKind::Set { addr, value } => BoundKind::Set { addr: addr.clone(), value: value.clone() },
BoundKind::Define { name, addr, kind, value, captured_by } => BoundKind::Define { name: name.clone(), addr: addr.clone(), kind: *kind, value: value.clone(), captured_by: captured_by.clone() },
BoundKind::FieldAccessor(k) => BoundKind::FieldAccessor(k.clone()),
BoundKind::GetField { rec, field } => BoundKind::GetField { rec: rec.clone(), field: field.clone() },
BoundKind::If { cond, then_br, else_br } => BoundKind::If { cond: cond.clone(), then_br: then_br.clone(), else_br: else_br.clone() },
BoundKind::Destructure { pattern, value } => BoundKind::Destructure { pattern: pattern.clone(), value: value.clone() },
BoundKind::Lambda { params, upvalues, body, positional_count } => BoundKind::Lambda { params: params.clone(), upvalues: upvalues.clone(), body: body.clone(), positional_count: *positional_count },
BoundKind::Call { callee, args } => BoundKind::Call { callee: callee.clone(), args: args.clone() },
BoundKind::Again { args } => BoundKind::Again { args: args.clone() },
BoundKind::Pipe { inputs, lambda, out_type } => BoundKind::Pipe { inputs: inputs.clone(), lambda: lambda.clone(), out_type: out_type.clone() },
BoundKind::Block { exprs } => BoundKind::Block { exprs: exprs.clone() },
BoundKind::Tuple { elements } => BoundKind::Tuple { elements: elements.clone() },
BoundKind::Record { layout, values } => BoundKind::Record { layout: layout.clone(), values: values.clone() },
BoundKind::Expansion { original_call, bound_expanded } => BoundKind::Expansion { original_call: original_call.clone(), bound_expanded: bound_expanded.clone() },
BoundKind::Error => BoundKind::Error,
BoundKind::Extension(ext) => BoundKind::Extension(ext.clone()),
}
}
}
impl<P: CompilerPhase> PartialEq for BoundKind<P> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(BoundKind::Nop, BoundKind::Nop) => true,
@@ -191,87 +268,32 @@ where
(BoundKind::Get { addr: aa, name: na }, BoundKind::Get { addr: ab, name: nb }) => {
aa == ab && na == nb
}
(
BoundKind::Set {
addr: aa,
value: va,
},
BoundKind::Set {
addr: ab,
value: vb,
},
) => aa == ab && Rc::ptr_eq(va, vb),
(
BoundKind::Define {
name: na,
addr: aa,
kind: ka,
value: va,
captured_by: ca,
},
BoundKind::Define {
name: nb,
addr: ab,
kind: kb,
value: vb,
captured_by: cb,
},
) => na == nb && aa == ab && ka == kb && Rc::ptr_eq(va, vb) && ca == cb,
(BoundKind::Set { addr: aa, value: va }, BoundKind::Set { addr: ab, value: vb }) => {
aa == ab && Rc::ptr_eq(va, vb)
}
(BoundKind::Define { name: na, addr: aa, kind: ka, value: va, captured_by: ca }, BoundKind::Define { name: nb, addr: ab, kind: kb, value: vb, captured_by: cb }) => {
na == nb && aa == ab && ka == kb && Rc::ptr_eq(va, vb) && ca == cb
}
(BoundKind::FieldAccessor(a), BoundKind::FieldAccessor(b)) => a == b,
(
BoundKind::GetField { rec: ra, field: fa },
BoundKind::GetField { rec: rb, field: fb },
) => Rc::ptr_eq(ra, rb) && fa == fb,
(
BoundKind::If {
cond: ca,
then_br: ta,
else_br: ea,
},
BoundKind::If {
cond: cb,
then_br: tb,
else_br: eb,
},
) => Rc::ptr_eq(ca, cb) && Rc::ptr_eq(ta, tb) && match (ea, eb) {
(Some(a), Some(b)) => Rc::ptr_eq(a, b),
(None, None) => true,
_ => false,
},
(
BoundKind::Destructure {
pattern: pa,
value: va,
},
BoundKind::Destructure {
pattern: pb,
value: vb,
},
) => Rc::ptr_eq(pa, pb) && Rc::ptr_eq(va, vb),
(
BoundKind::Lambda {
params: pa,
upvalues: ua,
body: ba,
positional_count: pca,
},
BoundKind::Lambda {
params: pb,
upvalues: ub,
body: bb,
positional_count: pcb,
},
) => Rc::ptr_eq(pa, pb) && ua == ub && Rc::ptr_eq(ba, bb) && pca == pcb,
(
BoundKind::Call {
callee: ca,
args: aa,
},
BoundKind::Call {
callee: cb,
args: ab,
},
) => Rc::ptr_eq(ca, cb) && Rc::ptr_eq(aa, ab),
(BoundKind::GetField { rec: ra, field: fa }, BoundKind::GetField { rec: rb, field: fb }) => {
Rc::ptr_eq(ra, rb) && fa == fb
}
(BoundKind::If { cond: ca, then_br: ta, else_br: ea }, BoundKind::If { cond: cb, then_br: tb, else_br: eb }) => {
Rc::ptr_eq(ca, cb) && Rc::ptr_eq(ta, tb) && match (ea, eb) {
(Some(a), Some(b)) => Rc::ptr_eq(a, b),
(None, None) => true,
_ => false,
}
}
(BoundKind::Destructure { pattern: pa, value: va }, BoundKind::Destructure { pattern: pb, value: vb }) => {
Rc::ptr_eq(pa, pb) && Rc::ptr_eq(va, vb)
}
(BoundKind::Lambda { params: pa, upvalues: ua, body: ba, positional_count: pca }, BoundKind::Lambda { params: pb, upvalues: ub, body: bb, positional_count: pcb }) => {
Rc::ptr_eq(pa, pb) && ua == ub && Rc::ptr_eq(ba, bb) && pca == pcb
}
(BoundKind::Call { callee: ca, args: aa }, BoundKind::Call { callee: cb, args: ab }) => {
Rc::ptr_eq(ca, cb) && Rc::ptr_eq(aa, ab)
}
(BoundKind::Again { args: aa }, BoundKind::Again { args: ab }) => Rc::ptr_eq(aa, ab),
(BoundKind::Block { exprs: ea }, BoundKind::Block { exprs: eb }) => {
ea.len() == eb.len() && ea.iter().zip(eb.iter()).all(|(a, b)| Rc::ptr_eq(a, b))
@@ -279,46 +301,26 @@ where
(BoundKind::Tuple { elements: ea }, BoundKind::Tuple { elements: eb }) => {
ea.len() == eb.len() && ea.iter().zip(eb.iter()).all(|(a, b)| Rc::ptr_eq(a, b))
}
(
BoundKind::Record {
layout: la,
values: va,
},
BoundKind::Record {
layout: lb,
values: vb,
},
) => std::sync::Arc::ptr_eq(la, lb) && va.len() == vb.len() && va.iter().zip(vb.iter()).all(|(a, b)| Rc::ptr_eq(a, b)),
(
BoundKind::Expansion {
original_call: ca,
bound_expanded: ea,
},
BoundKind::Expansion {
original_call: cb,
bound_expanded: eb,
},
) => Rc::ptr_eq(ca, cb) && Rc::ptr_eq(ea, eb),
(BoundKind::Record { layout: la, values: va }, BoundKind::Record { layout: lb, values: vb }) => {
std::sync::Arc::ptr_eq(la, lb) && va.len() == vb.len() && va.iter().zip(vb.iter()).all(|(a, b)| Rc::ptr_eq(a, b))
}
(BoundKind::Expansion { original_call: ca, bound_expanded: ea }, BoundKind::Expansion { original_call: cb, bound_expanded: eb }) => {
Rc::ptr_eq(ca, cb) && Rc::ptr_eq(ea, eb)
}
(BoundKind::Error, BoundKind::Error) => true,
(BoundKind::Extension(_), BoundKind::Extension(_)) => false,
_ => false,
}
}
}
/// A single field in a Record literal (Key-Value pair)
pub type RecordField<T> = (Node<T>, Node<T>);
impl<T> BoundKind<T> {
impl<P: CompilerPhase> BoundKind<P> {
pub fn display_name(&self) -> String {
match self {
BoundKind::Nop => "NOP".to_string(),
BoundKind::Constant(v) => format!("CONST({})", v),
BoundKind::Get { addr, name } => format!("GET({}, {:?})", name.name, addr),
BoundKind::Set { addr, .. } => format!("SET({:?})", addr),
BoundKind::Define {
name, addr, kind, ..
} => {
BoundKind::Define { name, addr, kind, .. } => {
let k_str = match kind {
DeclarationKind::Variable => "VAR",
DeclarationKind::Parameter => "PARAM",
@@ -329,9 +331,7 @@ impl<T> BoundKind<T> {
BoundKind::GetField { field, .. } => format!("GET_FIELD(.{})", field.name()),
BoundKind::If { .. } => "IF".to_string(),
BoundKind::Destructure { .. } => "DESTRUCTURE".to_string(),
BoundKind::Lambda {
params, upvalues, ..
} => {
BoundKind::Lambda { params, upvalues, .. } => {
let p_str = match &params.kind {
BoundKind::Tuple { elements } => format!("p:{}", elements.len()),
_ => "p:1".to_string(),
+78 -103
View File
@@ -1,67 +1,34 @@
use crate::ast::compiler::bound_nodes::{BoundKind, Node};
use crate::ast::compiler::bound_nodes::{BoundKind, CompilerPhase, Node};
use crate::ast::types::Identity;
use std::collections::HashMap;
use std::rc::Rc;
pub struct CapturePass;
impl CapturePass {
pub fn apply<T: Clone>(node: Node<T>, capture_map: &HashMap<Identity, Vec<Identity>>) -> Node<T> {
pub fn apply<P: CompilerPhase>(node: Node<P>, capture_map: &HashMap<Identity, Vec<Identity>>) -> Node<P> {
Self::transform(node, capture_map)
}
fn transform<T: Clone>(mut node: Node<T>, capture_map: &HashMap<Identity, Vec<Identity>>) -> Node<T> {
use std::rc::Rc;
match node.kind {
fn transform<P: CompilerPhase>(mut node: Node<P>, capture_map: &HashMap<Identity, Vec<Identity>>) -> Node<P> {
node.kind = match node.kind {
BoundKind::Define {
name,
addr,
kind,
value,
..
mut captured_by,
} => {
let captured_by = capture_map.get(&node.identity).cloned().unwrap_or_default();
node.kind = BoundKind::Define {
if let Some(capturers) = capture_map.get(&node.identity) {
captured_by.extend(capturers.iter().cloned());
}
BoundKind::Define {
name,
addr,
kind,
value: Rc::new(Self::transform(value.as_ref().clone(), capture_map)),
captured_by,
};
}
BoundKind::If {
cond,
then_br,
else_br,
} => {
node.kind = BoundKind::If {
cond: Rc::new(Self::transform(cond.as_ref().clone(), capture_map)),
then_br: Rc::new(Self::transform(then_br.as_ref().clone(), capture_map)),
else_br: else_br.map(|e| Rc::new(Self::transform(e.as_ref().clone(), capture_map))),
};
}
BoundKind::Set { addr, value } => {
node.kind = BoundKind::Set {
addr,
value: Rc::new(Self::transform(value.as_ref().clone(), capture_map)),
};
}
BoundKind::FieldAccessor(_) => {}
BoundKind::GetField { rec, field } => {
node.kind = BoundKind::GetField {
rec: Rc::new(Self::transform(rec.as_ref().clone(), capture_map)),
field,
};
}
BoundKind::Destructure { pattern, value } => {
node.kind = BoundKind::Destructure {
pattern: Rc::new(Self::transform(pattern.as_ref().clone(), capture_map)),
value: Rc::new(Self::transform(value.as_ref().clone(), capture_map)),
};
}
}
BoundKind::Lambda {
@@ -69,27 +36,38 @@ impl CapturePass {
upvalues,
body,
positional_count,
} => {
node.kind = BoundKind::Lambda {
params: Rc::new(Self::transform(params.as_ref().clone(), capture_map)),
upvalues,
body: Rc::new(Self::transform(body.as_ref().clone(), capture_map)),
positional_count,
};
}
} => BoundKind::Lambda {
params: Rc::new(Self::transform(params.as_ref().clone(), capture_map)),
upvalues,
body: Rc::new(Self::transform(body.as_ref().clone(), capture_map)),
positional_count,
},
BoundKind::Call { callee, args } => {
node.kind = BoundKind::Call {
callee: Rc::new(Self::transform(callee.as_ref().clone(), capture_map)),
args: Rc::new(Self::transform(args.as_ref().clone(), capture_map)),
};
}
BoundKind::Call { callee, args } => BoundKind::Call {
callee: Rc::new(Self::transform(callee.as_ref().clone(), capture_map)),
args: Rc::new(Self::transform(args.as_ref().clone(), capture_map)),
},
BoundKind::Again { args } => BoundKind::Again {
args: Rc::new(Self::transform(args.as_ref().clone(), capture_map)),
},
BoundKind::If {
cond,
then_br,
else_br,
} => BoundKind::If {
cond: Rc::new(Self::transform(cond.as_ref().clone(), capture_map)),
then_br: Rc::new(Self::transform(then_br.as_ref().clone(), capture_map)),
else_br: else_br
.map(|e| Rc::new(Self::transform(e.as_ref().clone(), capture_map))),
},
BoundKind::Destructure { pattern, value } => BoundKind::Destructure {
pattern: Rc::new(Self::transform(pattern.as_ref().clone(), capture_map)),
value: Rc::new(Self::transform(value.as_ref().clone(), capture_map)),
},
BoundKind::Again { args } => {
node.kind = BoundKind::Again {
args: Rc::new(Self::transform(args.as_ref().clone(), capture_map)),
};
}
BoundKind::Pipe {
inputs,
lambda,
@@ -99,56 +77,53 @@ impl CapturePass {
for input in inputs {
t_inputs.push(Rc::new(Self::transform(input.as_ref().clone(), capture_map)));
}
node.kind = BoundKind::Pipe {
BoundKind::Pipe {
inputs: t_inputs,
lambda: Rc::new(Self::transform(lambda.as_ref().clone(), capture_map)),
out_type: out_type.clone(),
};
}
BoundKind::Block { exprs } => {
node.kind = BoundKind::Block {
exprs: exprs
.into_iter()
.map(|e| Rc::new(Self::transform(e.as_ref().clone(), capture_map)))
.collect(),
};
out_type,
}
}
BoundKind::Tuple { elements } => {
node.kind = BoundKind::Tuple {
elements: elements
.into_iter()
.map(|e| Rc::new(Self::transform(e.as_ref().clone(), capture_map)))
.collect(),
};
}
BoundKind::Block { exprs } => BoundKind::Block {
exprs: exprs
.into_iter()
.map(|e| Rc::new(Self::transform(e.as_ref().clone(), capture_map)))
.collect(),
},
BoundKind::Record { layout, values } => {
node.kind = BoundKind::Record {
layout,
values: values
.into_iter()
.map(|v| Rc::new(Self::transform(v.as_ref().clone(), capture_map)))
.collect(),
};
}
BoundKind::Tuple { elements } => BoundKind::Tuple {
elements: elements
.into_iter()
.map(|e| Rc::new(Self::transform(e.as_ref().clone(), capture_map)))
.collect(),
},
BoundKind::Record { layout, values } => BoundKind::Record {
layout,
values: values
.into_iter()
.map(|v| Rc::new(Self::transform(v.as_ref().clone(), capture_map)))
.collect(),
},
BoundKind::Expansion {
original_call,
bound_expanded,
} => {
node.kind = BoundKind::Expansion {
original_call,
bound_expanded: Rc::new(Self::transform(bound_expanded.as_ref().clone(), capture_map)),
};
}
} => BoundKind::Expansion {
original_call,
bound_expanded: Rc::new(Self::transform(
bound_expanded.as_ref().clone(),
capture_map,
)),
},
BoundKind::Nop
| BoundKind::Constant(_)
| BoundKind::Get { .. }
| BoundKind::Extension(_)
| BoundKind::Error => {}
}
BoundKind::GetField { rec, field } => BoundKind::GetField {
rec: Rc::new(Self::transform(rec.as_ref().clone(), capture_map)),
field,
},
other => other,
};
node
}
}
+4 -5
View File
@@ -1,5 +1,4 @@
use crate::ast::compiler::bound_nodes::{BoundKind, Node};
use std::fmt::Debug;
use crate::ast::compiler::bound_nodes::{BoundKind, CompilerPhase, Node};
/// Human-readable AST dumper for the bound AST.
pub struct Dumper {
@@ -9,7 +8,7 @@ pub struct Dumper {
impl Dumper {
/// Produces a formatted string representation of the given bound AST node and its children.
pub fn dump<T: Debug>(node: &Node<T>) -> String {
pub fn dump<P: CompilerPhase>(node: &Node<P>) -> String {
let mut dumper = Self {
output: String::new(),
indent: 0,
@@ -24,14 +23,14 @@ impl Dumper {
}
}
fn log<T: Debug>(&mut self, label: &str, node: &Node<T>) {
fn log<P: CompilerPhase>(&mut self, label: &str, node: &Node<P>) {
self.write_indent();
self.output.push_str(label);
self.output
.push_str(&format!(" <Metadata: {:?}>\n", node.ty));
}
fn visit<T: Debug>(&mut self, node: &Node<T>) {
fn visit<P: CompilerPhase>(&mut self, node: &Node<P>) {
match &node.kind {
BoundKind::Nop => self.log("Nop", node),
BoundKind::Constant(v) => {
+6 -6
View File
@@ -1,21 +1,21 @@
use crate::ast::compiler::bound_nodes::{Address, BoundKind, GlobalIdx, Node};
use crate::ast::compiler::bound_nodes::{Address, BoundKind, CompilerPhase, GlobalIdx, Node};
use std::collections::HashMap;
use std::rc::Rc;
/// A pass that collects all global function definitions (lambdas) into a registry.
/// This allows the Specializer to retrieve the original AST of a function for monomorphization.
pub struct LambdaCollector<'a, T> {
registry: &'a mut HashMap<GlobalIdx, Rc<Node<T>>>,
pub struct LambdaCollector<'a, P: CompilerPhase> {
registry: &'a mut HashMap<GlobalIdx, Rc<Node<P>>>,
}
impl<'a, T: Clone> LambdaCollector<'a, T> {
impl<'a, P: CompilerPhase> LambdaCollector<'a, P> {
/// Performs a full traversal of the AST and populates the provided registry.
pub fn collect(node: &Node<T>, registry: &'a mut HashMap<GlobalIdx, Rc<Node<T>>>) {
pub fn collect(node: &Node<P>, registry: &'a mut HashMap<GlobalIdx, Rc<Node<P>>>) {
let mut collector = Self { registry };
collector.visit(node);
}
fn visit(&mut self, node: &Node<T>) {
fn visit(&mut self, node: &Node<P>) {
match &node.kind {
BoundKind::Block { exprs } => {
for expr in exprs {
+6 -29
View File
@@ -1,31 +1,7 @@
use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind, LocalSlot, Node};
use crate::ast::types::StaticType;
use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind, ExecNode, Node, RuntimeMetadata, StackOffset, VirtualId};
use std::collections::HashMap;
use std::fmt::Debug;
use std::rc::Rc;
#[derive(Clone)]
pub struct RuntimeMetadata {
pub ty: StaticType,
pub is_tail: bool,
/// The analyzed node, containing metrics and a link to the original TypedNode.
pub original: Rc<AnalyzedNode>,
pub stack_size: u32,
}
impl Debug for RuntimeMetadata {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Metadata")
.field("ty", &self.ty)
.field("is_tail", &self.is_tail)
.field("stack_size", &self.stack_size)
.finish()
}
}
/// The ExecNode is the AST used by the VM. It carries TCO flags and links to metrics.
pub type ExecNode = Node<RuntimeMetadata>;
struct StackAllocator {
mapping: HashMap<u32, u32>,
next_slot: u32,
@@ -39,19 +15,20 @@ impl StackAllocator {
}
}
fn map_slot(&mut self, slot: LocalSlot) -> LocalSlot {
fn map_slot(&mut self, slot: VirtualId) -> StackOffset {
let entry = self.mapping.entry(slot.0).or_insert_with(|| {
let s = self.next_slot;
self.next_slot += 1;
s
});
LocalSlot(*entry)
StackOffset(*entry)
}
fn map_address(&mut self, addr: Address) -> Address {
fn map_address(&mut self, addr: Address<VirtualId>) -> Address<StackOffset> {
match addr {
Address::Local(slot) => Address::Local(self.map_slot(slot)),
other => other,
Address::Upvalue(idx) => Address::Upvalue(idx),
Address::Global(idx) => Address::Global(idx),
}
}
}
+1 -1
View File
@@ -769,7 +769,7 @@ mod tests {
locals.insert(
Symbol::from("*"),
crate::ast::compiler::binder::LocalInfo {
addr: Address::Local(crate::ast::compiler::bound_nodes::LocalSlot(0)),
addr: Address::Local(crate::ast::compiler::bound_nodes::VirtualId(0)),
identity: crate::ast::types::NodeIdentity::new(crate::ast::types::SourceLocation {
line: 0,
col: 0,
+3 -3
View File
@@ -1,4 +1,4 @@
use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind, LocalSlot};
use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind, VirtualId};
use crate::ast::types::{Purity, Value};
use crate::ast::vm::Closure;
use std::cell::RefCell;
@@ -24,7 +24,7 @@ impl<'a> Inliner<'a> {
}
}
pub fn is_inlinable_value(&self, val: &Value, addr: Address) -> bool {
pub fn is_inlinable_value(&self, val: &Value, addr: Address<VirtualId>) -> bool {
let type_ok = match val {
Value::Int(_)
| Value::Float(_)
@@ -141,7 +141,7 @@ impl<'a> Inliner<'a> {
}
}
pub fn collect_parameter_slots_set(&self, node: &AnalyzedNode, slots: &mut HashSet<LocalSlot>) {
pub fn collect_parameter_slots_set(&self, node: &AnalyzedNode, slots: &mut HashSet<VirtualId>) {
match &node.kind {
BoundKind::Define {
addr: Address::Local(slot),
+15 -15
View File
@@ -1,17 +1,17 @@
use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind, LocalSlot, Node, UpvalueIdx};
use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind, Node, UpvalueIdx, VirtualId};
use crate::ast::types::Value;
use std::collections::{HashMap, HashSet};
use std::rc::Rc;
#[derive(Default)]
pub struct SubstitutionMap {
pub values: HashMap<Address, Value>,
pub ast_substitutions: HashMap<Address, Rc<AnalyzedNode>>,
pub slot_mapping: HashMap<LocalSlot, LocalSlot>,
pub assigned: HashSet<Address>,
pub values: HashMap<Address<VirtualId>, Value>,
pub ast_substitutions: HashMap<Address<VirtualId>, Rc<AnalyzedNode>>,
pub slot_mapping: HashMap<VirtualId, VirtualId>,
pub assigned: HashSet<Address<VirtualId>>,
pub next_slot: u32,
pub used: HashSet<Address>,
pub captured_slots: HashSet<LocalSlot>,
pub used: HashSet<Address<VirtualId>>,
pub captured_slots: HashSet<VirtualId>,
}
impl SubstitutionMap {
@@ -43,40 +43,40 @@ impl SubstitutionMap {
inner
}
pub fn add_ast_substitution(&mut self, addr: Address, node: AnalyzedNode) {
pub fn add_ast_substitution(&mut self, addr: Address<VirtualId>, node: AnalyzedNode) {
self.ast_substitutions.insert(addr, Rc::new(node));
}
pub fn map_slot(&mut self, old_slot: LocalSlot) -> LocalSlot {
pub fn map_slot(&mut self, old_slot: VirtualId) -> VirtualId {
if let Some(&new_slot) = self.slot_mapping.get(&old_slot) {
return new_slot;
}
let new_slot = LocalSlot(self.next_slot);
let new_slot = VirtualId(self.next_slot);
self.slot_mapping.insert(old_slot, new_slot);
self.next_slot += 1;
new_slot
}
pub fn map_address(&mut self, addr: Address) -> Address {
pub fn map_address(&mut self, addr: Address<VirtualId>) -> Address<VirtualId> {
match addr {
Address::Local(slot) => Address::Local(self.map_slot(slot)),
other => other,
}
}
pub fn add_value(&mut self, addr: Address, val: Value) {
pub fn add_value(&mut self, addr: Address<VirtualId>, val: Value) {
self.values.insert(addr, val);
}
pub fn get_value(&self, addr: &Address) -> Option<&Value> {
pub fn get_value(&self, addr: &Address<VirtualId>) -> Option<&Value> {
self.values.get(addr)
}
pub fn remove_value(&mut self, addr: &Address) {
pub fn remove_value(&mut self, addr: &Address<VirtualId>) {
self.values.remove(addr);
}
fn reindex_addr(&self, addr: Address, mapping: &[Option<u32>]) -> Address {
fn reindex_addr(&self, addr: Address<VirtualId>, mapping: &[Option<u32>]) -> Address<VirtualId> {
if let Address::Upvalue(idx) = addr
&& let Some(res) = mapping.get(idx.0 as usize)
&& let Some(new_idx) = res
+5 -5
View File
@@ -1,4 +1,4 @@
use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind, GlobalIdx};
use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind, GlobalIdx, VirtualId};
use crate::ast::types::{Identity, Value};
use crate::ast::vm::Closure;
use std::collections::HashSet;
@@ -32,17 +32,17 @@ impl PathTracker {
// --- UsageInfo ---
#[derive(Default)]
pub struct UsageInfo {
pub used: HashSet<Address>,
pub assigned: HashSet<Address>,
pub used: HashSet<Address<VirtualId>>,
pub assigned: HashSet<Address<VirtualId>>,
pub used_identities: HashSet<Identity>,
}
impl UsageInfo {
pub fn is_assigned(&self, addr: &Address) -> bool {
pub fn is_assigned(&self, addr: &Address<VirtualId>) -> bool {
self.assigned.contains(addr)
}
pub fn is_used(&self, addr: &Address) -> bool {
pub fn is_used(&self, addr: &Address<VirtualId>) -> bool {
self.used.contains(addr)
}
+5 -5
View File
@@ -1,4 +1,4 @@
use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind, Node, NodeMetrics};
use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, AnalyzedPhase, BoundKind, Node, NodeMetrics, VirtualId};
use crate::ast::types::{Purity, Signature, StaticType, Value};
use std::cell::RefCell;
use std::collections::HashMap;
@@ -6,16 +6,16 @@ use std::rc::Rc;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct MonoCacheKey {
pub address: Address,
pub address: Address<VirtualId>,
pub arg_types: Vec<StaticType>,
}
pub type CompileFunc = Rc<dyn Fn(Rc<Node>, &[StaticType]) -> Result<(Value, StaticType), String>>;
pub type CompileFunc = Rc<dyn Fn(Rc<Node<AnalyzedPhase>>, &[StaticType]) -> Result<(Value, StaticType), String>>;
pub type RtlLookupFunc = Rc<dyn Fn(&str, &[StaticType]) -> Option<(Value, StaticType)>>;
pub trait FunctionRegistry {
fn resolve(&self, addr: Address) -> Option<Rc<Node>>;
fn resolve_analyzed(&self, _addr: Address) -> Option<Rc<AnalyzedNode>> {
fn resolve(&self, addr: Address<VirtualId>) -> Option<Rc<Node<AnalyzedPhase>>>;
fn resolve_analyzed(&self, _addr: Address<VirtualId>) -> Option<Rc<AnalyzedNode>> {
None
}
}
+24 -36
View File
@@ -1,4 +1,4 @@
use crate::ast::compiler::bound_nodes::{Address, BoundKind, Node, TypedNode};
use crate::ast::compiler::bound_nodes::{Address, BoundKind, BoundPhase, CompilerPhase, Node, TypedNode, VirtualId};
use crate::ast::diagnostics::Diagnostics;
use crate::ast::types::StaticType;
use std::collections::HashMap;
@@ -33,7 +33,7 @@ impl<'a> TypeContext<'a> {
}
}
fn get_type(&self, addr: Address) -> StaticType {
fn get_type(&self, addr: Address<VirtualId>) -> StaticType {
match addr {
Address::Local(slot) => self
.slots
@@ -54,7 +54,7 @@ impl<'a> TypeContext<'a> {
}
}
fn set_type(&mut self, addr: Address, ty: StaticType) {
fn set_type(&mut self, addr: Address<VirtualId>, ty: StaticType) {
match addr {
Address::Local(slot) => {
self.slots.insert(slot.0, ty);
@@ -81,7 +81,18 @@ impl TypeChecker {
pub fn check(
&self,
node: &Node,
node: &Node<BoundPhase>,
arg_types: &[StaticType],
diag: &mut Diagnostics,
) -> TypedNode {
self.check_node_as_bound(node, arg_types, diag)
}
/// Allows re-checking a node from any phase as if it were a bound node.
/// This is useful for specialization where we re-type a TypedNode with more specific info.
pub fn check_node_as_bound<P: CompilerPhase<LocalAddress = VirtualId>>(
&self,
node: &Node<P>,
arg_types: &[StaticType],
diag: &mut Diagnostics,
) -> TypedNode {
@@ -92,20 +103,17 @@ impl TypeChecker {
body,
positional_count,
} => {
// 1. Determine types of captured variables (Root lambdas have none)
let mut upvalue_types = Vec::with_capacity(upvalues.len());
for _ in upvalues {
for &_addr in upvalues {
upvalue_types.push(StaticType::Any);
}
// 2. Create the specialized context
let root_ctx = TypeContext::new(0, vec![], &self.root_types, None);
let mut lambda_ctx =
TypeContext::new(64, upvalue_types, &self.root_types, Some(&root_ctx));
// 3. INJECT specialized argument types into slots
let arg_tuple_ty = if arg_types.is_empty() {
StaticType::Any // No specialized info
StaticType::Any
} else {
StaticType::Tuple(arg_types.to_vec())
};
@@ -117,11 +125,8 @@ impl TypeChecker {
diag,
);
// 4. Check body with the new types
let body_typed = self.check_node(body, &mut lambda_ctx, diag);
let ret_ty = body_typed.ty.clone();
// 5. Construct specialized function type
let final_params_ty = params_typed.ty.clone();
let fn_ty = StaticType::Function(Box::new(crate::ast::types::Signature {
@@ -147,9 +152,9 @@ impl TypeChecker {
}
}
fn check_params(
fn check_params<P: CompilerPhase<LocalAddress = VirtualId>>(
&self,
node: &Node,
node: &Node<P>,
specialized_ty: &StaticType,
ctx: &mut TypeContext,
diag: &mut Diagnostics,
@@ -182,10 +187,6 @@ impl TypeChecker {
addr,
value: _value,
} => {
// In an assignment pattern, 'value' is just a Nop placeholder from the Binder.
// We update the type of the address (if it's a local or global) to match the specialized type.
// Note: For now, we assume assignments are compatible if types were already inferred,
// or we could add a check here against existing type.
(
BoundKind::Set {
addr: *addr,
@@ -240,7 +241,7 @@ impl TypeChecker {
StaticType::Error => StaticType::Error,
_ => StaticType::Any,
};
let t = self.check_params(el, &sub_ty, ctx, diag);
let t = self.check_params(el.as_ref(), &sub_ty, ctx, diag);
elem_types.push(t.ty.clone());
typed_elements.push(Rc::new(t));
}
@@ -268,9 +269,9 @@ impl TypeChecker {
}
}
fn check_node(
fn check_node<P: CompilerPhase<LocalAddress = VirtualId>>(
&self,
node: &Node,
node: &Node<P>,
ctx: &mut TypeContext,
diag: &mut Diagnostics,
) -> TypedNode {
@@ -373,7 +374,7 @@ impl TypeChecker {
BoundKind::Destructure { pattern, value } => {
let val_typed = self.check_node(value, ctx, diag);
let pat_typed = self.check_params(pattern, &val_typed.ty, ctx, diag);
let pat_typed = self.check_params(pattern.as_ref(), &val_typed.ty, ctx, diag);
let ty = val_typed.ty.clone();
(
BoundKind::Destructure {
@@ -397,13 +398,11 @@ impl TypeChecker {
if let Some(e) = else_br {
let et = self.check_node(e, ctx, diag);
// Basic type promotion: if types differ, fall back to Any
if et.ty != final_ty {
final_ty = StaticType::Any;
}
else_typed = Some(Rc::new(et));
} else {
// If without else returns Optional(Then) (T | Void)
final_ty = StaticType::Optional(Box::new(then_typed.ty.clone()));
}
@@ -422,7 +421,6 @@ impl TypeChecker {
let mut arg_types = Vec::with_capacity(inputs.len());
for input in inputs {
let typed_input = self.check_node(input, ctx, diag);
// Unwrap Series(T) or Stream(T) to T for the lambda argument
let arg_ty = if let StaticType::Series(inner) = &typed_input.ty {
*inner.clone()
} else if let StaticType::Stream(inner) = &typed_input.ty {
@@ -434,11 +432,9 @@ impl TypeChecker {
typed_inputs.push(Rc::new(typed_input));
}
// Specialized check for the lambda using the input types!
let typed_lambda = self.check(lambda, &arg_types, diag);
let typed_lambda = self.check_node_as_bound(lambda.as_ref(), &arg_types, diag);
let ret_ty = if let StaticType::Function(sig) = &typed_lambda.ty {
// If the lambda returns an Optional(T), the pipeline filters Void and stores T!
if let StaticType::Optional(inner) = &sig.ret {
*inner.clone()
} else {
@@ -476,17 +472,14 @@ impl TypeChecker {
body,
positional_count,
} => {
// 1. Determine types of captured variables
let mut upvalue_types = Vec::with_capacity(upvalues.len());
for &addr in upvalues {
upvalue_types.push(ctx.get_type(addr));
}
// 2. Create nested context for lambda body
let mut lambda_ctx =
TypeContext::new(64, upvalue_types, ctx.root_types, Some(ctx));
// 3. Check parameters and body
let params_typed = self.check_params(
params.as_ref(),
&StaticType::Any,
@@ -494,13 +487,11 @@ impl TypeChecker {
diag,
);
// Set current params type for 'again' validation
lambda_ctx.current_params_ty = Some(params_typed.ty.clone());
let body_typed = self.check_node(body, &mut lambda_ctx, diag);
let ret_ty = body_typed.ty.clone();
// 4. Construct function type
let fn_ty = StaticType::Function(Box::new(crate::ast::types::Signature {
params: params_typed.ty.clone(),
ret: ret_ty,
@@ -520,7 +511,6 @@ impl TypeChecker {
BoundKind::Call { callee, args } => {
let callee_typed = self.check_node(callee, ctx, diag);
// Manually check args (Tuple) to prevent Vector/Matrix promotion
let args_typed = if let BoundKind::Tuple { elements } = &args.kind {
let mut typed_elements = Vec::new();
let mut elem_types = Vec::new();
@@ -537,7 +527,6 @@ impl TypeChecker {
ty: StaticType::Tuple(elem_types),
}
} else {
// Should not happen as parser wraps args in Tuple, but fallback safely
self.check_node(args, ctx, diag)
};
@@ -565,7 +554,6 @@ impl TypeChecker {
}
BoundKind::Again { args } => {
// Manually check args (Tuple) to prevent Vector/Matrix promotion
let args_typed = if let BoundKind::Tuple { elements } = &args.kind {
let mut typed_elements = Vec::new();
let mut elem_types = Vec::new();
+31 -24
View File
@@ -10,12 +10,12 @@ use std::path::{Path, PathBuf};
use std::rc::Rc;
use crate::ast::compiler::bound_nodes::{
Address, AnalyzedNode, BoundKind, GlobalAnalyzedRegistry, GlobalFunctionRegistry, GlobalIdx,
Node,
Address, AnalyzedNode, BoundKind, ExecNode, GlobalAnalyzedRegistry, GlobalFunctionRegistry, GlobalIdx,
Node, VirtualId,
};
use crate::ast::compiler::dumper::Dumper;
use crate::ast::compiler::lambda_collector::LambdaCollector;
use crate::ast::compiler::lowering::{Lowering, ExecNode};
use crate::ast::compiler::lowering::Lowering;
use crate::ast::compiler::macros::{MacroEvaluator, MacroExpander, MacroRegistry};
use crate::ast::compiler::optimizer::Optimizer;
use crate::ast::compiler::specializer::{FunctionRegistry, MonoCache, Specializer};
@@ -89,19 +89,11 @@ pub struct Environment {
}
struct EnvFunctionRegistry {
registry: Rc<RefCell<GlobalFunctionRegistry>>,
analyzed_registry: Rc<RefCell<GlobalAnalyzedRegistry>>,
}
impl FunctionRegistry for EnvFunctionRegistry {
fn resolve(&self, addr: Address) -> Option<Rc<Node>> {
if let Address::Global(idx) = addr {
self.registry.borrow().get(&idx).cloned()
} else {
None
}
}
fn resolve_analyzed(&self, addr: Address) -> Option<Rc<AnalyzedNode>> {
fn resolve(&self, addr: Address<VirtualId>) -> Option<Rc<Node<crate::ast::compiler::bound_nodes::AnalyzedPhase>>> {
if let Address::Global(idx) = addr {
self.analyzed_registry.borrow().get(&idx).cloned()
} else {
@@ -406,7 +398,7 @@ impl Environment {
if !current_scope.locals.contains_key(sym) {
let mut slot_count = self.root_slot_count.borrow_mut();
let slot = crate::ast::compiler::bound_nodes::LocalSlot(*slot_count);
let slot = VirtualId(*slot_count);
current_scope.locals.insert(
sym.clone(),
crate::ast::compiler::binder::LocalInfo {
@@ -484,15 +476,15 @@ impl Environment {
LambdaCollector::collect(&bound_ast, &mut self.function_registry.borrow_mut());
let checker = TypeChecker::new(self.root_types.clone());
let wrapped_ast = if let crate::ast::compiler::bound_nodes::BoundKind::Lambda { .. } = bound_ast.kind {
let wrapped_ast = if let BoundKind::Lambda { .. } = bound_ast.kind {
bound_ast
} else {
crate::ast::compiler::bound_nodes::Node {
Node {
identity: bound_ast.identity.clone(),
kind: crate::ast::compiler::bound_nodes::BoundKind::Lambda {
params: std::rc::Rc::new(crate::ast::compiler::bound_nodes::Node {
kind: BoundKind::Lambda {
params: std::rc::Rc::new(Node {
identity: bound_ast.identity.clone(),
kind: crate::ast::compiler::bound_nodes::BoundKind::Tuple { elements: vec![] },
kind: BoundKind::Tuple { elements: vec![] },
ty: (),
}),
upvalues: vec![],
@@ -668,7 +660,7 @@ impl Environment {
{
let closure = Rc::new(crate::ast::vm::Closure::new(
params.clone(),
body.ty.original.clone(),
body.ty.original.clone(),
body.clone(),
Vec::new(),
*positional_count,
@@ -717,13 +709,11 @@ impl Environment {
fn specialize_node(&self, node: AnalyzedNode) -> AnalyzedNode {
let registry = Rc::new(EnvFunctionRegistry {
registry: self.function_registry.clone(),
analyzed_registry: self.typed_function_registry.clone(),
});
let rtl_lookup = Rc::new(|name: &str, args: &[StaticType]| intrinsics::lookup(name, args));
let typed_reg = self.typed_function_registry.clone();
let syntax_reg = self.function_registry.clone();
let mono_cache = self.monomorph_cache.clone();
let root_values = self.root_values.clone();
let root_types = self.root_types.clone();
@@ -731,12 +721,30 @@ impl Environment {
let optimization = self.optimization;
let compiler = Rc::new(
move |func_template: Rc<Node>,
move |func_template: Rc<Node<crate::ast::compiler::bound_nodes::AnalyzedPhase>>,
arg_types: &[StaticType]|
-> Result<(Value, StaticType), String> {
let mut diag = Diagnostics::new();
let checker = TypeChecker::new(root_types.clone());
let retyped_ast = checker.check(func_template.as_ref(), arg_types, &mut diag);
// For specialization, we re-type-check the analyzed node.
// Note: AnalyzedNode.ty.original is the TypedNode.
// But TypeChecker expects Node<BoundPhase>. We need to go from TypedNode -> BoundNode.
// However, since TypedNode is just Node<TypedPhase>, we can't easily "un-type" it.
// Instead, we access the original AST from the binder if we had it,
// OR we make the TypeChecker generic.
// For now, we assume the TypedNode can be used where a BoundNode is expected
// if we strip the metadata.
// A better way is to store the BoundNode in NodeMetrics as well.
// Temporary fix: Re-binding from source would be too expensive.
// Let's assume for now that we can specialize directly on the TypedNode
// or that we need a small helper to transform TypedNode -> BoundNode.
// Realizing that TypedNode (StaticType) is very similar to BoundNode (()),
// we can just use the internal transform.
let retyped_ast = checker.check_node_as_bound(func_template.ty.original.as_ref(), arg_types, &mut diag);
if diag.has_errors() {
return Err(diag
@@ -750,7 +758,6 @@ impl Environment {
let analyzed = Analyzer::analyze(&retyped_ast, &root_purity.borrow());
let sub_registry = Rc::new(EnvFunctionRegistry {
registry: syntax_reg.clone(),
analyzed_registry: typed_reg.clone(),
});
let sub_rtl_lookup =
+7 -8
View File
@@ -1,5 +1,4 @@
use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind};
use crate::ast::compiler::lowering::ExecNode;
use crate::ast::compiler::bound_nodes::{Address, AnalyzedNode, BoundKind, ExecNode, StackOffset};
use crate::ast::types::{Object, Value};
use std::any::Any;
use std::cell::RefCell;
@@ -335,7 +334,7 @@ impl VM {
val.clone()
};
self.set_value(addr.clone(), store_val)?;
self.set_value(*addr, store_val)?;
// Define always evaluates to the unwrapped value for immediate use.
Ok(val)
}
@@ -351,7 +350,7 @@ impl VM {
}
Ok(val)
}
BoundKind::Get { addr, .. } => self.get_value(addr.clone()),
BoundKind::Get { addr, .. } => self.get_value(*addr),
BoundKind::FieldAccessor(k) => Ok(Value::FieldAccessor(k.clone())),
@@ -399,7 +398,7 @@ impl VM {
BoundKind::Set { addr, value } => {
let val = self.eval_internal(obs, value)?;
self.set_value(addr.clone(), val.clone())?;
self.set_value(*addr, val.clone())?;
Ok(val)
}
BoundKind::If {
@@ -882,7 +881,7 @@ impl VM {
}
}
fn capture_upvalue(&mut self, addr: Address) -> Result<Rc<RefCell<Value>>, String> {
fn capture_upvalue(&mut self, addr: Address<StackOffset>) -> Result<Rc<RefCell<Value>>, String> {
match addr {
Address::Local(slot) => {
let frame = self.frames.last().ok_or("No call frame")?;
@@ -918,7 +917,7 @@ impl VM {
}
}
fn get_value(&self, addr: Address) -> Result<Value, String> {
fn get_value(&self, addr: Address<StackOffset>) -> Result<Value, String> {
match addr {
Address::Local(slot) => {
let frame = self.frames.last().ok_or("No call frame")?;
@@ -958,7 +957,7 @@ impl VM {
}
}
fn set_value(&mut self, addr: Address, value: Value) -> Result<(), String> {
fn set_value(&mut self, addr: Address<StackOffset>, value: Value) -> Result<(), String> {
match addr {
Address::Local(slot) => {
let frame = self.frames.last().ok_or("No call frame")?;