Add generic NodeKind enum
This commit introduces a new `NodeKind` enum that serves as a unified representation for AST nodes across different compilation phases. It replaces the separate `SyntaxKind` and `BoundKind` enums, allowing phase-specific information to be attached through generic type parameters. This change simplifies the AST structure and makes it easier to manage node information consistently throughout the compilation process. The `NodeKind` enum now holds all possible AST node variants, with phase-specific data encapsulated within `CompilerPhase` trait implementations.
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
use crate::ast::nodes::Symbol;
|
use crate::ast::nodes::Symbol;
|
||||||
use crate::ast::types::{Identity, StaticType, Value};
|
use crate::ast::types::{Identity, StaticType, Value};
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub struct VirtualId(pub u32);
|
pub struct VirtualId(pub u32);
|
||||||
@@ -66,6 +67,68 @@ pub enum DeclarationKind {
|
|||||||
pub trait CompilerPhase: std::fmt::Debug + 'static {
|
pub trait CompilerPhase: std::fmt::Debug + 'static {
|
||||||
type Metadata: std::fmt::Debug + Clone + PartialEq;
|
type Metadata: std::fmt::Debug + Clone + PartialEq;
|
||||||
type LocalAddress: std::fmt::Debug + Clone + Copy + PartialEq + Eq + std::hash::Hash;
|
type LocalAddress: std::fmt::Debug + Clone + Copy + PartialEq + Eq + std::hash::Hash;
|
||||||
|
|
||||||
|
/// Semantic role and address of an identifier (reference vs. declaration).
|
||||||
|
type Binding: std::fmt::Debug + Clone + PartialEq;
|
||||||
|
/// Binding metadata for `def` nodes (captured_by info).
|
||||||
|
type DefInfo: std::fmt::Debug + Clone + PartialEq;
|
||||||
|
/// Target address for `assign` nodes.
|
||||||
|
type AssignInfo: std::fmt::Debug + Clone + PartialEq;
|
||||||
|
/// Closure metadata for `lambda` nodes (upvalues, positional count).
|
||||||
|
type LambdaInfo: std::fmt::Debug + Clone + PartialEq;
|
||||||
|
/// Record field layout for O(1) access.
|
||||||
|
type RecordLayout: std::fmt::Debug + Clone + PartialEq;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Phase-specific binding info types ──────────────────────────────────────────
|
||||||
|
|
||||||
|
/// Semantic role of an identifier after binding.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum IdentifierBinding<L = VirtualId> {
|
||||||
|
/// A variable reference (read access).
|
||||||
|
Reference(Address<L>),
|
||||||
|
/// A variable or parameter declaration (write/bind target).
|
||||||
|
Declaration {
|
||||||
|
addr: Address<L>,
|
||||||
|
kind: DeclarationKind,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Binding metadata attached to `def` nodes.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct DefBinding {
|
||||||
|
/// Identities of lambdas that capture this definition's variable.
|
||||||
|
pub captured_by: Vec<Identity>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Target address attached to `assign` nodes.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct AssignBinding<L = VirtualId> {
|
||||||
|
pub addr: Address<L>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Closure metadata attached to `lambda` nodes.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct LambdaBinding<L = VirtualId> {
|
||||||
|
/// Addresses of captured variables from enclosing scopes.
|
||||||
|
pub upvalues: Vec<Address<L>>,
|
||||||
|
/// Number of positional parameters (None = variadic).
|
||||||
|
pub positional_count: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── SyntaxPhase ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/// The initial phase produced by the parser. All annotation slots are `()`.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
|
pub struct SyntaxPhase;
|
||||||
|
impl CompilerPhase for SyntaxPhase {
|
||||||
|
type Metadata = ();
|
||||||
|
type LocalAddress = ();
|
||||||
|
type Binding = ();
|
||||||
|
type DefInfo = ();
|
||||||
|
type AssignInfo = ();
|
||||||
|
type LambdaInfo = ();
|
||||||
|
type RecordLayout = ();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
@@ -73,6 +136,11 @@ pub struct BoundPhase;
|
|||||||
impl CompilerPhase for BoundPhase {
|
impl CompilerPhase for BoundPhase {
|
||||||
type Metadata = ();
|
type Metadata = ();
|
||||||
type LocalAddress = VirtualId;
|
type LocalAddress = VirtualId;
|
||||||
|
type Binding = IdentifierBinding<VirtualId>;
|
||||||
|
type DefInfo = DefBinding;
|
||||||
|
type AssignInfo = AssignBinding<VirtualId>;
|
||||||
|
type LambdaInfo = LambdaBinding<VirtualId>;
|
||||||
|
type RecordLayout = Arc<crate::ast::types::RecordLayout>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
@@ -80,6 +148,11 @@ pub struct TypedPhase;
|
|||||||
impl CompilerPhase for TypedPhase {
|
impl CompilerPhase for TypedPhase {
|
||||||
type Metadata = StaticType;
|
type Metadata = StaticType;
|
||||||
type LocalAddress = VirtualId;
|
type LocalAddress = VirtualId;
|
||||||
|
type Binding = IdentifierBinding<VirtualId>;
|
||||||
|
type DefInfo = DefBinding;
|
||||||
|
type AssignInfo = AssignBinding<VirtualId>;
|
||||||
|
type LambdaInfo = LambdaBinding<VirtualId>;
|
||||||
|
type RecordLayout = Arc<crate::ast::types::RecordLayout>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
@@ -87,6 +160,11 @@ pub struct AnalyzedPhase;
|
|||||||
impl CompilerPhase for AnalyzedPhase {
|
impl CompilerPhase for AnalyzedPhase {
|
||||||
type Metadata = NodeMetrics;
|
type Metadata = NodeMetrics;
|
||||||
type LocalAddress = VirtualId;
|
type LocalAddress = VirtualId;
|
||||||
|
type Binding = IdentifierBinding<VirtualId>;
|
||||||
|
type DefInfo = DefBinding;
|
||||||
|
type AssignInfo = AssignBinding<VirtualId>;
|
||||||
|
type LambdaInfo = LambdaBinding<VirtualId>;
|
||||||
|
type RecordLayout = Arc<crate::ast::types::RecordLayout>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
@@ -94,6 +172,11 @@ pub struct RuntimePhase;
|
|||||||
impl CompilerPhase for RuntimePhase {
|
impl CompilerPhase for RuntimePhase {
|
||||||
type Metadata = RuntimeMetadata;
|
type Metadata = RuntimeMetadata;
|
||||||
type LocalAddress = StackOffset;
|
type LocalAddress = StackOffset;
|
||||||
|
type Binding = IdentifierBinding<StackOffset>;
|
||||||
|
type DefInfo = DefBinding;
|
||||||
|
type AssignInfo = AssignBinding<StackOffset>;
|
||||||
|
type LambdaInfo = LambdaBinding<StackOffset>;
|
||||||
|
type RecordLayout = Arc<crate::ast::types::RecordLayout>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A bound AST node, decorated with phase-specific information P.
|
/// A bound AST node, decorated with phase-specific information P.
|
||||||
@@ -350,3 +433,235 @@ impl<P: CompilerPhase> BoundKind<P> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── NodeKind<P>: Unified AST node kind ─────────────────────────────────────────
|
||||||
|
|
||||||
|
/// A key-value pair in a record literal: `(key_node, value_node)`.
|
||||||
|
pub type RecordFieldPair<P> = (Rc<Node<P>>, Rc<Node<P>>);
|
||||||
|
|
||||||
|
/// Unified AST node kind, replacing both `SyntaxKind` and `BoundKind<P>`.
|
||||||
|
///
|
||||||
|
/// The structure is always consistent with the syntax the user wrote.
|
||||||
|
/// Phase-specific information is carried in `P::*` annotation slots,
|
||||||
|
/// which are `()` in `SyntaxPhase` and concrete types in later phases.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum NodeKind<P: CompilerPhase = SyntaxPhase> {
|
||||||
|
Nop,
|
||||||
|
Constant(Value),
|
||||||
|
Identifier {
|
||||||
|
symbol: Symbol,
|
||||||
|
binding: P::Binding,
|
||||||
|
},
|
||||||
|
FieldAccessor(crate::ast::types::Keyword),
|
||||||
|
If {
|
||||||
|
cond: Rc<Node<P>>,
|
||||||
|
then_br: Rc<Node<P>>,
|
||||||
|
else_br: Option<Rc<Node<P>>>,
|
||||||
|
},
|
||||||
|
Def {
|
||||||
|
pattern: Rc<Node<P>>,
|
||||||
|
value: Rc<Node<P>>,
|
||||||
|
info: P::DefInfo,
|
||||||
|
},
|
||||||
|
Assign {
|
||||||
|
target: Rc<Node<P>>,
|
||||||
|
value: Rc<Node<P>>,
|
||||||
|
info: P::AssignInfo,
|
||||||
|
},
|
||||||
|
Lambda {
|
||||||
|
params: Rc<Node<P>>,
|
||||||
|
body: Rc<Node<P>>,
|
||||||
|
info: P::LambdaInfo,
|
||||||
|
},
|
||||||
|
Call {
|
||||||
|
callee: Rc<Node<P>>,
|
||||||
|
args: Rc<Node<P>>,
|
||||||
|
},
|
||||||
|
Again {
|
||||||
|
args: Rc<Node<P>>,
|
||||||
|
},
|
||||||
|
Pipe {
|
||||||
|
inputs: Vec<Rc<Node<P>>>,
|
||||||
|
lambda: Rc<Node<P>>,
|
||||||
|
},
|
||||||
|
Block {
|
||||||
|
exprs: Vec<Rc<Node<P>>>,
|
||||||
|
},
|
||||||
|
Tuple {
|
||||||
|
elements: Vec<Rc<Node<P>>>,
|
||||||
|
},
|
||||||
|
Record {
|
||||||
|
fields: Vec<RecordFieldPair<P>>,
|
||||||
|
layout: P::RecordLayout,
|
||||||
|
},
|
||||||
|
/// Macro declaration (only valid in `SyntaxPhase`).
|
||||||
|
MacroDecl {
|
||||||
|
name: Symbol,
|
||||||
|
params: Rc<Node<P>>,
|
||||||
|
body: Rc<Node<P>>,
|
||||||
|
},
|
||||||
|
/// Quasiquote template (only valid in `SyntaxPhase`).
|
||||||
|
Template(Rc<Node<P>>),
|
||||||
|
/// Unquote placeholder inside a template (only valid in `SyntaxPhase`).
|
||||||
|
Placeholder(Rc<Node<P>>),
|
||||||
|
/// Splice placeholder inside a template (only valid in `SyntaxPhase`).
|
||||||
|
Splice(Rc<Node<P>>),
|
||||||
|
/// Expanded macro call, preserving the original call for debugging.
|
||||||
|
Expansion {
|
||||||
|
original_call: Rc<Node<SyntaxPhase>>,
|
||||||
|
expanded: Rc<Node<P>>,
|
||||||
|
},
|
||||||
|
Error,
|
||||||
|
Extension(Box<dyn BoundExtension<P>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: CompilerPhase> Clone for NodeKind<P> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
match self {
|
||||||
|
NodeKind::Nop => NodeKind::Nop,
|
||||||
|
NodeKind::Constant(v) => NodeKind::Constant(v.clone()),
|
||||||
|
NodeKind::Identifier { symbol, binding } => NodeKind::Identifier {
|
||||||
|
symbol: symbol.clone(),
|
||||||
|
binding: binding.clone(),
|
||||||
|
},
|
||||||
|
NodeKind::FieldAccessor(k) => NodeKind::FieldAccessor(*k),
|
||||||
|
NodeKind::If { cond, then_br, else_br } => NodeKind::If {
|
||||||
|
cond: cond.clone(),
|
||||||
|
then_br: then_br.clone(),
|
||||||
|
else_br: else_br.clone(),
|
||||||
|
},
|
||||||
|
NodeKind::Def { pattern, value, info } => NodeKind::Def {
|
||||||
|
pattern: pattern.clone(),
|
||||||
|
value: value.clone(),
|
||||||
|
info: info.clone(),
|
||||||
|
},
|
||||||
|
NodeKind::Assign { target, value, info } => NodeKind::Assign {
|
||||||
|
target: target.clone(),
|
||||||
|
value: value.clone(),
|
||||||
|
info: info.clone(),
|
||||||
|
},
|
||||||
|
NodeKind::Lambda { params, body, info } => NodeKind::Lambda {
|
||||||
|
params: params.clone(),
|
||||||
|
body: body.clone(),
|
||||||
|
info: info.clone(),
|
||||||
|
},
|
||||||
|
NodeKind::Call { callee, args } => NodeKind::Call {
|
||||||
|
callee: callee.clone(),
|
||||||
|
args: args.clone(),
|
||||||
|
},
|
||||||
|
NodeKind::Again { args } => NodeKind::Again { args: args.clone() },
|
||||||
|
NodeKind::Pipe { inputs, lambda } => NodeKind::Pipe {
|
||||||
|
inputs: inputs.clone(),
|
||||||
|
lambda: lambda.clone(),
|
||||||
|
},
|
||||||
|
NodeKind::Block { exprs } => NodeKind::Block { exprs: exprs.clone() },
|
||||||
|
NodeKind::Tuple { elements } => NodeKind::Tuple { elements: elements.clone() },
|
||||||
|
NodeKind::Record { fields, layout } => NodeKind::Record {
|
||||||
|
fields: fields.clone(),
|
||||||
|
layout: layout.clone(),
|
||||||
|
},
|
||||||
|
NodeKind::MacroDecl { name, params, body } => NodeKind::MacroDecl {
|
||||||
|
name: name.clone(),
|
||||||
|
params: params.clone(),
|
||||||
|
body: body.clone(),
|
||||||
|
},
|
||||||
|
NodeKind::Template(inner) => NodeKind::Template(inner.clone()),
|
||||||
|
NodeKind::Placeholder(inner) => NodeKind::Placeholder(inner.clone()),
|
||||||
|
NodeKind::Splice(inner) => NodeKind::Splice(inner.clone()),
|
||||||
|
NodeKind::Expansion { original_call, expanded } => NodeKind::Expansion {
|
||||||
|
original_call: original_call.clone(),
|
||||||
|
expanded: expanded.clone(),
|
||||||
|
},
|
||||||
|
NodeKind::Error => NodeKind::Error,
|
||||||
|
NodeKind::Extension(ext) => NodeKind::Extension(ext.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: CompilerPhase> PartialEq for NodeKind<P> {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
match (self, other) {
|
||||||
|
(NodeKind::Nop, NodeKind::Nop) => true,
|
||||||
|
(NodeKind::Constant(a), NodeKind::Constant(b)) => a == b,
|
||||||
|
(NodeKind::Identifier { symbol: sa, binding: ba }, NodeKind::Identifier { symbol: sb, binding: bb }) => {
|
||||||
|
sa == sb && ba == bb
|
||||||
|
}
|
||||||
|
(NodeKind::FieldAccessor(a), NodeKind::FieldAccessor(b)) => a == b,
|
||||||
|
(NodeKind::If { cond: ca, then_br: ta, else_br: ea }, NodeKind::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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(NodeKind::Def { pattern: pa, value: va, info: ia }, NodeKind::Def { pattern: pb, value: vb, info: ib }) => {
|
||||||
|
Rc::ptr_eq(pa, pb) && Rc::ptr_eq(va, vb) && ia == ib
|
||||||
|
}
|
||||||
|
(NodeKind::Assign { target: ta, value: va, info: ia }, NodeKind::Assign { target: tb, value: vb, info: ib }) => {
|
||||||
|
Rc::ptr_eq(ta, tb) && Rc::ptr_eq(va, vb) && ia == ib
|
||||||
|
}
|
||||||
|
(NodeKind::Lambda { params: pa, body: ba, info: ia }, NodeKind::Lambda { params: pb, body: bb, info: ib }) => {
|
||||||
|
Rc::ptr_eq(pa, pb) && Rc::ptr_eq(ba, bb) && ia == ib
|
||||||
|
}
|
||||||
|
(NodeKind::Call { callee: ca, args: aa }, NodeKind::Call { callee: cb, args: ab }) => {
|
||||||
|
Rc::ptr_eq(ca, cb) && Rc::ptr_eq(aa, ab)
|
||||||
|
}
|
||||||
|
(NodeKind::Again { args: aa }, NodeKind::Again { args: ab }) => Rc::ptr_eq(aa, ab),
|
||||||
|
(NodeKind::Pipe { inputs: ia, lambda: la }, NodeKind::Pipe { inputs: ib, lambda: lb }) => {
|
||||||
|
ia.len() == ib.len()
|
||||||
|
&& ia.iter().zip(ib.iter()).all(|(a, b)| Rc::ptr_eq(a, b))
|
||||||
|
&& Rc::ptr_eq(la, lb)
|
||||||
|
}
|
||||||
|
(NodeKind::Block { exprs: ea }, NodeKind::Block { exprs: eb }) => {
|
||||||
|
ea.len() == eb.len() && ea.iter().zip(eb.iter()).all(|(a, b)| Rc::ptr_eq(a, b))
|
||||||
|
}
|
||||||
|
(NodeKind::Tuple { elements: ea }, NodeKind::Tuple { elements: eb }) => {
|
||||||
|
ea.len() == eb.len() && ea.iter().zip(eb.iter()).all(|(a, b)| Rc::ptr_eq(a, b))
|
||||||
|
}
|
||||||
|
(NodeKind::Record { fields: fa, layout: la }, NodeKind::Record { fields: fb, layout: lb }) => {
|
||||||
|
la == lb
|
||||||
|
&& fa.len() == fb.len()
|
||||||
|
&& fa.iter().zip(fb.iter()).all(|((ka, va), (kb, vb))| Rc::ptr_eq(ka, kb) && Rc::ptr_eq(va, vb))
|
||||||
|
}
|
||||||
|
(NodeKind::MacroDecl { name: na, params: pa, body: ba }, NodeKind::MacroDecl { name: nb, params: pb, body: bb }) => {
|
||||||
|
na == nb && Rc::ptr_eq(pa, pb) && Rc::ptr_eq(ba, bb)
|
||||||
|
}
|
||||||
|
(NodeKind::Template(a), NodeKind::Template(b)) => Rc::ptr_eq(a, b),
|
||||||
|
(NodeKind::Placeholder(a), NodeKind::Placeholder(b)) => Rc::ptr_eq(a, b),
|
||||||
|
(NodeKind::Splice(a), NodeKind::Splice(b)) => Rc::ptr_eq(a, b),
|
||||||
|
(NodeKind::Expansion { original_call: ca, expanded: ea }, NodeKind::Expansion { original_call: cb, expanded: eb }) => {
|
||||||
|
Rc::ptr_eq(ca, cb) && Rc::ptr_eq(ea, eb)
|
||||||
|
}
|
||||||
|
(NodeKind::Error, NodeKind::Error) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: CompilerPhase> NodeKind<P> {
|
||||||
|
pub fn display_name(&self) -> String {
|
||||||
|
match self {
|
||||||
|
NodeKind::Nop => "NOP".to_string(),
|
||||||
|
NodeKind::Constant(v) => format!("CONST({})", v),
|
||||||
|
NodeKind::Identifier { symbol, .. } => format!("ID({})", symbol.name),
|
||||||
|
NodeKind::FieldAccessor(k) => format!("FIELD_ACCESSOR(.{})", k.name()),
|
||||||
|
NodeKind::If { .. } => "IF".to_string(),
|
||||||
|
NodeKind::Def { .. } => "DEF".to_string(),
|
||||||
|
NodeKind::Assign { .. } => "ASSIGN".to_string(),
|
||||||
|
NodeKind::Lambda { .. } => "LAMBDA".to_string(),
|
||||||
|
NodeKind::Call { .. } => "CALL".to_string(),
|
||||||
|
NodeKind::Again { .. } => "AGAIN".to_string(),
|
||||||
|
NodeKind::Pipe { .. } => "PIPE".to_string(),
|
||||||
|
NodeKind::Block { .. } => "BLOCK".to_string(),
|
||||||
|
NodeKind::Tuple { elements } => format!("TUPLE({})", elements.len()),
|
||||||
|
NodeKind::Record { fields, .. } => format!("RECORD({})", fields.len()),
|
||||||
|
NodeKind::MacroDecl { name, .. } => format!("MACRO({})", name.name),
|
||||||
|
NodeKind::Template(_) => "TEMPLATE".to_string(),
|
||||||
|
NodeKind::Placeholder(_) => "PLACEHOLDER".to_string(),
|
||||||
|
NodeKind::Splice(_) => "SPLICE".to_string(),
|
||||||
|
NodeKind::Expansion { .. } => "EXPANSION".to_string(),
|
||||||
|
NodeKind::Extension(ext) => ext.display_name(),
|
||||||
|
NodeKind::Error => "ERROR".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user