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::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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user