diff --git a/Cargo.lock b/Cargo.lock index 4ca9fedf9..a08224c12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -493,6 +493,12 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "indextree" +version = "4.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497f036ac2fae75c34224648a77802e5dd4e9cfb56f4713ab6b12b7160a0523b" + [[package]] name = "io-lifetimes" version = "1.0.5" @@ -838,6 +844,14 @@ dependencies = [ "oxc_parser", ] +[[package]] +name = "oxc_semantic" +version = "0.0.0" +dependencies = [ + "indextree", + "oxc_ast", +] + [[package]] name = "percent-encoding" version = "2.2.0" diff --git a/Cargo.toml b/Cargo.toml index a2ed42e4d..cd4adadfb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -27,6 +27,7 @@ serde_json = "1.0.93" thiserror = "1.0.38" jemallocator = "0.5.0" clap = "4.1.4" +indextree = "4.5.0" [profile.release] lto = "fat" diff --git a/crates/oxc_ast/src/lib.rs b/crates/oxc_ast/src/lib.rs index ff09ad1c0..86477b13b 100644 --- a/crates/oxc_ast/src/lib.rs +++ b/crates/oxc_ast/src/lib.rs @@ -15,6 +15,7 @@ pub mod span; pub mod syntax_directed_operations; pub mod visit; +pub use ast_kind::AstKind; pub use num_bigint::BigUint; pub use crate::ast_builder::*; diff --git a/crates/oxc_semantic/Cargo.toml b/crates/oxc_semantic/Cargo.toml new file mode 100644 index 000000000..3c2e3b4ec --- /dev/null +++ b/crates/oxc_semantic/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "oxc_semantic" +authors.workspace = true +description.workspace = true +edition.workspace = true +homepage.workspace = true +keywords.workspace = true +license.workspace = true +repository.workspace = true +version.workspace = true + +[dependencies] +oxc_ast = { path = "../oxc_ast" } + +indextree = { workspace = true } diff --git a/crates/oxc_semantic/src/Cargo.toml b/crates/oxc_semantic/src/Cargo.toml new file mode 100644 index 000000000..b96ce8b96 --- /dev/null +++ b/crates/oxc_semantic/src/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "oxc_printer" +authors.workspace = true +description.workspace = true +edition.workspace = true +homepage.workspace = true +keywords.workspace = true +license.workspace = true +repository.workspace = true +version.workspace = true diff --git a/crates/oxc_semantic/src/builder.rs b/crates/oxc_semantic/src/builder.rs new file mode 100644 index 000000000..f30830fb7 --- /dev/null +++ b/crates/oxc_semantic/src/builder.rs @@ -0,0 +1,56 @@ +//! Semantic Builder +//! This builds: +//! * The untyped and flattened ast nodes into an indextree + +use oxc_ast::{ast::Program, visit::Visit, AstKind}; + +use crate::{ + node::{AstNodeId, AstNodes, SemanticNode}, + Semantic, +}; + +#[derive(Debug)] +pub struct SemanticBuilder<'a> { + nodes: AstNodes<'a>, + current_node_id: AstNodeId, +} + +impl<'a> SemanticBuilder<'a> { + #[must_use] + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + let mut nodes = AstNodes::default(); + let semantic_node = SemanticNode::new(AstKind::Root); + let current_node_id = nodes.new_node(semantic_node).into(); + Self { nodes, current_node_id } + } + + #[must_use] + pub fn build(mut self, program: &'a Program<'a>) -> Semantic<'a> { + // AST pass + self.visit_program(program); + Semantic { nodes: self.nodes } + } + + fn create_ast_node(&mut self, kind: AstKind<'a>) { + let ast_node = SemanticNode::new(kind); + let node_id = self.nodes.new_node(ast_node); + self.current_node_id.append(node_id, &mut self.nodes); + self.current_node_id = node_id.into(); + } + + fn pop_ast_node(&mut self) { + self.current_node_id = + self.nodes[self.current_node_id.indextree_id()].parent().unwrap().into(); + } +} + +impl<'a> Visit<'a> for SemanticBuilder<'a> { + fn enter_node(&mut self, kind: AstKind<'a>) { + self.create_ast_node(kind); + } + + fn leave_node(&mut self, _kind: AstKind<'a>) { + self.pop_ast_node(); + } +} diff --git a/crates/oxc_semantic/src/lib.rs b/crates/oxc_semantic/src/lib.rs new file mode 100644 index 000000000..ddfaa1374 --- /dev/null +++ b/crates/oxc_semantic/src/lib.rs @@ -0,0 +1,16 @@ +mod builder; +mod node; + +pub use builder::SemanticBuilder; +use node::AstNodes; + +pub struct Semantic<'a> { + nodes: AstNodes<'a>, +} + +impl<'a> Semantic<'a> { + #[must_use] + pub const fn nodes(&self) -> &AstNodes<'a> { + &self.nodes + } +} diff --git a/crates/oxc_semantic/src/node/id.rs b/crates/oxc_semantic/src/node/id.rs new file mode 100644 index 000000000..ede3a7934 --- /dev/null +++ b/crates/oxc_semantic/src/node/id.rs @@ -0,0 +1,38 @@ +use std::ops::Deref; + +use indextree::NodeId; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub struct AstNodeId(NodeId); + +impl AstNodeId { + #[must_use] + pub const fn new(node_id: NodeId) -> Self { + Self(node_id) + } + + #[must_use] + pub const fn indextree_id(&self) -> NodeId { + self.0 + } +} + +impl From for AstNodeId { + fn from(node_id: NodeId) -> Self { + Self(node_id) + } +} + +impl From for NodeId { + fn from(ast_node_id: AstNodeId) -> Self { + ast_node_id.indextree_id() + } +} + +impl Deref for AstNodeId { + type Target = NodeId; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} diff --git a/crates/oxc_semantic/src/node/mod.rs b/crates/oxc_semantic/src/node/mod.rs new file mode 100644 index 000000000..73edd9aeb --- /dev/null +++ b/crates/oxc_semantic/src/node/mod.rs @@ -0,0 +1,27 @@ +mod id; +mod tree; + +pub use id::AstNodeId; +use oxc_ast::AstKind; +pub use tree::AstNodes; + +/// Indextree node containing a semantic node +pub type AstNode<'a> = indextree::Node>; + +/// Semantic node contains all the semantic information about an ast node. +#[derive(Debug, Clone, Copy)] +pub struct SemanticNode<'a> { + /// A pointer to the ast node, which resides in the `bumpalo` memory arena. + kind: AstKind<'a>, +} + +impl<'a> SemanticNode<'a> { + #[must_use] + pub const fn new(kind: AstKind<'a>) -> Self { + Self { kind } + } + + pub const fn kind(&self) -> AstKind<'a> { + self.kind + } +} diff --git a/crates/oxc_semantic/src/node/tree.rs b/crates/oxc_semantic/src/node/tree.rs new file mode 100644 index 000000000..55d8982e4 --- /dev/null +++ b/crates/oxc_semantic/src/node/tree.rs @@ -0,0 +1,56 @@ +use std::ops::{Deref, DerefMut, Index, IndexMut}; + +use indextree::{Arena, NodeId}; + +use super::{AstNode, AstNodeId, SemanticNode}; + +/// Untyped AST nodes flattened into an indextree +#[derive(Debug, Default)] +pub struct AstNodes<'a> { + /// The memory storage of the indextree is backed by a vector, + /// which allows for efficient traversal. + /// This also allows for parallel traversal by using `rayon`. + nodes: Arena>, +} + +impl<'a> Index for AstNodes<'a> { + type Output = AstNode<'a>; + + fn index(&self, id: NodeId) -> &Self::Output { + &self.nodes[id] + } +} + +impl<'a> IndexMut for AstNodes<'a> { + fn index_mut(&mut self, id: NodeId) -> &mut AstNode<'a> { + &mut self.nodes[id] + } +} + +impl<'a> Index for AstNodes<'a> { + type Output = SemanticNode<'a>; + + fn index(&self, id: AstNodeId) -> &Self::Output { + self.nodes[id.indextree_id()].get() + } +} + +impl<'a> IndexMut for AstNodes<'a> { + fn index_mut(&mut self, id: AstNodeId) -> &mut SemanticNode<'a> { + self.nodes[id.indextree_id()].get_mut() + } +} + +impl<'a> Deref for AstNodes<'a> { + type Target = Arena>; + + fn deref(&self) -> &Self::Target { + &self.nodes + } +} + +impl<'a> DerefMut for AstNodes<'a> { + fn deref_mut(&mut self) -> &mut Arena> { + &mut self.nodes + } +}