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:
2026-03-21 12:43:46 +01:00
parent e4be31729e
commit 72244d9da7
+315
View File
@@ -1,6 +1,7 @@
use crate::ast::nodes::Symbol;
use crate::ast::types::{Identity, StaticType, Value};
use std::rc::Rc;
use std::sync::Arc;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct VirtualId(pub u32);
@@ -66,6 +67,68 @@ pub enum DeclarationKind {
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;
/// 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)]
@@ -73,6 +136,11 @@ pub struct BoundPhase;
impl CompilerPhase for BoundPhase {
type Metadata = ();
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)]
@@ -80,6 +148,11 @@ pub struct TypedPhase;
impl CompilerPhase for TypedPhase {
type Metadata = StaticType;
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)]
@@ -87,6 +160,11 @@ pub struct AnalyzedPhase;
impl CompilerPhase for AnalyzedPhase {
type Metadata = NodeMetrics;
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)]
@@ -94,6 +172,11 @@ pub struct RuntimePhase;
impl CompilerPhase for RuntimePhase {
type Metadata = RuntimeMetadata;
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.
@@ -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(),
}
}
}