14 Commits
49 changed files with 1001 additions and 338 deletions

No files matched your search

-1
View File
@@ -1,3 +1,2 @@
--format documentation
--color --color
--require spec_helper --require spec_helper
+18
View File
@@ -1,3 +1,21 @@
## v3.0.0
### New Features
- Add support for multiple starting rules (#38)
- Add `p_free_tree()` functions to reclaim generated tree memory
- Add `free_token_node` grammar statement to reclaim user-allocated memory stored in a Token tree node `pvalue` field
- Add valgrind memory leak tests to unit tests
- Fix build issues for C++ to officially support C++ target output
### Improvements
- Document `p_lex()` and `p_token_info_t` in user guide (#37)
### Breaking changes
- Rename AST generation mode to tree generation mode (see [UPGRADING.md]())
## v2.3.0 ## v2.3.0
### New Features ### New Features
+12 -10
View File
@@ -2,39 +2,41 @@ GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
base64 (0.3.0) base64 (0.3.0)
date (3.4.1) date (3.5.1)
diff-lcs (1.6.2) diff-lcs (1.6.2)
docile (1.4.1) docile (1.4.1)
erb (5.0.2) erb (6.0.1)
psych (5.2.6) psych (5.3.1)
date date
stringio stringio
rake (13.3.0) rake (13.3.1)
rdoc (6.14.2) rdoc (7.2.0)
erb erb
psych (>= 4.0.0) psych (>= 4.0.0)
tsort
redcarpet (3.6.1) redcarpet (3.6.1)
rspec (3.13.1) rspec (3.13.2)
rspec-core (~> 3.13.0) rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0) rspec-expectations (~> 3.13.0)
rspec-mocks (~> 3.13.0) rspec-mocks (~> 3.13.0)
rspec-core (3.13.5) rspec-core (3.13.6)
rspec-support (~> 3.13.0) rspec-support (~> 3.13.0)
rspec-expectations (3.13.5) rspec-expectations (3.13.5)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0) rspec-support (~> 3.13.0)
rspec-mocks (3.13.5) rspec-mocks (3.13.7)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0) rspec-support (~> 3.13.0)
rspec-support (3.13.4) rspec-support (3.13.7)
simplecov (0.22.0) simplecov (0.22.0)
docile (~> 1.1) docile (~> 1.1)
simplecov-html (~> 0.11) simplecov-html (~> 0.11)
simplecov_json_formatter (~> 0.1) simplecov_json_formatter (~> 0.1)
simplecov-html (0.13.2) simplecov-html (0.13.2)
simplecov_json_formatter (0.1.4) simplecov_json_formatter (0.1.4)
stringio (3.1.7) stringio (3.2.0)
syntax (1.2.2) syntax (1.2.2)
tsort (0.2.0)
PLATFORMS PLATFORMS
ruby ruby
+4 -2
View File
@@ -6,8 +6,10 @@ Propane is a LALR Parser Generator (LPG) which:
* generates a built-in lexer to tokenize input * generates a built-in lexer to tokenize input
* supports UTF-8 lexer inputs * supports UTF-8 lexer inputs
* generates a table-driven shift/reduce parser to parse input in linear time * generates a table-driven shift/reduce parser to parse input in linear time
* targets C or D language outputs * targets C, C++, or D language outputs
* optionally supports automatic full AST generation * optionally supports automatic full parse tree generation
* supports starting parsing from multiple start rules
* tracks input text start and end positions for all matched tokens/rules
* is MIT-licensed * is MIT-licensed
* is distributable as a standalone Ruby script * is distributable as a standalone Ruby script
+14
View File
@@ -1,5 +1,7 @@
require "rake/clean" require "rake/clean"
require "rspec/core/rake_task" require "rspec/core/rake_task"
require "simplecov"
require "stringio"
CLEAN.include %w[spec/run gen .yardoc yard coverage dist] CLEAN.include %w[spec/run gen .yardoc yard coverage dist]
@@ -12,6 +14,18 @@ RSpec::Core::RakeTask.new(:spec, :example_pattern) do |task, args|
task.rspec_opts = %W[-e "#{args.example_pattern}" -f documentation] task.rspec_opts = %W[-e "#{args.example_pattern}" -f documentation]
end end
end end
task :spec do |task, args|
unless ENV["dist_specs"]
original_stdout = $stdout
sio = StringIO.new
$stdout = sio
SimpleCov.collate Dir["coverage/.resultset.json"]
$stdout = original_stdout
sio.string.lines.each do |line|
$stdout.write(line) unless line =~ /Coverage report generated for/
end
end
end
# dspec task is useful to test the distributable release script, but is not # dspec task is useful to test the distributable release script, but is not
# useful for coverage information. # useful for coverage information.
+7
View File
@@ -0,0 +1,7 @@
## v3.0.0
### Grammar changes
- Rename `ast;` statement to `tree;`.
- Rename `ast_prefix;` statement to `tree_prefix;`.
- Rename `ast_suffix;` statement to `tree_suffix;`.
+117 -44
View File
@@ -638,7 +638,7 @@ typedef struct
* reduce action. * reduce action.
*/ */
parser_state_id_t n_states; parser_state_id_t n_states;
<% if @grammar.ast %> <% if @grammar.tree %>
/** /**
* Map of rule components to rule set child fields. * Map of rule components to rule set child fields.
@@ -646,7 +646,7 @@ typedef struct
uint16_t const * rule_set_node_field_index_map; uint16_t const * rule_set_node_field_index_map;
/** /**
* Number of rule set AST node fields. * Number of rule set tree node fields.
*/ */
uint16_t rule_set_node_field_array_size; uint16_t rule_set_node_field_array_size;
@@ -688,19 +688,21 @@ typedef struct
/** Parser value from this state. */ /** Parser value from this state. */
<%= @grammar.prefix %>value_t pvalue; <%= @grammar.prefix %>value_t pvalue;
<% if @grammar.ast %> <% if @grammar.tree %>
/** AST node. */ /** tree node. */
void * ast_node; void * tree_node;
<% end %> <% end %>
} state_value_t; } state_value_t;
/** Common AST node structure. */ /** Common tree node structure. */
typedef struct typedef struct TreeNode_s
{ {
<%= @grammar.prefix %>position_t position; <%= @grammar.prefix %>position_t position;
<%= @grammar.prefix %>position_t end_position; <%= @grammar.prefix %>position_t end_position;
void * fields[]; uint16_t n_fields;
} ASTNode; uint8_t is_token;
struct TreeNode_s * fields[];
} TreeNode;
/** Parser shift table. */ /** Parser shift table. */
static const shift_t parser_shift_table[] = { static const shift_t parser_shift_table[] = {
@@ -709,7 +711,7 @@ static const shift_t parser_shift_table[] = {
<% end %> <% end %>
}; };
<% if @grammar.ast %> <% if @grammar.tree %>
<% @grammar.rules.each do |rule| %> <% @grammar.rules.each do |rule| %>
<% unless rule.flat_rule_set_node_field_index_map? %> <% unless rule.flat_rule_set_node_field_index_map? %>
const uint16_t r_<%= rule.name.gsub("$", "_") %><%= rule.id %>_node_field_index_map[<%= rule.rule_set_node_field_index_map.size %>] = {<%= rule.rule_set_node_field_index_map.map {|v| v.to_s}.join(", ") %>}; const uint16_t r_<%= rule.name.gsub("$", "_") %><%= rule.id %>_node_field_index_map[<%= rule.rule_set_node_field_index_map.size %>] = {<%= rule.rule_set_node_field_index_map.map {|v| v.to_s}.join(", ") %>};
@@ -724,14 +726,14 @@ static const reduce_t parser_reduce_table[] = {
<%= reduce[:token_id] %>u, /* Token: <%= reduce[:token] ? reduce[:token].name : "(any)" %> */ <%= reduce[:token_id] %>u, /* Token: <%= reduce[:token] ? reduce[:token].name : "(any)" %> */
<%= reduce[:rule_id] %>u, /* Rule ID */ <%= reduce[:rule_id] %>u, /* Rule ID */
<%= reduce[:rule_set_id] %>u, /* Rule set ID (<%= reduce[:rule].rule_set.name %>) */ <%= reduce[:rule_set_id] %>u, /* Rule set ID (<%= reduce[:rule].rule_set.name %>) */
<% if @grammar.ast %> <% if @grammar.tree %>
<%= reduce[:n_states] %>u, /* Number of states */ <%= reduce[:n_states] %>u, /* Number of states */
<% if reduce[:rule].flat_rule_set_node_field_index_map? %> <% if reduce[:rule].flat_rule_set_node_field_index_map? %>
NULL, /* No rule set node field index map (flat map) */ NULL, /* No rule set node field index map (flat map) */
<% else %> <% else %>
&r_<%= reduce[:rule].name.gsub("$", "_") %><%= reduce[:rule].id %>_node_field_index_map[0], /* Rule set node field index map */ &r_<%= reduce[:rule].name.gsub("$", "_") %><%= reduce[:rule].id %>_node_field_index_map[0], /* Rule set node field index map */
<% end %> <% end %>
<%= reduce[:rule].rule_set.ast_fields.size %>, /* Number of AST fields */ <%= reduce[:rule].rule_set.tree_fields.size %>, /* Number of tree fields */
<%= reduce[:propagate_optional_target] %>}, /* Propagate optional target? */ <%= reduce[:propagate_optional_target] %>}, /* Propagate optional target? */
<% else %> <% else %>
<%= reduce[:n_states] %>u}, <%= reduce[:n_states] %>u},
@@ -805,7 +807,7 @@ static void state_values_stack_push(state_values_stack_t * stack)
if (current_length >= current_capacity) if (current_length >= current_capacity)
{ {
size_t const new_capacity = current_capacity * 2u; size_t const new_capacity = current_capacity * 2u;
state_value_t * new_entries = malloc(new_capacity * sizeof(state_value_t)); state_value_t * new_entries = (state_value_t *)malloc(new_capacity * sizeof(state_value_t));
memcpy(new_entries, stack->entries, current_length * sizeof(state_value_t)); memcpy(new_entries, stack->entries, current_length * sizeof(state_value_t));
free(stack->entries); free(stack->entries);
stack->capacity = new_capacity; stack->capacity = new_capacity;
@@ -839,7 +841,7 @@ static void state_values_stack_free(state_values_stack_t * stack)
free(stack->entries); free(stack->entries);
} }
<% unless @grammar.ast %> <% unless @grammar.tree %>
/** /**
* Execute user code associated with a parser rule. * Execute user code associated with a parser rule.
* *
@@ -922,6 +924,8 @@ static size_t check_reduce(size_t state_id, <%= @grammar.prefix %>token_t token)
* *
* @param context * @param context
* Lexer/parser context structure. * Lexer/parser context structure.
* @start_state_id
* ID of the state in which to start.
* *
* @retval P_SUCCESS * @retval P_SUCCESS
* The parser successfully matched the input text. The parse result value * The parser successfully matched the input text. The parse result value
@@ -934,19 +938,20 @@ static size_t check_reduce(size_t state_id, <%= @grammar.prefix %>token_t token)
* @reval P_UNEXPECTED_INPUT * @reval P_UNEXPECTED_INPUT
* Input text does not match any lexer pattern. * Input text does not match any lexer pattern.
*/ */
size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context) static size_t parse_from(<%= @grammar.prefix %>context_t * context, size_t start_state_id)
{ {
<%= @grammar.prefix %>token_info_t token_info; <%= @grammar.prefix %>token_info_t token_info;
<%= @grammar.prefix %>token_t token = INVALID_TOKEN_ID; <%= @grammar.prefix %>token_t token = INVALID_TOKEN_ID;
state_values_stack_t statevalues; state_values_stack_t statevalues;
size_t reduced_rule_set = INVALID_ID; size_t reduced_rule_set = INVALID_ID;
<% if @grammar.ast %> <% if @grammar.tree %>
void * reduced_parser_node; void * reduced_parser_node;
<% else %> <% else %>
<%= @grammar.prefix %>value_t reduced_parser_value; <%= @grammar.prefix %>value_t reduced_parser_value;
<% end %> <% end %>
state_values_stack_init(&statevalues); state_values_stack_init(&statevalues);
state_values_stack_push(&statevalues); state_values_stack_push(&statevalues);
state_values_stack_index(&statevalues, -1)->state_id = start_state_id;
size_t result; size_t result;
for (;;) for (;;)
{ {
@@ -971,8 +976,8 @@ size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context)
if ((shift_state != INVALID_ID) && (token == TOKEN___EOF)) if ((shift_state != INVALID_ID) && (token == TOKEN___EOF))
{ {
/* Successful parse. */ /* Successful parse. */
<% if @grammar.ast %> <% if @grammar.tree %>
context->parse_result = (<%= @grammar.ast_prefix %><%= @grammar.start_rule %><%= @grammar.ast_suffix %> *)state_values_stack_index(&statevalues, -1)->ast_node; context->parse_result = state_values_stack_index(&statevalues, -1)->tree_node;
<% else %> <% else %>
context->parse_result = state_values_stack_index(&statevalues, -1)->pvalue; context->parse_result = state_values_stack_index(&statevalues, -1)->pvalue;
<% end %> <% end %>
@@ -988,13 +993,15 @@ size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context)
if (reduced_rule_set == INVALID_ID) if (reduced_rule_set == INVALID_ID)
{ {
/* We shifted a token, mark it consumed. */ /* We shifted a token, mark it consumed. */
<% if @grammar.ast %> <% if @grammar.tree %>
<%= @grammar.ast_prefix %>Token<%= @grammar.ast_suffix %> * token_ast_node = malloc(sizeof(<%= @grammar.ast_prefix %>Token<%= @grammar.ast_suffix %>)); <%= @grammar.tree_prefix %>Token<%= @grammar.tree_suffix %> * token_tree_node = (<%= @grammar.tree_prefix %>Token<%= @grammar.tree_suffix %> *)malloc(sizeof(<%= @grammar.tree_prefix %>Token<%= @grammar.tree_suffix %>));
token_ast_node->position = token_info.position; token_tree_node->position = token_info.position;
token_ast_node->end_position = token_info.end_position; token_tree_node->end_position = token_info.end_position;
token_ast_node->token = token; token_tree_node->n_fields = 0u;
token_ast_node->pvalue = token_info.pvalue; token_tree_node->is_token = 1u;
state_values_stack_index(&statevalues, -1)->ast_node = token_ast_node; token_tree_node->token = token;
token_tree_node->pvalue = token_info.pvalue;
state_values_stack_index(&statevalues, -1)->tree_node = token_tree_node;
<% else %> <% else %>
state_values_stack_index(&statevalues, -1)->pvalue = token_info.pvalue; state_values_stack_index(&statevalues, -1)->pvalue = token_info.pvalue;
<% end %> <% end %>
@@ -1003,8 +1010,8 @@ size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context)
else else
{ {
/* We shifted a RuleSet. */ /* We shifted a RuleSet. */
<% if @grammar.ast %> <% if @grammar.tree %>
state_values_stack_index(&statevalues, -1)->ast_node = reduced_parser_node; state_values_stack_index(&statevalues, -1)->tree_node = reduced_parser_node;
<% else %> <% else %>
state_values_stack_index(&statevalues, -1)->pvalue = reduced_parser_value; state_values_stack_index(&statevalues, -1)->pvalue = reduced_parser_value;
<%= @grammar.prefix %>value_t new_parse_result; <%= @grammar.prefix %>value_t new_parse_result;
@@ -1020,39 +1027,38 @@ size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context)
if (reduce_index != INVALID_ID) if (reduce_index != INVALID_ID)
{ {
/* We have something to reduce. */ /* We have something to reduce. */
<% if @grammar.ast %> <% if @grammar.tree %>
if (parser_reduce_table[reduce_index].propagate_optional_target) if (parser_reduce_table[reduce_index].propagate_optional_target)
{ {
reduced_parser_node = state_values_stack_index(&statevalues, -1)->ast_node; reduced_parser_node = state_values_stack_index(&statevalues, -1)->tree_node;
} }
else if (parser_reduce_table[reduce_index].n_states > 0) 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; 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(TreeNode) + n_fields * sizeof(void *);
TreeNode * node = (TreeNode *)malloc(bytes);
memset(node, 0, bytes);
node->position = INVALID_POSITION; node->position = INVALID_POSITION;
node->end_position = INVALID_POSITION; node->end_position = INVALID_POSITION;
for (size_t i = 0; i < n_fields; i++) node->n_fields = n_fields;
{
node->fields[i] = NULL;
}
if (parser_reduce_table[reduce_index].rule_set_node_field_index_map == NULL) 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++) 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] = (TreeNode *)state_values_stack_index(&statevalues, -(int)parser_reduce_table[reduce_index].n_states + (int)i)->tree_node;
} }
} }
else else
{ {
for (size_t i = 0; i < parser_reduce_table[reduce_index].n_states; i++) 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]] = (TreeNode *)state_values_stack_index(&statevalues, -(int)parser_reduce_table[reduce_index].n_states + (int)i)->tree_node;
} }
} }
bool position_found = false; bool position_found = false;
for (size_t i = 0; i < n_fields; i++) for (size_t i = 0; i < n_fields; i++)
{ {
ASTNode * child = (ASTNode *)node->fields[i]; TreeNode * child = node->fields[i];
if ((child != NULL) && <%= @grammar.prefix %>position_valid(child->position)) if ((child != NULL) && <%= @grammar.prefix %>position_valid(child->position))
{ {
if (!position_found) if (!position_found)
@@ -1074,6 +1080,7 @@ size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context)
memset(&reduced_parser_value2, 0, sizeof(reduced_parser_value2)); 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) 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; return P_USER_TERMINATED;
} }
reduced_parser_value = reduced_parser_value2; reduced_parser_value = reduced_parser_value2;
@@ -1097,6 +1104,19 @@ size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context)
return result; return result;
} }
size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context)
{
return parse_from(context, 0u);
}
<% @grammar.start_rules.each_with_index do |start_rule, i| %>
size_t <%= @grammar.prefix %>parse_<%= start_rule %>(<%= @grammar.prefix %>context_t * context)
{
return parse_from(context, <%= i %>u);
}
<% end %>
/** /**
* Get the parse result value. * Get the parse result value.
* *
@@ -1105,18 +1125,29 @@ size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context)
* *
* @return Parse result value. * @return Parse result value.
*/ */
<% if @grammar.ast %> <% if @grammar.tree %>
<%= @grammar.ast_prefix %><%= @grammar.start_rule %><%= @grammar.ast_suffix %> * <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context) <%= @grammar.tree_prefix %><%= @grammar.start_rules[0] %><%= @grammar.tree_suffix %> * <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context)
{
return (<%= @grammar.tree_prefix %><%= @grammar.start_rules[0] %><%= @grammar.tree_suffix %> *) context->parse_result;
}
<% @grammar.start_rules.each_with_index do |start_rule, i| %>
<%= @grammar.tree_prefix %><%= start_rule %><%= @grammar.tree_suffix %> * <%= @grammar.prefix %>result_<%= start_rule %>(<%= @grammar.prefix %>context_t * context)
{
return (<%= @grammar.tree_prefix %><%= start_rule %><%= @grammar.tree_suffix %> *) context->parse_result;
}
<% end %>
<% else %> <% else %>
<%= start_rule_type[1] %> <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context) <%= start_rule_type[1] %> <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context)
<% end %>
{ {
<% if @grammar.ast %>
return context->parse_result;
<% else %>
return context->parse_result.v_<%= start_rule_type[0] %>; return context->parse_result.v_<%= start_rule_type[0] %>;
<% end %>
} }
<% @grammar.start_rules.each_with_index do |start_rule, i| %>
<%= start_rule_type(i)[1] %> <%= @grammar.prefix %>result_<%= start_rule %>(<%= @grammar.prefix %>context_t * context)
{
return context->parse_result.v_<%= start_rule_type(i)[0] %>;
}
<% end %>
<% end %>
/** /**
* Get the current text input position. * Get the current text input position.
@@ -1153,3 +1184,45 @@ size_t <%= @grammar.prefix %>user_terminate_code(<%= @grammar.prefix %>context_t
{ {
return context->token; return context->token;
} }
<% if @grammar.tree %>
static void free_tree_node(TreeNode * node)
{
if (node->is_token)
{
<% if @grammar.free_token_node %>
<%= @grammar.free_token_node %>((<%= @grammar.tree_prefix %>Token<%= @grammar.tree_suffix %> *) node);
<% end %>
/* 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_tree_node(node->fields[i]);
}
}
}
free(node);
}
/**
* Free all tree node memory.
*/
void <%= @grammar.prefix %>free_tree(<%= @grammar.tree_prefix %><%= @grammar.start_rules[0] %><%= @grammar.tree_suffix %> * tree)
{
free_tree_node((TreeNode *)tree);
}
<% @grammar.start_rules.each_with_index do |start_rule, i| %>
/**
* Free all tree node memory.
*/
void <%= @grammar.prefix %>free_tree_<%= start_rule %>(<%= @grammar.tree_prefix %><%= start_rule %><%= @grammar.tree_suffix %> * tree)
{
free_tree_node((TreeNode *)tree);
}
<% end %>
<% end %>
+70 -43
View File
@@ -75,7 +75,7 @@ public struct <%= @grammar.prefix %>position_t
} }
} }
<% if @grammar.ast %> <% if @grammar.tree %>
/** Parser values type. */ /** Parser values type. */
public alias <%= @grammar.prefix %>value_t = <%= @grammar.ptype %>; public alias <%= @grammar.prefix %>value_t = <%= @grammar.ptype %>;
<% else %> <% else %>
@@ -88,19 +88,19 @@ public union <%= @grammar.prefix %>value_t
} }
<% end %> <% end %>
<% if @grammar.ast %> <% if @grammar.tree %>
/** Common AST node structure. */ /** Common tree node structure. */
private struct ASTNode 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;
void *[0] fields; void *[0] fields;
} }
/** AST node types. @{ */ /** Tree node types. @{ */
public struct <%= @grammar.ast_prefix %>Token<%= @grammar.ast_suffix %> public struct <%= @grammar.tree_prefix %>Token<%= @grammar.tree_suffix %>
{ {
/* ASTNode 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;
<%= @grammar.prefix %>token_t token; <%= @grammar.prefix %>token_t token;
@@ -110,11 +110,11 @@ public struct <%= @grammar.ast_prefix %>Token<%= @grammar.ast_suffix %>
<% @parser.rule_sets.each do |name, rule_set| %> <% @parser.rule_sets.each do |name, rule_set| %>
<% next if name.start_with?("$") %> <% next if name.start_with?("$") %>
<% next if rule_set.optional? %> <% next if rule_set.optional? %>
public struct <%= @grammar.ast_prefix %><%= name %><%= @grammar.ast_suffix %> 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;
<% rule_set.ast_fields.each do |fields| %> <% rule_set.tree_fields.each do |fields| %>
union union
{ {
<% fields.each do |field_name, type| %> <% fields.each do |field_name, type| %>
@@ -172,8 +172,8 @@ public struct <%= @grammar.prefix %>context_t
/* Parser context data. */ /* Parser context data. */
/** Parse result value. */ /** Parse result value. */
<% if @grammar.ast %> <% if @grammar.tree %>
<%= @grammar.ast_prefix %><%= @grammar.start_rule %><%= @grammar.ast_suffix %> * parse_result; void * parse_result;
<% else %> <% else %>
<%= @grammar.prefix %>value_t parse_result; <%= @grammar.prefix %>value_t parse_result;
<% end %> <% end %>
@@ -797,7 +797,7 @@ private struct reduce_t
* reduce action. * reduce action.
*/ */
parser_state_id_t n_states; parser_state_id_t n_states;
<% if @grammar.ast %> <% if @grammar.tree %>
/** /**
* Map of rule components to rule set child fields. * Map of rule components to rule set child fields.
@@ -805,7 +805,7 @@ private struct reduce_t
immutable(ushort) * rule_set_node_field_index_map; immutable(ushort) * rule_set_node_field_index_map;
/** /**
* Number of rule set AST node fields. * Number of rule set tree node fields.
*/ */
ushort rule_set_node_field_array_size; ushort rule_set_node_field_array_size;
@@ -847,9 +847,9 @@ private struct state_value_t
/** Parser value from this state. */ /** Parser value from this state. */
<%= @grammar.prefix %>value_t pvalue; <%= @grammar.prefix %>value_t pvalue;
<% if @grammar.ast %> <% if @grammar.tree %>
/** AST node. */ /** Tree node. */
void * ast_node; void * tree_node;
<% end %> <% end %>
this(size_t state_id) this(size_t state_id)
@@ -865,7 +865,7 @@ private immutable shift_t[] parser_shift_table = [
<% end %> <% end %>
]; ];
<% if @grammar.ast %> <% if @grammar.tree %>
<% @grammar.rules.each do |rule| %> <% @grammar.rules.each do |rule| %>
<% unless rule.flat_rule_set_node_field_index_map? %> <% unless rule.flat_rule_set_node_field_index_map? %>
immutable ushort[<%= rule.rule_set_node_field_index_map.size %>] r_<%= rule.name.gsub("$", "_") %><%= rule.id %>_node_field_index_map = [<%= rule.rule_set_node_field_index_map.map {|v| v.to_s}.join(", ") %>]; immutable ushort[<%= rule.rule_set_node_field_index_map.size %>] r_<%= rule.name.gsub("$", "_") %><%= rule.id %>_node_field_index_map = [<%= rule.rule_set_node_field_index_map.map {|v| v.to_s}.join(", ") %>];
@@ -880,14 +880,14 @@ private immutable reduce_t[] parser_reduce_table = [
<%= reduce[:token_id] %>u, /* Token: <%= reduce[:token] ? reduce[:token].name : "(any)" %> */ <%= reduce[:token_id] %>u, /* Token: <%= reduce[:token] ? reduce[:token].name : "(any)" %> */
<%= reduce[:rule_id] %>u, /* Rule ID */ <%= reduce[:rule_id] %>u, /* Rule ID */
<%= reduce[:rule_set_id] %>u, /* Rule set ID (<%= reduce[:rule].rule_set.name %>) */ <%= reduce[:rule_set_id] %>u, /* Rule set ID (<%= reduce[:rule].rule_set.name %>) */
<% if @grammar.ast %> <% if @grammar.tree %>
<%= reduce[:n_states] %>u, /* Number of states */ <%= reduce[:n_states] %>u, /* Number of states */
<% if reduce[:rule].flat_rule_set_node_field_index_map? %> <% if reduce[:rule].flat_rule_set_node_field_index_map? %>
null, /* No rule set node field index map (flat map) */ null, /* No rule set node field index map (flat map) */
<% else %> <% else %>
&r_<%= reduce[:rule].name.gsub("$", "_") %><%= reduce[:rule].id %>_node_field_index_map[0], /* Rule set node field index map */ &r_<%= reduce[:rule].name.gsub("$", "_") %><%= reduce[:rule].id %>_node_field_index_map[0], /* Rule set node field index map */
<% end %> <% end %>
<%= reduce[:rule].rule_set.ast_fields.size %>, /* Number of AST fields */ <%= reduce[:rule].rule_set.tree_fields.size %>, /* Number of tree fields */
<%= reduce[:propagate_optional_target] %>), /* Propagate optional target? */ <%= reduce[:propagate_optional_target] %>), /* Propagate optional target? */
<% else %> <% else %>
<%= reduce[:n_states] %>u), /* Number of states */ <%= reduce[:n_states] %>u), /* Number of states */
@@ -902,7 +902,7 @@ private immutable parser_state_t[] parser_state_table = [
<% end %> <% end %>
]; ];
<% unless @grammar.ast %> <% unless @grammar.tree %>
/** /**
* Execute user code associated with a parser rule. * Execute user code associated with a parser rule.
* *
@@ -985,6 +985,8 @@ private size_t check_reduce(size_t state_id, <%= @grammar.prefix %>token_t token
* *
* @param context * @param context
* Lexer/parser context structure. * Lexer/parser context structure.
* @start_state_id
* ID of the state in which to start.
* *
* @retval P_SUCCESS * @retval P_SUCCESS
* The parser successfully matched the input text. The parse result value * The parser successfully matched the input text. The parse result value
@@ -997,13 +999,14 @@ private size_t check_reduce(size_t state_id, <%= @grammar.prefix %>token_t token
* @reval P_UNEXPECTED_INPUT * @reval P_UNEXPECTED_INPUT
* Input text does not match any lexer pattern. * Input text does not match any lexer pattern.
*/ */
public size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context) private size_t parse_from(<%= @grammar.prefix %>context_t * context, size_t start_state_id)
{ {
<%= @grammar.prefix %>token_info_t token_info; <%= @grammar.prefix %>token_info_t token_info;
<%= @grammar.prefix %>token_t token = INVALID_TOKEN_ID; <%= @grammar.prefix %>token_t token = INVALID_TOKEN_ID;
state_value_t[] statevalues = new state_value_t[](1); state_value_t[] statevalues = new state_value_t[](1);
statevalues[0].state_id = start_state_id;
size_t reduced_rule_set = INVALID_ID; size_t reduced_rule_set = INVALID_ID;
<% if @grammar.ast %> <% if @grammar.tree %>
void * reduced_parser_node; void * reduced_parser_node;
<% else %> <% else %>
<%= @grammar.prefix %>value_t reduced_parser_value; <%= @grammar.prefix %>value_t reduced_parser_value;
@@ -1030,8 +1033,8 @@ public size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * cont
if ((shift_state != INVALID_ID) && (token == TOKEN___EOF)) if ((shift_state != INVALID_ID) && (token == TOKEN___EOF))
{ {
/* Successful parse. */ /* Successful parse. */
<% if @grammar.ast %> <% if @grammar.tree %>
context.parse_result = cast(<%= @grammar.ast_prefix %><%= @grammar.start_rule %><%= @grammar.ast_suffix %> *)statevalues[$-1].ast_node; context.parse_result = statevalues[$-1].tree_node;
<% else %> <% else %>
context.parse_result = statevalues[$-1].pvalue; context.parse_result = statevalues[$-1].pvalue;
<% end %> <% end %>
@@ -1045,9 +1048,9 @@ public size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * cont
if (reduced_rule_set == INVALID_ID) if (reduced_rule_set == INVALID_ID)
{ {
/* We shifted a token, mark it consumed. */ /* We shifted a token, mark it consumed. */
<% if @grammar.ast %> <% if @grammar.tree %>
<%= @grammar.ast_prefix %>Token<%= @grammar.ast_suffix %> * token_ast_node = new <%= @grammar.ast_prefix %>Token<%= @grammar.ast_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, token, token_info.pvalue);
statevalues[$-1].ast_node = token_ast_node; statevalues[$-1].tree_node = token_tree_node;
<% else %> <% else %>
statevalues[$-1].pvalue = token_info.pvalue; statevalues[$-1].pvalue = token_info.pvalue;
<% end %> <% end %>
@@ -1056,8 +1059,8 @@ public size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * cont
else else
{ {
/* We shifted a RuleSet. */ /* We shifted a RuleSet. */
<% if @grammar.ast %> <% if @grammar.tree %>
statevalues[$-1].ast_node = reduced_parser_node; statevalues[$-1].tree_node = reduced_parser_node;
<% else %> <% else %>
statevalues[$-1].pvalue = reduced_parser_value; statevalues[$-1].pvalue = reduced_parser_value;
<%= @grammar.prefix %>value_t new_parse_result; <%= @grammar.prefix %>value_t new_parse_result;
@@ -1072,16 +1075,16 @@ public size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * cont
if (reduce_index != INVALID_ID) if (reduce_index != INVALID_ID)
{ {
/* We have something to reduce. */ /* We have something to reduce. */
<% if @grammar.ast %> <% if @grammar.tree %>
if (parser_reduce_table[reduce_index].propagate_optional_target) if (parser_reduce_table[reduce_index].propagate_optional_target)
{ {
reduced_parser_node = statevalues[$ - 1].ast_node; reduced_parser_node = statevalues[$ - 1].tree_node;
} }
else if (parser_reduce_table[reduce_index].n_states > 0) 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; size_t n_fields = parser_reduce_table[reduce_index].rule_set_node_field_array_size;
size_t node_size = ASTNode.sizeof + n_fields * (void *).sizeof; size_t node_size = TreeNode.sizeof + n_fields * (void *).sizeof;
ASTNode * node = cast(ASTNode *)malloc(node_size); TreeNode * node = cast(TreeNode *)malloc(node_size);
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;
@@ -1093,20 +1096,20 @@ public size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * cont
{ {
foreach (i; 0..parser_reduce_table[reduce_index].n_states) foreach (i; 0..parser_reduce_table[reduce_index].n_states)
{ {
node.fields[i] = statevalues[$ - parser_reduce_table[reduce_index].n_states + i].ast_node; node.fields[i] = statevalues[$ - parser_reduce_table[reduce_index].n_states + i].tree_node;
} }
} }
else else
{ {
foreach (i; 0..parser_reduce_table[reduce_index].n_states) foreach (i; 0..parser_reduce_table[reduce_index].n_states)
{ {
node.fields[parser_reduce_table[reduce_index].rule_set_node_field_index_map[i]] = statevalues[$ - parser_reduce_table[reduce_index].n_states + i].ast_node; node.fields[parser_reduce_table[reduce_index].rule_set_node_field_index_map[i]] = statevalues[$ - parser_reduce_table[reduce_index].n_states + i].tree_node;
} }
} }
bool position_found = false; bool position_found = false;
foreach (i; 0..n_fields) foreach (i; 0..n_fields)
{ {
ASTNode * child = cast(ASTNode *)node.fields[i]; TreeNode * child = cast(TreeNode *)node.fields[i];
if (child && child.position.valid) if (child && child.position.valid)
{ {
if (!position_found) if (!position_found)
@@ -1147,6 +1150,19 @@ public size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * cont
} }
} }
public size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context)
{
return parse_from(context, 0u);
}
<% @grammar.start_rules.each_with_index do |start_rule, i| %>
public size_t <%= @grammar.prefix %>parse_<%= start_rule %>(<%= @grammar.prefix %>context_t * context)
{
return parse_from(context, <%= i %>u);
}
<% end %>
/** /**
* Get the parse result value. * Get the parse result value.
* *
@@ -1155,18 +1171,29 @@ public size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * cont
* *
* @return Parse result value. * @return Parse result value.
*/ */
<% if @grammar.ast %> <% if @grammar.tree %>
public <%= @grammar.ast_prefix %><%= @grammar.start_rule %><%= @grammar.ast_suffix %> * <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context) public <%= @grammar.tree_prefix %><%= @grammar.start_rules[0] %><%= @grammar.tree_suffix %> * <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context)
{
return cast(<%= @grammar.tree_prefix %><%= @grammar.start_rules[0] %><%= @grammar.tree_suffix %> *)context.parse_result;
}
<% @grammar.start_rules.each_with_index do |start_rule, i| %>
public <%= @grammar.tree_prefix %><%= start_rule %><%= @grammar.tree_suffix %> * <%= @grammar.prefix %>result_<%= start_rule %>(<%= @grammar.prefix %>context_t * context)
{
return cast(<%= @grammar.tree_prefix %><%= start_rule %><%= @grammar.tree_suffix %> *)context.parse_result;
}
<% end %>
<% else %> <% else %>
public <%= start_rule_type[1] %> <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context) public <%= start_rule_type[1] %> <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context)
<% end %>
{ {
<% if @grammar.ast %>
return context.parse_result;
<% else %>
return context.parse_result.v_<%= start_rule_type[0] %>; return context.parse_result.v_<%= start_rule_type[0] %>;
<% end %>
} }
<% @grammar.start_rules.each_with_index do |start_rule, i| %>
public <%= start_rule_type(i)[1] %> <%= @grammar.prefix %>result_<%= start_rule %>(<%= @grammar.prefix %>context_t * context)
{
return context.parse_result.v_<%= start_rule_type(i)[0] %>;
}
<% end %>
<% end %>
/** /**
* Get the current text input position. * Get the current text input position.
+34 -13
View File
@@ -58,7 +58,7 @@ typedef struct
/** User header code blocks. */ /** User header code blocks. */
<%= @grammar.code_blocks.fetch("header", "") %> <%= @grammar.code_blocks.fetch("header", "") %>
<% if @grammar.ast %> <% if @grammar.tree %>
/** Parser values type. */ /** Parser values type. */
typedef <%= @grammar.ptype %> <%= @grammar.prefix %>value_t; typedef <%= @grammar.ptype %> <%= @grammar.prefix %>value_t;
<% else %> <% else %>
@@ -71,16 +71,18 @@ typedef union
} <%= @grammar.prefix %>value_t; } <%= @grammar.prefix %>value_t;
<% end %> <% end %>
<% if @grammar.ast %> <% if @grammar.tree %>
/** AST node types. @{ */ /** Tree node types. @{ */
typedef struct <%= @grammar.ast_prefix %>Token<%= @grammar.ast_suffix %> typedef struct <%= @grammar.tree_prefix %>Token<%= @grammar.tree_suffix %>
{ {
/* ASTNode 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;
uint16_t n_fields;
uint8_t is_token;
<%= @grammar.prefix %>token_t token; <%= @grammar.prefix %>token_t token;
<%= @grammar.prefix %>value_t pvalue; <%= @grammar.prefix %>value_t pvalue;
} <%= @grammar.ast_prefix %>Token<%= @grammar.ast_suffix %>; } <%= @grammar.tree_prefix %>Token<%= @grammar.tree_suffix %>;
<% @parser.rule_sets.each do |name, rule_set| %> <% @parser.rule_sets.each do |name, rule_set| %>
<% next if name.start_with?("$") %> <% next if name.start_with?("$") %>
@@ -91,11 +93,14 @@ struct <%= name %>;
<% @parser.rule_sets.each do |name, rule_set| %> <% @parser.rule_sets.each do |name, rule_set| %>
<% next if name.start_with?("$") %> <% next if name.start_with?("$") %>
<% next if rule_set.optional? %> <% next if rule_set.optional? %>
typedef struct <%= @grammar.ast_prefix %><%= name %><%= @grammar.ast_suffix %> typedef struct <%= @grammar.tree_prefix %><%= name %><%= @grammar.tree_suffix %>
{ {
<% # 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;
<% rule_set.ast_fields.each do |fields| %> uint16_t n_fields;
uint8_t is_token;
<% rule_set.tree_fields.each do |fields| %>
union union
{ {
<% fields.each do |field_name, type| %> <% fields.each do |field_name, type| %>
@@ -103,7 +108,7 @@ typedef struct <%= @grammar.ast_prefix %><%= name %><%= @grammar.ast_suffix %>
<% end %> <% end %>
}; };
<% end %> <% end %>
} <%= @grammar.ast_prefix %><%= name %><%= @grammar.ast_suffix %>; } <%= @grammar.tree_prefix %><%= name %><%= @grammar.tree_suffix %>;
<% end %> <% end %>
/** @} */ /** @} */
@@ -156,8 +161,8 @@ typedef struct
/* Parser context data. */ /* Parser context data. */
/** Parse result value. */ /** Parse result value. */
<% if @grammar.ast %> <% if @grammar.tree %>
<%= @grammar.ast_prefix %><%= @grammar.start_rule %><%= @grammar.ast_suffix %> * parse_result; void * parse_result;
<% else %> <% else %>
<%= @grammar.prefix %>value_t parse_result; <%= @grammar.prefix %>value_t parse_result;
<% end %> <% end %>
@@ -184,11 +189,27 @@ size_t <%= @grammar.prefix %>decode_code_point(uint8_t const * input, size_t inp
size_t <%= @grammar.prefix %>lex(<%= @grammar.prefix %>context_t * context, <%= @grammar.prefix %>token_info_t * out_token_info); size_t <%= @grammar.prefix %>lex(<%= @grammar.prefix %>context_t * context, <%= @grammar.prefix %>token_info_t * out_token_info);
size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context); size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context);
<% @grammar.start_rules.each_with_index do |start_rule, i| %>
size_t <%= @grammar.prefix %>parse_<%= start_rule %>(<%= @grammar.prefix %>context_t * context);
<% end %>
<% if @grammar.ast %> <% if @grammar.tree %>
<%= @grammar.ast_prefix %><%= @grammar.start_rule %><%= @grammar.ast_suffix %> * <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context); <%= @grammar.tree_prefix %><%= @grammar.start_rules[0] %><%= @grammar.tree_suffix %> * <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context);
<% @grammar.start_rules.each_with_index do |start_rule, i| %>
<%= @grammar.tree_prefix %><%= start_rule %><%= @grammar.tree_suffix %> * <%= @grammar.prefix %>result_<%= start_rule %>(<%= @grammar.prefix %>context_t * context);
<% end %>
<% else %> <% else %>
<%= start_rule_type[1] %> <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context); <%= start_rule_type[1] %> <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context);
<% @grammar.start_rules.each_with_index do |start_rule, i| %>
<%= start_rule_type(i)[1] %> <%= @grammar.prefix %>result_<%= start_rule %>(<%= @grammar.prefix %>context_t * context);
<% end %>
<% end %>
<% if @grammar.tree %>
void <%= @grammar.prefix %>free_tree(<%= @grammar.tree_prefix %><%= @grammar.start_rules[0] %><%= @grammar.tree_suffix %> * tree);
<% @grammar.start_rules.each_with_index do |start_rule, i| %>
void <%= @grammar.prefix %>free_tree_<%= start_rule %>(<%= @grammar.tree_prefix %><%= start_rule %><%= @grammar.tree_suffix %> * tree);
<% end %>
<% end %> <% end %>
<%= @grammar.prefix %>position_t <%= @grammar.prefix %>position(<%= @grammar.prefix %>context_t * context); <%= @grammar.prefix %>position_t <%= @grammar.prefix %>position(<%= @grammar.prefix %>context_t * context);
+182 -35
View File
@@ -13,8 +13,9 @@ Propane is a LALR Parser Generator (LPG) which:
* generates a built-in lexer to tokenize input * generates a built-in lexer to tokenize input
* supports UTF-8 lexer inputs * supports UTF-8 lexer inputs
* generates a table-driven shift/reduce parser to parse input in linear time * generates a table-driven shift/reduce parser to parse input in linear time
* targets C or D language outputs * targets C, C++, or D language outputs
* optionally supports automatic full AST generation * optionally supports automatic full parse tree generation
* supports starting parsing from multiple start rules
* tracks input text start and end positions for all matched tokens/rules * tracks input text start and end positions for all matched tokens/rules
* is MIT-licensed * is MIT-licensed
* is distributable as a standalone Ruby script * is distributable as a standalone Ruby script
@@ -189,21 +190,21 @@ rule.
Parser values for the rules or tokens in the rule pattern can be accessed Parser values for the rules or tokens in the rule pattern can be accessed
positionally with tokens `$1`, `$2`, `$3`, etc... positionally with tokens `$1`, `$2`, `$3`, etc...
Parser rule code blocks are not available in AST generation mode. Parser rule code blocks are not available in tree generation mode.
In AST generation mode, a full parse tree is automatically constructed in In tree generation mode, a full parse tree is automatically constructed in
memory for user code to traverse after parsing is complete. memory for user code to traverse after parsing is complete.
##> AST generation mode - the `ast` statement ##> Tree generation mode - the `tree` statement
To activate AST generation mode, place the `ast` statement in your grammar file: To activate tree generation mode, place the `tree` statement in your grammar file:
``` ```
ast; tree;
``` ```
It is recommended to place this statement early in the grammar. It is recommended to place this statement early in the grammar.
In AST generation mode various aspects of propane's behavior are changed: In tree generation mode various aspects of propane's behavior are changed:
* Only one `ptype` is allowed. * Only one `ptype` is allowed.
* Parser user code blocks are not supported. * Parser user code blocks are not supported.
@@ -214,10 +215,10 @@ In AST generation mode various aspects of propane's behavior are changed:
with the `start` grammar statement, the name of the start struct will be with the `start` grammar statement, the name of the start struct will be
given by the user-specified start rule instead of `Start`. given by the user-specified start rule instead of `Start`.
Example AST generation grammar: Example tree generation grammar:
``` ```
ast; tree;
ptype int; ptype int;
@@ -284,23 +285,23 @@ assert_eq(22, itemsmore.item.pToken1.pvalue);
assert(itemsmore.pItemsMore is null); assert(itemsmore.pItemsMore is null);
``` ```
## `ast_prefix` and `ast_suffix` statements ## `tree_prefix` and `tree_suffix` statements
In AST generation mode, structure types are defined and named based on the In tree generation mode, structure types are defined and named based on the
rules in the grammar. rules in the grammar.
Additionally, a structure type called `Token` is generated to hold parsed Additionally, a structure type called `Token` is generated to hold parsed
token information. token information.
These structure names can be modified by using the `ast_prefix` or `ast_suffix` These structure names can be modified by using the `tree_prefix` or `tree_suffix`
statements in the grammar file. statements in the grammar file.
The field names that point to instances of the structures are not affected by The field names that point to instances of the structures are not affected by
the `ast_prefix` or `ast_suffix` values. the `tree_prefix` or `tree_suffix` values.
For example, if the following two lines were added to the example above: For example, if the following two lines were added to the example above:
``` ```
ast_prefix ABC; tree_prefix ABC;
ast_suffix XYZ; tree_suffix XYZ;
``` ```
Then the types would be used as such instead: Then the types would be used as such instead:
@@ -326,6 +327,36 @@ assert(itemsmore.pItem.pItem.pItem !is null);
assert(itemsmore.pItem.pItem.pItem.pToken1 !is null); assert(itemsmore.pItem.pItem.pItem.pToken1 !is null);
``` ```
## 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`,
the `free_token_node` grammar statement can be used to specify the name of a
function which will be called during the `p_free_tree()` call to free the memory
associated with a token node.
Example:
```
<<
static void free_token(Token * token)
{
free(token->pvalue);
}
>>
tree;
free_token_node free_token;
ptype int *;
token a <<
$$ = (int *)malloc(sizeof(int));
*$$ = 1;
>>
token b <<
$$ = (int *)malloc(sizeof(int));
*$$ = 2;
>>
Start -> a:a b:b;
```
##> 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
@@ -611,7 +642,7 @@ In this example:
* a reduced `Values`'s parser value has a type of `Value[]`. * a reduced `Values`'s parser value has a type of `Value[]`.
* a reduced `KeyValue`'s parser value has a type of `Value[string]`. * a reduced `KeyValue`'s parser value has a type of `Value[string]`.
When AST generation mode is active, the `ptype` functionality works differently. When tree generation mode is active, the `ptype` functionality works differently.
In this mode, only one `ptype` is used by the parser. In this mode, only one `ptype` is used by the parser.
Lexer user code blocks may assign a parse value to the generated `Token` node Lexer user code blocks may assign a parse value to the generated `Token` node
by assigning to `$$` within a lexer code block. by assigning to `$$` within a lexer code block.
@@ -670,8 +701,8 @@ A field can be immediately followed by a `?` character to signify that it is
optional. optional.
A field can optionally be followed by a `:` and then a field alias name. A field can optionally be followed by a `:` and then a field alias name.
If present, the field alias name is used to refer to the field value in user If present, the field alias name is used to refer to the field value in user
code blocks, or if AST mode is active, the field alias name is used as the code blocks, or if tree generation mode is active, the field alias name is used
field name in the generated AST node structure. as the field name in the generated tree node structure.
An optional and named field must use the format `field?:name`. An optional and named field must use the format `field?:name`.
Example: Example:
@@ -695,7 +726,7 @@ The `$$` symbol accesses the output parser value for this rule.
The above examples demonstrate how the parser values for the rule components The above examples demonstrate how the parser values for the rule components
can be used to produce the parser value for the accepted rule. can be used to produce the parser value for the accepted rule.
Parser rule code blocks are not allowed and not used when AST generation mode Parser rule code blocks are not allowed and not used when tree generation mode
is active. is active.
##> Specifying the parser start rule name - the `start` statement ##> Specifying the parser start rule name - the `start` statement
@@ -708,6 +739,22 @@ Example:
start MyStartRule; start MyStartRule;
``` ```
Multiple start rules can be specified, either with multiple `start` statements
or one `start` statement listing multiple start rules.
Example:
```
start Module ModuleItem Statement Expression;
```
When multiple start rules are specified, multiple `p_parse_*()` functions,
`p_result_*()`, and `p_free_tree_*()` functions (in tree mode) are generated.
A default `p_parse()`, `p_result()`, `p_free_tree()` are generated corresponding
to the first start rule.
Additionally, each start rule causes the generation of another version of each
of these functions, for example `p_parse_Statement()`, `p_result_Statement()`,
and `p_free_tree_Statement()`.
##> Specifying the parser module name - the `module` statement ##> Specifying the parser module name - the `module` statement
The `module` statement can be used to specify the module name for a generated The `module` statement can be used to specify the module name for a generated
@@ -751,7 +798,7 @@ Some example uses of this functionality could be to:
* Detect integer overflow when lexing an integer literal constant. * Detect integer overflow when lexing an integer literal constant.
* Detect and report an error as soon as possible during parsing before continuing to parse any more of the input. * Detect and report an error as soon as possible during parsing before continuing to parse any more of the input.
* Determine whether parsing should stop and instead be performed using a different parser version. * Determine whether parsing should stop and instead be retried using a different parser version.
To terminate parsing from a lexer or parser user code block, use the To terminate parsing from a lexer or parser user code block, use the
`$terminate(code)` function, passing an integer expression argument. `$terminate(code)` function, passing an integer expression argument.
@@ -787,7 +834,7 @@ Propane generates the following result code constants:
* `P_EOF`: The lexer reached the end of the input string. * `P_EOF`: The lexer reached the end of the input string.
* `P_USER_TERMINATED`: A parser user code block has requested to terminate the parser. * `P_USER_TERMINATED`: A parser user code block has requested to terminate the parser.
Result codes are returned by the functions `p_decode_code_point()`, `p_lex()`, and `p_parse()`. Result codes are returned by the API functions `p_decode_code_point()`, `p_lex()`, and `p_parse()`.
##> Types ##> Types
@@ -807,7 +854,7 @@ A pointer to this instance is passed to the generated functions.
### `p_position_t` ### `p_position_t`
The `p_position_t` structure contains two fields `row` and `col`. The `p_position_t` structure contains two fields: `row` and `col`.
These fields contain the 1-based row and column describing a parser position. These fields contain the 1-based row and column describing a parser position.
For D targets, the `p_position_t` structure can be checked for validity by For D targets, the `p_position_t` structure can be checked for validity by
@@ -817,17 +864,27 @@ For C targets, the `p_position_t` structure can be checked for validity by
calling `p_position_valid(pos)` where `pos` is a `p_position_t` structure calling `p_position_valid(pos)` where `pos` is a `p_position_t` structure
instance. instance.
### AST Node Types ### `p_token_info_t`
If AST generation mode is enabled, a structure type for each rule will be The `p_token_info_t` structure contains the following fields:
* `position` (`p_position_t`) holds the text position of the first code point in the token.
* `end_position` (`p_position_t`) holds the text position of the last code point in the token.
* `length` (`size_t`) holds the number of input bytes used by the token.
* `token` (`p_token_t`) holds the token ID of the lexed token
* `pvalue` (`p_value_t`) holds the parser value associated with the token.
### Tree Node Types
If tree generation mode is enabled, a structure type for each rule will be
generated. generated.
The name of the structure type is given by the name of the rule. The name of the structure type is given by the name of the rule.
Additionally a structure type called `Token` is generated to represent an Additionally a structure type called `Token` is generated to represent a
AST node which refers to a raw parser token rather than a composite rule. tree node which refers to a raw parser token rather than a composite rule.
#### AST Node Fields #### Tree Node Fields
All AST nodes have a `position` field specifying the text position of the All tree nodes have a `position` field specifying the text position of the
beginning of the matched token or rule, and an `end_position` field specifying beginning of the matched token or rule, and an `end_position` field specifying
the text position of the end of the matched token or rule. the text position of the end of the matched token or rule.
Each of these fields are instances of the `p_position_t` structure. Each of these fields are instances of the `p_position_t` structure.
@@ -846,7 +903,7 @@ A `Token` node has the following additional fields:
* `pvalue` which specifies the parser value for the token. If a lexer user * `pvalue` which specifies the parser value for the token. If a lexer user
code block assigned to `$$`, the assigned value will be stored here. code block assigned to `$$`, the assigned value will be stored here.
AST node structures for rules contain generated fields based on the Tree node structures for rules contain generated fields based on the
right hand side components specified for all rules of a given name. right hand side components specified for all rules of a given name.
In this example: In this example:
@@ -869,10 +926,10 @@ The `Items` structure will have fields:
* `pItemsMore` and `pItemsMore2` which point to the parsed `ItemsMore` structure. * `pItemsMore` and `pItemsMore2` which point to the parsed `ItemsMore` structure.
If a rule can be empty (for example in the second `Items` rule above), then If a rule can be empty (for example in the second `Items` rule above), then
an instance of a pointer to that rule's generated AST node will be null if the an instance of a pointer to that rule's generated tree node will be null if the
parser matches the empty rule pattern. parser matches the empty rule pattern.
The non-positional AST node field pointer will not be generated if there are The non-positional tree node field pointer will not be generated if there are
multiple positions in which an instance of the node it points to could be multiple positions in which an instance of the node it points to could be
present. present.
For example, in the below rules: For example, in the below rules:
@@ -892,7 +949,7 @@ If the first rule is matched, then `pOne1` and `pTwo2` will be non-null while
If the second rule is matched instead, then the opposite would be the case. If the second rule is matched instead, then the opposite would be the case.
If a field alias is present in a rule definition, an additional field will be If a field alias is present in a rule definition, an additional field will be
generated in the AST node with the field alias name. generated in the tree node with the field alias name.
For example: For example:
``` ```
@@ -927,6 +984,44 @@ p_context_t context;
p_context_init(&context, input); p_context_init(&context, input);
``` ```
### `p_lex`
The `p_lex()` function is the main entry point to the lexer.
It is normally called automatically by the generated parser to retrieve the
next input token for the parser and does not need to be called by the user.
However, the user may initialize a context and call `p_lex()` to use the
generated lexer in a standalone mode.
Example:
```
p_context_t context;
p_context_init(&context, input, input_length);
p_token_info_t token_info;
size_t result = p_lex(&context, &token_info);
switch (result)
{
case P_DECODE_ERROR:
/* UTF-8 decode error */
break;
case P_UNEXPECTED_INPUT:
/* Input text does not match any lexer pattern. */
break;
case P_USER_TERMINATED:
/* Lexer user code block requested to terminate the lexer. */
break;
case P_SUCCESS:
/*
* token_info.position holds the text position of the first code point in the token.
* token_info.end_position holds the text position of the last code point in the token.
* token_info.length holds the number of input bytes used by the token.
* token_info.token holds the token ID of the lexed token
* token_info.pvalue holds the parser value associated with the token.
*/
break;
}
```
### `p_parse` ### `p_parse`
The `p_parse()` function is the main entry point to the parser. The `p_parse()` function is the main entry point to the parser.
@@ -940,6 +1035,16 @@ p_context_init(&context, input, input_length);
size_t result = p_parse(&context); size_t result = p_parse(&context);
``` ```
When multiple start rules are specified, a separate parse function is generated
for each which starts parsing at the given rule.
For example, if `Statement` is specified as a start rule:
```
size_t result = p_parse_Statement(&context);
```
In this case, the parser will start parsing with the `Statement` rule.
### `p_position_valid` ### `p_position_valid`
The `p_position_valid()` function is only generated for C targets. The `p_position_valid()` function is only generated for C targets.
@@ -975,8 +1080,25 @@ if (p_parse(&context) == P_SUCCESS)
} }
``` ```
If AST generation mode is active, then the `p_result()` function returns a If tree generation mode is active, then the `p_result()` function returns a
`Start *` pointing to the `Start` AST structure. `Start *` pointing to the `Start` tree node structure.
When multiple start rules are specified, a separate result function is generated
for each which returns the parse result for the corresponding rule.
For example, if `Statement` is specified as a start rule:
```
p_context_t context;
p_context_init(&context, input, input_length);
size_t result = p_parse(&context);
if (p_parse_Statement(&context) == P_SUCCESS)
{
result = p_result_Statement(&context);
}
```
In this case, the parser will start parsing with the `Statement` rule and the
parse result from the `Statement` rule will be returned.
### `p_position` ### `p_position`
@@ -1050,6 +1172,31 @@ assert(code_point == 0x1F9E1u);
assert(code_point_length == 4u); assert(code_point_length == 4u);
``` ```
### `p_free_tree`
The `p_free_tree()` 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()`.
The `p_free_tree()` 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
`pvalue`, in order to properly free this memory a `free_token_node` function
should be specified in the grammar file.
If specified, the `free_token_node` function will be called during the
`p_free_tree()` process to allow user code to free any memory associated with
a token node's `pvalue`.
When multiple start rules are specified, a separate `p_free_tree` function is
generated for each which frees the tree resulting from parsing the given rule.
For example, if `Statement` is specified as a start rule:
```
p_free_tree_Statement(statement_tree);
```
In this case, Propane will free a `Statement` tree structure returned by the
`p_parse_Statement(&context)` function.
##> Data ##> Data
### `p_token_names` ### `p_token_names`
+1 -1
View File
@@ -20,7 +20,7 @@ syn match propaneOperator "->"
syn match propaneFieldAlias ":[a-zA-Z0-9_]\+" contains=propaneFieldOperator syn match propaneFieldAlias ":[a-zA-Z0-9_]\+" contains=propaneFieldOperator
syn match propaneFieldOperator ":" contained syn match propaneFieldOperator ":" contained
syn match propaneOperator "?" syn match propaneOperator "?"
syn keyword propaneKeyword ast ast_prefix ast_suffix drop module prefix ptype start token tokenid syn keyword propaneKeyword drop free_token_node module prefix ptype start token tokenid tree tree_prefix tree_suffix
syn region propaneRegex start="/" end="/" skip="\v\\\\|\\/" syn region propaneRegex start="/" end="/" skip="\v\\\\|\\/"
+21 -14
View File
@@ -11,24 +11,28 @@ class Propane
@log = StringIO.new @log = StringIO.new
end end
@language = @language =
if output_file =~ /\.([a-z]+)$/ if output_file.end_with?(".d")
$1
else
"d" "d"
else
"c"
end end
@options = options @options = options
process_grammar! process_grammar!
end end
def generate def generate
extensions = [@language] extensions = [nil]
if @language == "c" if @language == "c"
extensions += %w[h] extensions += %w[h]
end end
extensions.each do |extension| extensions.each do |extension|
template = Assets.get("parser.#{extension}.erb") template = Assets.get("parser.#{extension || @language}.erb")
erb = ERB.new(template, trim_mode: "<>") if extension
output_file = @output_file.sub(%r{\.[a-z]+$}, ".#{extension}") output_file = @output_file.sub(%r{\.[a-z]+$}, ".#{extension}")
else
output_file = @output_file
end
erb = ERB.new(template, trim_mode: "<>")
result = erb.result(binding.clone) result = erb.result(binding.clone)
File.open(output_file, "wb") do |fh| File.open(output_file, "wb") do |fh|
fh.write(result) fh.write(result)
@@ -67,12 +71,15 @@ class Propane
end end
tokens_by_name[token.name] = token tokens_by_name[token.name] = token
end end
# Check for user start rule. # Create real start rule(s).
unless @grammar.rules.find {|rule| rule.name == @grammar.start_rule} real_start_rules = @grammar.start_rules.map do |start_rule|
raise Error.new("Start rule `#{@grammar.start_rule}` not found") unless @grammar.rules.find {|rule| rule.name == start_rule}
raise Error.new("Start rule `#{start_rule}` not found")
end end
# Add "real" start rule. Rule.new("$#{start_rule}", [start_rule, "$EOF"], nil, nil, nil)
@grammar.rules.unshift(Rule.new("$Start", [@grammar.start_rule, "$EOF"], nil, nil, nil)) end
# Add real start rules before user-given rules.
@grammar.rules = real_start_rules + @grammar.rules
# Generate and add rules for optional components. # Generate and add rules for optional components.
generate_optional_component_rules!(tokens_by_name) generate_optional_component_rules!(tokens_by_name)
# Build rule sets. # Build rule sets.
@@ -291,7 +298,7 @@ class Propane
end end
else else
code = code.gsub(/\$\$/) do |match| code = code.gsub(/\$\$/) do |match|
if @grammar.ast if @grammar.tree
case @language case @language
when "c" when "c"
"out_token_info->pvalue" "out_token_info->pvalue"
@@ -328,9 +335,9 @@ class Propane
# #
# @return [Array<String>] # @return [Array<String>]
# Start rule parser value type name and type string. # Start rule parser value type name and type string.
def start_rule_type def start_rule_type(start_rule_index = 0)
start_rule = @grammar.rules.find do |rule| start_rule = @grammar.rules.find do |rule|
rule.name == @grammar.start_rule rule.name == @grammar.start_rules[start_rule_index]
end end
[start_rule.ptypename, @grammar.ptypes[start_rule.ptypename]] [start_rule.ptypename, @grammar.ptypes[start_rule.ptypename]]
end end
+47 -34
View File
@@ -5,13 +5,14 @@ class Propane
# Reserve identifiers beginning with a double-underscore for internal use. # Reserve identifiers beginning with a double-underscore for internal use.
IDENTIFIER_REGEX = /(?:[a-zA-Z]|_[a-zA-Z0-9])[a-zA-Z_0-9]*/ IDENTIFIER_REGEX = /(?:[a-zA-Z]|_[a-zA-Z0-9])[a-zA-Z_0-9]*/
attr_reader :ast attr_reader :tree
attr_reader :ast_prefix attr_reader :tree_prefix
attr_reader :ast_suffix attr_reader :tree_suffix
attr_reader :free_token_node
attr_reader :modulename attr_reader :modulename
attr_reader :patterns attr_reader :patterns
attr_reader :rules attr_accessor :rules
attr_reader :start_rule attr_reader :start_rules
attr_reader :tokens attr_reader :tokens
attr_reader :code_blocks attr_reader :code_blocks
attr_reader :ptypes attr_reader :ptypes
@@ -19,7 +20,7 @@ class Propane
def initialize(input) def initialize(input)
@patterns = [] @patterns = []
@start_rule = "Start" @start_rules = []
@tokens = [] @tokens = []
@rules = [] @rules = []
@code_blocks = {} @code_blocks = {}
@@ -29,10 +30,12 @@ class Propane
@input = input.gsub("\r\n", "\n") @input = input.gsub("\r\n", "\n")
@ptypes = {"default" => "void *"} @ptypes = {"default" => "void *"}
@prefix = "p_" @prefix = "p_"
@ast = false @tree = false
@ast_prefix = "" @tree_prefix = ""
@ast_suffix = "" @tree_suffix = ""
@free_token_node = nil
parse_grammar! parse_grammar!
@start_rules << "Start" if @start_rules.empty?
end end
def ptype def ptype
@@ -59,9 +62,10 @@ class Propane
if parse_white_space! if parse_white_space!
elsif parse_comment_line! elsif parse_comment_line!
elsif @modeline.nil? && parse_mode_label! elsif @modeline.nil? && parse_mode_label!
elsif parse_ast_statement! elsif parse_tree_statement!
elsif parse_ast_prefix_statement! elsif parse_tree_prefix_statement!
elsif parse_ast_suffix_statement! elsif parse_tree_suffix_statement!
elsif parse_free_token_node_statement!
elsif parse_module_statement! elsif parse_module_statement!
elsif parse_ptype_statement! elsif parse_ptype_statement!
elsif parse_pattern_statement! elsif parse_pattern_statement!
@@ -94,21 +98,27 @@ class Propane
consume!(/#.*\n/) consume!(/#.*\n/)
end end
def parse_ast_statement! def parse_tree_statement!
if consume!(/ast\s*;/) if consume!(/tree\s*;/)
@ast = true @tree = true
end end
end end
def parse_ast_prefix_statement! def parse_tree_prefix_statement!
if md = consume!(/ast_prefix\s+(\w+)\s*;/) if md = consume!(/tree_prefix\s+(\w+)\s*;/)
@ast_prefix = md[1] @tree_prefix = md[1]
end end
end end
def parse_ast_suffix_statement! def parse_tree_suffix_statement!
if md = consume!(/ast_suffix\s+(\w+)\s*;/) if md = consume!(/tree_suffix\s+(\w+)\s*;/)
@ast_suffix = md[1] @tree_suffix = md[1]
end
end
def parse_free_token_node_statement!
if md = consume!(/free_token_node\s+(\w+)\s*;/)
@free_token_node = md[1]
end end
end end
@@ -126,8 +136,8 @@ class Propane
if consume!(/ptype\s+/) if consume!(/ptype\s+/)
name = "default" name = "default"
if md = consume!(/(#{IDENTIFIER_REGEX})\s*=\s*/) if md = consume!(/(#{IDENTIFIER_REGEX})\s*=\s*/)
if @ast if @tree
raise Error.new("Multiple ptypes are unsupported in AST mode") raise Error.new("Multiple ptypes are unsupported in tree mode")
end end
name = md[1] name = md[1]
end end
@@ -141,8 +151,8 @@ class Propane
md = consume!(/(#{IDENTIFIER_REGEX})\s*/, "expected token name") md = consume!(/(#{IDENTIFIER_REGEX})\s*/, "expected token name")
name = md[1] name = md[1]
if md = consume!(/\((#{IDENTIFIER_REGEX})\)\s*/) if md = consume!(/\((#{IDENTIFIER_REGEX})\)\s*/)
if @ast if @tree
raise Error.new("Multiple ptypes are unsupported in AST mode") raise Error.new("Multiple ptypes are unsupported in tree mode")
end end
ptypename = md[1] ptypename = md[1]
end end
@@ -165,8 +175,8 @@ class Propane
md = consume!(/(#{IDENTIFIER_REGEX})\s*/, "expected token name") md = consume!(/(#{IDENTIFIER_REGEX})\s*/, "expected token name")
name = md[1] name = md[1]
if md = consume!(/\((#{IDENTIFIER_REGEX})\)\s*/) if md = consume!(/\((#{IDENTIFIER_REGEX})\)\s*/)
if @ast if @tree
raise Error.new("Multiple ptypes are unsupported in AST mode") raise Error.new("Multiple ptypes are unsupported in tree mode")
end end
ptypename = md[1] ptypename = md[1]
end end
@@ -195,12 +205,12 @@ class Propane
def parse_rule_statement! def parse_rule_statement!
if md = consume!(/(#{IDENTIFIER_REGEX})\s*(?:\((#{IDENTIFIER_REGEX})\))?\s*->\s*/) if md = consume!(/(#{IDENTIFIER_REGEX})\s*(?:\((#{IDENTIFIER_REGEX})\))?\s*->\s*/)
rule_name, ptypename = *md[1, 2] rule_name, ptypename = *md[1, 2]
if @ast && ptypename if @tree && ptypename
raise Error.new("Multiple ptypes are unsupported in AST mode") raise Error.new("Multiple ptypes are unsupported in tree mode")
end end
md = consume!(/((?:#{IDENTIFIER_REGEX}\??(?::#{IDENTIFIER_REGEX})?\s*)*)\s*/, "expected rule component list") md = consume!(/((?:#{IDENTIFIER_REGEX}\??(?::#{IDENTIFIER_REGEX})?\s*)*)\s*/, "expected rule component list")
components = md[1].strip.split(/\s+/) components = md[1].strip.split(/\s+/)
if @ast if @tree
consume!(/;/, "expected `;'") consume!(/;/, "expected `;'")
else else
unless code = parse_code_block! unless code = parse_code_block!
@@ -217,8 +227,8 @@ class Propane
if pattern = parse_pattern! if pattern = parse_pattern!
consume!(/\s+/) consume!(/\s+/)
if md = consume!(/\((#{IDENTIFIER_REGEX})\)\s*/) if md = consume!(/\((#{IDENTIFIER_REGEX})\)\s*/)
if @ast if @tree
raise Error.new("Multiple ptypes are unsupported in AST mode") raise Error.new("Multiple ptypes are unsupported in tree mode")
end end
ptypename = md[1] ptypename = md[1]
end end
@@ -232,8 +242,11 @@ class Propane
end end
def parse_start_statement! def parse_start_statement!
if md = consume!(/start\s+(\w+)\s*;/) if md = consume!(/start\s+([\w\s]*);/)
@start_rule = md[1] start_rules = md[1].split(/\s+/).map(&:strip)
start_rules.each do |start_rule|
@start_rules << start_rule unless @start_rules.include?(start_rule)
end
end end
end end
+12 -3
View File
@@ -16,11 +16,20 @@ class Propane
@warnings = Set.new @warnings = Set.new
@errors = Set.new @errors = Set.new
@options = options @options = options
start_item = Item.new(grammar.rules.first, 0) start_items = grammar.rules[0...grammar.start_rules.length].map do |start_rule|
eval_item_sets = Set[ItemSet.new([start_item])] Item.new(start_rule, 0)
end
start_item_sets = start_items.map {|item| ItemSet.new([item])}
eval_item_sets = Set[*start_item_sets]
while eval_item_sets.size > 0 while eval_item_sets.size > 0
item_set = eval_item_sets.first item_set =
if start_item_sets.size > 0
# Ensure we evaluate start_item_sets first in order
start_item_sets.slice!(0)
else
eval_item_sets.first
end
eval_item_sets.delete(item_set) eval_item_sets.delete(item_set)
unless @item_sets_set.include?(item_set) unless @item_sets_set.include?(item_set)
item_set.id = @item_sets.size item_set.id = @item_sets.size
+1 -1
View File
@@ -36,7 +36,7 @@ class Propane
# @return [Array<Integer>] # @return [Array<Integer>]
# Map this rule's components to their positions in the parent RuleSet's # Map this rule's components to their positions in the parent RuleSet's
# node field pointer array. This is used for AST construction. # node field pointer array. This is used for tree construction.
attr_accessor :rule_set_node_field_index_map attr_accessor :rule_set_node_field_index_map
# Construct a Rule. # Construct a Rule.
+23 -23
View File
@@ -4,8 +4,8 @@ class Propane
class RuleSet class RuleSet
# @return [Array<Hash>] # @return [Array<Hash>]
# AST fields. # tree fields.
attr_reader :ast_fields attr_reader :tree_fields
# @return [Integer] # @return [Integer]
# ID of the RuleSet. # ID of the RuleSet.
@@ -100,28 +100,28 @@ class Propane
# Finalize a RuleSet after adding all Rules to it. # Finalize a RuleSet after adding all Rules to it.
def finalize(grammar) def finalize(grammar)
if grammar.ast if grammar.tree
build_ast_fields(grammar) build_tree_fields(grammar)
end end
end end
private private
# Build the set of AST fields for this RuleSet. # Build the set of tree fields for this RuleSet.
# #
# This is an Array of Hashes. Each entry in the Array corresponds to a # This is an Array of Hashes. Each entry in the Array corresponds to a
# field location in the AST node. The entry is a Hash. It could have one or # field location in the tree node. The entry is a Hash. It could have one or
# two keys. It will always have the field name with a positional suffix as # two keys. It will always have the field name with a positional suffix as
# a key. It may also have the field name without the positional suffix if # a key. It may also have the field name without the positional suffix if
# that field only exists in one position across all Rules in the RuleSet. # that field only exists in one position across all Rules in the RuleSet.
# #
# @return [void] # @return [void]
def build_ast_fields(grammar) def build_tree_fields(grammar)
field_ast_node_indexes = {} field_tree_node_indexes = {}
field_indexes_across_all_rules = {} field_indexes_across_all_rules = {}
# Stores the index into @ast_fields by field alias name. # Stores the index into @tree_fields by field alias name.
field_aliases = {} field_aliases = {}
@ast_fields = [] @tree_fields = []
@rules.each do |rule| @rules.each do |rule|
rule.components.each_with_index do |component, i| rule.components.each_with_index do |component, i|
if component.is_a?(RuleSet) && component.optional? if component.is_a?(RuleSet) && component.optional?
@@ -132,25 +132,25 @@ class Propane
else else
node_name = component.name node_name = component.name
end end
struct_name = "#{grammar.ast_prefix}#{node_name}#{grammar.ast_suffix}" struct_name = "#{grammar.tree_prefix}#{node_name}#{grammar.tree_suffix}"
field_name = "p#{node_name}#{i + 1}" field_name = "p#{node_name}#{i + 1}"
unless field_ast_node_indexes[field_name] unless field_tree_node_indexes[field_name]
field_ast_node_indexes[field_name] = @ast_fields.size field_tree_node_indexes[field_name] = @tree_fields.size
@ast_fields << {field_name => struct_name} @tree_fields << {field_name => struct_name}
end end
rule.aliases.each do |alias_name, index| rule.aliases.each do |alias_name, index|
if index == i if index == i
alias_ast_fields_index = field_ast_node_indexes[field_name] alias_tree_fields_index = field_tree_node_indexes[field_name]
if field_aliases[alias_name] && field_aliases[alias_name] != alias_ast_fields_index if field_aliases[alias_name] && field_aliases[alias_name] != alias_tree_fields_index
raise Error.new("Error: conflicting AST node field positions for alias `#{alias_name}` in rule #{rule.name} defined on line #{rule.line_number}") raise Error.new("Error: conflicting tree node field positions for alias `#{alias_name}` in rule #{rule.name} defined on line #{rule.line_number}")
end end
field_aliases[alias_name] = alias_ast_fields_index field_aliases[alias_name] = alias_tree_fields_index
@ast_fields[alias_ast_fields_index][alias_name] = @ast_fields[alias_ast_fields_index].first[1] @tree_fields[alias_tree_fields_index][alias_name] = @tree_fields[alias_tree_fields_index].first[1]
end end
end end
field_indexes_across_all_rules[node_name] ||= Set.new field_indexes_across_all_rules[node_name] ||= Set.new
field_indexes_across_all_rules[node_name] << field_ast_node_indexes[field_name] field_indexes_across_all_rules[node_name] << field_tree_node_indexes[field_name]
rule.rule_set_node_field_index_map[i] = field_ast_node_indexes[field_name] rule.rule_set_node_field_index_map[i] = field_tree_node_indexes[field_name]
end end
end end
field_indexes_across_all_rules.each do |node_name, indexes_across_all_rules| field_indexes_across_all_rules.each do |node_name, indexes_across_all_rules|
@@ -158,8 +158,8 @@ class Propane
# If this field was only seen in one position across all rules, # If this field was only seen in one position across all rules,
# then add an alias to the positional field name that does not # then add an alias to the positional field name that does not
# include the position. # include the position.
@ast_fields[indexes_across_all_rules.first]["p#{node_name}"] = @tree_fields[indexes_across_all_rules.first]["p#{node_name}"] =
"#{grammar.ast_prefix}#{node_name}#{grammar.ast_suffix}" "#{grammar.tree_prefix}#{node_name}#{grammar.tree_suffix}"
end end
end end
end end
+1 -1
View File
@@ -1,3 +1,3 @@
class Propane class Propane
VERSION = "2.3.0" VERSION = "3.0.0"
end end
+2 -2
View File
@@ -120,11 +120,11 @@ string: /\\t/ <<
>> >>
string: /\\u[0-9a-fA-F]{4}/ << string: /\\u[0-9a-fA-F]{4}/ <<
/* Not actually going to encode the code point for this example... */ /* Not actually going to encode the code point for this example... */
char s[] = {'{', match[2], match[3], match[4], match[5], '}', 0}; char s[] = {'{', (char)match[2], (char)match[3], (char)match[4], (char)match[5], '}', 0};
str_append(&string_value, s); str_append(&string_value, s);
>> >>
string: /[^\\]/ << string: /[^\\]/ <<
char s[] = {match[0], 0}; char s[] = {(char)match[0], 0};
str_append(&string_value, s); str_append(&string_value, s);
>> >>
Start -> Value << Start -> Value <<
+3 -3
View File
@@ -5,7 +5,7 @@
JSONValue * JSONValue_new(size_t id) JSONValue * JSONValue_new(size_t id)
{ {
JSONValue * jv = calloc(1, sizeof(JSONValue)); JSONValue * jv = (JSONValue *)calloc(1, sizeof(JSONValue));
jv->id = id; jv->id = id;
return jv; return jv;
} }
@@ -29,7 +29,7 @@ void JSONObject_append(JSONValue * object, char const * name, JSONValue * value)
} }
} }
size_t const new_size = size + 1; size_t const new_size = size + 1;
void * new_entries = malloc(sizeof(object->object.entries[0]) * new_size); JSONObjectEntry * new_entries = (JSONObjectEntry *)malloc(sizeof(object->object.entries[0]) * new_size);
if (size > 0) if (size > 0)
{ {
memcpy(new_entries, object->object.entries, size * sizeof(object->object.entries[0])); memcpy(new_entries, object->object.entries, size * sizeof(object->object.entries[0]));
@@ -52,7 +52,7 @@ void JSONArray_append(JSONValue * array, JSONValue * value)
{ {
size_t const size = array->array.size; size_t const size = array->array.size;
size_t const new_size = size + 1; size_t const new_size = size + 1;
JSONValue ** new_entries = malloc(sizeof(JSONValue *) * new_size); JSONValue ** new_entries = (JSONValue **)malloc(sizeof(JSONValue *) * new_size);
if (array->array.size > 0) if (array->array.size > 0)
{ {
memcpy(new_entries, array->array.entries, sizeof(JSONValue *) * size); memcpy(new_entries, array->array.entries, sizeof(JSONValue *) * size);
+7 -5
View File
@@ -11,6 +11,12 @@
#define JSON_FALSE 5u #define JSON_FALSE 5u
#define JSON_NULL 6u #define JSON_NULL 6u
typedef struct JSONObjectEntry_s
{
char const * name;
struct JSONValue_s * value;
} JSONObjectEntry;
typedef struct JSONValue_s typedef struct JSONValue_s
{ {
size_t id; size_t id;
@@ -19,11 +25,7 @@ typedef struct JSONValue_s
struct struct
{ {
size_t size; size_t size;
struct JSONObjectEntry * entries;
{
char const * name;
struct JSONValue_s * value;
} * entries;
} object; } object;
struct struct
{ {
+194 -96
View File
@@ -65,26 +65,47 @@ EOF
end end
def compile(test_files, options = {}) def compile(test_files, options = {})
test_files = Array(test_files) test_files = Array(test_files).map do |test_file|
if !File.exist?(test_file) && test_file.end_with?(".cpp")
test_file.sub(%r{\.cpp$}, ".c")
else
test_file
end
end
options[:parsers] ||= [""] options[:parsers] ||= [""]
parsers = options[:parsers].map do |name| parsers = options[:parsers].map do |name|
"spec/run/testparser#{name}.#{options[:language]}" "spec/run/testparser#{name}.#{options[:language]}"
end end
case options[:language] case options[:language]
when "c" when "c"
result = system(*%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++ -g -x c++ -Wall -o spec/run/testparser -Ispec -Ispec/run], *parsers, *test_files, "spec/testutils.c", "-lm"]
when "d" when "d"
result = system(*%w[ldc2 -g --unittest -of spec/run/testparser -Ispec], *parsers, *test_files, "spec/testutils.d") command = [*%w[ldc2 -g --unittest -of spec/run/testparser -Ispec], *parsers, *test_files, "spec/testutils.d"]
end end
result = system(*command)
expect(result).to be_truthy expect(result).to be_truthy
end end
def run_test def run_test(options)
stdout, stderr, status = Open3.capture3("spec/run/testparser") stdout, stderr, status = Open3.capture3("spec/run/testparser")
File.binwrite("spec/run/.stderr", stderr) File.binwrite("spec/run/.stderr", stderr)
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.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")
vgout = stdout + stderr
File.binwrite("spec/run/.vgout", vgout)
vgout.scan(/lost: (\d+) bytes/) do |match|
bytes = $1.to_i
if bytes > 0
raise "Valgrind detected memory leak"
end
end
end
results
end end
def lines(str) def lines(str)
@@ -224,20 +245,20 @@ EOF
expect(results.status).to_not eq 0 expect(results.status).to_not eq 0
end end
it "errors when an alias is in different positions for different rules in a rule set when AST mode is enabled" do it "errors when an alias is in different positions for different rules in a rule set when tree mode is enabled" do
write_grammar <<EOF write_grammar <<EOF
ast; tree;
token a; token a;
token b; token b;
Start -> a:foo b; Start -> a:foo b;
Start -> b b:foo; Start -> b b:foo;
EOF EOF
results = run_propane(extra_args: %w[-w], capture: true) results = run_propane(extra_args: %w[-w], capture: true)
expect(results.stderr).to match %r{Error: conflicting AST node field positions for alias `foo`} expect(results.stderr).to match %r{Error: conflicting tree node field positions for alias `foo`}
expect(results.status).to_not eq 0 expect(results.status).to_not eq 0
end end
it "does not error when an alias is in different positions for different rules in a rule set when AST mode is not enabled" do it "does not error when an alias is in different positions for different rules in a rule set when tree mode is not enabled" do
write_grammar <<EOF write_grammar <<EOF
token a; token a;
token b; token b;
@@ -261,7 +282,7 @@ EOF
expect(results.status).to_not eq 0 expect(results.status).to_not eq 0
end end
%w[d c].each do |language| %w[d c cpp].each do |language|
context "#{language.upcase} language" do context "#{language.upcase} language" do
@@ -277,14 +298,14 @@ Foo -> plus <<>>
EOF EOF
run_propane(language: language) run_propane(language: language)
compile("spec/test_lexer.#{language}", language: language) compile("spec/test_lexer.#{language}", language: language)
results = run_test results = run_test(language: language)
expect(results.stderr).to eq "" expect(results.stderr).to eq ""
expect(results.status).to eq 0 expect(results.status).to eq 0
end end
it "detects a lexer error when an unknown character is seen" do it "detects a lexer error when an unknown character is seen" do
case language case language
when "c" when "c", "cpp"
write_grammar <<EOF write_grammar <<EOF
ptype int; ptype int;
token int /\\d+/ << token int /\\d+/ <<
@@ -315,7 +336,7 @@ EOF
end end
run_propane(language: language) run_propane(language: language)
compile("spec/test_lexer_unknown_character.#{language}", 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.stderr).to eq ""
expect(results.status).to eq 0 expect(results.status).to eq 0
end end
@@ -338,7 +359,7 @@ EOF
it "generates a parser that does basic math - user guide example" do it "generates a parser that does basic math - user guide example" do
case language case language
when "c" when "c", "cpp"
write_grammar <<EOF write_grammar <<EOF
<< <<
#include <math.h> #include <math.h>
@@ -409,7 +430,7 @@ EOF
end end
run_propane(language: language) run_propane(language: language)
compile("spec/test_basic_math_grammar.#{language}", 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.stderr).to eq ""
expect(results.status).to eq 0 expect(results.status).to eq 0
end end
@@ -435,7 +456,7 @@ R2 -> a b;
EOF EOF
run_propane(language: language) run_propane(language: language)
compile("spec/test_parser_identical_rules_lookahead.#{language}", 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 expect(results.status).to eq 0
end end
@@ -450,13 +471,13 @@ R1 -> b;
EOF EOF
run_propane(language: language) run_propane(language: language)
compile("spec/test_parser_rule_from_multiple_states.#{language}", 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 expect(results.status).to eq 0
end end
it "executes user code when matching lexer token" do it "executes user code when matching lexer token" do
case language case language
when "c" when "c", "cpp"
write_grammar <<EOF write_grammar <<EOF
<< <<
#include <stdio.h> #include <stdio.h>
@@ -485,7 +506,7 @@ EOF
end end
run_propane(language: language) run_propane(language: language)
compile("spec/test_user_code.#{language}", language: language) compile("spec/test_user_code.#{language}", language: language)
results = run_test results = run_test(language: language)
expect(results.status).to eq 0 expect(results.status).to eq 0
verify_lines(results.stdout, [ verify_lines(results.stdout, [
"abc!", "abc!",
@@ -498,7 +519,7 @@ EOF
it "supports a pattern statement" do it "supports a pattern statement" do
case language case language
when "c" when "c", "cpp"
write_grammar <<EOF write_grammar <<EOF
<< <<
#include <stdio.h> #include <stdio.h>
@@ -521,7 +542,7 @@ EOF
end end
run_propane(language: language) run_propane(language: language)
compile("spec/test_pattern.#{language}", language: language) compile("spec/test_pattern.#{language}", language: language)
results = run_test results = run_test(language: language)
expect(results.status).to eq 0 expect(results.status).to eq 0
verify_lines(results.stdout, [ verify_lines(results.stdout, [
"def!", "def!",
@@ -534,7 +555,7 @@ EOF
it "supports returning tokens from pattern code blocks" do it "supports returning tokens from pattern code blocks" do
case language case language
when "c" when "c", "cpp"
write_grammar <<EOF write_grammar <<EOF
<< <<
#include <stdio.h> #include <stdio.h>
@@ -563,7 +584,7 @@ EOF
end end
run_propane(language: language) run_propane(language: language)
compile("spec/test_return_token_from_pattern.#{language}", 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 expect(results.status).to eq 0
verify_lines(results.stdout, [ verify_lines(results.stdout, [
"def!", "def!",
@@ -574,7 +595,7 @@ EOF
it "supports lexer modes" do it "supports lexer modes" do
case language case language
when "c" when "c", "cpp"
write_grammar <<EOF write_grammar <<EOF
<< <<
#include <stdio.h> #include <stdio.h>
@@ -621,7 +642,7 @@ EOF
end end
run_propane(language: language) run_propane(language: language)
compile("spec/test_lexer_modes.#{language}", language: language) compile("spec/test_lexer_modes.#{language}", language: language)
results = run_test results = run_test(language: language)
expect(results.status).to eq 0 expect(results.status).to eq 0
verify_lines(results.stdout, [ verify_lines(results.stdout, [
"begin string mode", "begin string mode",
@@ -635,7 +656,7 @@ EOF
it "multiple lexer modes may apply to a pattern" do it "multiple lexer modes may apply to a pattern" do
case language case language
when "c" when "c", "cpp"
write_grammar <<EOF write_grammar <<EOF
<< <<
#include <stdio.h> #include <stdio.h>
@@ -679,7 +700,7 @@ EOF
end end
run_propane(language: language) run_propane(language: language)
compile("spec/test_lexer_multiple_modes.#{language}", 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 expect(results.status).to eq 0
verify_lines(results.stdout, [ verify_lines(results.stdout, [
"ident: d", "ident: d",
@@ -691,7 +712,7 @@ EOF
it "executes user code associated with a parser rule" do it "executes user code associated with a parser rule" do
case language case language
when "c" when "c", "cpp"
write_grammar <<EOF write_grammar <<EOF
<< <<
#include <stdio.h> #include <stdio.h>
@@ -716,7 +737,7 @@ EOF
end end
run_propane(language: language) run_propane(language: language)
compile("spec/test_parser_rule_user_code.#{language}", 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 expect(results.status).to eq 0
verify_lines(results.stdout, [ verify_lines(results.stdout, [
"A!", "A!",
@@ -727,7 +748,7 @@ EOF
it "parses lists" do it "parses lists" do
write_grammar <<EOF write_grammar <<EOF
ptype #{language == "c" ? "uint32_t" : "uint"}; ptype #{language == "d" ? "uint" : "uint32_t"};
token a; token a;
Start -> As << $$ = $1; >> Start -> As << $$ = $1; >>
As -> << $$ = 0u; >> As -> << $$ = 0u; >>
@@ -735,7 +756,7 @@ As -> As a << $$ = $1 + 1u; >>
EOF EOF
run_propane(language: language) run_propane(language: language)
compile("spec/test_parsing_lists.#{language}", 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.status).to eq 0
expect(results.stderr).to eq "" expect(results.stderr).to eq ""
end end
@@ -762,14 +783,14 @@ EOF
it "provides matched text to user code blocks" do it "provides matched text to user code blocks" do
case language case language
when "c" when "c", "cpp"
write_grammar <<EOF write_grammar <<EOF
<< <<
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
>> >>
token id /[a-zA-Z_][a-zA-Z0-9_]*/ << token id /[a-zA-Z_][a-zA-Z0-9_]*/ <<
char * t = malloc(match_length + 1); char * t = (char *)malloc(match_length + 1);
strncpy(t, (char *)match, match_length); strncpy(t, (char *)match, match_length);
printf("Matched token is %s\\n", t); printf("Matched token is %s\\n", t);
free(t); free(t);
@@ -789,7 +810,7 @@ EOF
end end
run_propane(language: language) run_propane(language: language)
compile("spec/test_lexer_match_text.#{language}", 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 expect(results.status).to eq 0
verify_lines(results.stdout, [ verify_lines(results.stdout, [
"Matched token is identifier_123", "Matched token is identifier_123",
@@ -799,7 +820,7 @@ EOF
it "allows storing a result value for the lexer" do it "allows storing a result value for the lexer" do
case language case language
when "c" when "c", "cpp"
write_grammar <<EOF write_grammar <<EOF
ptype uint64_t; ptype uint64_t;
token word /[a-z]+/ << token word /[a-z]+/ <<
@@ -822,7 +843,7 @@ EOF
end end
run_propane(language: language) run_propane(language: language)
compile("spec/test_lexer_result_value.#{language}", 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.stderr).to eq ""
expect(results.status).to eq 0 expect(results.status).to eq 0
end end
@@ -837,13 +858,14 @@ Start -> a num;
EOF EOF
run_propane(language: language) run_propane(language: language)
compile("spec/test_error_positions.#{language}", 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.stderr).to eq ""
expect(results.status).to eq 0 expect(results.status).to eq 0
end end
it "allows creating a JSON parser" do it "allows creating a JSON parser" do
write_grammar(File.read("spec/json_parser.#{language}.propane")) ext = language == "cpp" ? "c" : language
write_grammar(File.read("spec/json_parser.#{ext}.propane"))
run_propane(language: language) run_propane(language: language)
compile(["spec/test_parsing_json.#{language}", "spec/json_types.#{language}"], language: language) compile(["spec/test_parsing_json.#{language}", "spec/json_types.#{language}"], language: language)
end end
@@ -865,7 +887,7 @@ Start -> b c b;
EOF EOF
run_propane(name: "myp2", language: language) run_propane(name: "myp2", language: language)
compile("spec/test_multiple_parsers.#{language}", parsers: %w[myp1 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.stderr).to eq ""
expect(results.status).to eq 0 expect(results.status).to eq 0
end end
@@ -884,7 +906,7 @@ Any -> c;
EOF EOF
run_propane(language: language) run_propane(language: language)
compile("spec/test_user_terminate_lexer.#{language}", 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.stderr).to eq ""
expect(results.status).to eq 0 expect(results.status).to eq 0
end end
@@ -902,14 +924,14 @@ Any -> ;
EOF EOF
run_propane(language: language) run_propane(language: language)
compile("spec/test_user_terminate.#{language}", 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.stderr).to eq ""
expect(results.status).to eq 0 expect(results.status).to eq 0
end end
it "matches backslash escape sequences" do it "matches backslash escape sequences" do
case language case language
when "c" when "c", "cpp"
write_grammar <<EOF write_grammar <<EOF
<< <<
#include <stdio.h> #include <stdio.h>
@@ -946,7 +968,7 @@ EOF
end end
run_propane(language: language) run_propane(language: language)
compile("spec/test_match_backslashes.#{language}", 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.stderr).to eq ""
expect(results.status).to eq 0 expect(results.status).to eq 0
verify_lines(results.stdout, [ verify_lines(results.stdout, [
@@ -974,9 +996,9 @@ EOF
run_propane(language: language) run_propane(language: language)
end end
it "generates an AST" do it "generates a tree" do
write_grammar <<EOF write_grammar <<EOF
ast; tree;
ptype int; ptype int;
@@ -1010,17 +1032,17 @@ One -> one;
Two -> two; Two -> two;
EOF EOF
run_propane(language: language) run_propane(language: language)
compile("spec/test_ast.#{language}", language: language) compile("spec/test_tree.#{language}", language: language)
results = run_test results = run_test(language: language)
expect(results.stderr).to eq "" expect(results.stderr).to eq ""
expect(results.status).to eq 0 expect(results.status).to eq 0
end end
it "supports AST node prefix and suffix" do it "supports tree node prefix and suffix" do
write_grammar <<EOF write_grammar <<EOF
ast; tree;
ast_prefix P ; tree_prefix P ;
ast_suffix S; tree_suffix S;
ptype int; ptype int;
@@ -1054,8 +1076,8 @@ One -> one;
Two -> two; Two -> two;
EOF EOF
run_propane(language: language) run_propane(language: language)
compile("spec/test_ast_ps.#{language}", language: language) compile("spec/test_tree_ps.#{language}", language: language)
results = run_test results = run_test(language: language)
expect(results.stderr).to eq "" expect(results.stderr).to eq ""
expect(results.status).to eq 0 expect(results.status).to eq 0
end end
@@ -1070,15 +1092,15 @@ EOF
compile("spec/test_start_rule.#{language}", language: language) compile("spec/test_start_rule.#{language}", language: language)
end end
it "allows specifying a different start rule with AST generation" do it "allows specifying a different start rule with tree generation" do
write_grammar <<EOF write_grammar <<EOF
ast; tree;
token hi; token hi;
start Top; start Top;
Top -> hi; Top -> hi;
EOF EOF
run_propane(language: language) run_propane(language: language)
compile("spec/test_start_rule_ast.#{language}", language: language) compile("spec/test_start_rule_tree.#{language}", language: language)
end end
it "allows marking a rule component as optional" do it "allows marking a rule component as optional" do
@@ -1123,13 +1145,13 @@ Start -> a? b R? <<
printf("b: %d\\n", $2); printf("b: %d\\n", $2);
printf("R: %s\\n", $3 == NULL ? "" : $3); printf("R: %s\\n", $3 == NULL ? "" : $3);
>> >>
R -> c d << $$ = "cd"; >> R -> c d << $$ = (char *)"cd"; >>
R (string) -> d c << $$ = "dc"; >> R (string) -> d c << $$ = (char *)"dc"; >>
EOF EOF
end end
run_propane(language: language) run_propane(language: language)
compile("spec/test_optional_rule_component.#{language}", 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.stderr).to eq ""
expect(results.status).to eq 0 expect(results.status).to eq 0
verify_lines(results.stdout, [ verify_lines(results.stdout, [
@@ -1145,10 +1167,10 @@ EOF
]) ])
end end
it "allows marking a rule component as optional in AST generation mode" do it "allows marking a rule component as optional in tree generation mode" do
if language == "d" if language == "d"
write_grammar <<EOF write_grammar <<EOF
ast; tree;
<< <<
import std.stdio; import std.stdio;
@@ -1164,7 +1186,7 @@ R -> d c;
EOF EOF
else else
write_grammar <<EOF write_grammar <<EOF
ast; tree;
<< <<
#include <stdio.h> #include <stdio.h>
@@ -1180,16 +1202,16 @@ R -> d c;
EOF EOF
end end
run_propane(language: language) run_propane(language: language)
compile("spec/test_optional_rule_component_ast.#{language}", language: language) compile("spec/test_optional_rule_component_tree.#{language}", language: language)
results = run_test results = run_test(language: language)
expect(results.stderr).to eq "" expect(results.stderr).to eq ""
expect(results.status).to eq 0 expect(results.status).to eq 0
end end
it "allows naming an optional rule component in AST generation mode" do it "allows naming an optional rule component in tree generation mode" do
if language == "d" if language == "d"
write_grammar <<EOF write_grammar <<EOF
ast; tree;
<< <<
import std.stdio; import std.stdio;
@@ -1205,7 +1227,7 @@ R -> d c;
EOF EOF
else else
write_grammar <<EOF write_grammar <<EOF
ast; tree;
<< <<
#include <stdio.h> #include <stdio.h>
@@ -1221,15 +1243,15 @@ R -> d c;
EOF EOF
end end
run_propane(language: language) run_propane(language: language)
compile("spec/test_named_optional_rule_component_ast.#{language}", language: language) compile("spec/test_named_optional_rule_component_tree.#{language}", language: language)
results = run_test results = run_test(language: language)
expect(results.stderr).to eq "" expect(results.stderr).to eq ""
expect(results.status).to eq 0 expect(results.status).to eq 0
end end
it "stores token and rule positions in AST nodes" do it "stores token and rule positions in tree nodes" do
write_grammar <<EOF write_grammar <<EOF
ast; tree;
token a; token a;
token bb; token bb;
@@ -1241,15 +1263,15 @@ T -> bb;
T -> c; T -> c;
EOF EOF
run_propane(language: language) run_propane(language: language)
compile("spec/test_ast_token_positions.#{language}", language: language) compile("spec/test_tree_token_positions.#{language}", language: language)
results = run_test results = run_test(language: language)
expect(results.stderr).to eq "" expect(results.stderr).to eq ""
expect(results.status).to eq 0 expect(results.status).to eq 0
end end
it "stores invalid positions for empty rule matches" do it "stores invalid positions for empty rule matches" do
write_grammar <<EOF write_grammar <<EOF
ast; tree;
token a; token a;
token bb; token bb;
@@ -1261,15 +1283,15 @@ T -> a A;
A -> bb? c?; A -> bb? c?;
EOF EOF
run_propane(language: language) run_propane(language: language)
compile("spec/test_ast_invalid_positions.#{language}", language: language) compile("spec/test_tree_invalid_positions.#{language}", language: language)
results = run_test results = run_test(language: language)
expect(results.stderr).to eq "" expect(results.stderr).to eq ""
expect(results.status).to eq 0 expect(results.status).to eq 0
end end
it "allows specifying field aliases in AST mode" do it "allows specifying field aliases in tree mode" do
write_grammar <<EOF write_grammar <<EOF
ast; tree;
token a; token a;
token b; token b;
@@ -1281,15 +1303,15 @@ T -> b;
T -> c; T -> c;
EOF EOF
run_propane(language: language) run_propane(language: language)
compile("spec/test_ast_field_aliases.#{language}", language: language) compile("spec/test_tree_field_aliases.#{language}", language: language)
results = run_test results = run_test(language: language)
expect(results.stderr).to eq "" expect(results.stderr).to eq ""
expect(results.status).to eq 0 expect(results.status).to eq 0
end end
it "aliases the correct field when multiple rules are in a rule set in AST mode" do it "aliases the correct field when multiple rules are in a rule set in tree mode" do
write_grammar <<EOF write_grammar <<EOF
ast; tree;
token a; token a;
token b; token b;
@@ -1304,13 +1326,13 @@ T -> b;
T -> c; T -> c;
EOF EOF
run_propane(language: language) run_propane(language: language)
compile("spec/test_ast_field_aliases.#{language}", language: language) compile("spec/test_tree_field_aliases.#{language}", language: language)
results = run_test results = run_test(language: language)
expect(results.stderr).to eq "" expect(results.stderr).to eq ""
expect(results.status).to eq 0 expect(results.status).to eq 0
end end
it "allows specifying field aliases when AST mode is not enabled" do it "allows specifying field aliases when tree mode is not enabled" do
if language == "d" if language == "d"
write_grammar <<EOF write_grammar <<EOF
<< <<
@@ -1332,9 +1354,9 @@ EOF
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
>> >>
ptype char const *; ptype char *;
token id /[a-zA-Z_][a-zA-Z0-9_]*/ << token id /[a-zA-Z_][a-zA-Z0-9_]*/ <<
char * s = malloc(match_length + 1); char * s = (char *)malloc(match_length + 1);
strncpy(s, (char const *)match, match_length); strncpy(s, (char const *)match, match_length);
s[match_length] = 0; s[match_length] = 0;
$$ = s; $$ = s;
@@ -1343,18 +1365,20 @@ drop /\\s+/;
Start -> id:first id:second << Start -> id:first id:second <<
printf("first is %s\\n", ${first}); printf("first is %s\\n", ${first});
printf("second is %s\\n", ${second}); printf("second is %s\\n", ${second});
free(${first});
free(${second});
>> >>
EOF EOF
end end
run_propane(language: language) run_propane(language: language)
compile("spec/test_field_aliases.#{language}", 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.stderr).to eq ""
expect(results.status).to eq 0 expect(results.status).to eq 0
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
it "aliases the correct field when multiple rules are in a rule set when AST mode is not enabled" do it "aliases the correct field when multiple rules are in a rule set when tree mode is not enabled" do
if language == "d" if language == "d"
write_grammar <<EOF write_grammar <<EOF
<< <<
@@ -1379,9 +1403,9 @@ EOF
#include <stdio.h> #include <stdio.h>
#include <string.h> #include <string.h>
>> >>
ptype char const *; ptype char *;
token id /[a-zA-Z_][a-zA-Z0-9_]*/ << token id /[a-zA-Z_][a-zA-Z0-9_]*/ <<
char * s = malloc(match_length + 1); char * s = (char *)malloc(match_length + 1);
strncpy(s, (char const *)match, match_length); strncpy(s, (char const *)match, match_length);
s[match_length] = 0; s[match_length] = 0;
$$ = s; $$ = s;
@@ -1392,26 +1416,100 @@ Start -> Foo;
Start -> id:first id:second << Start -> id:first id:second <<
printf("first is %s\\n", ${first}); printf("first is %s\\n", ${first});
printf("second is %s\\n", ${second}); printf("second is %s\\n", ${second});
free(${first});
free(${second});
>> >>
Foo -> ; Foo -> ;
EOF EOF
end end
run_propane(language: language) run_propane(language: language)
compile("spec/test_field_aliases.#{language}", 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.stderr).to eq ""
expect(results.status).to eq 0 expect(results.status).to eq 0
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
it "does not free memory allocated for AST nodes" do it "does not free memory allocated for tree nodes" do
write_grammar(File.read("spec/ast_node_memory_remains.#{language}.propane")) ext = language == "cpp" ? "c" : language
write_grammar(File.read("spec/tree_node_memory_remains.#{ext}.propane"))
run_propane(language: language) run_propane(language: language)
compile("spec/test_ast_node_memory_remains.#{language}", language: language) compile("spec/test_tree_node_memory_remains.#{language}", language: language)
results = run_test results = run_test(language: language)
expect(results.stderr).to eq "" expect(results.stderr).to eq ""
expect(results.status).to eq 0 expect(results.status).to eq 0
end end
it "allows multiple starting rules" do
write_grammar <<EOF
ptype int;
token a << $$ = 1; >>
token b << $$ = 2; >>
token c << $$ = 3; >>
Start -> a b R;
Start -> Bs:bs << $$ = $1; >>
R -> c:c << $$ = $1; >>
Bs -> << $$ = 0; >>
Bs -> b:b Bs:bs << $$ = $1 + $2; >>
start Start R Bs;
EOF
run_propane(language: language)
compile("spec/test_starting_rules.#{language}", language: language)
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
it "allows multiple starting rules in tree mode" do
write_grammar <<EOF
tree;
ptype int;
token a << $$ = 1; >>
token b << $$ = 2; >>
token c << $$ = 3; >>
Start -> a b R;
Start -> Bs:bs;
R -> c:c;
Bs -> ;
Bs -> b:b Bs:bs;
start Start R Bs;
EOF
run_propane(language: language)
compile("spec/test_starting_rules_tree.#{language}", language: language)
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
if %w[c cpp].include?(language)
it "allows a user function to free token node memory in tree mode" do
write_grammar <<EOF
<<
static void free_token(Token * token)
{
free(token->pvalue);
}
>>
tree;
free_token_node free_token;
ptype int *;
token a <<
$$ = (int *)malloc(sizeof(int));
*$$ = 1;
>>
token b <<
$$ = (int *)malloc(sizeof(int));
*$$ = 2;
>>
Start -> a:a b:b;
EOF
run_propane(language: language)
compile("spec/test_free_tree_token_node_memory.#{language}", language: language)
results = run_test(language: language)
expect(results.stderr).to eq ""
expect(results.status).to eq 0
end
end
end end
end end
end end
+5
View File
@@ -2,6 +2,10 @@ unless ENV["dist_specs"]
require "bundler/setup" require "bundler/setup"
require "simplecov" require "simplecov"
class MyFormatter
def format(*args)
end
end
SimpleCov.start do SimpleCov.start do
add_filter "/spec/" add_filter "/spec/"
add_filter "/.bundle/" add_filter "/.bundle/"
@@ -12,6 +16,7 @@ unless ENV["dist_specs"]
end end
project_name "Propane" project_name "Propane"
merge_timeout 3600 merge_timeout 3600
formatter(MyFormatter)
end end
RSpec.configure do |config| RSpec.configure do |config|
+19
View File
@@ -0,0 +1,19 @@
#include "testparser.h"
#include <assert.h>
#include <string.h>
#include "testutils.h"
int main()
{
char const * input = "ab";
p_context_t context;
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert_eq(P_SUCCESS, p_parse(&context));
Start * start = p_result(&context);
assert(start->a != NULL);
assert(*start->a->pvalue == 1);
assert(start->b != NULL);
assert(*start->b->pvalue == 2);
p_free_tree(start);
}
@@ -17,6 +17,8 @@ int main()
assert(start->pR == NULL); assert(start->pR == NULL);
assert(start->r == NULL); assert(start->r == NULL);
p_free_tree(start);
input = "abcd"; input = "abcd";
p_context_init(&context, (uint8_t const *)input, strlen(input)); p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS); assert(p_parse(&context) == P_SUCCESS);
@@ -31,6 +33,8 @@ int main()
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_free_tree(start);
input = "bdc"; input = "bdc";
p_context_init(&context, (uint8_t const *)input, strlen(input)); p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS); assert(p_parse(&context) == P_SUCCESS);
@@ -40,6 +44,8 @@ int main()
assert(start->r != NULL); assert(start->r != NULL);
assert_eq(TOKEN_d, start->pR->pToken1->token); assert_eq(TOKEN_d, start->pR->pToken1->token);
p_free_tree(start);
return 0; return 0;
} }
@@ -16,6 +16,8 @@ int main()
assert(start->pR3 == NULL); assert(start->pR3 == NULL);
assert(start->pR == NULL); assert(start->pR == NULL);
p_free_tree(start);
input = "abcd"; input = "abcd";
p_context_init(&context, (uint8_t const *)input, strlen(input)); p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS); assert(p_parse(&context) == P_SUCCESS);
@@ -28,6 +30,8 @@ int main()
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_free_tree(start);
input = "bdc"; input = "bdc";
p_context_init(&context, (uint8_t const *)input, strlen(input)); p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS); assert(p_parse(&context) == P_SUCCESS);
@@ -37,6 +41,8 @@ int main()
assert(start->pR != NULL); assert(start->pR != NULL);
assert_eq(TOKEN_d, start->pR->pToken1->token); assert_eq(TOKEN_d, start->pR->pToken1->token);
p_free_tree(start);
return 0; return 0;
} }
@@ -13,5 +13,7 @@ int main()
assert(top->pToken != NULL); assert(top->pToken != NULL);
assert_eq(TOKEN_hi, top->pToken->token); assert_eq(TOKEN_hi, top->pToken->token);
p_free_tree(top);
return 0; return 0;
} }
File renamed without changes.
+27
View File
@@ -0,0 +1,27 @@
#include "testparser.h"
#include <assert.h>
#include <string.h>
#include "testutils.h"
int main()
{
char const * input = "bbbb";
p_context_t context;
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS);
int result = p_result(&context);
assert_eq(8, result);
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse_Bs(&context) == P_SUCCESS);
result = p_result_Bs(&context);
assert_eq(8, result);
input = "c";
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse_R(&context) == P_SUCCESS);
result = p_result_R(&context);
assert_eq(3, result);
return 0;
}
+29
View File
@@ -0,0 +1,29 @@
import testparser;
import std.stdio;
import testutils;
int main()
{
return 0;
}
unittest
{
string input = "bbbb";
p_context_t context;
p_context_init(&context, input);
assert(p_parse(&context) == P_SUCCESS);
int result = p_result(&context);
assert(result == 8);
p_context_init(&context, input);
assert(p_parse_Bs(&context) == P_SUCCESS);
result = p_result_Bs(&context);
assert(result == 8);
input = "c";
p_context_init(&context, input);
assert(p_parse_R(&context) == P_SUCCESS);
result = p_result_R(&context);
assert(result == 3);
}
+37
View File
@@ -0,0 +1,37 @@
#include "testparser.h"
#include <assert.h>
#include <string.h>
#include "testutils.h"
int main()
{
char const * input = "bbbb";
p_context_t context;
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS);
Start * start = p_result(&context);
assert_not_null(start->bs);
assert_not_null(start->bs->b);
assert_not_null(start->bs->bs->b);
assert_not_null(start->bs->bs->bs->b);
assert_not_null(start->bs->bs->bs->bs->b);
p_free_tree(start);
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse_Bs(&context) == P_SUCCESS);
Bs * bs = p_result_Bs(&context);
assert_not_null(bs->b);
assert_not_null(bs->bs->b);
assert_not_null(bs->bs->bs->b);
assert_not_null(bs->bs->bs->bs->b);
p_free_tree_Bs(bs);
input = "c";
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse_R(&context) == P_SUCCESS);
R * r = p_result_R(&context);
assert_not_null(r->c);
p_free_tree_R(r);
return 0;
}
+36
View File
@@ -0,0 +1,36 @@
import testparser;
import std.stdio;
import testutils;
int main()
{
return 0;
}
unittest
{
string input = "bbbb";
p_context_t context;
p_context_init(&context, input);
assert(p_parse(&context) == P_SUCCESS);
Start * start = p_result(&context);
assert(start.bs);
assert(start.bs.b);
assert(start.bs.bs.b);
assert(start.bs.bs.bs.b);
assert(start.bs.bs.bs.bs.b);
p_context_init(&context, input);
assert(p_parse_Bs(&context) == P_SUCCESS);
Bs * bs = p_result_Bs(&context);
assert(bs.b);
assert(bs.bs.b);
assert(bs.bs.bs.b);
assert(bs.bs.bs.bs.b);
input = "c";
p_context_init(&context, input);
assert(p_parse_R(&context) == P_SUCCESS);
R * r = p_result_R(&context);
assert(r.c);
}
+6
View File
@@ -33,12 +33,16 @@ int main()
assert_eq(22, itemsmore->pItem->pToken1->pvalue); assert_eq(22, itemsmore->pItem->pToken1->pvalue);
assert(itemsmore->pItemsMore == NULL); assert(itemsmore->pItemsMore == NULL);
p_free_tree(start);
input = ""; input = "";
p_context_init(&context, (uint8_t const *)input, strlen(input)); p_context_init(&context, (uint8_t const *)input, strlen(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 == NULL); assert(start->pItems == NULL);
p_free_tree(start);
input = "2 1"; input = "2 1";
p_context_init(&context, (uint8_t const *)input, strlen(input)); p_context_init(&context, (uint8_t const *)input, strlen(input));
assert_eq(P_SUCCESS, p_parse(&context)); 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->pTwo2 == NULL);
assert(start->pItems->pItem->pDual->pOne1 == NULL); assert(start->pItems->pItem->pDual->pOne1 == NULL);
p_free_tree(start);
return 0; return 0;
} }
File renamed without changes.
@@ -15,5 +15,7 @@ int main()
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_free_tree(start);
return 0; return 0;
} }
File renamed without changes.
@@ -30,6 +30,8 @@ int main()
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_free_tree(start);
input = "a\nbb"; input = "a\nbb";
p_context_init(&context, (uint8_t const *)input, strlen(input)); p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS); 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.row);
assert_eq(2, start->end_position.col); assert_eq(2, start->end_position.col);
p_free_tree(start);
input = "a\nc\nc"; input = "a\nc\nc";
p_context_init(&context, (uint8_t const *)input, strlen(input)); p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS); assert(p_parse(&context) == P_SUCCESS);
@@ -78,6 +82,8 @@ int main()
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_free_tree(start);
input = "a"; input = "a";
p_context_init(&context, (uint8_t const *)input, strlen(input)); p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS); 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.row);
assert_eq(1, start->end_position.col); assert_eq(1, start->end_position.col);
p_free_tree(start);
return 0; return 0;
} }
File renamed without changes.
@@ -386,7 +386,7 @@ int main(int argc, char * argv[])
} }
pmis = pmis->pModuleItems; pmis = pmis->pModuleItems;
} }
pfds = malloc(n_pfds * sizeof(PModuleItems *)); pfds = (PFunctionDefinition **)malloc(n_pfds * sizeof(PModuleItems *));
pmis = pmod->pModuleItems; pmis = pmod->pModuleItems;
size_t pfd_i = n_pfds; size_t pfd_i = n_pfds;
while (pmis != NULL) while (pmis != NULL)
@@ -411,5 +411,8 @@ int main(int argc, char * argv[])
} }
} }
free(pfds);
p_free_tree(pmod);
return 0; return 0;
} }
File renamed without changes.
@@ -33,12 +33,16 @@ int main()
assert_eq(22, itemsmore->pItem->pToken1->pvalue); assert_eq(22, itemsmore->pItem->pToken1->pvalue);
assert(itemsmore->pItemsMore == NULL); assert(itemsmore->pItemsMore == NULL);
p_free_tree(start);
input = ""; input = "";
p_context_init(&context, (uint8_t const *)input, strlen(input)); p_context_init(&context, (uint8_t const *)input, strlen(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 == NULL); assert(start->pItems == NULL);
p_free_tree(start);
input = "2 1"; input = "2 1";
p_context_init(&context, (uint8_t const *)input, strlen(input)); p_context_init(&context, (uint8_t const *)input, strlen(input));
assert_eq(P_SUCCESS, p_parse(&context)); 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->pTwo2 == NULL);
assert(start->pItems->pItem->pDual->pOne1 == NULL); assert(start->pItems->pItem->pDual->pOne1 == NULL);
p_free_tree(start);
return 0; return 0;
} }
File renamed without changes.
@@ -43,6 +43,8 @@ int main()
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_free_tree(start);
input = "\n\n bb\nc\ncc\n\n a"; input = "\n\n bb\nc\ncc\n\n a";
p_context_init(&context, (uint8_t const *)input, strlen(input)); p_context_init(&context, (uint8_t const *)input, strlen(input));
assert(p_parse(&context) == P_SUCCESS); assert(p_parse(&context) == P_SUCCESS);
@@ -80,5 +82,7 @@ int main()
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_free_tree(start);
return 0; return 0;
} }
File renamed without changes.
+20 -2
View File
@@ -14,10 +14,28 @@ void assert_eq_size_t_i(size_t expected, size_t actual, char const * file, size_
} }
} }
void assert_ne_size_t_i(size_t expected, size_t actual, char const * file, size_t line)
{
if (expected == actual)
{
fprintf(stderr, "%s:%lu: expected not %lu, got %lu\n", file, line, expected, actual);
assert(false);
}
}
void assert_not_null_i(void * ptr, char const * file, size_t line)
{
if (ptr == NULL)
{
fprintf(stderr, "%s:%lu: expected not NULL\n", file, line);
assert(false);
}
}
void str_init(str_t * str, char const * cs) void str_init(str_t * str, char const * cs)
{ {
size_t length = strlen(cs); size_t length = strlen(cs);
str->cs = malloc(length + 1u); str->cs = (char *)malloc(length + 1u);
strcpy(str->cs, cs); strcpy(str->cs, cs);
} }
@@ -25,7 +43,7 @@ void str_append(str_t * str, char const * cs)
{ {
size_t length = strlen(str->cs); size_t length = strlen(str->cs);
size_t length2 = strlen(cs); size_t length2 = strlen(cs);
char * new_cs = malloc(length + length2 + 1u); char * new_cs = (char *)malloc(length + length2 + 1u);
memcpy(new_cs, str->cs, length); memcpy(new_cs, str->cs, length);
strcpy(&new_cs[length], cs); strcpy(&new_cs[length], cs);
free(str->cs); free(str->cs);
+10
View File
@@ -5,6 +5,16 @@ void assert_eq_size_t_i(size_t expected, size_t actual, char const * file, size_
#define assert_eq(expected, actual) \ #define assert_eq(expected, actual) \
assert_eq_size_t_i(expected, actual, __FILE__, __LINE__) assert_eq_size_t_i(expected, actual, __FILE__, __LINE__)
void assert_ne_size_t_i(size_t expected, size_t actual, char const * file, size_t line);
#define assert_ne(expected, actual) \
assert_ne_size_t_i(expected, actual, __FILE__, __LINE__)
void assert_not_null_i(void * ptr, char const * file, size_t line);
#define assert_not_null(ptr) \
assert_not_null_i(ptr, __FILE__, __LINE__)
typedef struct typedef struct
{ {
char * cs; char * cs;
@@ -1,5 +1,5 @@
ast; tree;
ast_prefix P; tree_prefix P;
<<header <<header
#include <stdio.h> #include <stdio.h>
@@ -1,5 +1,5 @@
ast; tree;
ast_prefix P; tree_prefix P;
<< <<
import std.bigint; import std.bigint;