Rust: add Drop impl for p_context_t

This commit is contained in:
Josh Holtrop 2026-08-21 13:36:36 -04:00
parent 5afb3599f9
commit 450c2f1cff
3 changed files with 42 additions and 27 deletions

View File

@ -297,32 +297,36 @@ pub fn <%= @grammar.prefix %>context_new(input: &[u8]) -> <%= @grammar.prefix %>
context context
} }
/**
* Deinitialize and deallocate lexer/parser context structure.
*
* @param context
* Lexer/parser context structure.
*/
<% free_token_node_used = @grammar.tree && @grammar.free_token_node != "" %> <% free_token_node_used = @grammar.tree && @grammar.free_token_node != "" %>
<% if free_token_node_used %> <% if free_token_node_used %>
/* The context is taken as mutable for the benefit of the free_token_node user impl Drop for <%= @grammar.prefix %>context_t {
* code block below, which is permitted but not required to modify the node it /* Run the free_token_node user code block for every token node in the tree. */
* is freeing. */ fn drop(&mut self) {
#[allow(unused_mut)] /* Named so that ${context.<field>} expansions resolve here. */
<% else %> let context = self;
/* Without a free_token_node code block this function has nothing to do. The
* context is consumed and the memory it owns is released when it is dropped. */
#[allow(unused_variables)]
<% end %>
pub fn <%= @grammar.prefix %>context_delete(<%= free_token_node_used ? "mut " : "" %>context: <%= @grammar.prefix %>context_t) {
<% if free_token_node_used %>
for i in 0..context.<%= @grammar.prefix %>tree_nodes.len() { for i in 0..context.<%= @grammar.prefix %>tree_nodes.len() {
if context.<%= @grammar.prefix %>tree_nodes[i].is_token { if context.<%= @grammar.prefix %>tree_nodes[i].is_token {
let token_node_id = i; let token_node_id = i;
<%= expand_code(@grammar.free_token_node, false, nil, nil).gsub(/\btoken_tree_node\b/, "context.#{@grammar.prefix}tree_nodes[token_node_id]") %> <%= expand_code(@grammar.free_token_node, false, nil, nil).gsub(/\btoken_tree_node\b/, "context.#{@grammar.prefix}tree_nodes[token_node_id]") %>
} }
} }
}
}
<% end %> <% end %>
/**
* Deinitialize and deallocate lexer/parser context structure.
*
* The memory owned by the context is released when the context is dropped, so
* this function only has to consume it. It is provided for symmetry with
* <%= @grammar.prefix %>context_new() and with the other target languages;
* letting the context go out of scope has the same effect.
*
* @param context
* Lexer/parser context structure.
*/
#[allow(unused_variables)]
pub fn <%= @grammar.prefix %>context_delete(context: <%= @grammar.prefix %>context_t) {
} }
/************************************************************************** /**************************************************************************

View File

@ -530,15 +530,16 @@ Start -> a:a b:b;
The `free_token_node` statement user code block is not emitted for D language The `free_token_node` statement user code block is not emitted for D language
since D has a garbage collector. since D has a garbage collector.
The code block is emitted for the Rust target, where it runs from The code block is emitted for the Rust target, where it is run from a `Drop`
`p_context_delete()`. implementation generated for `p_context_t`.
It therefore runs exactly once however the context is disposed of, whether that
is by calling `p_context_delete()` or by simply letting the context go out of
scope.
A `ptype` or token user field which owns its memory (a `String`, a `Vec`, a A `ptype` or token user field which owns its memory (a `String`, a `Vec`, a
`Box`, and so on) is released when the context is dropped and does not need a `Box`, and so on) is released when the context is dropped and does not need a
`free_token_node` code block. `free_token_node` code block.
The statement is only needed for memory which Rust does not track, such as a The statement is only needed for memory which Rust does not track, such as a
raw pointer obtained from `Box::into_raw()`. raw pointer obtained from `Box::into_raw()`.
Note that the generated `p_context_t` does not implement `Drop`, so a
`free_token_node` code block only runs if `p_context_delete()` is called.
##> `lex_fn` statement - specifying a custom lexer function ##> `lex_fn` statement - specifying a custom lexer function
@ -1691,9 +1692,9 @@ provide a code block which frees that memory; if specified, the
For Rust targets, `p_context_delete()` takes the context by value and consumes For Rust targets, `p_context_delete()` takes the context by value and consumes
it. it.
The memory owned by the context is released when the context is dropped, so the Everything the context owns is released when it is dropped, including running
call is only strictly required when the grammar supplies a `free_token_node` any `free_token_node` code block, so calling this function is optional for a
code block, which runs from `p_context_delete()`. Rust target; letting the context go out of scope has the same effect.
Rust example: Rust example:

View File

@ -10,8 +10,18 @@ fn main() {
assert!(start.b().valid()); assert!(start.b().valid());
assert_eq!(2, unsafe { *start.b().pvalue() }); assert_eq!(2, unsafe { *start.b().pvalue() });
/* The free_token_node code block runs from p_context_delete(), not before. */ /* The free_token_node code block runs when the context is disposed of, not
* before, and frees each of the two token nodes exactly once. */
assert_eq!(0, FREED.load(Ordering::SeqCst)); assert_eq!(0, FREED.load(Ordering::SeqCst));
p_context_delete(context); p_context_delete(context);
assert_eq!(2, FREED.load(Ordering::SeqCst)); assert_eq!(2, FREED.load(Ordering::SeqCst));
/* Letting the context go out of scope runs the code block too, so a caller
* which never calls p_context_delete() does not leak. */
{
let mut context = p_context_new(b"ab");
assert_eq!(P_SUCCESS, p_parse(&mut context));
assert_eq!(2, FREED.load(Ordering::SeqCst));
}
assert_eq!(4, FREED.load(Ordering::SeqCst));
} }