Skip to content

Commit

Permalink
Remove the CST Visitor API (#665)
Browse files Browse the repository at this point in the history
We discussed internally that the Visitor API for (dynamically typed) CST
is a lot less useful now that we have the `Iterator` interface
implemented in Rust (and hopefully
[`Iterator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators#iterators)
protocol in TS in the future), so let's remove the CST Visitor API
altogether.

The Visitor
[pattern](https://rust-unofficial.github.io/patterns/patterns/behavioural/visitor.html)
will be a lot useful once we have typed, heterogenous data such as the
new AST (#634), so let's wait until then with designing and exposing
such interface.
  • Loading branch information
Xanewok authored Nov 20, 2023
1 parent bdff92b commit 4b5f8b4
Show file tree
Hide file tree
Showing 28 changed files with 184 additions and 508 deletions.
5 changes: 5 additions & 0 deletions .changeset/purple-parents-teach.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nomicfoundation/slang": minor
---

Remove the CST Visitor API in favor of the Cursor API
1 change: 0 additions & 1 deletion crates/codegen/parser/generator/src/code_generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,6 @@ impl CodeGenerator {
"parse_error.rs",
"parse_output.rs",
"text_index.rs",
"visitor.rs",
"napi/napi_cst.rs",
"napi/napi_cursor.rs",
"napi/napi_parse_error.rs",
Expand Down
8 changes: 8 additions & 0 deletions crates/codegen/parser/runtime/src/cst.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ impl Node {
}
}

/// Returns a slice of the children (not all descendants) of this node.
pub fn children(&self) -> &[Node] {
match self {
Self::Rule(node) => &node.children,
Self::Token(_) => &[],
}
}

pub fn create_cursor(&self, text_offset: TextIndex) -> Cursor {
Cursor::new(self.clone(), text_offset)
}
Expand Down
13 changes: 8 additions & 5 deletions crates/codegen/parser/runtime/src/cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,14 @@ impl Cursor {
self.current.text_range()
}

pub fn path_rule_nodes(&self) -> Vec<Rc<RuleNode>> {
self.path
.iter()
.map(|path_element| path_element.rule_node.clone())
.collect()
/// Returns the depth of the current node in the CST, i.e. the number of ancestors.
pub fn depth(&self) -> usize {
self.path.len()
}

/// Returns an iterator over the current node's ancestors, starting from the cursor root node.
pub fn ancestors(&self) -> impl Iterator<Item = &Rc<RuleNode>> {
self.path.iter().map(|elem| &elem.rule_node)
}

/// Attempts to go to current node's next one, according to the DFS pre-order traversal.
Expand Down
1 change: 0 additions & 1 deletion crates/codegen/parser/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ pub(crate) mod lexer;
pub mod parse_error;
pub mod parse_output;
pub mod text_index;
pub mod visitor;

#[cfg(feature = "slang_napi_interfaces")]
pub mod napi;
3 changes: 1 addition & 2 deletions crates/codegen/parser/runtime/src/napi/napi_cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,7 @@ impl Cursor {
#[napi(ts_return_type = "Array<cst.RuleNode>")]
pub fn path_rule_nodes(&self, env: Env) -> Vec<JsObject> {
self.0
.path_rule_nodes()
.iter()
.ancestors()
.map(|rust_rule_node| rust_rule_node.to_js(&env))
.collect()
}
Expand Down
1 change: 0 additions & 1 deletion crates/codegen/parser/runtime/src/templates/mod.rs.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ pub(crate) mod lexer;
pub mod parse_error;
pub mod parse_output;
pub mod text_index;
pub mod visitor;

#[cfg(feature = "slang_napi_interfaces")]
pub mod napi;
75 changes: 0 additions & 75 deletions crates/codegen/parser/runtime/src/visitor.rs
Original file line number Diff line number Diff line change
@@ -1,76 +1 @@
use std::ops::ControlFlow;
use std::rc::Rc;

use super::{cst::*, cursor::Cursor};

/// A Visitor pattern for traversing the CST.
///
/// The trait supports fallible iteration, i.e. the visitor can early return an error from the visit.
pub trait Visitor<E> {
/// Called when the [`Visitor`] enters a [`RuleNode`].
fn rule_enter(
&mut self,
_node: &Rc<RuleNode>,
_cursor: &Cursor,
) -> Result<ControlFlow<(), Step>, E> {
Ok(ControlFlow::Continue(Step::In))
}

/// Called when the [`Visitor`] exits a [`RuleNode`].
fn rule_exit(&mut self, _node: &Rc<RuleNode>, _cursor: &Cursor) -> Result<ControlFlow<()>, E> {
Ok(ControlFlow::Continue(()))
}

/// Called when the [`Visitor`] enters a [`TokenNode`].
fn token(&mut self, _node: &Rc<TokenNode>, _cursor: &Cursor) -> Result<ControlFlow<()>, E> {
Ok(ControlFlow::Continue(()))
}
}

/// Whether the [`Visitor`] should should enter the children of a [`RuleNode`] or not.
pub enum Step {
In,
Over,
}

impl Cursor {
pub fn drive_visitor<E, V: Visitor<E>>(
&mut self,
visitor: &mut V,
) -> Result<ControlFlow<()>, E> {
if self.is_completed() {
return Ok(ControlFlow::Continue(()));
}

loop {
// Node clone is cheap because it's just an enum around an Rc
match self.node() {
Node::Rule(rule_node) => {
match visitor.rule_enter(&rule_node, self)? {
ControlFlow::Break(()) => return Ok(ControlFlow::Break(())),
ControlFlow::Continue(Step::In) => {
if self.go_to_first_child() {
self.drive_visitor(visitor)?;
self.go_to_parent();
}
}
ControlFlow::Continue(Step::Over) => {}
}
if visitor.rule_exit(&rule_node, self)? == ControlFlow::Break(()) {
return Ok(ControlFlow::Break(()));
}
}

Node::Token(token_node) => {
if visitor.token(&token_node, self)? == ControlFlow::Break(()) {
return Ok(ControlFlow::Break(()));
}
}
}

if !self.go_to_next_sibling() {
return Ok(ControlFlow::Continue(()));
}
}
}
}
8 changes: 8 additions & 0 deletions crates/solidity/outputs/cargo/crate/src/generated/cst.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 8 additions & 5 deletions crates/solidity/outputs/cargo/crate/src/generated/cursor.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion crates/solidity/outputs/cargo/crate/src/generated/mod.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

78 changes: 0 additions & 78 deletions crates/solidity/outputs/cargo/crate/src/generated/visitor.rs

This file was deleted.

24 changes: 24 additions & 0 deletions crates/solidity/outputs/cargo/tests/src/doc_examples/cursor_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,30 @@ fn cursor_api_using_iter() -> Result<()> {
return Ok(());
}

#[test]
fn cursor_api_using_iter_combinators() -> Result<()> {
let language = Language::new(Version::parse("0.8.0")?)?;
let parse_output = language.parse(ProductionKind::ContractDefinition, "contract Foo {}");

let cursor = parse_output.create_tree_cursor();

let contract_names: Vec<_> = cursor
.filter_map(|node| {
let node = node.as_rule_with_kind(&[RuleKind::ContractDefinition])?;
let name = node
.children
.iter()
.find_map(|node| node.as_token_with_kind(&[TokenKind::Identifier]))?;

Some(name.text.clone())
})
.collect();

assert_eq!(contract_names, &["Foo"]);

return Ok(());
}

#[test]
fn cursor_as_iter() -> Result<()> {
let language = Language::new(Version::parse("0.8.0")?)?;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
mod cursor_api;
mod simple_contract;
mod visitor_api;

This file was deleted.

8 changes: 8 additions & 0 deletions crates/solidity/outputs/npm/crate/src/generated/cst.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 4b5f8b4

Please sign in to comment.