diff --git a/assets/parser.rs.erb b/assets/parser.rs.erb index c9f46e6..0270acc 100644 --- a/assets/parser.rs.erb +++ b/assets/parser.rs.erb @@ -297,32 +297,36 @@ pub fn <%= @grammar.prefix %>context_new(input: &[u8]) -> <%= @grammar.prefix %> context } +<% free_token_node_used = @grammar.tree && @grammar.free_token_node != "" %> +<% if free_token_node_used %> +impl Drop for <%= @grammar.prefix %>context_t { + /* Run the free_token_node user code block for every token node in the tree. */ + fn drop(&mut self) { + /* Named so that ${context.} expansions resolve here. */ + let context = self; + for i in 0..context.<%= @grammar.prefix %>tree_nodes.len() { + if context.<%= @grammar.prefix %>tree_nodes[i].is_token { + 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]") %> + } + } + } +} + +<% 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. */ -<% free_token_node_used = @grammar.tree && @grammar.free_token_node != "" %> -<% if free_token_node_used %> -/* The context is taken as mutable for the benefit of the free_token_node user - * code block below, which is permitted but not required to modify the node it - * is freeing. */ -#[allow(unused_mut)] -<% else %> -/* 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() { - if context.<%= @grammar.prefix %>tree_nodes[i].is_token { - 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]") %> - } - } -<% end %> +pub fn <%= @grammar.prefix %>context_delete(context: <%= @grammar.prefix %>context_t) { } /************************************************************************** diff --git a/doc/user_guide.md b/doc/user_guide.md index 6e1e020..6971d2e 100644 --- a/doc/user_guide.md +++ b/doc/user_guide.md @@ -530,15 +530,16 @@ Start -> a:a b:b; The `free_token_node` statement user code block is not emitted for D language since D has a garbage collector. -The code block is emitted for the Rust target, where it runs from -`p_context_delete()`. +The code block is emitted for the Rust target, where it is run from a `Drop` +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 `Box`, and so on) is released when the context is dropped and does not need a `free_token_node` code block. The statement is only needed for memory which Rust does not track, such as a 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 @@ -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 it. -The memory owned by the context is released when the context is dropped, so the -call is only strictly required when the grammar supplies a `free_token_node` -code block, which runs from `p_context_delete()`. +Everything the context owns is released when it is dropped, including running +any `free_token_node` code block, so calling this function is optional for a +Rust target; letting the context go out of scope has the same effect. Rust example: diff --git a/spec/test_tree_delete_token_node_memory.rs b/spec/test_tree_delete_token_node_memory.rs index e10ff93..24bdcf5 100644 --- a/spec/test_tree_delete_token_node_memory.rs +++ b/spec/test_tree_delete_token_node_memory.rs @@ -10,8 +10,18 @@ fn main() { assert!(start.b().valid()); 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)); p_context_delete(context); 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)); }