From dc95fe041dd6b309cd4f4baa3ee54a798cebb319 Mon Sep 17 00:00:00 2001 From: Josh Holtrop Date: Wed, 2 Sep 2026 21:00:47 -0400 Subject: [PATCH] Add node_id() to C++ and D tree node handles --- CHANGELOG.md | 8 ++++++++ assets/parser.d.erb | 12 ++++++++++++ doc/user_guide.md | 7 ++++--- lib/propane/generator.rb | 9 ++++++--- spec/test_optional_rule_component_tree.d | 1 + spec/test_tree.cpp | 3 +++ 6 files changed, 34 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fbb3c9a..a3d53c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## v5.1.0 + +### New Features + +- Add a `node_id()` accessor to the C++ and D tree node handle types, for node + identity comparison. This matches the existing `p_node_id()` macro (C) and + `node_id()` method (Rust). + ## v5.0.0 ### New Features diff --git a/assets/parser.d.erb b/assets/parser.d.erb index 8b7cc8e..634aeac 100644 --- a/assets/parser.d.erb +++ b/assets/parser.d.erb @@ -149,6 +149,12 @@ public struct <%= @grammar.tree_prefix %>Token<%= @grammar.tree_suffix %> return __id != 0u; } + /** Return the node ID (for identity comparison). */ + @property <%= @grammar.prefix %>node_id_t node_id() + { + return __id; + } + /** Access the underlying node record (token, pvalue, and user fields). */ @property ref <%= @grammar.prefix %>node_data_t __node() { @@ -177,6 +183,12 @@ public struct <%= @grammar.tree_prefix %><%= rule_set.name %><%= @grammar.tree_s return __id != 0u; } + /** Return the node ID (for identity comparison). */ + @property <%= @grammar.prefix %>node_id_t node_id() + { + return __id; + } + /** Text position of the first code point spanned by this node. */ @property <%= @grammar.prefix %>position_t position() { diff --git a/doc/user_guide.md b/doc/user_guide.md index 6971d2e..ccf5b0c 100644 --- a/doc/user_guide.md +++ b/doc/user_guide.md @@ -349,10 +349,11 @@ accessors on a node handle: for token payload and user fields), and `p_node_id(node)` (for identity comparison). * C++: handle methods called with `()`, e.g. `node.field()`, `node.valid()`, - `node.position()`, `node.token()`, `node.pvalue()`, and `node.data()`. The - C-style functions and macros above are also available. + `node.position()`, `node.token()`, `node.pvalue()`, `node.data()`, and + `node.node_id()` (for identity comparison). The C-style functions and + macros above are also available. * D: `@property` accessors, e.g. `node.field`, `node.valid`, `node.position`, - `node.token`, `node.pvalue`. + `node.token`, `node.pvalue`, and `node.node_id` (for identity comparison). * Rust: handle methods called with `()`, e.g. `node.field()`, `node.valid()`, `node.position()`, `node.end_position()`, `node.n_fields()`, `node.token()`, `node.pvalue()`, `node.data()` (a reference to the node diff --git a/lib/propane/generator.rb b/lib/propane/generator.rb index 4999e68..0067947 100644 --- a/lib/propane/generator.rb +++ b/lib/propane/generator.rb @@ -696,9 +696,10 @@ class Propane end # Generate the C++ tree node handle class declarations for the header. - # Only valid() is defined inline; every other method dereferences the - # context, which is still an incomplete type here, so those are declared - # and defined out of line once the context is complete. + # Only valid() and node_id() are defined inline; every other method + # dereferences the context, which is still an incomplete type here, so + # those are declared and defined out of line once the context is + # complete. def cpp_tree_handle_types_header p = @grammar.prefix out = [] @@ -711,6 +712,7 @@ class Propane out << " #{p}context_t * __context;" out << " #{p}node_id_t __id;" out << " bool valid() const { return __id != 0u; }" + out << " #{p}node_id_t node_id() const { return __id; }" out << " #{p}node_data_t * data() const;" out << " #{p}position_t position() const;" out << " #{p}position_t end_position() const;" @@ -726,6 +728,7 @@ class Propane out << " #{p}context_t * __context;" out << " #{p}node_id_t __id;" out << " bool valid() const { return __id != 0u; }" + out << " #{p}node_id_t node_id() const { return __id; }" out << " #{p}node_data_t * data() const;" out << " #{p}position_t position() const;" out << " #{p}position_t end_position() const;" diff --git a/spec/test_optional_rule_component_tree.d b/spec/test_optional_rule_component_tree.d index fef6ad3..8888f49 100644 --- a/spec/test_optional_rule_component_tree.d +++ b/spec/test_optional_rule_component_tree.d @@ -31,6 +31,7 @@ unittest assert(start.pR3.valid); assert(start.pR.valid); assert(start.pR == start.pR3); + assert_eq(start.pR.node_id, start.pR3.node_id); assert_eq(TOKEN_c, start.pR.pToken1.token); p_context_delete(context); diff --git a/spec/test_tree.cpp b/spec/test_tree.cpp index cf0fa5a..fcc5707 100644 --- a/spec/test_tree.cpp +++ b/spec/test_tree.cpp @@ -13,6 +13,8 @@ int main() assert(start.pItems1().valid()); assert(start.pItems().valid()); Items items = start.pItems(); + assert_ne(0u, items.node_id()); + assert_eq(start.pItems().node_id(), items.node_id()); assert(items.pItem().valid()); assert(items.pItem().pToken1().valid()); assert_eq(TOKEN_a, items.pItem().pToken1().token()); @@ -40,6 +42,7 @@ int main() assert_eq(P_SUCCESS, p_parse(context)); start = p_result(context); assert(!start.pItems().valid()); + assert_eq(0u, start.pItems().node_id()); p_context_delete(context);