Skip to content

Commit

Permalink
fix NAPI cursor types and expose cursor.depth (#676)
Browse files Browse the repository at this point in the history
- expose `cursor.depth` API.
- add `cst.Node` union type.
- type check NAPI `index.d.ts`.
- replace `Cursor::find_*()` APIs with `go_to_*()` alternatives.

Fixes #636
  • Loading branch information
OmarTawfik authored Dec 3, 2023
1 parent 8bca40a commit b496d36
Show file tree
Hide file tree
Showing 23 changed files with 297 additions and 337 deletions.
5 changes: 5 additions & 0 deletions .changeset/twelve-pianos-notice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nomicfoundation/slang": minor
---

Fix NAPI `cursor` types and expose `cursor.depth`.
1 change: 1 addition & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"inheritdoc",
"ipfs",
"mkdocs",
"napi",
"nomic",
"nomicfoundation",
"struct",
Expand Down
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
"**/Pipfile.lock": true
},
"triggerTaskOnSave.tasks": {
"Run Rust Analyzer Checks": ["**/*.rs.jinja2"],
"Run Rust Analyzer Checks": ["**/*.jinja2"],
"Validate Solidity Definition": ["crates/solidity/inputs/language/definition/**/*.yml"]
},
"typescript.tsdk": "node_modules/typescript/lib",
Expand Down
86 changes: 33 additions & 53 deletions crates/codegen/parser/runtime/src/cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use std::rc::Rc;

use super::{
cst::{Node, RuleNode, TokenNode},
cst::{Node, RuleNode},
kinds::*,
text_index::{TextIndex, TextRange},
};
Expand Down Expand Up @@ -162,11 +162,7 @@ impl Cursor {
return false;
}

if !self.go_to_first_child() {
return self.go_to_next_non_descendent();
}

true
self.go_to_first_child() || self.go_to_next_non_descendent()
}

/// Attempts to go to current node's next non-descendent.
Expand Down Expand Up @@ -199,6 +195,7 @@ impl Cursor {
return false;
}
}

while self.go_to_last_child() {}

true
Expand Down Expand Up @@ -344,7 +341,7 @@ impl Cursor {

if self.current.child_number > 0 {
if let Some(parent_path_element) = self.path.last() {
let new_child_number = self.current.child_number + 1;
let new_child_number = self.current.child_number - 1;
let new_child = parent_path_element.rule_node.children[new_child_number].clone();

self.current = PathNode {
Expand All @@ -359,62 +356,45 @@ impl Cursor {
false
}

pub fn find_matching<R, F: Fn(&Cursor) -> Option<R>>(&mut self, filter_map: F) -> Option<R> {
while !self.is_completed {
if let Some(result) = filter_map(self) {
return Some(result);
}
self.go_to_next();
}

None
}

/// In contrast to `Iterator::find_*`, this does not consume the first item when found.
fn find_noconsume<F: Fn(&Node) -> Option<R>, R>(&mut self, predicate: F) -> Option<R> {
while !self.is_completed {
match predicate(&self.current.node) {
Some(result) => return Some(result),
_ => {
self.go_to_next();
}
}
}

None
/// Attempts to go to the next token, according to the DFS pre-order traversal.
///
/// Returns `false` if the cursor is finished and at the root.
pub fn go_to_next_token(&mut self) -> bool {
self.go_to_next_matching(|node| matches!(node, Node::Token(_)))
}

/// Finds the first token with either of the given kinds.
/// Attempts to go to the next token with any of the given kinds, according to the DFS pre-order traversal.
///
/// Does not consume the iterator if the first item matches.
pub fn find_token_with_kind(&mut self, kinds: &[TokenKind]) -> Option<Rc<TokenNode>> {
self.find_noconsume(|node| node.as_token_with_kind(kinds).cloned())
/// Returns `false` if the cursor is finished and at the root.
pub fn go_to_next_token_with_kinds(&mut self, kinds: &[TokenKind]) -> bool {
self.go_to_next_matching(
|node| matches!(node, Node::Token(token) if kinds.contains(&token.kind)),
)
}

/// Finds the first token node matching the given predicate.
/// Attempts to go to the next rule, according to the DFS pre-order traversal.
///
/// Does not consume the iterator if the first item matches.
pub fn find_token_matching<F: Fn(&Rc<TokenNode>) -> bool>(
&mut self,
predicate: F,
) -> Option<Rc<TokenNode>> {
self.find_noconsume(|node| node.as_token().filter(|node| predicate(node)).cloned())
/// Returns `false` if the cursor is finished and at the root.
pub fn go_to_next_rule(&mut self) -> bool {
self.go_to_next_matching(|node| matches!(node, Node::Rule(_)))
}

/// Finds the first rule node with either of the given kinds.
/// Attempts to go to the next rule with any of the given kinds, according to the DFS pre-order traversal.
///
/// Does not consume the iterator if the first item matches.
pub fn find_rule_with_kind(&mut self, kinds: &[RuleKind]) -> Option<Rc<RuleNode>> {
self.find_noconsume(|node| node.as_rule_with_kind(kinds).cloned())
/// Returns `false` if the cursor is finished and at the root.
pub fn go_to_next_rule_with_kinds(&mut self, kinds: &[RuleKind]) -> bool {
self.go_to_next_matching(
|node| matches!(node, Node::Rule(rule) if kinds.contains(&rule.kind)),
)
}

/// Finds the first rule node matching the given predicate.
///
/// Does not consume the iterator if the first item matches.
pub fn find_rule_matching<F: Fn(&Rc<RuleNode>) -> bool>(
&mut self,
predicate: F,
) -> Option<Rc<RuleNode>> {
self.find_noconsume(|node| node.as_rule().filter(|node| predicate(node)).cloned())
fn go_to_next_matching(&mut self, pred: impl Fn(&Node) -> bool) -> bool {
while self.go_to_next() {
if pred(&self.current.node) {
return true;
}
}

false
}
}
12 changes: 9 additions & 3 deletions crates/codegen/parser/runtime/src/napi/napi_cst.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ impl RuleNode {
(&self.0.text_len).into()
}

#[napi(ts_return_type = "Array<cst.RuleNode | cst.TokenNode>")]
#[napi(ts_return_type = "Array<cst.Node>")]
pub fn children(&self, env: Env) -> Vec<JsObject> {
self.0
.children
Expand All @@ -52,7 +52,10 @@ impl RuleNode {
}

#[napi(ts_return_type = "cursor.Cursor")]
pub fn create_cursor(&self, text_offset: TextIndex) -> Cursor {
pub fn create_cursor(
&self,
#[napi(ts_arg_type = "text_index.TextIndex")] text_offset: TextIndex,
) -> Cursor {
RustNode::Rule(self.0.clone())
.cursor_with_offset((&text_offset).into())
.into()
Expand Down Expand Up @@ -86,7 +89,10 @@ impl TokenNode {
}

#[napi(ts_return_type = "cursor.Cursor")]
pub fn create_cursor(&self, text_offset: TextIndex) -> Cursor {
pub fn create_cursor(
&self,
#[napi(ts_arg_type = "text_index.TextIndex")] text_offset: TextIndex,
) -> Cursor {
RustNode::Token(self.0.clone())
.cursor_with_offset((&text_offset).into())
.into()
Expand Down
42 changes: 23 additions & 19 deletions crates/codegen/parser/runtime/src/napi/napi_cursor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ impl Cursor {
self.0.is_completed()
}

#[napi(ts_return_type = "cst.RuleNode | cst.TokenNode")]
#[napi(ts_return_type = "cst.Node")]
pub fn node(&self, env: Env) -> JsObject {
self.0.node().to_js(&env)
}
Expand All @@ -62,6 +62,11 @@ impl Cursor {
(&self.0.text_range()).into()
}

#[napi(getter)]
pub fn depth(&self) -> u32 {
self.0.depth() as u32
}

#[napi(ts_return_type = "Array<cst.RuleNode>")]
pub fn ancestors(&self, env: Env) -> Vec<JsObject> {
self.0
Expand Down Expand Up @@ -115,30 +120,29 @@ impl Cursor {
self.0.go_to_previous_sibling()
}

// TODO: find_matching (taking JS function)
#[napi(ts_return_type = "cst.TokenNode | null")]
pub fn find_token_with_kind(
#[napi]
pub fn go_to_next_token(&mut self) -> bool {
self.0.go_to_next_token()
}

#[napi]
pub fn go_to_next_token_with_kinds(
&mut self,
#[napi(ts_arg_type = "Array<kinds.TokenKind>")] kinds: Vec<TokenKind>,
env: Env,
) -> Option<JsObject> {
self.0
.find_token_with_kind(&kinds[..])
.map(|token_node| token_node.to_js(&env))
) -> bool {
self.0.go_to_next_token_with_kinds(&kinds)
}

// TODO: find_token_matching (taking JS function)
#[napi]
pub fn go_to_next_rule(&mut self) -> bool {
self.0.go_to_next_rule()
}

#[napi(ts_return_type = "cst.RuleNode | null")]
pub fn find_rule_with_kind(
#[napi]
pub fn go_to_next_rule_with_kinds(
&mut self,
#[napi(ts_arg_type = "Array<kinds.RuleKind>")] kinds: Vec<RuleKind>,
env: Env,
) -> Option<JsObject> {
self.0
.find_rule_with_kind(&kinds[..])
.map(|token_node| token_node.to_js(&env))
) -> bool {
self.0.go_to_next_rule_with_kinds(&kinds)
}

// TODO: find_rule_matching (taking JS function)
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ impl From<RustParseOutput> for ParseOutput {

#[napi(namespace = "parse_output")]
impl ParseOutput {
#[napi(ts_return_type = "cst.RuleNode | cst.TokenNode")]
#[napi(ts_return_type = "cst.Node")]
pub fn tree(&self, env: Env) -> napi::JsObject {
self.0.tree().to_js(&env)
}
Expand Down
12 changes: 7 additions & 5 deletions crates/infra/cli/src/toolchains/napi/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,19 +87,21 @@ struct LicenseHeaderTemplate {

fn process_generated_files(napi_output: &NapiCliOutput) -> Result<()> {
let templates_dir =
CargoWorkspace::locate_source_crate("infra_cli")?.join("src/toolchains/napi");
let mut codegen = Codegen::read_write(&templates_dir)?;
CargoWorkspace::locate_source_crate("solidity_npm_package")?.join("templates");

let template_path = templates_dir.join("napi_license_header.js.jinja2");
let mut codegen = Codegen::read_write(&templates_dir)?;

for source in &napi_output.source_files {
let file_name = source.unwrap_name();
let contents = source.read_to_string()?;
let destination = NapiResolver::generated_dir().join(source.unwrap_name());

let destination_path = NapiResolver::generated_dir().join(file_name);
let template_path = templates_dir.join(format!("{file_name}.jinja2"));

codegen.render(
LicenseHeaderTemplate { contents },
&template_path,
destination,
destination_path,
)?;
}

Expand Down
Loading

0 comments on commit b496d36

Please sign in to comment.