5 Commits
Author SHA1 Message Date
josh e992e6344a Update CHANGELOG for v4.1.0 2026-02-28 22:40:55 -05:00
josh a05a55ceb8 v4.1.0 2026-02-28 22:38:40 -05:00
josh e9ecf33f58 Run valgrind on D targets in addition to C and C++ 2026-02-28 21:50:13 -05:00
josh dd687d0299 Add p_context_delete() and p_tree_delete() for D targets 2026-02-28 21:12:35 -05:00
josh d440d0442d Expand token_user_fields unit test 2026-02-22 09:38:42 -05:00
16 changed files with 148 additions and 37 deletions

No files matched your search

+6
View File
@@ -1,3 +1,9 @@
## v4.1.0
### New Features
- Add `p_context_delete()` and `p_tree_delete()` for D targets.
## v4.0.0 ## v4.0.0
### New Features ### New Features
+50 -2
View File
@@ -9,7 +9,7 @@ module <%= @grammar.modulename %>;
<% end %> <% end %>
import core.memory; import core.memory;
import core.stdc.stdlib : malloc; import core.stdc.stdlib : malloc, free;
/************************************************************************** /**************************************************************************
* User code blocks * User code blocks
@@ -94,6 +94,8 @@ private struct TreeNode
{ {
<%= @grammar.prefix %>position_t position; <%= @grammar.prefix %>position_t position;
<%= @grammar.prefix %>position_t end_position; <%= @grammar.prefix %>position_t end_position;
ushort n_fields;
bool is_token;
void *[0] fields; void *[0] fields;
} }
@@ -103,6 +105,8 @@ public struct <%= @grammar.tree_prefix %>Token<%= @grammar.tree_suffix %>
/* TreeNode fields must be present in the same order here. */ /* TreeNode fields must be present in the same order here. */
<%= @grammar.prefix %>position_t position; <%= @grammar.prefix %>position_t position;
<%= @grammar.prefix %>position_t end_position; <%= @grammar.prefix %>position_t end_position;
ushort n_fields;
bool is_token;
<%= @grammar.prefix %>token_t token; <%= @grammar.prefix %>token_t token;
<%= @grammar.prefix %>value_t pvalue; <%= @grammar.prefix %>value_t pvalue;
<%= @grammar.token_user_fields %> <%= @grammar.token_user_fields %>
@@ -115,6 +119,8 @@ public struct <%= @grammar.tree_prefix %><%= name %><%= @grammar.tree_suffix %>
{ {
<%= @grammar.prefix %>position_t position; <%= @grammar.prefix %>position_t position;
<%= @grammar.prefix %>position_t end_position; <%= @grammar.prefix %>position_t end_position;
ushort n_fields;
bool is_token;
<% rule_set.tree_fields.each do |fields| %> <% rule_set.tree_fields.each do |fields| %>
union union
{ {
@@ -225,6 +231,7 @@ private enum size_t INVALID_ID = cast(size_t)-1;
*************************************************************************/ *************************************************************************/
/** /**
* Allocate and initialize lexer/parser context structure.
* *
* Deinitialize and deallocate with <%= @grammar.prefix %>context_delete(). * Deinitialize and deallocate with <%= @grammar.prefix %>context_delete().
* *
@@ -249,6 +256,16 @@ private enum size_t INVALID_ID = cast(size_t)-1;
return context; return context;
} }
/**
* Deinitialize and deallocate lexer/parser context structure.
*
* @param context
* Lexer/parser context structure allocated with <%= @grammar.prefix %>context_new().
*/
void <%= @grammar.prefix %>context_delete(<%= @grammar.prefix %>context_t * context)
{
}
/************************************************************************** /**************************************************************************
* Decoder * Decoder
*************************************************************************/ *************************************************************************/
@@ -1054,7 +1071,7 @@ private size_t parse_from(<%= @grammar.prefix %>context_t * context, size_t star
{ {
/* We shifted a token, mark it consumed. */ /* We shifted a token, mark it consumed. */
<% if @grammar.tree %> <% if @grammar.tree %>
<%= @grammar.tree_prefix %>Token<%= @grammar.tree_suffix %> * token_tree_node = new <%= @grammar.tree_prefix %>Token<%= @grammar.tree_suffix %>(token_info.position, token_info.end_position, token, token_info.pvalue); <%= @grammar.tree_prefix %>Token<%= @grammar.tree_suffix %> * token_tree_node = new <%= @grammar.tree_prefix %>Token<%= @grammar.tree_suffix %>(token_info.position, token_info.end_position, 0u, true, token, token_info.pvalue);
<%= expand_code(@grammar.on_token_node, false, nil, nil) %> <%= expand_code(@grammar.on_token_node, false, nil, nil) %>
statevalues[$-1].tree_node = token_tree_node; statevalues[$-1].tree_node = token_tree_node;
<% else %> <% else %>
@@ -1094,6 +1111,8 @@ private size_t parse_from(<%= @grammar.prefix %>context_t * context, size_t star
GC.addRange(node, node_size); GC.addRange(node, node_size);
node.position = <%= @grammar.prefix %>position_t.INVALID; node.position = <%= @grammar.prefix %>position_t.INVALID;
node.end_position = <%= @grammar.prefix %>position_t.INVALID; node.end_position = <%= @grammar.prefix %>position_t.INVALID;
node.n_fields = cast(ushort)n_fields;
node.is_token = false;
foreach (i; 0..n_fields) foreach (i; 0..n_fields)
{ {
node.fields[i] = null; node.fields[i] = null;
@@ -1201,6 +1220,35 @@ public <%= start_rule_type(i)[1] %> <%= @grammar.prefix %>result_<%= start_rule
<% end %> <% end %>
<% end %> <% end %>
<% if @grammar.tree %>
private void tree_delete(TreeNode * node)
{
if (!node.is_token)
{
for (size_t i = 0u; i < node.n_fields; i++)
{
if (node.fields[i])
{
tree_delete(cast(TreeNode *)node.fields[i]);
}
}
GC.removeRange(node);
free(node);
}
}
void <%= @grammar.prefix %>tree_delete(<%= @grammar.tree_prefix %><%= @grammar.start_rules[0] %><%= @grammar.tree_suffix %> * tree)
{
tree_delete(cast(TreeNode *)tree);
}
<% @grammar.start_rules.each_with_index do |start_rule, i| %>
void <%= @grammar.prefix %>tree_delete_<%= start_rule %>(<%= @grammar.tree_prefix %><%= start_rule %><%= @grammar.tree_suffix %> * tree)
{
tree_delete(cast(TreeNode *)tree);
}
<% end %>
<% end %>
/** /**
* Get the current text input position. * Get the current text input position.
* *
-4
View File
@@ -1130,8 +1130,6 @@ p_context_t * context = p_context_new(input);
The `p_context_delete()` function must be called to deinitialize and deallocate The `p_context_delete()` function must be called to deinitialize and deallocate
a context structure allocated by `p_context_init()`. a context structure allocated by `p_context_init()`.
This function is not available for D language since D has a garbage collector.
### `p_lex` ### `p_lex`
The `p_lex()` function is the main entry point to the lexer. The `p_lex()` function is the main entry point to the lexer.
@@ -1320,8 +1318,6 @@ assert(code_point_length == 4u);
The `p_tree_delete()` function can be used to free the memory used by the tree. The `p_tree_delete()` function can be used to free the memory used by the tree.
It should be passed the same value that is returned by `p_result()`. 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.
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 the `free_token_node` statement `pvalue`, in order to properly free this memory the `free_token_node` statement
should be used to provide a code block that frees this memory. should be used to provide a code block that frees this memory.
+1 -1
View File
@@ -1,3 +1,3 @@
class Propane class Propane
VERSION = "4.0.0" VERSION = "4.1.0"
end end
+13 -12
View File
@@ -95,15 +95,13 @@ EOF
File.binwrite("spec/run/.stdout", stdout) File.binwrite("spec/run/.stdout", stdout)
stderr.sub!(/^.*modules passed unittests\n/, "") stderr.sub!(/^.*modules passed unittests\n/, "")
results = Results.new(stdout, stderr, status) results = Results.new(stdout, stderr, status)
if %w[c cpp].include?(options[:language]) stdout, stderr, status = Open3.capture3("valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --verbose spec/run/testparser")
stdout, stderr, status = Open3.capture3("valgrind --leak-check=full --show-leak-kinds=all --track-origins=yes --verbose spec/run/testparser") vgout = stdout + stderr
vgout = stdout + stderr File.binwrite("spec/run/.vgout", vgout)
File.binwrite("spec/run/.vgout", vgout) vgout.scan(/(?:definitely|indirectly) lost: (\d+) bytes/) do |match|
vgout.scan(/lost: (\d+) bytes/) do |match| bytes = $1.to_i
bytes = $1.to_i if bytes > 0
if bytes > 0 raise "Valgrind detected memory leak"
raise "Valgrind detected memory leak"
end
end end
end end
results results
@@ -1431,6 +1429,9 @@ EOF
expect(results.stdout).to match /first is foo1.*second is bar2/m expect(results.stdout).to match /first is foo1.*second is bar2/m
end end
# D garbage collector was freeing memory for nodes when it should not
# have been. This was due to pointers being in the TreeNode that was
# allocated by C malloc() function instead of allocated using D.
it "does not free memory allocated for tree nodes" do it "does not free memory allocated for tree nodes" do
ext = language == "cpp" ? "c" : language ext = language == "cpp" ? "c" : language
write_grammar(File.read("spec/tree_node_memory_remains.#{ext}.propane")) write_grammar(File.read("spec/tree_node_memory_remains.#{ext}.propane"))
@@ -1623,7 +1624,7 @@ drop /#(.*)\\n/ <<
token id /\\w+/; token id /\\w+/;
Start -> IDs; Start -> IDs;
IDs -> ; IDs -> ;
IDs -> id IDs; IDs -> id:id IDs;
EOF EOF
elsif language == "c" elsif language == "c"
write_grammar <<EOF write_grammar <<EOF
@@ -1665,7 +1666,7 @@ drop /#(.*)\\n/ <<
token id /\\w+/; token id /\\w+/;
Start -> IDs; Start -> IDs;
IDs -> ; IDs -> ;
IDs -> id IDs; IDs -> id:id IDs;
EOF EOF
else # C++ else # C++
write_grammar <<EOF write_grammar <<EOF
@@ -1690,7 +1691,7 @@ drop /#(.*)\\n/ <<
token id /\\w+/; token id /\\w+/;
Start -> IDs; Start -> IDs;
IDs -> ; IDs -> ;
IDs -> id IDs; IDs -> id:id IDs;
EOF EOF
end end
run_propane(language: language) run_propane(language: language)
@@ -10,8 +10,7 @@ int main()
unittest unittest
{ {
string input = "b"; string input = "b";
p_context_t * context; p_context_t * context = p_context_new(input);
context = p_context_new(input);
assert(p_parse(context) == P_SUCCESS); assert(p_parse(context) == P_SUCCESS);
Start * start = p_result(context); Start * start = p_result(context);
assert(start.pToken1 is null); assert(start.pToken1 is null);
@@ -21,6 +20,8 @@ unittest
assert(start.pR is null); assert(start.pR is null);
assert(start.r is null); assert(start.r is null);
p_tree_delete(start);
input = "abcd"; input = "abcd";
context = p_context_new(input); context = p_context_new(input);
assert(p_parse(context) == P_SUCCESS); assert(p_parse(context) == P_SUCCESS);
@@ -35,6 +36,8 @@ unittest
assert(start.pR == start.r); assert(start.pR == start.r);
assert_eq(TOKEN_c, start.pR.pToken1.token); assert_eq(TOKEN_c, start.pR.pToken1.token);
p_tree_delete(start);
input = "bdc"; input = "bdc";
context = p_context_new(input); context = p_context_new(input);
assert(p_parse(context) == P_SUCCESS); assert(p_parse(context) == P_SUCCESS);
@@ -43,4 +46,6 @@ unittest
assert(start.pToken2 !is null); assert(start.pToken2 !is null);
assert(start.pR !is null); assert(start.pR !is null);
assert_eq(TOKEN_d, start.pR.pToken1.token); assert_eq(TOKEN_d, start.pR.pToken1.token);
p_tree_delete(start);
} }
+7 -2
View File
@@ -10,8 +10,7 @@ int main()
unittest unittest
{ {
string input = "b"; string input = "b";
p_context_t * context; p_context_t * context = p_context_new(input);
context = p_context_new(input);
assert(p_parse(context) == P_SUCCESS); assert(p_parse(context) == P_SUCCESS);
Start * start = p_result(context); Start * start = p_result(context);
assert(start.pToken1 is null); assert(start.pToken1 is null);
@@ -20,6 +19,8 @@ unittest
assert(start.pR3 is null); assert(start.pR3 is null);
assert(start.pR is null); assert(start.pR is null);
p_tree_delete(start);
input = "abcd"; input = "abcd";
context = p_context_new(input); context = p_context_new(input);
assert(p_parse(context) == P_SUCCESS); assert(p_parse(context) == P_SUCCESS);
@@ -32,6 +33,8 @@ unittest
assert(start.pR == start.pR3); assert(start.pR == start.pR3);
assert_eq(TOKEN_c, start.pR.pToken1.token); assert_eq(TOKEN_c, start.pR.pToken1.token);
p_tree_delete(start);
input = "bdc"; input = "bdc";
context = p_context_new(input); context = p_context_new(input);
assert(p_parse(context) == P_SUCCESS); assert(p_parse(context) == P_SUCCESS);
@@ -40,4 +43,6 @@ unittest
assert(start.pToken2 !is null); assert(start.pToken2 !is null);
assert(start.pR !is null); assert(start.pR !is null);
assert_eq(TOKEN_d, start.pR.pToken1.token); assert_eq(TOKEN_d, start.pR.pToken1.token);
p_tree_delete(start);
} }
+7 -2
View File
@@ -10,8 +10,7 @@ int main()
unittest unittest
{ {
string input = "bbbb"; string input = "bbbb";
p_context_t * context; p_context_t * context = p_context_new(input);
context = p_context_new(input);
assert(p_parse(context) == P_SUCCESS); assert(p_parse(context) == P_SUCCESS);
Start * start = p_result(context); Start * start = p_result(context);
assert(start.bs); assert(start.bs);
@@ -20,6 +19,8 @@ unittest
assert(start.bs.bs.bs.b); assert(start.bs.bs.bs.b);
assert(start.bs.bs.bs.bs.b); assert(start.bs.bs.bs.bs.b);
p_tree_delete(start);
context = p_context_new(input); context = p_context_new(input);
assert(p_parse_Bs(context) == P_SUCCESS); assert(p_parse_Bs(context) == P_SUCCESS);
Bs * bs = p_result_Bs(context); Bs * bs = p_result_Bs(context);
@@ -28,9 +29,13 @@ unittest
assert(bs.bs.bs.b); assert(bs.bs.bs.b);
assert(bs.bs.bs.bs.b); assert(bs.bs.bs.bs.b);
p_tree_delete_Bs(bs);
input = "c"; input = "c";
context = p_context_new(input); context = p_context_new(input);
assert(p_parse_R(context) == P_SUCCESS); assert(p_parse_R(context) == P_SUCCESS);
R * r = p_result_R(context); R * r = p_result_R(context);
assert(r.c); assert(r.c);
p_tree_delete_R(r);
} }
+16
View File
@@ -19,6 +19,22 @@ int main()
context = p_context_new((uint8_t const *)input, strlen(input)); context = p_context_new((uint8_t const *)input, strlen(input));
assert(p_parse(context) == P_SUCCESS); assert(p_parse(context) == P_SUCCESS);
Start * start = p_result(context); Start * start = p_result(context);
assert(start->pIDs);
assert(start->pIDs->id);
#ifdef __cplusplus
assert(start->pIDs->id->comments == "# c1\n# c2\n");
#else
assert(start->pIDs->id->comments);
assert(strcmp(start->pIDs->id->comments, "# c1\n# c2\n") == 0);
#endif
assert(start->pIDs->pIDs);
assert(start->pIDs->pIDs->id);
#ifdef __cplusplus
assert(start->pIDs->pIDs->id->comments == "# s1\n# s2\n");
#else
assert(start->pIDs->pIDs->id->comments);
assert(strcmp(start->pIDs->pIDs->id->comments, "# s1\n# s2\n") == 0);
#endif
#ifndef __cplusplus #ifndef __cplusplus
free(context->comments); free(context->comments);
+9 -2
View File
@@ -17,8 +17,15 @@ unittest
" # s1\n" ~ " # s1\n" ~
" # s2\n" ~ " # s2\n" ~
"second\n"; "second\n";
p_context_t * context; p_context_t * context = p_context_new(input);
context = p_context_new(input);
assert(p_parse(context) == P_SUCCESS); assert(p_parse(context) == P_SUCCESS);
Start * start = p_result(context); Start * start = p_result(context);
assert(start.pIDs);
assert(start.pIDs.id);
assert(start.pIDs.id.comments == "# c1\n# c2\n");
assert(start.pIDs.pIDs);
assert(start.pIDs.pIDs.id);
assert(start.pIDs.pIDs.id.comments == "# s1\n# s2\n");
p_tree_delete(start);
} }
+7 -2
View File
@@ -10,8 +10,7 @@ int main()
unittest unittest
{ {
string input = "a, ((b)), b"; string input = "a, ((b)), b";
p_context_t * context; p_context_t * context = p_context_new(input);
context = p_context_new(input);
assert_eq(P_SUCCESS, p_parse(context)); assert_eq(P_SUCCESS, p_parse(context));
Start * start = p_result(context); Start * start = p_result(context);
assert(start.pItems1 !is null); assert(start.pItems1 !is null);
@@ -37,12 +36,16 @@ unittest
assert_eq(22, itemsmore.pItem.pToken1.pvalue); assert_eq(22, itemsmore.pItem.pToken1.pvalue);
assert(itemsmore.pItemsMore is null); assert(itemsmore.pItemsMore is null);
p_tree_delete(start);
input = ""; input = "";
context = p_context_new(input); context = p_context_new(input);
assert_eq(P_SUCCESS, p_parse(context)); assert_eq(P_SUCCESS, p_parse(context));
start = p_result(context); start = p_result(context);
assert(start.pItems is null); assert(start.pItems is null);
p_tree_delete(start);
input = "2 1"; input = "2 1";
context = p_context_new(input); context = p_context_new(input);
assert_eq(P_SUCCESS, p_parse(context)); assert_eq(P_SUCCESS, p_parse(context));
@@ -54,4 +57,6 @@ unittest
assert(start.pItems.pItem.pDual.pOne2 !is null); assert(start.pItems.pItem.pDual.pOne2 !is null);
assert(start.pItems.pItem.pDual.pTwo2 is null); assert(start.pItems.pItem.pDual.pTwo2 is null);
assert(start.pItems.pItem.pDual.pOne1 is null); assert(start.pItems.pItem.pDual.pOne1 is null);
p_tree_delete(start);
} }
+3 -2
View File
@@ -10,12 +10,13 @@ int main()
unittest unittest
{ {
string input = "\na\nb\nc"; string input = "\na\nb\nc";
p_context_t * context; p_context_t * context = p_context_new(input);
context = p_context_new(input);
assert(p_parse(context) == P_SUCCESS); assert(p_parse(context) == P_SUCCESS);
Start * start = p_result(context); Start * start = p_result(context);
assert_eq(TOKEN_a, start.first.pToken.token); assert_eq(TOKEN_a, start.first.pToken.token);
assert_eq(TOKEN_b, start.second.pToken.token); assert_eq(TOKEN_b, start.second.pToken.token);
assert_eq(TOKEN_c, start.third.pToken.token); assert_eq(TOKEN_c, start.third.pToken.token);
p_tree_delete(start);
} }
+9 -2
View File
@@ -10,8 +10,7 @@ int main()
unittest unittest
{ {
string input = "\na\n bb ccc"; string input = "\na\n bb ccc";
p_context_t * context; p_context_t * context = p_context_new(input);
context = p_context_new(input);
assert(p_parse(context) == P_SUCCESS); assert(p_parse(context) == P_SUCCESS);
Start * start = p_result(context); Start * start = p_result(context);
@@ -34,6 +33,8 @@ unittest
assert_eq(3, start.end_position.row); assert_eq(3, start.end_position.row);
assert_eq(8, start.end_position.col); assert_eq(8, start.end_position.col);
p_tree_delete(start);
input = "a\nbb"; input = "a\nbb";
context = p_context_new(input); context = p_context_new(input);
assert(p_parse(context) == P_SUCCESS); assert(p_parse(context) == P_SUCCESS);
@@ -58,6 +59,8 @@ unittest
assert_eq(2, start.end_position.row); assert_eq(2, start.end_position.row);
assert_eq(2, start.end_position.col); assert_eq(2, start.end_position.col);
p_tree_delete(start);
input = "a\nc\nc"; input = "a\nc\nc";
context = p_context_new(input); context = p_context_new(input);
assert(p_parse(context) == P_SUCCESS); assert(p_parse(context) == P_SUCCESS);
@@ -82,6 +85,8 @@ unittest
assert_eq(3, start.end_position.row); assert_eq(3, start.end_position.row);
assert_eq(1, start.end_position.col); assert_eq(1, start.end_position.col);
p_tree_delete(start);
input = "a"; input = "a";
context = p_context_new(input); context = p_context_new(input);
assert(p_parse(context) == P_SUCCESS); assert(p_parse(context) == P_SUCCESS);
@@ -101,4 +106,6 @@ unittest
assert_eq(1, start.position.col); assert_eq(1, start.position.col);
assert_eq(1, start.end_position.row); assert_eq(1, start.end_position.row);
assert_eq(1, start.end_position.col); assert_eq(1, start.end_position.col);
p_tree_delete(start);
} }
+1
View File
@@ -405,4 +405,5 @@ def main() -> int
stderr.writeln("Index ", i, ": expected ", expected[i].name, "/", expected[i].token, ", got ", pfds[i].name.pvalue.s, "/", pfds[i].returntype.pType.pTypeBase.pToken1.token); stderr.writeln("Index ", i, ": expected ", expected[i].name, "/", expected[i].token, ", got ", pfds[i].name.pvalue.s, "/", pfds[i].returntype.pType.pTypeBase.pToken1.token);
} }
} }
p_tree_delete(pmod);
} }
+7 -2
View File
@@ -10,8 +10,7 @@ int main()
unittest unittest
{ {
string input = "a, ((b)), b"; string input = "a, ((b)), b";
p_context_t * context; p_context_t * context = p_context_new(input);
context = p_context_new(input);
assert_eq(P_SUCCESS, p_parse(context)); assert_eq(P_SUCCESS, p_parse(context));
PStartS * start = p_result(context); PStartS * start = p_result(context);
assert(start.pItems1 !is null); assert(start.pItems1 !is null);
@@ -37,12 +36,16 @@ unittest
assert_eq(22, itemsmore.pItem.pToken1.pvalue); assert_eq(22, itemsmore.pItem.pToken1.pvalue);
assert(itemsmore.pItemsMore is null); assert(itemsmore.pItemsMore is null);
p_tree_delete(start);
input = ""; input = "";
context = p_context_new(input); context = p_context_new(input);
assert_eq(P_SUCCESS, p_parse(context)); assert_eq(P_SUCCESS, p_parse(context));
start = p_result(context); start = p_result(context);
assert(start.pItems is null); assert(start.pItems is null);
p_tree_delete(start);
input = "2 1"; input = "2 1";
context = p_context_new(input); context = p_context_new(input);
assert_eq(P_SUCCESS, p_parse(context)); assert_eq(P_SUCCESS, p_parse(context));
@@ -54,4 +57,6 @@ unittest
assert(start.pItems.pItem.pDual.pOne2 !is null); assert(start.pItems.pItem.pDual.pOne2 !is null);
assert(start.pItems.pItem.pDual.pTwo2 is null); assert(start.pItems.pItem.pDual.pTwo2 is null);
assert(start.pItems.pItem.pDual.pOne1 is null); assert(start.pItems.pItem.pDual.pOne1 is null);
p_tree_delete(start);
} }
+5 -2
View File
@@ -10,8 +10,7 @@ int main()
unittest unittest
{ {
string input = "abbccc"; string input = "abbccc";
p_context_t * context; p_context_t * context = p_context_new(input);
context = p_context_new(input);
assert(p_parse(context) == P_SUCCESS); assert(p_parse(context) == P_SUCCESS);
Start * start = p_result(context); Start * start = p_result(context);
@@ -47,6 +46,8 @@ unittest
assert_eq(1, start.end_position.row); assert_eq(1, start.end_position.row);
assert_eq(6, start.end_position.col); assert_eq(6, start.end_position.col);
p_tree_delete(start);
input = "\n\n bb\nc\ncc\n\n a"; input = "\n\n bb\nc\ncc\n\n a";
context = p_context_new(input); context = p_context_new(input);
assert(p_parse(context) == P_SUCCESS); assert(p_parse(context) == P_SUCCESS);
@@ -83,4 +84,6 @@ unittest
assert_eq(3, start.position.col); assert_eq(3, start.position.col);
assert_eq(7, start.end_position.row); assert_eq(7, start.end_position.row);
assert_eq(6, start.end_position.col); assert_eq(6, start.end_position.col);
p_tree_delete(start);
} }