Combine free_token_user_fields and free_token_node statements

This commit is contained in:
Josh Holtrop 2026-02-21 21:37:23 -05:00
parent d4ad67c23d
commit de3fb0d120
4 changed files with 39 additions and 61 deletions

View File

@ -1219,11 +1219,8 @@ static void tree_delete(TreeNode * node)
{ {
if (node->is_token) if (node->is_token)
{ {
<%= @grammar.tree_prefix %>Token<%= @grammar.tree_suffix %> * token_tree_node = (<%= @grammar.tree_prefix %>Token<%= @grammar.tree_suffix %> *) node; <%= @grammar.tree_prefix %>Token<%= @grammar.tree_suffix %> * token_tree_node = (<%= @grammar.tree_prefix %>Token<%= @grammar.tree_suffix %> *)node;
<% if @grammar.free_token_node %> <%= expand_code(@grammar.free_token_node, false, nil, nil) %>
<%= @grammar.free_token_node %>(token_tree_node);
<% end %>
<%= expand_code(@grammar.free_token_user_fields, false, nil, nil) %>
<% if @cpp %> <% if @cpp %>
delete token_tree_node; delete token_tree_node;
<% else %> <% else %>

View File

@ -297,7 +297,7 @@ drop /#(.*)\n/ <<
``` ```
If a pointer to any allocated memory is stored in a user-defined context field, If a pointer to any allocated memory is stored in a user-defined context field,
the `free_token_user_fields` statement can be used to supply a code block which the `free_token_node` statement can be used to supply a code block which
will be executed immediately before the token node is freed. will be executed immediately before the token node is freed.
For C++, the `delete` statement is used to free the token tree node, so the For C++, the `delete` statement is used to free the token tree node, so the
destructor for any custom token user fields will be called. destructor for any custom token user fields will be called.
@ -326,25 +326,6 @@ drop /#(.*)\n/ <<
>> >>
``` ```
### Freeing allocated memory in a custom token user field - the `free_token_user_fields` statement
The `free_token_user_fields` statement allows the user to provide a code block
which will be executed immediately prior to freeing the token tree node.
For example (C):
```
token_user_fields <<
char * comments;
>>
on_token_node <<
${token.comments} = (char *)malloc(some_len);
>>
free_token_user_fields <<
free(${token.comments});
>>
```
##> Tree generation mode - the `tree` statement ##> Tree generation mode - the `tree` statement
To activate tree generation mode, place the `tree` statement in your grammar file: To activate tree generation mode, place the `tree` statement in your grammar file:
@ -478,22 +459,18 @@ assert(itemsmore.pItem.pItem.pItem.pToken1 !is null);
## Freeing user-allocated memory in token node `pvalue`: the `free_token_node` statement ## Freeing user-allocated memory in token node `pvalue`: the `free_token_node` statement
If user lexer code block allocates memory to store in a token node's `pvalue`, If user lexer code block allocates memory to store in a token node's `pvalue`
the `free_token_node` grammar statement can be used to specify the name of a or any custom token user fields store pointers to allocated memory, the
function which will be called during the `p_tree_delete()` call to free the memory `free_token_node` grammar statement can be used to provide a code block which
associated with a token node. can be used to free memory properly.
Example: Example freeing `pvalue` (C):
``` ```
<<
static void free_token(Token * token)
{
free(token->pvalue);
}
>>
tree; tree;
free_token_node free_token; free_token_node <<
free(${token.pvalue});
>>
ptype int *; ptype int *;
token a << token a <<
$$ = (int *)malloc(sizeof(int)); $$ = (int *)malloc(sizeof(int));
@ -506,6 +483,23 @@ token b <<
Start -> a:a b:b; Start -> a:a b:b;
``` ```
Example freeing custom token user fields (C):
```
token_user_fields <<
char * comments;
>>
on_token_node <<
${token.comments} = (char *)malloc(some_len);
>>
free_token_node <<
free(${token.comments});
>>
```
The `free_token_node` statement user code block is not emitted for D language
since D has a garbage collector.
##> Specifying tokens - the `token` statement ##> Specifying tokens - the `token` statement
The `token` statement allows defining a lexer token and a pattern to match that The `token` statement allows defining a lexer token and a pattern to match that
@ -1329,9 +1323,9 @@ It should be passed the same value that is returned by `p_result()`.
The `p_tree_delete()` function is only available for C/C++ output targets. The `p_tree_delete()` function is only available for C/C++ output targets.
Note that if any lexer user code block allocates memory to store in a token's Note that if any lexer user code block allocates memory to store in a token's
`pvalue`, in order to properly free this memory a `free_token_node` function `pvalue`, in order to properly free this memory the `free_token_node` statement
should be specified in the grammar file. should be used to provide a code block that frees this memory.
If specified, the `free_token_node` function will be called during the If specified, the `free_token_node` code block will be executed during the
`p_tree_delete()` process to allow user code to free any memory associated with `p_tree_delete()` process to allow user code to free any memory associated with
a token node's `pvalue`. a token node's `pvalue`.

View File

@ -20,7 +20,6 @@ class Propane
attr_reader :prefix attr_reader :prefix
attr_reader :on_token_node attr_reader :on_token_node
attr_reader :token_user_fields attr_reader :token_user_fields
attr_reader :free_token_user_fields
def initialize(input) def initialize(input)
@patterns = [] @patterns = []
@ -37,11 +36,10 @@ class Propane
@tree = false @tree = false
@tree_prefix = "" @tree_prefix = ""
@tree_suffix = "" @tree_suffix = ""
@free_token_node = nil @free_token_node = ""
@context_user_fields = nil @context_user_fields = nil
@on_token_node = "" @on_token_node = ""
@token_user_fields = nil @token_user_fields = nil
@free_token_user_fields = ""
parse_grammar! parse_grammar!
@start_rules << "Start" if @start_rules.empty? @start_rules << "Start" if @start_rules.empty?
end end
@ -78,7 +76,6 @@ class Propane
elsif parse_module_statement! elsif parse_module_statement!
elsif parse_on_token_node_statement! elsif parse_on_token_node_statement!
elsif parse_token_user_fields_statement! elsif parse_token_user_fields_statement!
elsif parse_free_token_user_fields_statement!
elsif parse_ptype_statement! elsif parse_ptype_statement!
elsif parse_pattern_statement! elsif parse_pattern_statement!
elsif parse_start_statement! elsif parse_start_statement!
@ -138,12 +135,6 @@ class Propane
end end
end end
def parse_free_token_node_statement!
if md = consume!(/free_token_node\s+(\w+)\s*;/)
@free_token_node = md[1]
end
end
def parse_module_statement! def parse_module_statement!
if consume!(/module\s+/) if consume!(/module\s+/)
md = consume!(/([\w.]+)\s*/, "expected module name") md = consume!(/([\w.]+)\s*/, "expected module name")
@ -173,12 +164,12 @@ class Propane
end end
end end
def parse_free_token_user_fields_statement! def parse_free_token_node_statement!
if md = consume!(/free_token_user_fields\b\s*/) if md = consume!(/free_token_node\b\s*/)
unless code = parse_code_block! unless code = parse_code_block!
raise Error.new("Line #{@line_number}: expected code block") raise Error.new("Line #{@line_number}: expected code block")
end end
@free_token_user_fields += code @free_token_node += code
end end
end end

View File

@ -1485,14 +1485,10 @@ EOF
if %w[c cpp].include?(language) if %w[c cpp].include?(language)
it "allows a user function to free token node memory in tree mode" do it "allows a user function to free token node memory in tree mode" do
write_grammar <<EOF write_grammar <<EOF
<<
static void free_token(Token * token)
{
free(token->pvalue);
}
>>
tree; tree;
free_token_node free_token; free_token_node <<
free(${token.pvalue});
>>
ptype int *; ptype int *;
token a << token a <<
$$ = (int *)malloc(sizeof(int)); $$ = (int *)malloc(sizeof(int));
@ -1641,7 +1637,7 @@ context_user_fields <<
token_user_fields << token_user_fields <<
char * comments; char * comments;
>> >>
free_token_user_fields << free_token_node <<
free(${token.comments}); free(${token.comments});
>> >>
on_token_node << on_token_node <<