Add p_free_ast() to free AST node memory for C targets

This commit is contained in:
Josh Holtrop 2026-02-08 00:02:24 -05:00
parent e223d03d7c
commit 9c03d20083
12 changed files with 129 additions and 46 deletions

View File

@ -695,11 +695,13 @@ typedef struct
} state_value_t;
/** Common AST node structure. */
typedef struct
typedef struct ASTNode_s
{
<%= @grammar.prefix %>position_t position;
<%= @grammar.prefix %>position_t end_position;
void * fields[];
uint16_t n_fields;
uint8_t is_token;
struct ASTNode_s * fields[];
} ASTNode;
/** Parser shift table. */
@ -992,6 +994,8 @@ size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context)
<%= @grammar.ast_prefix %>Token<%= @grammar.ast_suffix %> * token_ast_node = (<%= @grammar.ast_prefix %>Token<%= @grammar.ast_suffix %> *)malloc(sizeof(<%= @grammar.ast_prefix %>Token<%= @grammar.ast_suffix %>));
token_ast_node->position = token_info.position;
token_ast_node->end_position = token_info.end_position;
token_ast_node->n_fields = 0u;
token_ast_node->is_token = 1u;
token_ast_node->token = token;
token_ast_node->pvalue = token_info.pvalue;
state_values_stack_index(&statevalues, -1)->ast_node = token_ast_node;
@ -1028,31 +1032,30 @@ size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context)
else if (parser_reduce_table[reduce_index].n_states > 0)
{
size_t n_fields = parser_reduce_table[reduce_index].rule_set_node_field_array_size;
ASTNode * node = (ASTNode *)malloc(sizeof(ASTNode) + n_fields * sizeof(void *));
size_t bytes = sizeof(ASTNode) + n_fields * sizeof(void *);
ASTNode * node = (ASTNode *)malloc(bytes);
memset(node, 0, bytes);
node->position = INVALID_POSITION;
node->end_position = INVALID_POSITION;
for (size_t i = 0; i < n_fields; i++)
{
node->fields[i] = NULL;
}
node->n_fields = n_fields;
if (parser_reduce_table[reduce_index].rule_set_node_field_index_map == NULL)
{
for (size_t i = 0; i < parser_reduce_table[reduce_index].n_states; i++)
{
node->fields[i] = state_values_stack_index(&statevalues, -(int)parser_reduce_table[reduce_index].n_states + (int)i)->ast_node;
node->fields[i] = (ASTNode *)state_values_stack_index(&statevalues, -(int)parser_reduce_table[reduce_index].n_states + (int)i)->ast_node;
}
}
else
{
for (size_t i = 0; i < parser_reduce_table[reduce_index].n_states; i++)
{
node->fields[parser_reduce_table[reduce_index].rule_set_node_field_index_map[i]] = state_values_stack_index(&statevalues, -(int)parser_reduce_table[reduce_index].n_states + (int)i)->ast_node;
node->fields[parser_reduce_table[reduce_index].rule_set_node_field_index_map[i]] = (ASTNode *)state_values_stack_index(&statevalues, -(int)parser_reduce_table[reduce_index].n_states + (int)i)->ast_node;
}
}
bool position_found = false;
for (size_t i = 0; i < n_fields; i++)
{
ASTNode * child = (ASTNode *)node->fields[i];
ASTNode * child = node->fields[i];
if ((child != NULL) && <%= @grammar.prefix %>position_valid(child->position))
{
if (!position_found)
@ -1074,6 +1077,7 @@ size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context)
memset(&reduced_parser_value2, 0, sizeof(reduced_parser_value2));
if (parser_user_code(&reduced_parser_value2, parser_reduce_table[reduce_index].rule, &statevalues, parser_reduce_table[reduce_index].n_states, context) == P_USER_TERMINATED)
{
state_values_stack_free(&statevalues);
return P_USER_TERMINATED;
}
reduced_parser_value = reduced_parser_value2;
@ -1153,3 +1157,32 @@ size_t <%= @grammar.prefix %>user_terminate_code(<%= @grammar.prefix %>context_t
{
return context->token;
}
<% if @grammar.ast %>
static void free_ast_node(ASTNode * node)
{
if (node->is_token)
{
/* TODO: free value_t */
}
else if (node->n_fields > 0u)
{
for (size_t i = 0u; i < node->n_fields; i++)
{
if (node->fields[i] != NULL)
{
free_ast_node(node->fields[i]);
}
}
}
free(node);
}
/**
* Free all AST node memory.
*/
void <%= @grammar.prefix %>free_ast(<%= @grammar.ast_prefix %><%= @grammar.start_rule %><%= @grammar.ast_suffix %> * ast)
{
free_ast_node((ASTNode *)ast);
}
<% end %>

View File

@ -75,9 +75,11 @@ typedef union
/** AST node types. @{ */
typedef struct <%= @grammar.ast_prefix %>Token<%= @grammar.ast_suffix %>
{
/* ASTNode fields must be present in the same order here. */
<% # ASTNode fields must be present in the same order here. # %>
<%= @grammar.prefix %>position_t position;
<%= @grammar.prefix %>position_t end_position;
uint16_t n_fields;
uint8_t is_token;
<%= @grammar.prefix %>token_t token;
<%= @grammar.prefix %>value_t pvalue;
} <%= @grammar.ast_prefix %>Token<%= @grammar.ast_suffix %>;
@ -93,8 +95,11 @@ struct <%= name %>;
<% next if rule_set.optional? %>
typedef struct <%= @grammar.ast_prefix %><%= name %><%= @grammar.ast_suffix %>
{
<% # ASTNode fields must be present in the same order here. # %>
<%= @grammar.prefix %>position_t position;
<%= @grammar.prefix %>position_t end_position;
uint16_t n_fields;
uint8_t is_token;
<% rule_set.ast_fields.each do |fields| %>
union
{
@ -187,6 +192,8 @@ size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context);
<% if @grammar.ast %>
<%= @grammar.ast_prefix %><%= @grammar.start_rule %><%= @grammar.ast_suffix %> * <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context);
void <%= @grammar.prefix %>free_ast(<%= @grammar.ast_prefix %><%= @grammar.start_rule %><%= @grammar.ast_suffix %> * ast);
<% else %>
<%= start_rule_type[1] %> <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context);
<% end %>

View File

@ -78,9 +78,9 @@ EOF
end
case options[:language]
when "c"
command = [*%w[gcc -Wall -o spec/run/testparser -Ispec -Ispec/run], *parsers, *test_files, "spec/testutils.c", "-lm"]
command = [*%w[gcc -g -Wall -o spec/run/testparser -Ispec -Ispec/run], *parsers, *test_files, "spec/testutils.c", "-lm"]
when "cpp"
command = [*%w[g++ -x c++ -Wall -o spec/run/testparser -Ispec -Ispec/run], *parsers, *test_files, "spec/testutils.c", "-lm"]
command = [*%w[g++ -g -x c++ -Wall -o spec/run/testparser -Ispec -Ispec/run], *parsers, *test_files, "spec/testutils.c", "-lm"]
when "d"
command = [*%w[ldc2 -g --unittest -of spec/run/testparser -Ispec], *parsers, *test_files, "spec/testutils.d"]
end
@ -88,12 +88,13 @@ EOF
expect(result).to be_truthy
end
def run_test
def run_test(options)
stdout, stderr, status = Open3.capture3("spec/run/testparser")
File.binwrite("spec/run/.stderr", stderr)
File.binwrite("spec/run/.stdout", stdout)
stderr.sub!(/^.*modules passed unittests\n/, "")
Results.new(stdout, stderr, status)
results = Results.new(stdout, stderr, status)
results
end
def lines(str)
@ -286,7 +287,7 @@ Foo -> plus <<>>
EOF
run_propane(language: language)
compile("spec/test_lexer.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
@ -324,7 +325,7 @@ EOF
end
run_propane(language: language)
compile("spec/test_lexer_unknown_character.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
@ -418,7 +419,7 @@ EOF
end
run_propane(language: language)
compile("spec/test_basic_math_grammar.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
@ -444,7 +445,7 @@ R2 -> a b;
EOF
run_propane(language: language)
compile("spec/test_parser_identical_rules_lookahead.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.status).to eq 0
end
@ -459,7 +460,7 @@ R1 -> b;
EOF
run_propane(language: language)
compile("spec/test_parser_rule_from_multiple_states.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.status).to eq 0
end
@ -494,7 +495,7 @@ EOF
end
run_propane(language: language)
compile("spec/test_user_code.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.status).to eq 0
verify_lines(results.stdout, [
"abc!",
@ -530,7 +531,7 @@ EOF
end
run_propane(language: language)
compile("spec/test_pattern.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.status).to eq 0
verify_lines(results.stdout, [
"def!",
@ -572,7 +573,7 @@ EOF
end
run_propane(language: language)
compile("spec/test_return_token_from_pattern.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.status).to eq 0
verify_lines(results.stdout, [
"def!",
@ -630,7 +631,7 @@ EOF
end
run_propane(language: language)
compile("spec/test_lexer_modes.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.status).to eq 0
verify_lines(results.stdout, [
"begin string mode",
@ -688,7 +689,7 @@ EOF
end
run_propane(language: language)
compile("spec/test_lexer_multiple_modes.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.status).to eq 0
verify_lines(results.stdout, [
"ident: d",
@ -725,7 +726,7 @@ EOF
end
run_propane(language: language)
compile("spec/test_parser_rule_user_code.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.status).to eq 0
verify_lines(results.stdout, [
"A!",
@ -744,7 +745,7 @@ As -> As a << $$ = $1 + 1u; >>
EOF
run_propane(language: language)
compile("spec/test_parsing_lists.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.status).to eq 0
expect(results.stderr).to eq ""
end
@ -798,7 +799,7 @@ EOF
end
run_propane(language: language)
compile("spec/test_lexer_match_text.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.status).to eq 0
verify_lines(results.stdout, [
"Matched token is identifier_123",
@ -831,7 +832,7 @@ EOF
end
run_propane(language: language)
compile("spec/test_lexer_result_value.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
@ -846,7 +847,7 @@ Start -> a num;
EOF
run_propane(language: language)
compile("spec/test_error_positions.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
@ -875,7 +876,7 @@ Start -> b c b;
EOF
run_propane(name: "myp2", language: language)
compile("spec/test_multiple_parsers.#{language}", parsers: %w[myp1 myp2], language: language)
results = run_test
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
@ -894,7 +895,7 @@ Any -> c;
EOF
run_propane(language: language)
compile("spec/test_user_terminate_lexer.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
@ -912,7 +913,7 @@ Any -> ;
EOF
run_propane(language: language)
compile("spec/test_user_terminate.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
@ -956,7 +957,7 @@ EOF
end
run_propane(language: language)
compile("spec/test_match_backslashes.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
verify_lines(results.stdout, [
@ -1021,7 +1022,7 @@ Two -> two;
EOF
run_propane(language: language)
compile("spec/test_ast.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
@ -1065,7 +1066,7 @@ Two -> two;
EOF
run_propane(language: language)
compile("spec/test_ast_ps.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
@ -1139,7 +1140,7 @@ EOF
end
run_propane(language: language)
compile("spec/test_optional_rule_component.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
verify_lines(results.stdout, [
@ -1191,7 +1192,7 @@ EOF
end
run_propane(language: language)
compile("spec/test_optional_rule_component_ast.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
@ -1232,7 +1233,7 @@ EOF
end
run_propane(language: language)
compile("spec/test_named_optional_rule_component_ast.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
@ -1252,7 +1253,7 @@ T -> c;
EOF
run_propane(language: language)
compile("spec/test_ast_token_positions.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
@ -1272,7 +1273,7 @@ A -> bb? c?;
EOF
run_propane(language: language)
compile("spec/test_ast_invalid_positions.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
@ -1292,7 +1293,7 @@ T -> c;
EOF
run_propane(language: language)
compile("spec/test_ast_field_aliases.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
@ -1315,7 +1316,7 @@ T -> c;
EOF
run_propane(language: language)
compile("spec/test_ast_field_aliases.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
@ -1358,7 +1359,7 @@ EOF
end
run_propane(language: language)
compile("spec/test_field_aliases.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
expect(results.stdout).to match /first is foo1.*second is bar2/m
@ -1408,7 +1409,7 @@ EOF
end
run_propane(language: language)
compile("spec/test_field_aliases.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
expect(results.stdout).to match /first is foo1.*second is bar2/m
@ -1419,7 +1420,7 @@ EOF
write_grammar(File.read("spec/ast_node_memory_remains.#{ext}.propane"))
run_propane(language: language)
compile("spec/test_ast_node_memory_remains.#{language}", language: language)
results = run_test
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end

View File

@ -33,12 +33,16 @@ int main()
assert_eq(22, itemsmore->pItem->pToken1->pvalue);
assert(itemsmore->pItemsMore == NULL);
p_free_ast(start);
input = "";
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert_eq(P_SUCCESS, p_parse(&context));
start = p_result(&context);
assert(start->pItems == NULL);
p_free_ast(start);
input = "2 1";
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert_eq(P_SUCCESS, p_parse(&context));
@ -51,5 +55,7 @@ int main()
assert(start->pItems->pItem->pDual->pTwo2 == NULL);
assert(start->pItems->pItem->pDual->pOne1 == NULL);
p_free_ast(start);
return 0;
}

View File

@ -15,5 +15,7 @@ int main()
assert_eq(TOKEN_b, start->second->pToken->token);
assert_eq(TOKEN_c, start->third->pToken->token);
p_free_ast(start);
return 0;
}

View File

@ -30,6 +30,8 @@ int main()
assert_eq(3, start->end_position.row);
assert_eq(8, start->end_position.col);
p_free_ast(start);
input = "a\nbb";
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS);
@ -54,6 +56,8 @@ int main()
assert_eq(2, start->end_position.row);
assert_eq(2, start->end_position.col);
p_free_ast(start);
input = "a\nc\nc";
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS);
@ -78,6 +82,8 @@ int main()
assert_eq(3, start->end_position.row);
assert_eq(1, start->end_position.col);
p_free_ast(start);
input = "a";
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS);
@ -98,5 +104,7 @@ int main()
assert_eq(1, start->end_position.row);
assert_eq(1, start->end_position.col);
p_free_ast(start);
return 0;
}

View File

@ -411,5 +411,7 @@ int main(int argc, char * argv[])
}
}
p_free_ast(pmod);
return 0;
}

View File

@ -33,12 +33,16 @@ int main()
assert_eq(22, itemsmore->pItem->pToken1->pvalue);
assert(itemsmore->pItemsMore == NULL);
p_free_ast(start);
input = "";
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert_eq(P_SUCCESS, p_parse(&context));
start = p_result(&context);
assert(start->pItems == NULL);
p_free_ast(start);
input = "2 1";
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert_eq(P_SUCCESS, p_parse(&context));
@ -51,5 +55,7 @@ int main()
assert(start->pItems->pItem->pDual->pTwo2 == NULL);
assert(start->pItems->pItem->pDual->pOne1 == NULL);
p_free_ast(start);
return 0;
}

View File

@ -43,6 +43,8 @@ int main()
assert_eq(1, start->end_position.row);
assert_eq(6, start->end_position.col);
p_free_ast(start);
input = "\n\n bb\nc\ncc\n\n a";
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS);
@ -80,5 +82,7 @@ int main()
assert_eq(7, start->end_position.row);
assert_eq(6, start->end_position.col);
p_free_ast(start);
return 0;
}

View File

@ -17,6 +17,8 @@ int main()
assert(start->pR == NULL);
assert(start->r == NULL);
p_free_ast(start);
input = "abcd";
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS);
@ -31,6 +33,8 @@ int main()
assert(start->pR == start->r);
assert_eq(TOKEN_c, start->pR->pToken1->token);
p_free_ast(start);
input = "bdc";
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS);
@ -40,6 +44,8 @@ int main()
assert(start->r != NULL);
assert_eq(TOKEN_d, start->pR->pToken1->token);
p_free_ast(start);
return 0;
}

View File

@ -16,6 +16,8 @@ int main()
assert(start->pR3 == NULL);
assert(start->pR == NULL);
p_free_ast(start);
input = "abcd";
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS);
@ -28,6 +30,8 @@ int main()
assert(start->pR == start->pR3);
assert_eq(TOKEN_c, start->pR->pToken1->token);
p_free_ast(start);
input = "bdc";
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS);
@ -37,6 +41,8 @@ int main()
assert(start->pR != NULL);
assert_eq(TOKEN_d, start->pR->pToken1->token);
p_free_ast(start);
return 0;
}

View File

@ -13,5 +13,7 @@ int main()
assert(top->pToken != NULL);
assert_eq(TOKEN_hi, top->pToken->token);
p_free_ast(top);
return 0;
}