Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
64383cb0f4 | ||
|
|
256b7f9277 | ||
|
|
98390429a8 | ||
|
|
b04c0ad205 | ||
|
|
453be990b6 | ||
|
|
3c619acd29 | ||
|
|
ca9c23f96b | ||
|
|
1729546d69 | ||
|
|
7942a3be97 | ||
|
|
c28aebddb4 | ||
|
|
cb914e8bb0 | ||
|
|
ece95dd7e0 | ||
|
|
232af68081 | ||
|
|
23834bef82 | ||
|
|
7b698d7b31 | ||
|
|
45843da0df | ||
|
|
f27a8688f7 | ||
|
|
c459507612 | ||
|
|
feacbc907d | ||
|
|
5ac3ad1655 | ||
|
|
b641c64425 | ||
|
|
5245302f49 | ||
|
|
7a140623ff | ||
|
|
42035c3a55 | ||
|
|
d97f0f8f49 | ||
|
|
98e2beb425 | ||
|
|
30565545ee | ||
|
|
cd75077959 | ||
|
|
51b09a5799 |
No files matched your search
@@ -1,3 +1,55 @@
|
|||||||
|
## v4.7.0
|
||||||
|
|
||||||
|
### New Features
|
||||||
|
|
||||||
|
- Support parser rule user code blocks in tree generation mode.
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- propane.vim: do not highlight rule components as propane keywords
|
||||||
|
|
||||||
|
## v4.6.0
|
||||||
|
|
||||||
|
### New Features
|
||||||
|
|
||||||
|
- Add lexer user code API to access matched input text positions
|
||||||
|
- Track rule component text positions and add parser user code API to access
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Fixed a few user guide and source comments related to text input positions
|
||||||
|
|
||||||
|
## v4.5.0
|
||||||
|
|
||||||
|
### New Features
|
||||||
|
|
||||||
|
- Add `noline` grammar statement to skip emitting `#line` directives
|
||||||
|
- Attempt to autodetect target language (D/C++) in extra/vim/syntax/propane.vim
|
||||||
|
|
||||||
|
### Fixes
|
||||||
|
|
||||||
|
- Fix #line reset directives
|
||||||
|
- Update keyword list in extra/vim/syntax/propane.vim
|
||||||
|
- Fix propane.vim keyword detection
|
||||||
|
|
||||||
|
## v4.4.0
|
||||||
|
|
||||||
|
### New Features
|
||||||
|
|
||||||
|
- Add p_value_get() / p_value_get_XXX() accessors
|
||||||
|
|
||||||
|
## v4.3.0
|
||||||
|
|
||||||
|
### New Features
|
||||||
|
|
||||||
|
- Use #line for user code blocks to report input grammar position for errors.
|
||||||
|
|
||||||
|
## v4.2.0
|
||||||
|
|
||||||
|
### New Features
|
||||||
|
|
||||||
|
- Add support for a custom lex function.
|
||||||
|
|
||||||
## v4.1.0
|
## v4.1.0
|
||||||
|
|
||||||
### New Features
|
### New Features
|
||||||
|
|||||||
+5
-5
@@ -5,11 +5,11 @@ GEM
|
|||||||
date (3.5.1)
|
date (3.5.1)
|
||||||
diff-lcs (1.6.2)
|
diff-lcs (1.6.2)
|
||||||
docile (1.4.1)
|
docile (1.4.1)
|
||||||
erb (6.0.1)
|
erb (6.0.4)
|
||||||
psych (5.3.1)
|
psych (5.4.0)
|
||||||
date
|
date
|
||||||
stringio
|
stringio
|
||||||
rake (13.3.1)
|
rake (13.4.2)
|
||||||
rdoc (7.2.0)
|
rdoc (7.2.0)
|
||||||
erb
|
erb
|
||||||
psych (>= 4.0.0)
|
psych (>= 4.0.0)
|
||||||
@@ -24,7 +24,7 @@ GEM
|
|||||||
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.7)
|
rspec-mocks (3.13.8)
|
||||||
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.7)
|
rspec-support (3.13.7)
|
||||||
@@ -51,4 +51,4 @@ DEPENDENCIES
|
|||||||
syntax
|
syntax
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
2.3.7
|
4.0.14
|
||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2010-2024 Josh Holtrop
|
Copyright (c) 2010-2026 Josh Holtrop
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
+105
-21
@@ -486,6 +486,22 @@ static size_t attempt_lex_token(<%= @grammar.prefix %>context_t * context, <%= @
|
|||||||
case P_SUCCESS:
|
case P_SUCCESS:
|
||||||
{
|
{
|
||||||
<%= @grammar.prefix %>token_t token_to_accept = match_info.accepting_state->token;
|
<%= @grammar.prefix %>token_t token_to_accept = match_info.accepting_state->token;
|
||||||
|
/* Calculate the token length and start/end positions before invoking
|
||||||
|
* the lexer user code so that the user code can access them. The
|
||||||
|
* context input text position tracking is not updated until after the
|
||||||
|
* user code has run so that it is left unchanged if the user code
|
||||||
|
* requests to terminate the lexer. */
|
||||||
|
token_info.length = match_info.length;
|
||||||
|
if (match_info.end_delta_position.row != 0u)
|
||||||
|
{
|
||||||
|
token_info.end_position.row = token_info.position.row + match_info.end_delta_position.row;
|
||||||
|
token_info.end_position.col = match_info.end_delta_position.col;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
token_info.end_position.row = token_info.position.row;
|
||||||
|
token_info.end_position.col = token_info.position.col + match_info.end_delta_position.col;
|
||||||
|
}
|
||||||
if (match_info.accepting_state->code_id != INVALID_USER_CODE_ID)
|
if (match_info.accepting_state->code_id != INVALID_USER_CODE_ID)
|
||||||
{
|
{
|
||||||
uint8_t const * match = &context->input[context->input_index];
|
uint8_t const * match = &context->input[context->input_index];
|
||||||
@@ -524,17 +540,6 @@ static size_t attempt_lex_token(<%= @grammar.prefix %>context_t * context, <%= @
|
|||||||
return P_DROP;
|
return P_DROP;
|
||||||
}
|
}
|
||||||
token_info.token = token_to_accept;
|
token_info.token = token_to_accept;
|
||||||
token_info.length = match_info.length;
|
|
||||||
if (match_info.end_delta_position.row != 0u)
|
|
||||||
{
|
|
||||||
token_info.end_position.row = token_info.position.row + match_info.end_delta_position.row;
|
|
||||||
token_info.end_position.col = match_info.end_delta_position.col;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
token_info.end_position.row = token_info.position.row;
|
|
||||||
token_info.end_position.col = token_info.position.col + match_info.end_delta_position.col;
|
|
||||||
}
|
|
||||||
*out_token_info = token_info;
|
*out_token_info = token_info;
|
||||||
}
|
}
|
||||||
return P_SUCCESS;
|
return P_SUCCESS;
|
||||||
@@ -707,12 +712,14 @@ typedef struct
|
|||||||
/** Parser state ID. */
|
/** Parser state ID. */
|
||||||
size_t state_id;
|
size_t state_id;
|
||||||
|
|
||||||
/** Parser value from this state. */
|
|
||||||
<%= @grammar.prefix %>value_t pvalue;
|
|
||||||
|
|
||||||
<% if @grammar.tree %>
|
<% if @grammar.tree %>
|
||||||
/** tree node. */
|
/** tree node. */
|
||||||
void * tree_node;
|
void * tree_node;
|
||||||
|
<% else %>
|
||||||
|
<%= @grammar.prefix %>position_t position;
|
||||||
|
<%= @grammar.prefix %>position_t end_position;
|
||||||
|
/** Parser value from this state. */
|
||||||
|
<%= @grammar.prefix %>value_t pvalue;
|
||||||
<% end %>
|
<% end %>
|
||||||
} state_value_t;
|
} state_value_t;
|
||||||
|
|
||||||
@@ -866,6 +873,59 @@ static void state_values_stack_free(state_values_stack_t * stack)
|
|||||||
}
|
}
|
||||||
|
|
||||||
<% unless @grammar.tree %>
|
<% unless @grammar.tree %>
|
||||||
|
/**
|
||||||
|
* Get the rule position (start or end) for the currently matched rule.
|
||||||
|
*/
|
||||||
|
static <%= @grammar.prefix %>position_t get_rule_position(state_values_stack_t * statevalues, size_t i, size_t n_states, bool get_end)
|
||||||
|
{
|
||||||
|
if (n_states > 0u)
|
||||||
|
{
|
||||||
|
if (i == 0u)
|
||||||
|
{
|
||||||
|
if (get_end)
|
||||||
|
{
|
||||||
|
int stack_index = -1;
|
||||||
|
for (size_t j = 0u; j < n_states; j++)
|
||||||
|
{
|
||||||
|
state_value_t * sv = state_values_stack_index(statevalues, stack_index - (int)j);
|
||||||
|
if (<%= @grammar.prefix %>position_valid(sv->end_position))
|
||||||
|
{
|
||||||
|
return sv->end_position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int stack_index = -(int)n_states;
|
||||||
|
for (size_t j = 0u; j < n_states; j++)
|
||||||
|
{
|
||||||
|
state_value_t * sv = state_values_stack_index(statevalues, stack_index + (int)j);
|
||||||
|
if (<%= @grammar.prefix %>position_valid(sv->position))
|
||||||
|
{
|
||||||
|
return sv->position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (get_end)
|
||||||
|
{
|
||||||
|
return state_values_stack_index(statevalues, -1 - (int)n_states + (int)i)->end_position;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return state_values_stack_index(statevalues, -1 - (int)n_states + (int)i)->position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<%= @grammar.prefix %>position_t empty_pos;
|
||||||
|
memset(&empty_pos, 0, sizeof(empty_pos));
|
||||||
|
return empty_pos;
|
||||||
|
}
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% if !@grammar.tree || @grammar.parser_user_code_used? %>
|
||||||
/**
|
/**
|
||||||
* Execute user code associated with a parser rule.
|
* Execute user code associated with a parser rule.
|
||||||
*
|
*
|
||||||
@@ -876,7 +936,7 @@ static void state_values_stack_free(state_values_stack_t * stack)
|
|||||||
* @retval P_USER_TERMINATED
|
* @retval P_USER_TERMINATED
|
||||||
* User requested to terminate parsing.
|
* User requested to terminate parsing.
|
||||||
*/
|
*/
|
||||||
static size_t parser_user_code(<%= @grammar.prefix %>value_t * _pvalue, uint32_t rule, state_values_stack_t * statevalues, uint32_t n_states, <%= @grammar.prefix %>context_t * context)
|
static size_t parser_user_code(<%= @grammar.tree ? "void" : "#{@grammar.prefix}value_t" %> * _pvalue, uint32_t rule, state_values_stack_t * statevalues, uint32_t n_states, <%= @grammar.prefix %>context_t * context)
|
||||||
{
|
{
|
||||||
switch (rule)
|
switch (rule)
|
||||||
{
|
{
|
||||||
@@ -971,6 +1031,8 @@ static size_t parse_from(<%= @grammar.prefix %>context_t * context, size_t start
|
|||||||
<% if @grammar.tree %>
|
<% if @grammar.tree %>
|
||||||
void * reduced_parser_node;
|
void * reduced_parser_node;
|
||||||
<% else %>
|
<% else %>
|
||||||
|
<%= @grammar.prefix %>position_t reduced_position;
|
||||||
|
<%= @grammar.prefix %>position_t reduced_end_position;
|
||||||
<%= @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);
|
||||||
@@ -981,7 +1043,7 @@ static size_t parse_from(<%= @grammar.prefix %>context_t * context, size_t start
|
|||||||
{
|
{
|
||||||
if (token == INVALID_TOKEN_ID)
|
if (token == INVALID_TOKEN_ID)
|
||||||
{
|
{
|
||||||
size_t lexer_result = <%= @grammar.prefix %>lex(context, &token_info);
|
size_t lexer_result = <%= lex_fn %>(context, &token_info);
|
||||||
if (lexer_result != P_SUCCESS)
|
if (lexer_result != P_SUCCESS)
|
||||||
{
|
{
|
||||||
result = lexer_result;
|
result = lexer_result;
|
||||||
@@ -1013,7 +1075,8 @@ static size_t parse_from(<%= @grammar.prefix %>context_t * context, size_t start
|
|||||||
{
|
{
|
||||||
/* We have something to shift. */
|
/* We have something to shift. */
|
||||||
state_values_stack_push(&statevalues);
|
state_values_stack_push(&statevalues);
|
||||||
state_values_stack_index(&statevalues, -1)->state_id = shift_state;
|
state_value_t * new_state_info = state_values_stack_index(&statevalues, -1);
|
||||||
|
new_state_info->state_id = shift_state;
|
||||||
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. */
|
||||||
@@ -1030,9 +1093,11 @@ static size_t parse_from(<%= @grammar.prefix %>context_t * context, size_t start
|
|||||||
token_tree_node->token = token;
|
token_tree_node->token = token;
|
||||||
token_tree_node->pvalue = token_info.pvalue;
|
token_tree_node->pvalue = token_info.pvalue;
|
||||||
<%= expand_code(@grammar.on_token_node, false, nil, nil) %>
|
<%= expand_code(@grammar.on_token_node, false, nil, nil) %>
|
||||||
state_values_stack_index(&statevalues, -1)->tree_node = token_tree_node;
|
new_state_info->tree_node = token_tree_node;
|
||||||
<% else %>
|
<% else %>
|
||||||
state_values_stack_index(&statevalues, -1)->pvalue = token_info.pvalue;
|
new_state_info->position = token_info.position;
|
||||||
|
new_state_info->end_position = token_info.end_position;
|
||||||
|
new_state_info->pvalue = token_info.pvalue;
|
||||||
<% end %>
|
<% end %>
|
||||||
token = INVALID_TOKEN_ID;
|
token = INVALID_TOKEN_ID;
|
||||||
}
|
}
|
||||||
@@ -1040,9 +1105,11 @@ static size_t parse_from(<%= @grammar.prefix %>context_t * context, size_t start
|
|||||||
{
|
{
|
||||||
/* We shifted a RuleSet. */
|
/* We shifted a RuleSet. */
|
||||||
<% if @grammar.tree %>
|
<% if @grammar.tree %>
|
||||||
state_values_stack_index(&statevalues, -1)->tree_node = reduced_parser_node;
|
new_state_info->tree_node = reduced_parser_node;
|
||||||
<% else %>
|
<% else %>
|
||||||
state_values_stack_index(&statevalues, -1)->pvalue = reduced_parser_value;
|
new_state_info->pvalue = reduced_parser_value;
|
||||||
|
new_state_info->position = reduced_position;
|
||||||
|
new_state_info->end_position = reduced_end_position;
|
||||||
<%= @grammar.prefix %>value_t new_parse_result;
|
<%= @grammar.prefix %>value_t new_parse_result;
|
||||||
memset(&new_parse_result, 0, sizeof(new_parse_result));
|
memset(&new_parse_result, 0, sizeof(new_parse_result));
|
||||||
reduced_parser_value = new_parse_result;
|
reduced_parser_value = new_parse_result;
|
||||||
@@ -1104,6 +1171,13 @@ static size_t parse_from(<%= @grammar.prefix %>context_t * context, size_t start
|
|||||||
{
|
{
|
||||||
reduced_parser_node = NULL;
|
reduced_parser_node = NULL;
|
||||||
}
|
}
|
||||||
|
<% if @grammar.parser_user_code_used? %>
|
||||||
|
if (parser_user_code(reduced_parser_node, 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;
|
||||||
|
}
|
||||||
|
<% end %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= @grammar.prefix %>value_t reduced_parser_value2;
|
<%= @grammar.prefix %>value_t reduced_parser_value2;
|
||||||
memset(&reduced_parser_value2, 0, sizeof(reduced_parser_value2));
|
memset(&reduced_parser_value2, 0, sizeof(reduced_parser_value2));
|
||||||
@@ -1113,6 +1187,16 @@ static size_t parse_from(<%= @grammar.prefix %>context_t * context, size_t start
|
|||||||
return P_USER_TERMINATED;
|
return P_USER_TERMINATED;
|
||||||
}
|
}
|
||||||
reduced_parser_value = reduced_parser_value2;
|
reduced_parser_value = reduced_parser_value2;
|
||||||
|
if (parser_reduce_table[reduce_index].n_states > 0u)
|
||||||
|
{
|
||||||
|
reduced_position = get_rule_position(&statevalues, 0u, parser_reduce_table[reduce_index].n_states, false);
|
||||||
|
reduced_end_position = get_rule_position(&statevalues, 0u, parser_reduce_table[reduce_index].n_states, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
memset(&reduced_position, 0, sizeof(reduced_position));
|
||||||
|
memset(&reduced_end_position, 0, sizeof(reduced_end_position));
|
||||||
|
}
|
||||||
<% end %>
|
<% end %>
|
||||||
reduced_rule_set = parser_reduce_table[reduce_index].rule_set;
|
reduced_rule_set = parser_reduce_table[reduce_index].rule_set;
|
||||||
state_values_stack_pop(&statevalues, parser_reduce_table[reduce_index].n_states);
|
state_values_stack_pop(&statevalues, parser_reduce_table[reduce_index].n_states);
|
||||||
|
|||||||
+112
-18
@@ -59,10 +59,10 @@ public alias <%= @grammar.prefix %>code_point_t = uint;
|
|||||||
*/
|
*/
|
||||||
public struct <%= @grammar.prefix %>position_t
|
public struct <%= @grammar.prefix %>position_t
|
||||||
{
|
{
|
||||||
/** Input text row (0-based). */
|
/** Input text row (1-based). */
|
||||||
uint row;
|
uint row;
|
||||||
|
|
||||||
/** Input text column (0-based). */
|
/** Input text column (1-based). */
|
||||||
uint col;
|
uint col;
|
||||||
|
|
||||||
/** Invalid position value. */
|
/** Invalid position value. */
|
||||||
@@ -86,6 +86,22 @@ public union <%= @grammar.prefix %>value_t
|
|||||||
<%= typestring %> v_<%= name %>;
|
<%= typestring %> v_<%= name %>;
|
||||||
<% end %>
|
<% end %>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Parser value constructor(s). */
|
||||||
|
<% @grammar.ptypes.each do |name, typestring| %>
|
||||||
|
public <%= @grammar.prefix %>value_t <%= @grammar.prefix %>value<%= name == "default" ? "" : "_#{name}" %>(T)(T v)
|
||||||
|
{
|
||||||
|
return <%= @grammar.prefix %>value_t(v_<%= name %>: v);
|
||||||
|
}
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
/** Parser value accessor(s). */
|
||||||
|
<% @grammar.ptypes.each do |name, typestring| %>
|
||||||
|
public <%= typestring %> <%= @grammar.prefix %>value_get<%= name == "default" ? "" : "_#{name}" %>(<%= @grammar.prefix %>value_t * pvalue)
|
||||||
|
{
|
||||||
|
return pvalue.v_<%= name %>;
|
||||||
|
}
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% if @grammar.tree %>
|
<% if @grammar.tree %>
|
||||||
@@ -649,6 +665,22 @@ private size_t attempt_lex_token(<%= @grammar.prefix %>context_t * context, <%=
|
|||||||
{
|
{
|
||||||
case P_SUCCESS:
|
case P_SUCCESS:
|
||||||
<%= @grammar.prefix %>token_t token_to_accept = match_info.accepting_state.token;
|
<%= @grammar.prefix %>token_t token_to_accept = match_info.accepting_state.token;
|
||||||
|
/* Calculate the token length and start/end positions before invoking
|
||||||
|
* the lexer user code so that the user code can access them. The
|
||||||
|
* context input text position tracking is not updated until after the
|
||||||
|
* user code has run so that it is left unchanged if the user code
|
||||||
|
* requests to terminate the lexer. */
|
||||||
|
token_info.length = match_info.length;
|
||||||
|
if (match_info.end_delta_position.row != 0u)
|
||||||
|
{
|
||||||
|
token_info.end_position.row = token_info.position.row + match_info.end_delta_position.row;
|
||||||
|
token_info.end_position.col = match_info.end_delta_position.col;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
token_info.end_position.row = token_info.position.row;
|
||||||
|
token_info.end_position.col = token_info.position.col + match_info.end_delta_position.col;
|
||||||
|
}
|
||||||
if (match_info.accepting_state.code_id != INVALID_USER_CODE_ID)
|
if (match_info.accepting_state.code_id != INVALID_USER_CODE_ID)
|
||||||
{
|
{
|
||||||
string match = context.input[context.input_index..(context.input_index + match_info.length)];
|
string match = context.input[context.input_index..(context.input_index + match_info.length)];
|
||||||
@@ -687,17 +719,6 @@ private size_t attempt_lex_token(<%= @grammar.prefix %>context_t * context, <%=
|
|||||||
return P_DROP;
|
return P_DROP;
|
||||||
}
|
}
|
||||||
token_info.token = token_to_accept;
|
token_info.token = token_to_accept;
|
||||||
token_info.length = match_info.length;
|
|
||||||
if (match_info.end_delta_position.row != 0u)
|
|
||||||
{
|
|
||||||
token_info.end_position.row = token_info.position.row + match_info.end_delta_position.row;
|
|
||||||
token_info.end_position.col = match_info.end_delta_position.col;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
token_info.end_position.row = token_info.position.row;
|
|
||||||
token_info.end_position.col = token_info.position.col + match_info.end_delta_position.col;
|
|
||||||
}
|
|
||||||
*out_token_info = token_info;
|
*out_token_info = token_info;
|
||||||
return P_SUCCESS;
|
return P_SUCCESS;
|
||||||
|
|
||||||
@@ -866,12 +887,14 @@ private struct state_value_t
|
|||||||
/** Parser state ID. */
|
/** Parser state ID. */
|
||||||
size_t state_id;
|
size_t state_id;
|
||||||
|
|
||||||
/** Parser value from this state. */
|
|
||||||
<%= @grammar.prefix %>value_t pvalue;
|
|
||||||
|
|
||||||
<% if @grammar.tree %>
|
<% if @grammar.tree %>
|
||||||
/** Tree node. */
|
/** Tree node. */
|
||||||
void * tree_node;
|
void * tree_node;
|
||||||
|
<% else %>
|
||||||
|
<%= @grammar.prefix %>position_t position;
|
||||||
|
<%= @grammar.prefix %>position_t end_position;
|
||||||
|
/** Parser value from this state. */
|
||||||
|
<%= @grammar.prefix %>value_t pvalue;
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
this(size_t state_id)
|
this(size_t state_id)
|
||||||
@@ -925,6 +948,55 @@ private immutable parser_state_t[] parser_state_table = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
<% unless @grammar.tree %>
|
<% unless @grammar.tree %>
|
||||||
|
/**
|
||||||
|
* Get the rule position (start or end) for the currently matched rule.
|
||||||
|
*/
|
||||||
|
private <%= @grammar.prefix %>position_t get_rule_position(state_value_t[] statevalues, size_t i, size_t n_states, bool get_end)
|
||||||
|
{
|
||||||
|
if (n_states > 0u)
|
||||||
|
{
|
||||||
|
if (i == 0u)
|
||||||
|
{
|
||||||
|
if (get_end)
|
||||||
|
{
|
||||||
|
for (size_t j = 0u; j < n_states; j++)
|
||||||
|
{
|
||||||
|
state_value_t * sv = &statevalues[$-1-j];
|
||||||
|
if (sv.end_position.valid)
|
||||||
|
{
|
||||||
|
return sv.end_position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (size_t j = 0u; j < n_states; j++)
|
||||||
|
{
|
||||||
|
state_value_t * sv = &statevalues[$-n_states+j];
|
||||||
|
if (sv.position.valid)
|
||||||
|
{
|
||||||
|
return sv.position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (get_end)
|
||||||
|
{
|
||||||
|
return statevalues[$-1-n_states+i].end_position;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return statevalues[$-1-n_states+i].position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return <%= @grammar.prefix %>position_t.INVALID;
|
||||||
|
}
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% if !@grammar.tree || @grammar.parser_user_code_used? %>
|
||||||
/**
|
/**
|
||||||
* Execute user code associated with a parser rule.
|
* Execute user code associated with a parser rule.
|
||||||
*
|
*
|
||||||
@@ -935,7 +1007,7 @@ private immutable parser_state_t[] parser_state_table = [
|
|||||||
* @retval P_USER_TERMINATED
|
* @retval P_USER_TERMINATED
|
||||||
* User requested to terminate parsing.
|
* User requested to terminate parsing.
|
||||||
*/
|
*/
|
||||||
private size_t parser_user_code(<%= @grammar.prefix %>value_t * _pvalue, uint rule, state_value_t[] statevalues, uint n_states, <%= @grammar.prefix %>context_t * context)
|
private size_t parser_user_code(<%= @grammar.tree ? "void" : "#{@grammar.prefix}value_t" %> * _pvalue, uint rule, state_value_t[] statevalues, uint n_states, <%= @grammar.prefix %>context_t * context)
|
||||||
{
|
{
|
||||||
switch (rule)
|
switch (rule)
|
||||||
{
|
{
|
||||||
@@ -1031,13 +1103,15 @@ private size_t parse_from(<%= @grammar.prefix %>context_t * context, size_t star
|
|||||||
<% if @grammar.tree %>
|
<% if @grammar.tree %>
|
||||||
void * reduced_parser_node;
|
void * reduced_parser_node;
|
||||||
<% else %>
|
<% else %>
|
||||||
|
<%= @grammar.prefix %>position_t reduced_position;
|
||||||
|
<%= @grammar.prefix %>position_t reduced_end_position;
|
||||||
<%= @grammar.prefix %>value_t reduced_parser_value;
|
<%= @grammar.prefix %>value_t reduced_parser_value;
|
||||||
<% end %>
|
<% end %>
|
||||||
for (;;)
|
for (;;)
|
||||||
{
|
{
|
||||||
if (token == INVALID_TOKEN_ID)
|
if (token == INVALID_TOKEN_ID)
|
||||||
{
|
{
|
||||||
size_t lexer_result = <%= @grammar.prefix %>lex(context, &token_info);
|
size_t lexer_result = <%= lex_fn %>(context, &token_info);
|
||||||
if (lexer_result != P_SUCCESS)
|
if (lexer_result != P_SUCCESS)
|
||||||
{
|
{
|
||||||
return lexer_result;
|
return lexer_result;
|
||||||
@@ -1075,6 +1149,8 @@ private size_t parse_from(<%= @grammar.prefix %>context_t * context, size_t star
|
|||||||
<%= expand_code(@grammar.on_token_node, false, nil, nil) %>
|
<%= expand_code(@grammar.on_token_node, false, nil, nil) %>
|
||||||
statevalues[$-1].tree_node = token_tree_node;
|
statevalues[$-1].tree_node = token_tree_node;
|
||||||
<% else %>
|
<% else %>
|
||||||
|
statevalues[$-1].position = token_info.position;
|
||||||
|
statevalues[$-1].end_position = token_info.end_position;
|
||||||
statevalues[$-1].pvalue = token_info.pvalue;
|
statevalues[$-1].pvalue = token_info.pvalue;
|
||||||
<% end %>
|
<% end %>
|
||||||
token = INVALID_TOKEN_ID;
|
token = INVALID_TOKEN_ID;
|
||||||
@@ -1086,6 +1162,8 @@ private size_t parse_from(<%= @grammar.prefix %>context_t * context, size_t star
|
|||||||
statevalues[$-1].tree_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;
|
||||||
|
statevalues[$-1].position = reduced_position;
|
||||||
|
statevalues[$-1].end_position = reduced_end_position;
|
||||||
<%= @grammar.prefix %>value_t new_parse_result;
|
<%= @grammar.prefix %>value_t new_parse_result;
|
||||||
reduced_parser_value = new_parse_result;
|
reduced_parser_value = new_parse_result;
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -1151,6 +1229,12 @@ private size_t parse_from(<%= @grammar.prefix %>context_t * context, size_t star
|
|||||||
{
|
{
|
||||||
reduced_parser_node = null;
|
reduced_parser_node = null;
|
||||||
}
|
}
|
||||||
|
<% if @grammar.parser_user_code_used? %>
|
||||||
|
if (parser_user_code(reduced_parser_node, parser_reduce_table[reduce_index].rule, statevalues, parser_reduce_table[reduce_index].n_states, context) == P_USER_TERMINATED)
|
||||||
|
{
|
||||||
|
return P_USER_TERMINATED;
|
||||||
|
}
|
||||||
|
<% end %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= @grammar.prefix %>value_t reduced_parser_value2;
|
<%= @grammar.prefix %>value_t 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)
|
||||||
@@ -1158,6 +1242,16 @@ private size_t parse_from(<%= @grammar.prefix %>context_t * context, size_t star
|
|||||||
return P_USER_TERMINATED;
|
return P_USER_TERMINATED;
|
||||||
}
|
}
|
||||||
reduced_parser_value = reduced_parser_value2;
|
reduced_parser_value = reduced_parser_value2;
|
||||||
|
if (parser_reduce_table[reduce_index].n_states > 0u)
|
||||||
|
{
|
||||||
|
reduced_position = get_rule_position(statevalues, 0u, parser_reduce_table[reduce_index].n_states, false);
|
||||||
|
reduced_end_position = get_rule_position(statevalues, 0u, parser_reduce_table[reduce_index].n_states, true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
reduced_position = <%= @grammar.prefix %>position_t.INVALID;
|
||||||
|
reduced_end_position = <%= @grammar.prefix %>position_t.INVALID;
|
||||||
|
}
|
||||||
<% end %>
|
<% end %>
|
||||||
reduced_rule_set = parser_reduce_table[reduce_index].rule_set;
|
reduced_rule_set = parser_reduce_table[reduce_index].rule_set;
|
||||||
statevalues.length -= parser_reduce_table[reduce_index].n_states;
|
statevalues.length -= parser_reduce_table[reduce_index].n_states;
|
||||||
|
|||||||
+18
-2
@@ -45,10 +45,10 @@ typedef uint32_t <%= @grammar.prefix %>code_point_t;
|
|||||||
*/
|
*/
|
||||||
typedef struct
|
typedef struct
|
||||||
{
|
{
|
||||||
/** Input text row (0-based). */
|
/** Input text row (1-based). */
|
||||||
uint32_t row;
|
uint32_t row;
|
||||||
|
|
||||||
/** Input text column (0-based). */
|
/** Input text column (1-based). */
|
||||||
uint32_t col;
|
uint32_t col;
|
||||||
} <%= @grammar.prefix %>position_t;
|
} <%= @grammar.prefix %>position_t;
|
||||||
|
|
||||||
@@ -69,6 +69,22 @@ typedef union
|
|||||||
<%= typestring %> v_<%= name %>;
|
<%= typestring %> v_<%= name %>;
|
||||||
<% end %>
|
<% end %>
|
||||||
} <%= @grammar.prefix %>value_t;
|
} <%= @grammar.prefix %>value_t;
|
||||||
|
|
||||||
|
/** Parser value constructor(s). */
|
||||||
|
<% @grammar.ptypes.each do |name, typestring| %>
|
||||||
|
static inline <%= @grammar.prefix %>value_t <%= @grammar.prefix %>value<%= name == "default" ? "" : "_#{name}" %>(<%= typestring %> v)
|
||||||
|
{
|
||||||
|
return (<%= @grammar.prefix %>value_t){.v_<%= name %> = v};
|
||||||
|
}
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
/** Parser value accessor(s). */
|
||||||
|
<% @grammar.ptypes.each do |name, typestring| %>
|
||||||
|
static inline <%= typestring %> <%= @grammar.prefix %>value_get<%= name == "default" ? "" : "_#{name}" %>(<%= @grammar.prefix %>value_t const * pvalue)
|
||||||
|
{
|
||||||
|
return pvalue->v_<%= name %>;
|
||||||
|
}
|
||||||
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% if @grammar.tree %>
|
<% if @grammar.tree %>
|
||||||
|
|||||||
+227
-18
@@ -151,12 +151,37 @@ needed by a `ptype` directive, for example:
|
|||||||
|
|
||||||
### Lexer pattern code blocks
|
### Lexer pattern code blocks
|
||||||
|
|
||||||
#### C/C++
|
Lexer code blocks appear between `<<` and `>>` markers following a `drop`,
|
||||||
|
`token`, or pattern expression.
|
||||||
|
User code in a lexer code block will be executed when the lexer matches the
|
||||||
|
given pattern.
|
||||||
|
Assignment to the `$$` symbol will associate a parser value with the lexed
|
||||||
|
token.
|
||||||
|
This parser value can then be used later in a parser rule.
|
||||||
|
|
||||||
|
The input text positions of the matched token can also be accessed from within
|
||||||
|
a lexer code block.
|
||||||
|
Each of these positions is an instance of the `p_position_t` structure (see
|
||||||
|
`${#p_position_t}`), which contains 1-based `row` and `col` fields.
|
||||||
|
The start position of the matched token is accessed with `${position}`, and
|
||||||
|
the end position of the matched token is accessed with `${end_position}`.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
token integer /\d+/ <<
|
||||||
|
printf("integer token on row %d, col %d\n",
|
||||||
|
${position}.row, ${position}.col);
|
||||||
|
$$ = parse_integer(match, match_length);
|
||||||
|
>>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### C/C++ lexer code block arguments
|
||||||
|
|
||||||
The lexer code block is passed the following arguments:
|
The lexer code block is passed the following arguments:
|
||||||
|
|
||||||
* `match` - a pointer points to the text matched by the lexer pattern.
|
* `match` (`uint8_t const`) - the pointer points to the text matched by the lexer pattern.
|
||||||
* `match_length` - length of the matched text.
|
* `match_length` (`size_t`) - length of the matched text.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
@@ -174,11 +199,11 @@ token integer /\d+/ <<
|
|||||||
>>
|
>>
|
||||||
```
|
```
|
||||||
|
|
||||||
#### D
|
#### D lexer code block arguments
|
||||||
|
|
||||||
The lexer code block is passed the following arguments:
|
The lexer code block is passed the following arguments:
|
||||||
|
|
||||||
* `match` - a slice containing the text matched by the lexer pattern.
|
* `match` (`string`) - a slice containing the text matched by the lexer pattern.
|
||||||
|
|
||||||
```
|
```
|
||||||
ptype ulong;
|
ptype ulong;
|
||||||
@@ -194,13 +219,6 @@ token integer /\d+/ <<
|
|||||||
>>
|
>>
|
||||||
```
|
```
|
||||||
|
|
||||||
Lexer code blocks appear following a `drop`, `token`, or pattern expression.
|
|
||||||
User code in a lexer code block will be executed when the lexer matches the
|
|
||||||
given pattern.
|
|
||||||
Assignment to the `$$` symbol will associate a parser value with the lexed
|
|
||||||
token.
|
|
||||||
This parser value can then be used later in a parser rule.
|
|
||||||
|
|
||||||
### Parser rule code blocks
|
### Parser rule code blocks
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
@@ -217,9 +235,73 @@ 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 tree generation mode.
|
The input text positions for the reduced rule and for the individual rule
|
||||||
|
components can also be accessed from within a parser rule code block.
|
||||||
|
Each of these positions is an instance of the `p_position_t` structure (see
|
||||||
|
`${#p_position_t}`), which contains 1-based `row` and `col` fields.
|
||||||
|
|
||||||
|
The start position of the overall reduced rule is accessed with
|
||||||
|
`${$.position}`, and the end position of the overall reduced rule is accessed
|
||||||
|
with `${$.end_position}`.
|
||||||
|
|
||||||
|
The start and end positions of an individual rule component are accessed
|
||||||
|
positionally with `${N.position}` and `${N.end_position}`, where `N` is the
|
||||||
|
1-based index of the component (`${1.position}` for the first component,
|
||||||
|
`${2.position}` for the second, and so on).
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
Assignment -> ident equals Expr <<
|
||||||
|
printf("assignment on row %d, col %d\n",
|
||||||
|
${$.position}.row, ${$.position}.col);
|
||||||
|
printf("target identifier ends on row %d, col %d\n",
|
||||||
|
${1.end_position}.row, ${1.end_position}.col);
|
||||||
|
printf("expression starts on row %d, col %d\n",
|
||||||
|
${3.position}.row, ${3.position}.col);
|
||||||
|
>>
|
||||||
|
```
|
||||||
|
|
||||||
|
A rule or rule component that allows for an empty match may not have valid
|
||||||
|
positions.
|
||||||
|
In this case the position should be checked for validity before its `row` and
|
||||||
|
`col` fields are used (see `${#p_position_valid}`).
|
||||||
|
For C targets this can be accomplished with
|
||||||
|
`if (p_position_valid(${$.position}))` and for D targets this can be
|
||||||
|
accomplished with `if (${$.position}.valid)`.
|
||||||
|
|
||||||
In tree 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.
|
||||||
|
Parser rule code blocks are still supported in tree generation mode, but they
|
||||||
|
behave differently than when tree generation mode is not active.
|
||||||
|
The code block for a rule is executed after the rule has been matched and its
|
||||||
|
tree node has been fully formed.
|
||||||
|
Within the code block, `$$` refers to the tree node for the reduced rule, typed
|
||||||
|
as a pointer to that rule's generated tree node structure.
|
||||||
|
The tree nodes for the rule components are accessed positionally with `$1`,
|
||||||
|
`$2`, `$3`, etc..., each typed as a pointer to the generated tree node structure
|
||||||
|
for that component (a rule node or a `Token` node).
|
||||||
|
Field aliases (see the "Specifying parser rules" section) may also be used to
|
||||||
|
reference a component tree node by name; a field alias behaves identically to
|
||||||
|
the positional reference for that component.
|
||||||
|
The positional position expansions (`${$.position}`, `${N.position}`, etc...)
|
||||||
|
are not available in tree generation mode; the `position` and `end_position`
|
||||||
|
fields of the tree nodes can be accessed directly instead.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
tree;
|
||||||
|
|
||||||
|
Assignment -> ident equals Expr <<
|
||||||
|
/* $$ is the Assignment tree node, $1 is the ident Token node, and $3 is
|
||||||
|
* the Expr rule node. */
|
||||||
|
printf("assignment on row %d, col %d\n",
|
||||||
|
$$->position.row, $$->position.col);
|
||||||
|
printf("target identifier ends on row %d, col %d\n",
|
||||||
|
$1->end_position.row, $1->end_position.col);
|
||||||
|
>>
|
||||||
|
```
|
||||||
|
|
||||||
##> `context_user_fields` statement - adding custom fields to the context
|
##> `context_user_fields` statement - adding custom fields to the context
|
||||||
|
|
||||||
@@ -316,6 +398,61 @@ free_token_node <<
|
|||||||
The `free_token_node` statement user code block is not emitted for D language
|
The `free_token_node` statement user code block is not emitted for D language
|
||||||
since D has a garbage collector.
|
since D has a garbage collector.
|
||||||
|
|
||||||
|
##> `lex_fn` statement - specifying a custom lexer function
|
||||||
|
|
||||||
|
Propane generates both a lexer and a parser.
|
||||||
|
By default, the parser uses the generated `p_lex()` function directly to
|
||||||
|
return information for the next lexed token from the input stream.
|
||||||
|
|
||||||
|
However, the user can specify a custom lex function.
|
||||||
|
This function may or may not use the Propane generated `p_lex()` function under
|
||||||
|
the hood.
|
||||||
|
For example, a token sequence could be injected or repeated from a previously
|
||||||
|
saved macro definition.
|
||||||
|
|
||||||
|
Example (C/C++):
|
||||||
|
|
||||||
|
```
|
||||||
|
<<
|
||||||
|
static size_t mylexfn(p_context_t * context, p_token_info_t * out_token_info)
|
||||||
|
{
|
||||||
|
static size_t count;
|
||||||
|
size_t result = P_SUCCESS;
|
||||||
|
if (count > 0)
|
||||||
|
{
|
||||||
|
out_token_info->token = TOKEN_a;
|
||||||
|
out_token_info->pvalue = p_value(count);
|
||||||
|
count--;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = p_lex(context, out_token_info);
|
||||||
|
if (out_token_info->token == TOKEN_c)
|
||||||
|
{
|
||||||
|
count = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
>>
|
||||||
|
|
||||||
|
lex_fn mylexfn;
|
||||||
|
```
|
||||||
|
|
||||||
|
The `lex_fn` statement takes one argument specifying the name of the custom
|
||||||
|
lexer function.
|
||||||
|
The user must supply a value for the `token` field of the `p_token_info_t`
|
||||||
|
output structure so that the parser knows what token was lexed.
|
||||||
|
|
||||||
|
Additionally, if the parser user code makes use of the token's pvalue, then
|
||||||
|
the lexer function must supply a value for the `pvalue` field of the
|
||||||
|
`p_token_info_t` structure.
|
||||||
|
The `p_value()` generated API function could be useful for specifying
|
||||||
|
parser values to associate with the lexed token when tree generation is not
|
||||||
|
enabled.
|
||||||
|
When tree generation is enabled, the `pvalue` field can be set to an instance
|
||||||
|
of whatever type the user has defined as the `ptype` type.
|
||||||
|
|
||||||
##> `module` statement - specifying the generated parser module name
|
##> `module` statement - specifying the generated parser module name
|
||||||
|
|
||||||
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
|
||||||
@@ -328,6 +465,27 @@ module proj.parser;
|
|||||||
If a module statement is not present, then the generated D module will not
|
If a module statement is not present, then the generated D module will not
|
||||||
contain a module statement and the default module name will be used.
|
contain a module statement and the default module name will be used.
|
||||||
|
|
||||||
|
##> `noline` statement - disabling `#line` directives
|
||||||
|
|
||||||
|
By default, Propane emits `#line` directives into the generated output around
|
||||||
|
user code blocks.
|
||||||
|
These directives instruct the compiler to report any warnings or errors in the
|
||||||
|
user code using the file name and line number of the original grammar file,
|
||||||
|
rather than the generated output file.
|
||||||
|
This makes it easier to locate the source of a compiler diagnostic in the
|
||||||
|
grammar file.
|
||||||
|
|
||||||
|
The `noline` statement disables the emission of these `#line` directives.
|
||||||
|
|
||||||
|
```
|
||||||
|
noline;
|
||||||
|
```
|
||||||
|
|
||||||
|
When the `noline` statement is present, user code blocks are copied to the
|
||||||
|
generated output without any surrounding `#line` directives.
|
||||||
|
This can be useful when debugging the generated parser itself, or when the
|
||||||
|
`#line` directives interfere with other tooling.
|
||||||
|
|
||||||
##> `on_tree_node` statement - custom initialization of a token tree node
|
##> `on_tree_node` statement - custom initialization of a token tree node
|
||||||
|
|
||||||
The `on_token_node` statement can be used to provide code that initializes
|
The `on_token_node` statement can be used to provide code that initializes
|
||||||
@@ -581,7 +739,9 @@ It is recommended to place this statement early in the grammar.
|
|||||||
In tree 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 execute after the rule's tree node has been formed
|
||||||
|
and access the tree nodes via `$$`, `$1`, `$2`, etc... (see the "Parser rule
|
||||||
|
code blocks" section).
|
||||||
* Structure types are generated to represent the parsed tokens and rules as
|
* Structure types are generated to represent the parsed tokens and rules as
|
||||||
defined in the grammar.
|
defined in the grammar.
|
||||||
* The parse result from `p_result()` points to a `Start` struct containing
|
* The parse result from `p_result()` points to a `Start` struct containing
|
||||||
@@ -930,8 +1090,12 @@ 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 tree generation mode
|
In tree generation mode, parser rule code blocks access the reduced rule tree
|
||||||
is active.
|
node and its component tree nodes via `$$`, `$1`, `$2`, etc... (see the "Parser
|
||||||
|
rule code blocks" section).
|
||||||
|
Field aliases may still be used in tree generation mode to reference a component
|
||||||
|
tree node by name, behaving identically to the corresponding positional
|
||||||
|
reference.
|
||||||
|
|
||||||
##> User termination of the lexer or parser
|
##> User termination of the lexer or parser
|
||||||
|
|
||||||
@@ -1007,6 +1171,17 @@ 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.
|
||||||
|
|
||||||
|
### `p_value_t`
|
||||||
|
|
||||||
|
If tree generation mode is enabled, the `p_value_t` type is defined to be the
|
||||||
|
type given to the `ptype` statement in the grammar file.
|
||||||
|
|
||||||
|
If tree generation mode is not enabled, there could be more than one `ptype`
|
||||||
|
given, so the `p_value_t` type is a union of all possible `ptype` types.
|
||||||
|
In this case, the API functions `p_value()` and `p_value_XXX()` for each given
|
||||||
|
`ptype` name `XXX` are generated to return `p_value_t` instances holding the
|
||||||
|
corresponding `ptype`.
|
||||||
|
|
||||||
### `p_token_info_t`
|
### `p_token_info_t`
|
||||||
|
|
||||||
The `p_token_info_t` structure contains the following fields:
|
The `p_token_info_t` structure contains the following fields:
|
||||||
@@ -1016,6 +1191,9 @@ The `p_token_info_t` structure contains the following fields:
|
|||||||
* `length` (`size_t`) holds the number of input bytes used by 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
|
* `token` (`p_token_t`) holds the token ID of the lexed token
|
||||||
* `pvalue` (`p_value_t`) holds the parser value associated with the token.
|
* `pvalue` (`p_value_t`) holds the parser value associated with the token.
|
||||||
|
The actual user value can be extracted with `p_value_get(&token_info.pvalue)`
|
||||||
|
for the default value or `p_value_get_XXX(&token_info.pvalue)` for named
|
||||||
|
`ptype` values.
|
||||||
|
|
||||||
### Tree Node Types
|
### Tree Node Types
|
||||||
|
|
||||||
@@ -1256,7 +1434,7 @@ if (p_parse(context) == P_UNEXPECTED_TOKEN)
|
|||||||
{
|
{
|
||||||
p_position_t error_position = p_position(context);
|
p_position_t error_position = p_position(context);
|
||||||
fprintf(stderr, "Error: unexpected token at row %u column %u\n",
|
fprintf(stderr, "Error: unexpected token at row %u column %u\n",
|
||||||
error_position.row + 1, error_position.col + 1);
|
error_position.row, error_position.col);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -1336,6 +1514,37 @@ p_tree_delete_Statement(statement_tree);
|
|||||||
In this case, Propane will free a `Statement` tree structure returned by the
|
In this case, Propane will free a `Statement` tree structure returned by the
|
||||||
`p_parse_Statement(context)` function.
|
`p_parse_Statement(context)` function.
|
||||||
|
|
||||||
|
### `p_value`
|
||||||
|
|
||||||
|
The `p_value(v)` function builds an instance of the `p_value_t` with the
|
||||||
|
default member set to the value of `v`.
|
||||||
|
|
||||||
|
A `p_value_XXX(v)` function is defined for each user-defined `ptype` name with
|
||||||
|
the user-given name in place of the `XXX`.
|
||||||
|
|
||||||
|
These functions are useful for custom lexer functions which need to return a
|
||||||
|
parser value corresponding to a lexed token.
|
||||||
|
|
||||||
|
They are especially useful when tree generation mode is not active.
|
||||||
|
In that case, the `p_value_t` union can hold one of several different possible
|
||||||
|
value types.
|
||||||
|
|
||||||
|
### `p_value_get`
|
||||||
|
|
||||||
|
The `p_value_get()` accessor functions can be used to extract a user value
|
||||||
|
from a `p_value_t` union.
|
||||||
|
|
||||||
|
The `p_value_get(pvalue)` function accepts a pointer to a `p_value_t` and
|
||||||
|
returns the value of its default member.
|
||||||
|
|
||||||
|
A `p_value_get_XXX(pvalue)` function is generated for each user-defined `ptype`
|
||||||
|
name with the user-given name in place of the `XXX`, returning the value of the
|
||||||
|
corresponding member.
|
||||||
|
|
||||||
|
These functions are the counterpart to `p_value()` constructor functions and
|
||||||
|
are useful for reading the parser value associated with a lexed token, for
|
||||||
|
example `p_value_get(&token_info.pvalue)`.
|
||||||
|
|
||||||
##> Data
|
##> Data
|
||||||
|
|
||||||
### `p_token_names`
|
### `p_token_names`
|
||||||
@@ -1353,7 +1562,7 @@ if (p_parse(context) == P_UNEXPECTED_TOKEN)
|
|||||||
p_position_t error_position = p_position(context);
|
p_position_t error_position = p_position(context);
|
||||||
fprintf(stderr, "Error: unexpected token `%s' at row %u column %u\n",
|
fprintf(stderr, "Error: unexpected token `%s' at row %u column %u\n",
|
||||||
p_token_names[context->token],
|
p_token_names[context->token],
|
||||||
error_position.row + 1, error_position.col + 1);
|
error_position.row, error_position.col);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,11 @@ if exists("b:current_syntax")
|
|||||||
endif
|
endif
|
||||||
|
|
||||||
if !exists("b:propane_subtype")
|
if !exists("b:propane_subtype")
|
||||||
let b:propane_subtype = "d"
|
if search('\<import\s\+\%(std\|core\)\.', 'nw') > 0
|
||||||
|
let b:propane_subtype = "d"
|
||||||
|
else
|
||||||
|
let b:propane_subtype = "cpp"
|
||||||
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
exe "syn include @propaneTarget syntax/".b:propane_subtype.".vim"
|
exe "syn include @propaneTarget syntax/".b:propane_subtype.".vim"
|
||||||
@@ -16,18 +20,32 @@ exe "syn include @propaneTarget syntax/".b:propane_subtype.".vim"
|
|||||||
syn region propaneTarget matchgroup=propaneDelimiter start="<<" end=">>$" contains=@propaneTarget keepend
|
syn region propaneTarget matchgroup=propaneDelimiter start="<<" end=">>$" contains=@propaneTarget keepend
|
||||||
|
|
||||||
syn match propaneComment "#.*"
|
syn match propaneComment "#.*"
|
||||||
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 drop free_token_node free_token_user_fields module prefix ptype start token token_user_fields tokenid tree tree_prefix tree_suffix
|
" The right-hand side of a rule (after '->' up to '<<' or ';') lists symbol
|
||||||
|
" names that may coincide with propane keywords (e.g. 'start', 'token',
|
||||||
|
" 'tree'). Wrap it in a region that excludes keyword matches so those names
|
||||||
|
" are not highlighted as keywords. The '<<' is left unconsumed so the
|
||||||
|
" propaneTarget region can still match it.
|
||||||
|
syn region propaneRuleRhs matchgroup=propaneOperator start="->" end="\ze<<" end=";" contains=propaneFieldAlias,propaneRuleOperator,propaneComment keepend
|
||||||
|
syn match propaneRuleOperator "?" contained
|
||||||
|
" Keywords that introduce a user-defined name. The name is consumed by
|
||||||
|
" propaneName via nextgroup so a name matching a keyword (e.g. 'token start')
|
||||||
|
" is not highlighted as a keyword. These must be a match (not syn keyword)
|
||||||
|
" because a syn keyword always wins over a contained nextgroup match.
|
||||||
|
syn match propaneNameDecl "\<\%(tokenid\|token\|lex_fn\|module\|start\|tree_prefix\|tree_suffix\)\>" nextgroup=propaneName skipwhite
|
||||||
|
syn match propaneName "\<\h\w*\>" contained
|
||||||
|
syn match propaneKeyword "\<\%(context_user_fields\|drop\|free_token_node\|noline\|on_token_node\|prefix\|ptype\|token_user_fields\|tree\)\>"
|
||||||
|
|
||||||
syn region propaneRegex start="/" end="/" skip="\v\\\\|\\/"
|
syn region propaneRegex start="/" end="/" skip="\v\\\\|\\/"
|
||||||
|
|
||||||
hi def link propaneComment Comment
|
hi def link propaneComment Comment
|
||||||
hi def link propaneKeyword Keyword
|
hi def link propaneKeyword Keyword
|
||||||
|
hi def link propaneNameDecl Keyword
|
||||||
hi def link propaneRegex String
|
hi def link propaneRegex String
|
||||||
hi def link propaneOperator Operator
|
hi def link propaneOperator Operator
|
||||||
|
hi def link propaneRuleOperator Operator
|
||||||
hi def link propaneFieldOperator Operator
|
hi def link propaneFieldOperator Operator
|
||||||
hi def link propaneDelimiter Delimiter
|
hi def link propaneDelimiter Delimiter
|
||||||
hi def link propaneFieldAlias Identifier
|
hi def link propaneFieldAlias Identifier
|
||||||
+1
-1
@@ -33,7 +33,7 @@ class Propane
|
|||||||
|
|
||||||
def run(input_file, output_file, log_file, options)
|
def run(input_file, output_file, log_file, options)
|
||||||
begin
|
begin
|
||||||
grammar = Grammar.new(File.read(input_file))
|
grammar = Grammar.new(File.read(input_file), input_file)
|
||||||
generator = Generator.new(grammar, output_file, log_file, options)
|
generator = Generator.new(grammar, output_file, log_file, options)
|
||||||
generator.generate
|
generator.generate
|
||||||
rescue Error => e
|
rescue Error => e
|
||||||
|
|||||||
+99
-19
@@ -38,7 +38,13 @@ class Propane
|
|||||||
output_file = @output_file
|
output_file = @output_file
|
||||||
end
|
end
|
||||||
erb = ERB.new(template, trim_mode: "<>")
|
erb = ERB.new(template, trim_mode: "<>")
|
||||||
result = erb.result(binding.clone)
|
result = erb.result(binding.clone).lines.each_with_index.map do |line, i|
|
||||||
|
if line == "#linereset\n"
|
||||||
|
%[#line #{i + 2} "#{output_file}"\n]
|
||||||
|
else
|
||||||
|
line
|
||||||
|
end
|
||||||
|
end.join
|
||||||
File.open(output_file, "wb") do |fh|
|
File.open(output_file, "wb") do |fh|
|
||||||
fh.write(result)
|
fh.write(result)
|
||||||
end
|
end
|
||||||
@@ -179,7 +185,8 @@ class Propane
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
@grammar.rules << Rule.new(component, [], nil, ptypename, rule.line_number)
|
@grammar.rules << Rule.new(component, [], nil, ptypename, rule.line_number)
|
||||||
@grammar.rules << Rule.new(component, [c], "$$ = $1;\n", ptypename, rule.line_number)
|
optcode = @grammar.tree ? nil : "$$ = $1;\n"
|
||||||
|
@grammar.rules << Rule.new(component, [c], optcode, ptypename, rule.line_number)
|
||||||
optional_rules_added << component
|
optional_rules_added << component
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -290,31 +297,41 @@ class Propane
|
|||||||
end
|
end
|
||||||
if parser
|
if parser
|
||||||
code = code.gsub(/\$\$/) do |match|
|
code = code.gsub(/\$\$/) do |match|
|
||||||
case @language
|
if @grammar.tree
|
||||||
when "c"
|
case @language
|
||||||
"_pvalue->v_#{rule.ptypename}"
|
when "c"
|
||||||
when "d"
|
"((#{@grammar.tree_prefix}#{rule.name}#{@grammar.tree_suffix} *)_pvalue)"
|
||||||
"_pvalue.v_#{rule.ptypename}"
|
when "d"
|
||||||
|
"(cast(#{@grammar.tree_prefix}#{rule.name}#{@grammar.tree_suffix} *)_pvalue)"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
case @language
|
||||||
|
when "c"
|
||||||
|
"_pvalue->v_#{rule.ptypename}"
|
||||||
|
when "d"
|
||||||
|
"_pvalue.v_#{rule.ptypename}"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
code = code.gsub(/\$(\d+)/) do |match|
|
code = code.gsub(/\$(\d+)/) do |match|
|
||||||
|
parser_component_reference(rule, $1.to_i)
|
||||||
|
end
|
||||||
|
code = code.gsub(/\$\{(\$|\d+)\.position\}/) do |match|
|
||||||
index = $1.to_i
|
index = $1.to_i
|
||||||
case @language
|
"get_rule_position(statevalues, #{index}, n_states, false)"
|
||||||
when "c"
|
end
|
||||||
"state_values_stack_index(statevalues, -1 - (int)n_states + #{index})->pvalue.v_#{rule.components[index - 1].ptypename}"
|
code = code.gsub(/\$\{(\$|\d+)\.end_position\}/) do |match|
|
||||||
when "d"
|
index = $1.to_i
|
||||||
"statevalues[$-1-n_states+#{index}].pvalue.v_#{rule.components[index - 1].ptypename}"
|
"get_rule_position(statevalues, #{index}, n_states, true)"
|
||||||
end
|
|
||||||
end
|
end
|
||||||
code = code.gsub(/\$\{(\w+)\}/) do |match|
|
code = code.gsub(/\$\{(\w+)\}/) do |match|
|
||||||
aliasname = $1
|
aliasname = $1
|
||||||
if index = rule.aliases[aliasname]
|
if index = rule.aliases[aliasname]
|
||||||
case @language
|
# Field aliases are just a named reference to a positional rule
|
||||||
when "c"
|
# component, so reuse the same expansion as `$1', `$2', etc. Note
|
||||||
"state_values_stack_index(statevalues, -(int)n_states + #{index})->pvalue.v_#{rule.components[index].ptypename}"
|
# that rule.aliases stores a 0-based component index, so add 1 to
|
||||||
when "d"
|
# convert it to the 1-based index used for positional references.
|
||||||
"statevalues[$-n_states+#{index}].pvalue.v_#{rule.components[index].ptypename}"
|
parser_component_reference(rule, index + 1)
|
||||||
end
|
|
||||||
else
|
else
|
||||||
raise Error.new("Field alias '#{aliasname}' not found")
|
raise Error.new("Field alias '#{aliasname}' not found")
|
||||||
end
|
end
|
||||||
@@ -337,6 +354,22 @@ class Propane
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
code = code.gsub(/\$\{position\}/) do |match|
|
||||||
|
case @language
|
||||||
|
when "c"
|
||||||
|
"out_token_info->position"
|
||||||
|
when "d"
|
||||||
|
"out_token_info.position"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
code = code.gsub(/\$\{end_position\}/) do |match|
|
||||||
|
case @language
|
||||||
|
when "c"
|
||||||
|
"out_token_info->end_position"
|
||||||
|
when "d"
|
||||||
|
"out_token_info.end_position"
|
||||||
|
end
|
||||||
|
end
|
||||||
code = code.gsub(/\$mode\(([a-zA-Z_][a-zA-Z_0-9]*)\)/) do |match|
|
code = code.gsub(/\$mode\(([a-zA-Z_][a-zA-Z_0-9]*)\)/) do |match|
|
||||||
mode_name = $1
|
mode_name = $1
|
||||||
mode_id = @lexer.mode_id(mode_name)
|
mode_id = @lexer.mode_id(mode_name)
|
||||||
@@ -354,6 +387,53 @@ class Propane
|
|||||||
code
|
code
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Expand a positional reference to a parser rule component.
|
||||||
|
#
|
||||||
|
# This is used to expand `$1', `$2', etc. as well as field aliases (which
|
||||||
|
# are just named references to a positional rule component).
|
||||||
|
#
|
||||||
|
# @param rule [Rule]
|
||||||
|
# The Rule containing the user code.
|
||||||
|
# @param index [Integer]
|
||||||
|
# 1-based index of the rule component to reference.
|
||||||
|
#
|
||||||
|
# @return [String]
|
||||||
|
# Expanded rule component reference.
|
||||||
|
def parser_component_reference(rule, index)
|
||||||
|
component = rule.components[index - 1]
|
||||||
|
if @grammar.tree
|
||||||
|
# In tree mode a component reference yields a pointer to that
|
||||||
|
# component's tree node. An optional component propagates its target
|
||||||
|
# node (or null), so use the optional target's node type.
|
||||||
|
if component.is_a?(RuleSet) && component.optional?
|
||||||
|
component = component.option_target
|
||||||
|
end
|
||||||
|
node_name = component.is_a?(Token) ? "Token" : component.name
|
||||||
|
typename = "#{@grammar.tree_prefix}#{node_name}#{@grammar.tree_suffix}"
|
||||||
|
case @language
|
||||||
|
when "c"
|
||||||
|
"((#{typename} *)state_values_stack_index(statevalues, -1 - (int)n_states + #{index})->tree_node)"
|
||||||
|
when "d"
|
||||||
|
"(cast(#{typename} *)statevalues[$-1-n_states+#{index}].tree_node)"
|
||||||
|
end
|
||||||
|
else
|
||||||
|
case @language
|
||||||
|
when "c"
|
||||||
|
"state_values_stack_index(statevalues, -1 - (int)n_states + #{index})->pvalue.v_#{component.ptypename}"
|
||||||
|
when "d"
|
||||||
|
"statevalues[$-1-n_states+#{index}].pvalue.v_#{component.ptypename}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Get the lex function to use.
|
||||||
|
#
|
||||||
|
# @return [String]
|
||||||
|
# Lex function to use.
|
||||||
|
def lex_fn
|
||||||
|
@grammar.lex_fn || "#{@grammar.prefix}lex"
|
||||||
|
end
|
||||||
|
|
||||||
# Get the parser value type for the start rule.
|
# Get the parser value type for the start rule.
|
||||||
#
|
#
|
||||||
# @return [Array<String>]
|
# @return [Array<String>]
|
||||||
|
|||||||
+40
-12
@@ -6,6 +6,7 @@ class Propane
|
|||||||
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 :context_user_fields
|
attr_reader :context_user_fields
|
||||||
|
attr_reader :lex_fn
|
||||||
attr_reader :tree
|
attr_reader :tree
|
||||||
attr_reader :tree_prefix
|
attr_reader :tree_prefix
|
||||||
attr_reader :tree_suffix
|
attr_reader :tree_suffix
|
||||||
@@ -21,7 +22,8 @@ class Propane
|
|||||||
attr_reader :on_token_node
|
attr_reader :on_token_node
|
||||||
attr_reader :token_user_fields
|
attr_reader :token_user_fields
|
||||||
|
|
||||||
def initialize(input)
|
def initialize(input, filename)
|
||||||
|
@filename = filename
|
||||||
@patterns = []
|
@patterns = []
|
||||||
@start_rules = []
|
@start_rules = []
|
||||||
@tokens = []
|
@tokens = []
|
||||||
@@ -56,6 +58,10 @@ class Propane
|
|||||||
@tokens.size + 1
|
@tokens.size + 1
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def parser_user_code_used?
|
||||||
|
@rules.any? {|r| r.code}
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def parse_grammar!
|
def parse_grammar!
|
||||||
@@ -69,6 +75,7 @@ class Propane
|
|||||||
elsif parse_comment_line!
|
elsif parse_comment_line!
|
||||||
elsif @modeline.nil? && parse_mode_label!
|
elsif @modeline.nil? && parse_mode_label!
|
||||||
elsif parse_context_user_fields_statement!
|
elsif parse_context_user_fields_statement!
|
||||||
|
elsif parse_lex_fn!
|
||||||
elsif parse_tree_statement!
|
elsif parse_tree_statement!
|
||||||
elsif parse_tree_prefix_statement!
|
elsif parse_tree_prefix_statement!
|
||||||
elsif parse_tree_suffix_statement!
|
elsif parse_tree_suffix_statement!
|
||||||
@@ -85,6 +92,7 @@ class Propane
|
|||||||
elsif parse_rule_statement!
|
elsif parse_rule_statement!
|
||||||
elsif parse_code_block_statement!
|
elsif parse_code_block_statement!
|
||||||
elsif parse_prefix_statement!
|
elsif parse_prefix_statement!
|
||||||
|
elsif parse_noline_statement!
|
||||||
else
|
else
|
||||||
if @input.size > 25
|
if @input.size > 25
|
||||||
@input = @input.slice(0..20) + "..."
|
@input = @input.slice(0..20) + "..."
|
||||||
@@ -117,6 +125,12 @@ class Propane
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def parse_lex_fn!
|
||||||
|
if md = consume!(/lex_fn\b\s*(\w+)\s*;/)
|
||||||
|
@lex_fn = md[1]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def parse_tree_statement!
|
def parse_tree_statement!
|
||||||
if consume!(/tree\s*;/)
|
if consume!(/tree\s*;/)
|
||||||
@tree = true
|
@tree = true
|
||||||
@@ -253,12 +267,8 @@ class Propane
|
|||||||
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 @tree
|
unless code = parse_code_block!
|
||||||
consume!(/;/, "expected `;'")
|
consume!(/;/, "expected `;' or code block")
|
||||||
else
|
|
||||||
unless code = parse_code_block!
|
|
||||||
consume!(/;/, "expected `;' or code block")
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
@rules << Rule.new(rule_name, components, code, ptypename, @line_number)
|
@rules << Rule.new(rule_name, components, code, ptypename, @line_number)
|
||||||
@modeline = nil
|
@modeline = nil
|
||||||
@@ -296,8 +306,14 @@ class Propane
|
|||||||
def parse_code_block_statement!
|
def parse_code_block_statement!
|
||||||
if md = consume!(/<<([a-z]*)(.*?)>>\n/m)
|
if md = consume!(/<<([a-z]*)(.*?)>>\n/m)
|
||||||
name, code = md[1..2]
|
name, code = md[1..2]
|
||||||
code.sub!(/\A\n/, "")
|
code = code.chomp
|
||||||
code += "\n" unless code.end_with?("\n")
|
unless @noline
|
||||||
|
if code.start_with?("\n")
|
||||||
|
code = %[#line #{@line_number + 1} "#{@filename}"#{code}\n#linereset\n]
|
||||||
|
else
|
||||||
|
code = %[#line #{@line_number} "#{@filename}"\n#{code}\n#linereset\n]
|
||||||
|
end
|
||||||
|
end
|
||||||
if @code_blocks[name]
|
if @code_blocks[name]
|
||||||
@code_blocks[name] += code
|
@code_blocks[name] += code
|
||||||
else
|
else
|
||||||
@@ -315,6 +331,13 @@ class Propane
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def parse_noline_statement!
|
||||||
|
if md = consume!(/noline\s*;/)
|
||||||
|
@noline = true
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def parse_pattern!
|
def parse_pattern!
|
||||||
if md = consume!(%r{/})
|
if md = consume!(%r{/})
|
||||||
pattern = ""
|
pattern = ""
|
||||||
@@ -338,9 +361,14 @@ class Propane
|
|||||||
|
|
||||||
def parse_code_block!
|
def parse_code_block!
|
||||||
if md = consume!(/<<(.*?)>>\n/m)
|
if md = consume!(/<<(.*?)>>\n/m)
|
||||||
code = md[1]
|
code = md[1].chomp
|
||||||
code.sub!(/\A\n/, "")
|
unless @noline
|
||||||
code += "\n" unless code.end_with?("\n")
|
if code.start_with?("\n")
|
||||||
|
code = %[#line #{@line_number + 1} "#{@filename}"#{code}\n#linereset\n]
|
||||||
|
else
|
||||||
|
code = %[#line #{@line_number} "#{@filename}"\n#{code}\n#linereset\n]
|
||||||
|
end
|
||||||
|
end
|
||||||
code
|
code
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
class Propane
|
class Propane
|
||||||
VERSION = "4.1.0"
|
VERSION = "4.7.0"
|
||||||
end
|
end
|
||||||
@@ -28,7 +28,7 @@ B -> <<
|
|||||||
b = 0;
|
b = 0;
|
||||||
>>
|
>>
|
||||||
EOF
|
EOF
|
||||||
grammar = Grammar.new(input)
|
grammar = Grammar.new(input, "test.propane")
|
||||||
expect(grammar.modulename).to eq "a.b"
|
expect(grammar.modulename).to eq "a.b"
|
||||||
expect(grammar.ptype).to eq "XYZ *"
|
expect(grammar.ptype).to eq "XYZ *"
|
||||||
expect(grammar.ptypes).to eq("default" => "XYZ *")
|
expect(grammar.ptypes).to eq("default" => "XYZ *")
|
||||||
@@ -62,7 +62,7 @@ EOF
|
|||||||
expect(o).to_not be_nil
|
expect(o).to_not be_nil
|
||||||
expect(o.pattern).to eq "token_with_code"
|
expect(o.pattern).to eq "token_with_code"
|
||||||
expect(o.line_number).to eq 11
|
expect(o.line_number).to eq 11
|
||||||
expect(o.code).to eq "Code for the token\n"
|
expect(o.code).to eq %[#line 12 "test.propane"\nCode for the token\n#linereset\n]
|
||||||
|
|
||||||
o = grammar.tokens.find {|token| token.name == "token_with_no_pattern"}
|
o = grammar.tokens.find {|token| token.name == "token_with_no_pattern"}
|
||||||
expect(o).to_not be_nil
|
expect(o).to_not be_nil
|
||||||
@@ -83,7 +83,7 @@ EOF
|
|||||||
expect(o.name).to eq "A"
|
expect(o.name).to eq "A"
|
||||||
expect(o.components).to eq %w[B]
|
expect(o.components).to eq %w[B]
|
||||||
expect(o.line_number).to eq 19
|
expect(o.line_number).to eq 19
|
||||||
expect(o.code).to eq " a = 42;\n"
|
expect(o.code).to eq %[#line 20 "test.propane"\n a = 42;\n#linereset\n]
|
||||||
|
|
||||||
o = grammar.rules[1]
|
o = grammar.rules[1]
|
||||||
expect(o.name).to eq "B"
|
expect(o.name).to eq "B"
|
||||||
@@ -95,7 +95,7 @@ EOF
|
|||||||
expect(o.name).to eq "B"
|
expect(o.name).to eq "B"
|
||||||
expect(o.components).to eq []
|
expect(o.components).to eq []
|
||||||
expect(o.line_number).to eq 23
|
expect(o.line_number).to eq 23
|
||||||
expect(o.code).to eq " b = 0;\n"
|
expect(o.code).to eq %[#line 24 "test.propane"\n b = 0;\n#linereset\n]
|
||||||
end
|
end
|
||||||
|
|
||||||
it "parses code segments with semicolons" do
|
it "parses code segments with semicolons" do
|
||||||
@@ -113,7 +113,7 @@ tokenid token_with_no_pattern;
|
|||||||
|
|
||||||
prefix myparser_;
|
prefix myparser_;
|
||||||
EOF
|
EOF
|
||||||
grammar = Grammar.new(input)
|
grammar = Grammar.new(input, "test.propane")
|
||||||
expect(grammar.prefix).to eq "myparser_"
|
expect(grammar.prefix).to eq "myparser_"
|
||||||
|
|
||||||
o = grammar.tokens.find {|token| token.name == "code1"}
|
o = grammar.tokens.find {|token| token.name == "code1"}
|
||||||
@@ -122,7 +122,7 @@ EOF
|
|||||||
|
|
||||||
o = grammar.patterns.find {|pattern| pattern.token == o}
|
o = grammar.patterns.find {|pattern| pattern.token == o}
|
||||||
expect(o).to_not be_nil
|
expect(o).to_not be_nil
|
||||||
expect(o.code).to eq " a = b;\n return c;\n"
|
expect(o.code).to eq %[#line 2 "test.propane"\n a = b;\n return c;\n#linereset\n]
|
||||||
|
|
||||||
o = grammar.tokens.find {|token| token.name == "code2"}
|
o = grammar.tokens.find {|token| token.name == "code2"}
|
||||||
expect(o).to_not be_nil
|
expect(o).to_not be_nil
|
||||||
@@ -130,7 +130,42 @@ EOF
|
|||||||
|
|
||||||
o = grammar.patterns.find {|pattern| pattern.token == o}
|
o = grammar.patterns.find {|pattern| pattern.token == o}
|
||||||
expect(o).to_not be_nil
|
expect(o).to_not be_nil
|
||||||
expect(o.code).to eq %[ writeln("Hello there");\n]
|
expect(o.code).to eq %[#line 7 "test.propane"\n writeln("Hello there");\n#linereset\n]
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not emit #line directives with noline statement" do
|
||||||
|
input = <<EOF
|
||||||
|
noline;
|
||||||
|
|
||||||
|
token code1 <<
|
||||||
|
a = b;
|
||||||
|
return c;
|
||||||
|
>>
|
||||||
|
|
||||||
|
token code2 <<
|
||||||
|
writeln("Hello there");
|
||||||
|
>>
|
||||||
|
|
||||||
|
tokenid token_with_no_pattern;
|
||||||
|
|
||||||
|
prefix myparser_;
|
||||||
|
EOF
|
||||||
|
grammar = Grammar.new(input, "test.propane")
|
||||||
|
expect(grammar.prefix).to eq "myparser_"
|
||||||
|
|
||||||
|
o = grammar.tokens.find {|token| token.name == "code1"}
|
||||||
|
expect(o).to_not be_nil
|
||||||
|
|
||||||
|
o = grammar.patterns.find {|pattern| pattern.token == o}
|
||||||
|
expect(o).to_not be_nil
|
||||||
|
expect(o.code).to eq %[\n a = b;\n return c;]
|
||||||
|
|
||||||
|
o = grammar.tokens.find {|token| token.name == "code2"}
|
||||||
|
expect(o).to_not be_nil
|
||||||
|
|
||||||
|
o = grammar.patterns.find {|pattern| pattern.token == o}
|
||||||
|
expect(o).to_not be_nil
|
||||||
|
expect(o.code).to eq %[\n writeln("Hello there");]
|
||||||
end
|
end
|
||||||
|
|
||||||
it "supports mode labels" do
|
it "supports mode labels" do
|
||||||
@@ -144,7 +179,7 @@ m2: /bar/ <<
|
|||||||
drop /q/;
|
drop /q/;
|
||||||
m3: drop /r/;
|
m3: drop /r/;
|
||||||
EOF
|
EOF
|
||||||
grammar = Grammar.new(input)
|
grammar = Grammar.new(input, "test.propane")
|
||||||
|
|
||||||
o = grammar.tokens.find {|token| token.name == "a"}
|
o = grammar.tokens.find {|token| token.name == "a"}
|
||||||
expect(o).to_not be_nil
|
expect(o).to_not be_nil
|
||||||
@@ -197,7 +232,7 @@ tokenid int(integer);
|
|||||||
Start (node) -> R;
|
Start (node) -> R;
|
||||||
R -> abc int;
|
R -> abc int;
|
||||||
EOF
|
EOF
|
||||||
grammar = Grammar.new(input)
|
grammar = Grammar.new(input, "test.propane")
|
||||||
|
|
||||||
o = grammar.tokens.find {|token| token.name == "abc"}
|
o = grammar.tokens.find {|token| token.name == "abc"}
|
||||||
expect(o).to_not be_nil
|
expect(o).to_not be_nil
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ class TestLexer
|
|||||||
end
|
end
|
||||||
|
|
||||||
def run(grammar, input)
|
def run(grammar, input)
|
||||||
grammar = Propane::Grammar.new(grammar)
|
grammar = Propane::Grammar.new(grammar, "test.propane")
|
||||||
token_dfa = Propane::Lexer::DFA.new(grammar.patterns)
|
token_dfa = Propane::Lexer::DFA.new(grammar.patterns)
|
||||||
test_lexer = TestLexer.new(token_dfa)
|
test_lexer = TestLexer.new(token_dfa)
|
||||||
test_lexer.lex(input)
|
test_lexer.lex(input)
|
||||||
|
|||||||
@@ -745,6 +745,78 @@ EOF
|
|||||||
])
|
])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "executes user code associated with a parser rule in tree mode" do
|
||||||
|
case language
|
||||||
|
when "c", "cpp"
|
||||||
|
write_grammar <<EOF
|
||||||
|
tree;
|
||||||
|
context_user_fields <<
|
||||||
|
int start_n_fields;
|
||||||
|
int start_a_value;
|
||||||
|
int a_value;
|
||||||
|
int b_value;
|
||||||
|
p_token_t b_token;
|
||||||
|
int c_is_null;
|
||||||
|
int c_field_is_null;
|
||||||
|
int alias_a_value;
|
||||||
|
int alias_b_value;
|
||||||
|
>>
|
||||||
|
ptype int;
|
||||||
|
token a << $$ = 11; >>
|
||||||
|
token b << $$ = 22; >>
|
||||||
|
Start -> A:ay B:bee C <<
|
||||||
|
${context.start_n_fields} = $$->n_fields;
|
||||||
|
${context.start_a_value} = $$->pA->pToken1->pvalue;
|
||||||
|
${context.a_value} = $1->pToken1->pvalue;
|
||||||
|
${context.b_value} = $2->pToken1->pvalue;
|
||||||
|
${context.b_token} = $2->pToken1->token;
|
||||||
|
${context.c_field_is_null} = ($$->pC == NULL) ? 1 : 0;
|
||||||
|
${context.alias_a_value} = ${ay}->pToken1->pvalue;
|
||||||
|
${context.alias_b_value} = ${bee}->pToken1->pvalue;
|
||||||
|
>>
|
||||||
|
A -> a;
|
||||||
|
B -> b;
|
||||||
|
C -> << ${context.c_is_null} = ($$ == NULL) ? 1 : 0; >>
|
||||||
|
EOF
|
||||||
|
when "d"
|
||||||
|
write_grammar <<EOF
|
||||||
|
tree;
|
||||||
|
context_user_fields <<
|
||||||
|
int start_n_fields;
|
||||||
|
int start_a_value;
|
||||||
|
int a_value;
|
||||||
|
int b_value;
|
||||||
|
p_token_t b_token;
|
||||||
|
int c_is_null;
|
||||||
|
int c_field_is_null;
|
||||||
|
int alias_a_value;
|
||||||
|
int alias_b_value;
|
||||||
|
>>
|
||||||
|
ptype int;
|
||||||
|
token a << $$ = 11; >>
|
||||||
|
token b << $$ = 22; >>
|
||||||
|
Start -> A:ay B:bee C <<
|
||||||
|
${context.start_n_fields} = $$.n_fields;
|
||||||
|
${context.start_a_value} = $$.pA.pToken1.pvalue;
|
||||||
|
${context.a_value} = $1.pToken1.pvalue;
|
||||||
|
${context.b_value} = $2.pToken1.pvalue;
|
||||||
|
${context.b_token} = $2.pToken1.token;
|
||||||
|
${context.c_field_is_null} = ($$.pC is null) ? 1 : 0;
|
||||||
|
${context.alias_a_value} = ${ay}.pToken1.pvalue;
|
||||||
|
${context.alias_b_value} = ${bee}.pToken1.pvalue;
|
||||||
|
>>
|
||||||
|
A -> a;
|
||||||
|
B -> b;
|
||||||
|
C -> << ${context.c_is_null} = ($$ is null) ? 1 : 0; >>
|
||||||
|
EOF
|
||||||
|
end
|
||||||
|
run_propane(language: language)
|
||||||
|
compile("spec/test_parser_user_code_tree.#{language}", language: language)
|
||||||
|
results = run_test(language: language)
|
||||||
|
expect(results.stderr).to eq ""
|
||||||
|
expect(results.status).to eq 0
|
||||||
|
end
|
||||||
|
|
||||||
it "parses lists" do
|
it "parses lists" do
|
||||||
write_grammar <<EOF
|
write_grammar <<EOF
|
||||||
ptype #{language == "d" ? "uint" : "uint32_t"};
|
ptype #{language == "d" ? "uint" : "uint32_t"};
|
||||||
@@ -910,6 +982,57 @@ EOF
|
|||||||
expect(results.status).to eq 0
|
expect(results.status).to eq 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "allows lexer code blocks to access the matched token positions" do
|
||||||
|
case language
|
||||||
|
when "c", "cpp"
|
||||||
|
write_grammar <<EOF
|
||||||
|
<<
|
||||||
|
#include <stdio.h>
|
||||||
|
>>
|
||||||
|
context_user_fields <<
|
||||||
|
p_position_t last_start;
|
||||||
|
p_position_t last_end;
|
||||||
|
>>
|
||||||
|
drop /\\s+/;
|
||||||
|
token word /[a-z]+/ <<
|
||||||
|
${context.last_start} = ${position};
|
||||||
|
${context.last_end} = ${end_position};
|
||||||
|
>>
|
||||||
|
token stop /!/ <<
|
||||||
|
$terminate(42);
|
||||||
|
>>
|
||||||
|
Start -> Words;
|
||||||
|
Words -> ;
|
||||||
|
Words -> word Words;
|
||||||
|
Words -> stop Words;
|
||||||
|
EOF
|
||||||
|
when "d"
|
||||||
|
write_grammar <<EOF
|
||||||
|
context_user_fields <<
|
||||||
|
p_position_t last_start;
|
||||||
|
p_position_t last_end;
|
||||||
|
>>
|
||||||
|
drop /\\s+/;
|
||||||
|
token word /[a-z]+/ <<
|
||||||
|
${context.last_start} = ${position};
|
||||||
|
${context.last_end} = ${end_position};
|
||||||
|
>>
|
||||||
|
token stop /!/ <<
|
||||||
|
$terminate(42);
|
||||||
|
>>
|
||||||
|
Start -> Words;
|
||||||
|
Words -> ;
|
||||||
|
Words -> word Words;
|
||||||
|
Words -> stop Words;
|
||||||
|
EOF
|
||||||
|
end
|
||||||
|
run_propane(language: language)
|
||||||
|
compile("spec/test_lexer_positions.#{language}", language: language)
|
||||||
|
results = run_test(language: language)
|
||||||
|
expect(results.stderr).to eq ""
|
||||||
|
expect(results.status).to eq 0
|
||||||
|
end
|
||||||
|
|
||||||
it "allows the user to terminate the parser" do
|
it "allows the user to terminate the parser" do
|
||||||
write_grammar <<EOF
|
write_grammar <<EOF
|
||||||
token a;
|
token a;
|
||||||
@@ -1699,6 +1822,247 @@ EOF
|
|||||||
results = run_test(language: language)
|
results = run_test(language: language)
|
||||||
expect(results.status).to eq 0
|
expect(results.status).to eq 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "allows a custom lex function" do
|
||||||
|
if language == "d"
|
||||||
|
write_grammar <<EOF
|
||||||
|
<<
|
||||||
|
private size_t mylexfn(p_context_t * context, p_token_info_t * out_token_info)
|
||||||
|
{
|
||||||
|
static size_t count;
|
||||||
|
size_t result = P_SUCCESS;
|
||||||
|
if (count > 0)
|
||||||
|
{
|
||||||
|
out_token_info.token = TOKEN_a;
|
||||||
|
out_token_info.pvalue = p_value(count);
|
||||||
|
count--;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = p_lex(context, out_token_info);
|
||||||
|
if (out_token_info.token == TOKEN_c)
|
||||||
|
{
|
||||||
|
count = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
>>
|
||||||
|
|
||||||
|
ptype size_t;
|
||||||
|
lex_fn mylexfn;
|
||||||
|
|
||||||
|
token a << $$ = 7; >>
|
||||||
|
token b << $$ = 8; >>
|
||||||
|
token c << $$ = 9; >>
|
||||||
|
Start -> << $$ = 0; >>
|
||||||
|
Start -> Start ID << $$ = ($1 << 4) | $2; >>
|
||||||
|
ID -> a << $$ = $1; >>
|
||||||
|
ID -> b << $$ = $1; >>
|
||||||
|
ID -> c << $$ = $1; >>
|
||||||
|
EOF
|
||||||
|
else
|
||||||
|
write_grammar <<EOF
|
||||||
|
<<
|
||||||
|
static size_t mylexfn(p_context_t * context, p_token_info_t * out_token_info)
|
||||||
|
{
|
||||||
|
static size_t count;
|
||||||
|
size_t result = P_SUCCESS;
|
||||||
|
if (count > 0)
|
||||||
|
{
|
||||||
|
out_token_info->token = TOKEN_a;
|
||||||
|
out_token_info->pvalue = p_value(count);
|
||||||
|
count--;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
result = p_lex(context, out_token_info);
|
||||||
|
if (out_token_info->token == TOKEN_c)
|
||||||
|
{
|
||||||
|
count = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
>>
|
||||||
|
|
||||||
|
ptype size_t;
|
||||||
|
lex_fn mylexfn;
|
||||||
|
|
||||||
|
token a << $$ = 7; >>
|
||||||
|
token b << $$ = 8; >>
|
||||||
|
token c << $$ = 9; >>
|
||||||
|
Start -> << $$ = 0; >>
|
||||||
|
Start -> Start ID << $$ = ($1 << 4) | $2; >>
|
||||||
|
ID -> a << $$ = $1; >>
|
||||||
|
ID -> b << $$ = $1; >>
|
||||||
|
ID -> c << $$ = $1; >>
|
||||||
|
EOF
|
||||||
|
end
|
||||||
|
run_propane(language: language)
|
||||||
|
compile("spec/test_custom_lex_fn.#{language}", language: language)
|
||||||
|
results = run_test(language: language)
|
||||||
|
expect(results.status).to eq 0
|
||||||
|
end
|
||||||
|
|
||||||
|
it "provides accessor functions to extract user values from a parser value" do
|
||||||
|
if language == "d"
|
||||||
|
write_grammar <<EOF
|
||||||
|
ptype int;
|
||||||
|
ptype float = float;
|
||||||
|
ptype string = string;
|
||||||
|
|
||||||
|
drop /\\s+/;
|
||||||
|
token num /\\d+/ << $$ = 42; >>
|
||||||
|
token flt (float) /f/ << $$ = 1.5; >>
|
||||||
|
token str (string) /s/ << $$ = "hello"; >>
|
||||||
|
Start -> num flt str;
|
||||||
|
EOF
|
||||||
|
else
|
||||||
|
write_grammar <<EOF
|
||||||
|
ptype int;
|
||||||
|
ptype float = float;
|
||||||
|
ptype string = char *;
|
||||||
|
|
||||||
|
drop /\\s+/;
|
||||||
|
token num /\\d+/ << $$ = 42; >>
|
||||||
|
token flt (float) /f/ << $$ = 1.5; >>
|
||||||
|
token str (string) /s/ << $$ = (char *)"hello"; >>
|
||||||
|
Start -> num flt str;
|
||||||
|
EOF
|
||||||
|
end
|
||||||
|
run_propane(language: language)
|
||||||
|
compile("spec/test_value_accessors.#{language}", language: language)
|
||||||
|
results = run_test(language: language)
|
||||||
|
expect(results.stderr).to eq ""
|
||||||
|
expect(results.status).to eq 0
|
||||||
|
end
|
||||||
|
|
||||||
|
it "allows accessing rule and component text positions" do
|
||||||
|
if language == "d"
|
||||||
|
write_grammar <<EOF
|
||||||
|
<<
|
||||||
|
import std.stdio;
|
||||||
|
>>
|
||||||
|
drop /\\s+/;
|
||||||
|
token tok1;
|
||||||
|
token tok2;
|
||||||
|
token ident /[a-zA-Z_]\\w*/;
|
||||||
|
token num /\\d+/;
|
||||||
|
Num -> num;
|
||||||
|
Start -> ident Num <<
|
||||||
|
writeln("ident start: ", ${1.position}.row, ", ", ${1.position}.col);
|
||||||
|
writeln("ident end: ", ${1.end_position}.row, ", ", ${1.end_position}.col);
|
||||||
|
writeln("Num start: ", ${2.position}.row, ", ", ${2.position}.col);
|
||||||
|
writeln("Num end: ", ${2.end_position}.row, ", ", ${2.end_position}.col);
|
||||||
|
writeln("Start start: ", ${$.position}.row, ", ", ${$.position}.col);
|
||||||
|
writeln("Start end: ", ${$.end_position}.row, ", ", ${$.end_position}.col);
|
||||||
|
>>
|
||||||
|
R -> Empty tok2 <<
|
||||||
|
writeln("Empty start: ", ${1.position}.row, ", ", ${1.position}.col);
|
||||||
|
writeln("Empty end: ", ${1.end_position}.row, ", ", ${1.end_position}.col);
|
||||||
|
writeln("tok2 start: ", ${2.position}.row, ", ", ${2.position}.col);
|
||||||
|
writeln("tok2 end: ", ${2.end_position}.row, ", ", ${2.end_position}.col);
|
||||||
|
writeln("R start: ", ${$.position}.row, ", ", ${$.position}.col);
|
||||||
|
writeln("R end: ", ${$.end_position}.row, ", ", ${$.end_position}.col);
|
||||||
|
>>
|
||||||
|
R -> tok1 Empty <<
|
||||||
|
writeln("tok1 start: ", ${1.position}.row, ", ", ${1.position}.col);
|
||||||
|
writeln("tok1 end: ", ${1.end_position}.row, ", ", ${1.end_position}.col);
|
||||||
|
writeln("Empty start: ", ${2.position}.row, ", ", ${2.position}.col);
|
||||||
|
writeln("Empty end: ", ${2.end_position}.row, ", ", ${2.end_position}.col);
|
||||||
|
writeln("R2 start: ", ${$.position}.row, ", ", ${$.position}.col);
|
||||||
|
writeln("R2 end: ", ${$.end_position}.row, ", ", ${$.end_position}.col);
|
||||||
|
>>
|
||||||
|
Empty -> ;
|
||||||
|
Start -> R <<
|
||||||
|
writeln("StartR start: ", ${$.position}.row, ", ", ${$.position}.col);
|
||||||
|
writeln("StartR end: ", ${$.end_position}.row, ", ", ${$.end_position}.col);
|
||||||
|
>>
|
||||||
|
Start -> Empty <<
|
||||||
|
writeln("StartEmpty start: ", ${$.position}.row, ", ", ${$.position}.col);
|
||||||
|
writeln("StartEmpty end: ", ${$.end_position}.row, ", ", ${$.end_position}.col);
|
||||||
|
>>
|
||||||
|
EOF
|
||||||
|
else
|
||||||
|
write_grammar <<EOF
|
||||||
|
<<
|
||||||
|
#include <stdio.h>
|
||||||
|
>>
|
||||||
|
drop /\\s+/;
|
||||||
|
token tok1;
|
||||||
|
token tok2;
|
||||||
|
token ident /[a-zA-Z_]\\w*/;
|
||||||
|
token num /\\d+/;
|
||||||
|
Num -> num;
|
||||||
|
token pct /%/;
|
||||||
|
Start -> ident Num <<
|
||||||
|
printf("ident start: %d, %d\\n", ${1.position}.row, ${1.position}.col);
|
||||||
|
printf("ident end: %d, %d\\n", ${1.end_position}.row, ${1.end_position}.col);
|
||||||
|
printf("Num start: %d, %d\\n", ${2.position}.row, ${2.position}.col);
|
||||||
|
printf("Num end: %d, %d\\n", ${2.end_position}.row, ${2.end_position}.col);
|
||||||
|
printf("Start start: %d, %d\\n", ${$.position}.row, ${$.position}.col);
|
||||||
|
printf("Start end: %d, %d\\n", ${$.end_position}.row, ${$.end_position}.col);
|
||||||
|
>>
|
||||||
|
R -> Empty tok2 <<
|
||||||
|
printf("Empty start: %d, %d\\n", ${1.position}.row, ${1.position}.col);
|
||||||
|
printf("Empty end: %d, %d\\n", ${1.end_position}.row, ${1.end_position}.col);
|
||||||
|
printf("tok2 start: %d, %d\\n", ${2.position}.row, ${2.position}.col);
|
||||||
|
printf("tok2 end: %d, %d\\n", ${2.end_position}.row, ${2.end_position}.col);
|
||||||
|
printf("R start: %d, %d\\n", ${$.position}.row, ${$.position}.col);
|
||||||
|
printf("R end: %d, %d\\n", ${$.end_position}.row, ${$.end_position}.col);
|
||||||
|
>>
|
||||||
|
R -> tok1 Empty <<
|
||||||
|
printf("tok1 start: %d, %d\\n", ${1.position}.row, ${1.position}.col);
|
||||||
|
printf("tok1 end: %d, %d\\n", ${1.end_position}.row, ${1.end_position}.col);
|
||||||
|
printf("Empty start: %d, %d\\n", ${2.position}.row, ${2.position}.col);
|
||||||
|
printf("Empty end: %d, %d\\n", ${2.end_position}.row, ${2.end_position}.col);
|
||||||
|
printf("R2 start: %d, %d\\n", ${$.position}.row, ${$.position}.col);
|
||||||
|
printf("R2 end: %d, %d\\n", ${$.end_position}.row, ${$.end_position}.col);
|
||||||
|
>>
|
||||||
|
Empty -> ;
|
||||||
|
Start -> R <<
|
||||||
|
printf("StartR start: %d, %d\\n", ${$.position}.row, ${$.position}.col);
|
||||||
|
printf("StartR end: %d, %d\\n", ${$.end_position}.row, ${$.end_position}.col);
|
||||||
|
>>
|
||||||
|
Start -> Empty <<
|
||||||
|
printf("StartEmpty start: %d, %d\\n", ${$.position}.row, ${$.position}.col);
|
||||||
|
printf("StartEmpty end: %d, %d\\n", ${$.end_position}.row, ${$.end_position}.col);
|
||||||
|
>>
|
||||||
|
EOF
|
||||||
|
end
|
||||||
|
run_propane(language: language)
|
||||||
|
compile("spec/test_positions.#{language}", language: language)
|
||||||
|
results = run_test(language: language)
|
||||||
|
expect(results.stderr).to eq ""
|
||||||
|
expect(results.status).to eq 0
|
||||||
|
expect(results.stdout).to eq <<EOF
|
||||||
|
ident start: 1, 5
|
||||||
|
ident end: 1, 9
|
||||||
|
Num start: 3, 9
|
||||||
|
Num end: 3, 12
|
||||||
|
Start start: 1, 5
|
||||||
|
Start end: 3, 12
|
||||||
|
|
||||||
|
Empty start: 0, 0
|
||||||
|
Empty end: 0, 0
|
||||||
|
tok2 start: 2, 2
|
||||||
|
tok2 end: 2, 5
|
||||||
|
R start: 2, 2
|
||||||
|
R end: 2, 5
|
||||||
|
StartR start: 2, 2
|
||||||
|
StartR end: 2, 5
|
||||||
|
|
||||||
|
tok1 start: 1, 3
|
||||||
|
tok1 end: 1, 6
|
||||||
|
Empty start: 0, 0
|
||||||
|
Empty end: 0, 0
|
||||||
|
R2 start: 1, 3
|
||||||
|
R2 end: 1, 6
|
||||||
|
StartR start: 1, 3
|
||||||
|
StartR end: 1, 6
|
||||||
|
EOF
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#include "testparser.h"
|
||||||
|
#include "testutils.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
char const * input = "cbacba";
|
||||||
|
p_context_t * context = p_context_new((uint8_t const *)input, strlen(input));
|
||||||
|
assert_eq(P_SUCCESS, p_parse(context));
|
||||||
|
size_t result = p_result(context);
|
||||||
|
assert_eq(0x932187932187, result);
|
||||||
|
p_context_delete(context);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import testparser;
|
||||||
|
import std.stdio;
|
||||||
|
import testutils;
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
string input = "cbacba";
|
||||||
|
p_context_t * context = p_context_new(input);
|
||||||
|
assert_eq(P_SUCCESS, p_parse(context));
|
||||||
|
size_t result = p_result(context);
|
||||||
|
assert_eq(0x932187932187, result);
|
||||||
|
p_context_delete(context);
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
#include "testparser.h"
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
char const * input = "abc\n defg hi\n!";
|
||||||
|
p_context_t * context = p_context_new((uint8_t const *)input, strlen(input));
|
||||||
|
p_token_info_t token_info;
|
||||||
|
|
||||||
|
/* First token "abc" on row 1, cols 1-3. */
|
||||||
|
assert(p_lex(context, &token_info) == P_SUCCESS);
|
||||||
|
assert(token_info.token == TOKEN_word);
|
||||||
|
assert(context->last_start.row == 1u);
|
||||||
|
assert(context->last_start.col == 1u);
|
||||||
|
assert(context->last_end.row == 1u);
|
||||||
|
assert(context->last_end.col == 3u);
|
||||||
|
/* The lexer code block observed the same positions reported to the caller. */
|
||||||
|
assert(context->last_start.row == token_info.position.row);
|
||||||
|
assert(context->last_start.col == token_info.position.col);
|
||||||
|
assert(context->last_end.row == token_info.end_position.row);
|
||||||
|
assert(context->last_end.col == token_info.end_position.col);
|
||||||
|
|
||||||
|
/* Second token "defg" on row 2, cols 3-6. */
|
||||||
|
assert(p_lex(context, &token_info) == P_SUCCESS);
|
||||||
|
assert(token_info.token == TOKEN_word);
|
||||||
|
assert(context->last_start.row == 2u);
|
||||||
|
assert(context->last_start.col == 3u);
|
||||||
|
assert(context->last_end.row == 2u);
|
||||||
|
assert(context->last_end.col == 6u);
|
||||||
|
|
||||||
|
/* Third token "hi" on row 2, cols 8-9. */
|
||||||
|
assert(p_lex(context, &token_info) == P_SUCCESS);
|
||||||
|
assert(token_info.token == TOKEN_word);
|
||||||
|
assert(context->last_start.row == 2u);
|
||||||
|
assert(context->last_start.col == 8u);
|
||||||
|
assert(context->last_end.row == 2u);
|
||||||
|
assert(context->last_end.col == 9u);
|
||||||
|
|
||||||
|
/* The "!" stop token terminates the lexer. The context input text position
|
||||||
|
* must not be updated when the lexer user code requests termination, so it
|
||||||
|
* still points at the "!" token on row 3, col 1. */
|
||||||
|
assert(p_lex(context, &token_info) == P_USER_TERMINATED);
|
||||||
|
assert(p_user_terminate_code(context) == 42u);
|
||||||
|
assert(context->text_position.row == 3u);
|
||||||
|
assert(context->text_position.col == 1u);
|
||||||
|
|
||||||
|
p_context_delete(context);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import testparser;
|
||||||
|
import std.stdio;
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
string input = "abc\n defg hi\n!";
|
||||||
|
p_context_t * context = p_context_new(input);
|
||||||
|
p_token_info_t token_info;
|
||||||
|
|
||||||
|
/* First token "abc" on row 1, cols 1-3. */
|
||||||
|
assert(p_lex(context, &token_info) == P_SUCCESS);
|
||||||
|
assert(token_info.token == TOKEN_word);
|
||||||
|
assert(context.last_start == p_position_t(1, 1));
|
||||||
|
assert(context.last_end == p_position_t(1, 3));
|
||||||
|
/* The lexer code block observed the same positions reported to the caller. */
|
||||||
|
assert(context.last_start == token_info.position);
|
||||||
|
assert(context.last_end == token_info.end_position);
|
||||||
|
|
||||||
|
/* Second token "defg" on row 2, cols 3-6. */
|
||||||
|
assert(p_lex(context, &token_info) == P_SUCCESS);
|
||||||
|
assert(token_info.token == TOKEN_word);
|
||||||
|
assert(context.last_start == p_position_t(2, 3));
|
||||||
|
assert(context.last_end == p_position_t(2, 6));
|
||||||
|
|
||||||
|
/* Third token "hi" on row 2, cols 8-9. */
|
||||||
|
assert(p_lex(context, &token_info) == P_SUCCESS);
|
||||||
|
assert(token_info.token == TOKEN_word);
|
||||||
|
assert(context.last_start == p_position_t(2, 8));
|
||||||
|
assert(context.last_end == p_position_t(2, 9));
|
||||||
|
|
||||||
|
/* The "!" stop token terminates the lexer. The context input text position
|
||||||
|
* must not be updated when the lexer user code requests termination, so it
|
||||||
|
* still points at the "!" token on row 3, col 1. */
|
||||||
|
assert(p_lex(context, &token_info) == P_USER_TERMINATED);
|
||||||
|
assert(p_user_terminate_code(context) == 42u);
|
||||||
|
assert(context.text_position == p_position_t(3, 1));
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
#include "testparser.h"
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "testutils.h"
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
char const * input = "ab";
|
||||||
|
p_context_t * context;
|
||||||
|
context = p_context_new((uint8_t const *)input, strlen(input));
|
||||||
|
assert_eq(P_SUCCESS, p_parse(context));
|
||||||
|
|
||||||
|
/* The parser user code recorded values accessed via $$, $1, and $2 while
|
||||||
|
* the tree node for the Start rule was being formed. */
|
||||||
|
assert_eq(3, context->start_n_fields);
|
||||||
|
assert_eq(11, context->start_a_value);
|
||||||
|
assert_eq(11, context->a_value);
|
||||||
|
assert_eq(22, context->b_value);
|
||||||
|
assert_eq(TOKEN_b, context->b_token);
|
||||||
|
|
||||||
|
/* The empty-matched rule C has a null $$ tree node, and its field in the
|
||||||
|
* Start node is null as well. */
|
||||||
|
assert_eq(1, context->c_is_null);
|
||||||
|
assert_eq(1, context->c_field_is_null);
|
||||||
|
|
||||||
|
/* Field aliases reference the same component tree nodes as the positional
|
||||||
|
* references. */
|
||||||
|
assert_eq(11, context->alias_a_value);
|
||||||
|
assert_eq(22, context->alias_b_value);
|
||||||
|
|
||||||
|
Start * start = p_result(context);
|
||||||
|
assert(start->pA != NULL);
|
||||||
|
assert(start->pB != NULL);
|
||||||
|
assert(start->pC == NULL);
|
||||||
|
p_tree_delete(start);
|
||||||
|
p_context_delete(context);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import testparser;
|
||||||
|
import std.stdio;
|
||||||
|
import testutils;
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
string input = "ab";
|
||||||
|
p_context_t * context = p_context_new(input);
|
||||||
|
assert_eq(P_SUCCESS, p_parse(context));
|
||||||
|
|
||||||
|
/* The parser user code recorded values accessed via $$, $1, and $2 while
|
||||||
|
* the tree node for the Start rule was being formed. */
|
||||||
|
assert_eq(3, context.start_n_fields);
|
||||||
|
assert_eq(11, context.start_a_value);
|
||||||
|
assert_eq(11, context.a_value);
|
||||||
|
assert_eq(22, context.b_value);
|
||||||
|
assert_eq(TOKEN_b, context.b_token);
|
||||||
|
|
||||||
|
/* The empty-matched rule C has a null $$ tree node, and its field in the
|
||||||
|
* Start node is null as well. */
|
||||||
|
assert_eq(1, context.c_is_null);
|
||||||
|
assert_eq(1, context.c_field_is_null);
|
||||||
|
|
||||||
|
/* Field aliases reference the same component tree nodes as the positional
|
||||||
|
* references. */
|
||||||
|
assert_eq(11, context.alias_a_value);
|
||||||
|
assert_eq(22, context.alias_b_value);
|
||||||
|
|
||||||
|
Start * start = p_result(context);
|
||||||
|
assert(start.pA !is null);
|
||||||
|
assert(start.pB !is null);
|
||||||
|
assert(start.pC is null);
|
||||||
|
p_tree_delete(start);
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#include "testparser.h"
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
char const * input = " Hello\n\n 4200\n";
|
||||||
|
p_context_t * context;
|
||||||
|
context = p_context_new((uint8_t const *)input, strlen(input));
|
||||||
|
assert(p_parse(context) == P_SUCCESS);
|
||||||
|
p_context_delete(context);
|
||||||
|
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
input = "\n tok2";
|
||||||
|
context = p_context_new((uint8_t const *)input, strlen(input));
|
||||||
|
assert(p_parse(context) == P_SUCCESS);
|
||||||
|
p_context_delete(context);
|
||||||
|
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
input = " tok1";
|
||||||
|
context = p_context_new((uint8_t const *)input, strlen(input));
|
||||||
|
assert(p_parse(context) == P_SUCCESS);
|
||||||
|
p_context_delete(context);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import testparser;
|
||||||
|
import std.stdio;
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
string input = " Hello\n\n 4200\n";
|
||||||
|
p_context_t * context;
|
||||||
|
context = p_context_new(input);
|
||||||
|
assert(p_parse(context) == P_SUCCESS);
|
||||||
|
|
||||||
|
writeln();
|
||||||
|
|
||||||
|
input = "\n tok2";
|
||||||
|
context = p_context_new(input);
|
||||||
|
assert(p_parse(context) == P_SUCCESS);
|
||||||
|
|
||||||
|
writeln();
|
||||||
|
|
||||||
|
input = " tok1";
|
||||||
|
context = p_context_new(input);
|
||||||
|
assert(p_parse(context) == P_SUCCESS);
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#include "testparser.h"
|
||||||
|
#include <assert.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
p_token_info_t token_info;
|
||||||
|
char const * input = "42 f s";
|
||||||
|
p_context_t * context;
|
||||||
|
context = p_context_new((uint8_t const *)input, strlen(input));
|
||||||
|
|
||||||
|
/* Default ptype value extracted with p_value_get(). */
|
||||||
|
assert(p_lex(context, &token_info) == P_SUCCESS);
|
||||||
|
assert(token_info.token == TOKEN_num);
|
||||||
|
assert(p_value_get(&token_info.pvalue) == 42);
|
||||||
|
|
||||||
|
/* Named ptype values extracted with p_value_get_XXX(). */
|
||||||
|
assert(p_lex(context, &token_info) == P_SUCCESS);
|
||||||
|
assert(token_info.token == TOKEN_flt);
|
||||||
|
assert(p_value_get_float(&token_info.pvalue) == 1.5);
|
||||||
|
|
||||||
|
assert(p_lex(context, &token_info) == P_SUCCESS);
|
||||||
|
assert(token_info.token == TOKEN_str);
|
||||||
|
assert(strcmp(p_value_get_string(&token_info.pvalue), "hello") == 0);
|
||||||
|
|
||||||
|
p_context_delete(context);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import testparser;
|
||||||
|
import std.stdio;
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
p_token_info_t token_info;
|
||||||
|
string input = "42 f s";
|
||||||
|
p_context_t * context;
|
||||||
|
context = p_context_new(input);
|
||||||
|
|
||||||
|
/* Default ptype value extracted with p_value_get(). */
|
||||||
|
assert(p_lex(context, &token_info) == P_SUCCESS);
|
||||||
|
assert(token_info.token == TOKEN_num);
|
||||||
|
assert(p_value_get(&token_info.pvalue) == 42);
|
||||||
|
|
||||||
|
/* Named ptype values extracted with p_value_get_XXX(). */
|
||||||
|
assert(p_lex(context, &token_info) == P_SUCCESS);
|
||||||
|
assert(token_info.token == TOKEN_flt);
|
||||||
|
assert(p_value_get_float(&token_info.pvalue) == 1.5);
|
||||||
|
|
||||||
|
assert(p_lex(context, &token_info) == P_SUCCESS);
|
||||||
|
assert(token_info.token == TOKEN_str);
|
||||||
|
assert(p_value_get_string(&token_info.pvalue) == "hello");
|
||||||
|
}
|
||||||
Reference in new issue
Block a user