Compare commits
8 Commits
75c2a4cbdf
...
b4d43d39f6
| Author | SHA1 | Date | |
|---|---|---|---|
| b4d43d39f6 | |||
| 7ce1f8fc7f | |||
| 0aaa44faf4 | |||
| 2ebdeaa995 | |||
| c0016f6d58 | |||
| fb4288d5b9 | |||
| 2f9a29f7c5 | |||
| 946eb4eef7 |
14
CHANGELOG.md
14
CHANGELOG.md
@ -1,3 +1,17 @@
|
||||
## Unreleased
|
||||
|
||||
### New Features
|
||||
|
||||
- Add `p_parse_inner_XXX()` APIs that accept a caller-provided set of follow
|
||||
tokens. These behave the same as `p_parse_XXX()` by parsing starting at the
|
||||
given start rule, but instead of expecting the rest of the input to match
|
||||
the start rule they allow specifying a set of tokens that may follow the
|
||||
start rule.
|
||||
- Add `p_set_position()` API to set the current text position stored in the
|
||||
context. Useful for setting the initial text position to something other
|
||||
than `(1, 1)` for a nested parse operation.
|
||||
- Add `p_input_index()` API to get the current input text byte offset.
|
||||
|
||||
## v4.7.0
|
||||
|
||||
### New Features
|
||||
|
||||
@ -1008,8 +1008,17 @@ static size_t check_reduce(size_t state_id, <%= @grammar.prefix %>token_t token)
|
||||
*
|
||||
* @param context
|
||||
* Lexer/parser context structure.
|
||||
* @start_state_id
|
||||
* @param start_state_id
|
||||
* ID of the state in which to start.
|
||||
* @param start_rule_set_id
|
||||
* Rule set ID for the requested start rule. Only used when
|
||||
* @p follow_tokens is non-NULL, to gate follow-token shift success.
|
||||
* @param follow_tokens
|
||||
* Optional array of caller-provided follow tokens (tokens expected to
|
||||
* appear immediately after the start rule in some outer context). Used to
|
||||
* drive the "parse inner" retry logic. May be NULL for a standard parse.
|
||||
* @param n_follow_tokens
|
||||
* Number of entries in @p follow_tokens.
|
||||
*
|
||||
* @retval P_SUCCESS
|
||||
* The parser successfully matched the input text. The parse result value
|
||||
@ -1022,12 +1031,15 @@ static size_t check_reduce(size_t state_id, <%= @grammar.prefix %>token_t token)
|
||||
* @reval P_UNEXPECTED_INPUT
|
||||
* Input text does not match any lexer pattern.
|
||||
*/
|
||||
static size_t parse_from(<%= @grammar.prefix %>context_t * context, size_t start_state_id)
|
||||
static size_t parse_from(<%= @grammar.prefix %>context_t * context, size_t start_state_id,
|
||||
size_t start_rule_set_id,
|
||||
<%= @grammar.prefix %>token_t const * follow_tokens, size_t n_follow_tokens)
|
||||
{
|
||||
<%= @grammar.prefix %>token_info_t token_info;
|
||||
<%= @grammar.prefix %>token_t token = INVALID_TOKEN_ID;
|
||||
state_values_stack_t statevalues;
|
||||
size_t reduced_rule_set = INVALID_ID;
|
||||
size_t last_shifted_rule_set_id = INVALID_ID;
|
||||
<% if @grammar.tree %>
|
||||
void * reduced_parser_node;
|
||||
<% else %>
|
||||
@ -1051,6 +1063,18 @@ static size_t parse_from(<%= @grammar.prefix %>context_t * context, size_t start
|
||||
}
|
||||
token = token_info.token;
|
||||
}
|
||||
/* For a "parse inner" operation, determine once per iteration whether
|
||||
* the current token is a member of the caller-provided follow token
|
||||
* set. Used by both the shift-side and reduce-side retries below. */
|
||||
bool token_is_follow = false;
|
||||
for (size_t i = 0u; i < n_follow_tokens; i++)
|
||||
{
|
||||
if (token == follow_tokens[i])
|
||||
{
|
||||
token_is_follow = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
size_t shift_state = INVALID_ID;
|
||||
if (reduced_rule_set != INVALID_ID)
|
||||
{
|
||||
@ -1070,10 +1094,42 @@ static size_t parse_from(<%= @grammar.prefix %>context_t * context, size_t start
|
||||
result = P_SUCCESS;
|
||||
break;
|
||||
}
|
||||
if ((shift_state == INVALID_ID) && token_is_follow)
|
||||
{
|
||||
/* For a "parse inner" operation, if the incoming token is one
|
||||
* of the caller's follow tokens, retry the shift as
|
||||
* TOKEN___EOF. Only consider the parse complete if the reduced
|
||||
* start rule is the only thing on the parse stack (i.e. the
|
||||
* initial state plus a single shifted start rule set entry). */
|
||||
size_t retry_shift_state = check_shift(state_values_stack_index(&statevalues, -1)->state_id, TOKEN___EOF);
|
||||
if ((retry_shift_state != INVALID_ID) &&
|
||||
(statevalues.length == 2u) &&
|
||||
(last_shifted_rule_set_id == start_rule_set_id))
|
||||
{
|
||||
/* Successful parse via follow token. Rewind the input
|
||||
* position so that the follow token is not consumed from
|
||||
* the input stream and remains available for a subsequent
|
||||
* call to <%= @grammar.prefix %>lex() or a
|
||||
* <%= @grammar.prefix %>parse*() function. */
|
||||
context->input_index -= token_info.length;
|
||||
context->text_position = token_info.position;
|
||||
<% if @grammar.tree %>
|
||||
context->parse_result = state_values_stack_index(&statevalues, -1)->tree_node;
|
||||
<% else %>
|
||||
context->parse_result = state_values_stack_index(&statevalues, -1)->pvalue;
|
||||
<% end %>
|
||||
result = P_SUCCESS;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (shift_state != INVALID_ID)
|
||||
{
|
||||
/* We have something to shift. */
|
||||
/* We have something to shift. Track the last shifted rule set ID
|
||||
* (INVALID_ID if we just shifted a token) so the follow-token
|
||||
* shift retry can gate success on the reduced start rule being the
|
||||
* only thing on top of the initial state. */
|
||||
last_shifted_rule_set_id = reduced_rule_set;
|
||||
state_values_stack_push(&statevalues);
|
||||
state_value_t * new_state_info = state_values_stack_index(&statevalues, -1);
|
||||
new_state_info->state_id = shift_state;
|
||||
@ -1120,6 +1176,15 @@ static size_t parse_from(<%= @grammar.prefix %>context_t * context, size_t start
|
||||
}
|
||||
|
||||
size_t reduce_index = check_reduce(state_values_stack_index(&statevalues, -1)->state_id, token);
|
||||
if ((reduce_index == INVALID_ID) && token_is_follow)
|
||||
{
|
||||
/* For a "parse inner" operation, if the incoming token is one of
|
||||
* the caller's follow tokens, retry the reduce lookup as
|
||||
* TOKEN___EOF. Whatever reduce_index results (if any) is used
|
||||
* regardless of which rule set it reduces to; this allows chains
|
||||
* of reductions leading up to the start rule. */
|
||||
reduce_index = check_reduce(state_values_stack_index(&statevalues, -1)->state_id, TOKEN___EOF);
|
||||
}
|
||||
if (reduce_index != INVALID_ID)
|
||||
{
|
||||
/* We have something to reduce. */
|
||||
@ -1219,14 +1284,20 @@ static size_t parse_from(<%= @grammar.prefix %>context_t * context, size_t start
|
||||
|
||||
size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context)
|
||||
{
|
||||
return parse_from(context, 0u);
|
||||
return parse_from(context, 0u, <%= @parser.rule_sets[@grammar.start_rules[0]].id %>u, NULL, 0u);
|
||||
}
|
||||
|
||||
<% @grammar.start_rules.each_with_index do |start_rule, i| %>
|
||||
|
||||
size_t <%= @grammar.prefix %>parse_<%= start_rule %>(<%= @grammar.prefix %>context_t * context)
|
||||
{
|
||||
return parse_from(context, <%= i %>u);
|
||||
return parse_from(context, <%= i %>u, <%= @parser.rule_sets[start_rule].id %>u, NULL, 0u);
|
||||
}
|
||||
|
||||
size_t <%= @grammar.prefix %>parse_inner_<%= start_rule %>(<%= @grammar.prefix %>context_t * context,
|
||||
<%= @grammar.prefix %>token_t const * follow_tokens, size_t n_follow_tokens)
|
||||
{
|
||||
return parse_from(context, <%= i %>u, <%= @parser.rule_sets[start_rule].id %>u, follow_tokens, n_follow_tokens);
|
||||
}
|
||||
<% end %>
|
||||
|
||||
@ -1275,6 +1346,37 @@ size_t <%= @grammar.prefix %>parse_<%= start_rule %>(<%= @grammar.prefix %>conte
|
||||
return context->text_position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current text input position.
|
||||
*
|
||||
* This can be used to set the initial text position to something other than
|
||||
* (1, 1) for a nested parse operation so that error positions reported by
|
||||
* subsequent lexer/parser calls are relative to a larger enclosing document.
|
||||
*
|
||||
* @param context
|
||||
* Lexer/parser context structure.
|
||||
* @param position
|
||||
* Text position to set.
|
||||
*/
|
||||
void <%= @grammar.prefix %>set_position(<%= @grammar.prefix %>context_t * context, <%= @grammar.prefix %>position_t position)
|
||||
{
|
||||
context->text_position = position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current input text byte offset.
|
||||
*
|
||||
* @param context
|
||||
* Lexer/parser context structure.
|
||||
*
|
||||
* @return Current input text byte offset (measured from the start of the
|
||||
* input text passed to <%= @grammar.prefix %>context_new()).
|
||||
*/
|
||||
size_t <%= @grammar.prefix %>input_index(<%= @grammar.prefix %>context_t * context)
|
||||
{
|
||||
return context->input_index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user terminate code.
|
||||
*
|
||||
|
||||
@ -1079,8 +1079,16 @@ private size_t check_reduce(size_t state_id, <%= @grammar.prefix %>token_t token
|
||||
*
|
||||
* @param context
|
||||
* Lexer/parser context structure.
|
||||
* @start_state_id
|
||||
* @param start_state_id
|
||||
* ID of the state in which to start.
|
||||
* @param start_rule_set_id
|
||||
* Rule set ID for the requested start rule. Only used when
|
||||
* @p follow_tokens is non-empty, to gate follow-token shift success.
|
||||
* @param follow_tokens
|
||||
* Optional slice of caller-provided follow tokens (tokens expected to
|
||||
* appear immediately after the start rule in some outer context). Used to
|
||||
* drive the "parse inner" retry logic. May be null/empty for a standard
|
||||
* parse.
|
||||
*
|
||||
* @retval P_SUCCESS
|
||||
* The parser successfully matched the input text. The parse result value
|
||||
@ -1093,13 +1101,16 @@ private size_t check_reduce(size_t state_id, <%= @grammar.prefix %>token_t token
|
||||
* @reval P_UNEXPECTED_INPUT
|
||||
* Input text does not match any lexer pattern.
|
||||
*/
|
||||
private size_t parse_from(<%= @grammar.prefix %>context_t * context, size_t start_state_id)
|
||||
private size_t parse_from(<%= @grammar.prefix %>context_t * context, size_t start_state_id,
|
||||
size_t start_rule_set_id,
|
||||
const(<%= @grammar.prefix %>token_t)[] follow_tokens)
|
||||
{
|
||||
<%= @grammar.prefix %>token_info_t token_info;
|
||||
<%= @grammar.prefix %>token_t token = INVALID_TOKEN_ID;
|
||||
state_value_t[] statevalues = new state_value_t[](1);
|
||||
statevalues[0].state_id = start_state_id;
|
||||
size_t reduced_rule_set = INVALID_ID;
|
||||
size_t last_shifted_rule_set_id = INVALID_ID;
|
||||
<% if @grammar.tree %>
|
||||
void * reduced_parser_node;
|
||||
<% else %>
|
||||
@ -1118,6 +1129,18 @@ private size_t parse_from(<%= @grammar.prefix %>context_t * context, size_t star
|
||||
}
|
||||
token = token_info.token;
|
||||
}
|
||||
/* For a "parse inner" operation, determine once per iteration whether
|
||||
* the current token is a member of the caller-provided follow token
|
||||
* set. Used by both the shift-side and reduce-side retries below. */
|
||||
bool token_is_follow = false;
|
||||
foreach (eof_token; follow_tokens)
|
||||
{
|
||||
if (token == eof_token)
|
||||
{
|
||||
token_is_follow = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
size_t shift_state = INVALID_ID;
|
||||
if (reduced_rule_set != INVALID_ID)
|
||||
{
|
||||
@ -1136,10 +1159,41 @@ private size_t parse_from(<%= @grammar.prefix %>context_t * context, size_t star
|
||||
<% end %>
|
||||
return P_SUCCESS;
|
||||
}
|
||||
if ((shift_state == INVALID_ID) && token_is_follow)
|
||||
{
|
||||
/* For a "parse inner" operation, if the incoming token is one
|
||||
* of the caller's follow tokens, retry the shift as
|
||||
* TOKEN___EOF. Only consider the parse complete if the reduced
|
||||
* start rule is the only thing on the parse stack (i.e. the
|
||||
* initial state plus a single shifted start rule set entry). */
|
||||
size_t retry_shift_state = check_shift(statevalues[$-1].state_id, TOKEN___EOF);
|
||||
if ((retry_shift_state != INVALID_ID) &&
|
||||
(statevalues.length == 2u) &&
|
||||
(last_shifted_rule_set_id == start_rule_set_id))
|
||||
{
|
||||
/* Successful parse via follow token. Rewind the input
|
||||
* position so that the follow token is not consumed from
|
||||
* the input stream and remains available for a subsequent
|
||||
* call to <%= @grammar.prefix %>lex() or a
|
||||
* <%= @grammar.prefix %>parse*() function. */
|
||||
context.input_index -= token_info.length;
|
||||
context.text_position = token_info.position;
|
||||
<% if @grammar.tree %>
|
||||
context.parse_result = statevalues[$-1].tree_node;
|
||||
<% else %>
|
||||
context.parse_result = statevalues[$-1].pvalue;
|
||||
<% end %>
|
||||
return P_SUCCESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (shift_state != INVALID_ID)
|
||||
{
|
||||
/* We have something to shift. */
|
||||
/* We have something to shift. Track the last shifted rule set ID
|
||||
* (INVALID_ID if we just shifted a token) so the follow-token
|
||||
* shift retry can gate success on the reduced start rule being the
|
||||
* only thing on top of the initial state. */
|
||||
last_shifted_rule_set_id = reduced_rule_set;
|
||||
statevalues ~= state_value_t(shift_state);
|
||||
if (reduced_rule_set == INVALID_ID)
|
||||
{
|
||||
@ -1173,6 +1227,15 @@ private size_t parse_from(<%= @grammar.prefix %>context_t * context, size_t star
|
||||
}
|
||||
|
||||
size_t reduce_index = check_reduce(statevalues[$-1].state_id, token);
|
||||
if ((reduce_index == INVALID_ID) && token_is_follow)
|
||||
{
|
||||
/* For a "parse inner" operation, if the incoming token is one of
|
||||
* the caller's follow tokens, retry the reduce lookup as
|
||||
* TOKEN___EOF. Whatever reduce_index results (if any) is used
|
||||
* regardless of which rule set it reduces to; this allows chains
|
||||
* of reductions leading up to the start rule. */
|
||||
reduce_index = check_reduce(statevalues[$-1].state_id, TOKEN___EOF);
|
||||
}
|
||||
if (reduce_index != INVALID_ID)
|
||||
{
|
||||
/* We have something to reduce. */
|
||||
@ -1271,14 +1334,20 @@ private size_t parse_from(<%= @grammar.prefix %>context_t * context, size_t star
|
||||
|
||||
public size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context)
|
||||
{
|
||||
return parse_from(context, 0u);
|
||||
return parse_from(context, 0u, <%= @parser.rule_sets[@grammar.start_rules[0]].id %>u, null);
|
||||
}
|
||||
|
||||
<% @grammar.start_rules.each_with_index do |start_rule, i| %>
|
||||
|
||||
public size_t <%= @grammar.prefix %>parse_<%= start_rule %>(<%= @grammar.prefix %>context_t * context)
|
||||
{
|
||||
return parse_from(context, <%= i %>u);
|
||||
return parse_from(context, <%= i %>u, <%= @parser.rule_sets[start_rule].id %>u, null);
|
||||
}
|
||||
|
||||
public size_t <%= @grammar.prefix %>parse_inner_<%= start_rule %>(<%= @grammar.prefix %>context_t * context,
|
||||
const(<%= @grammar.prefix %>token_t)[] follow_tokens)
|
||||
{
|
||||
return parse_from(context, <%= i %>u, <%= @parser.rule_sets[start_rule].id %>u, follow_tokens);
|
||||
}
|
||||
<% end %>
|
||||
|
||||
@ -1356,6 +1425,37 @@ public <%= @grammar.prefix %>position_t <%= @grammar.prefix %>position(<%= @gram
|
||||
return context.text_position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the current text input position.
|
||||
*
|
||||
* This can be used to set the initial text position to something other than
|
||||
* (1, 1) for a nested parse operation so that error positions reported by
|
||||
* subsequent lexer/parser calls are relative to a larger enclosing document.
|
||||
*
|
||||
* @param context
|
||||
* Lexer/parser context structure.
|
||||
* @param position
|
||||
* Text position to set.
|
||||
*/
|
||||
public void <%= @grammar.prefix %>set_position(<%= @grammar.prefix %>context_t * context, <%= @grammar.prefix %>position_t position)
|
||||
{
|
||||
context.text_position = position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current input text byte offset.
|
||||
*
|
||||
* @param context
|
||||
* Lexer/parser context structure.
|
||||
*
|
||||
* @return Current input text byte offset (measured from the start of the
|
||||
* input text passed to <%= @grammar.prefix %>context_new()).
|
||||
*/
|
||||
public size_t <%= @grammar.prefix %>input_index(<%= @grammar.prefix %>context_t * context)
|
||||
{
|
||||
return context.input_index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user terminate code.
|
||||
*
|
||||
|
||||
@ -212,6 +212,8 @@ size_t <%= @grammar.prefix %>lex(<%= @grammar.prefix %>context_t * context, <%=
|
||||
size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context);
|
||||
<% @grammar.start_rules.each_with_index do |start_rule, i| %>
|
||||
size_t <%= @grammar.prefix %>parse_<%= start_rule %>(<%= @grammar.prefix %>context_t * context);
|
||||
size_t <%= @grammar.prefix %>parse_inner_<%= start_rule %>(<%= @grammar.prefix %>context_t * context,
|
||||
<%= @grammar.prefix %>token_t const * follow_tokens, size_t n_follow_tokens);
|
||||
<% end %>
|
||||
|
||||
<% if @grammar.tree %>
|
||||
@ -235,6 +237,10 @@ void <%= @grammar.prefix %>tree_delete_<%= start_rule %>(<%= @grammar.tree_prefi
|
||||
|
||||
<%= @grammar.prefix %>position_t <%= @grammar.prefix %>position(<%= @grammar.prefix %>context_t * context);
|
||||
|
||||
void <%= @grammar.prefix %>set_position(<%= @grammar.prefix %>context_t * context, <%= @grammar.prefix %>position_t position);
|
||||
|
||||
size_t <%= @grammar.prefix %>input_index(<%= @grammar.prefix %>context_t * context);
|
||||
|
||||
size_t <%= @grammar.prefix %>user_terminate_code(<%= @grammar.prefix %>context_t * context);
|
||||
|
||||
<%= @grammar.prefix %>token_t <%= @grammar.prefix %>token(<%= @grammar.prefix %>context_t * context);
|
||||
|
||||
@ -453,6 +453,41 @@ 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.
|
||||
|
||||
The custom lex function returns a result code to the parser.
|
||||
Returning `P_SUCCESS` indicates that the function has produced a token in the
|
||||
`token` output field and the parse should proceed.
|
||||
Returning any other result code stops the parse immediately; the parser
|
||||
propagates that code out of `p_parse()` (and the `p_parse_XXX()` and
|
||||
`p_parse_inner_XXX()` functions) unchanged.
|
||||
This allows a custom lex function to surface an error condition, for example by
|
||||
propagating a `P_UNEXPECTED_TOKEN`, `P_UNEXPECTED_INPUT`, or `P_DECODE_ERROR`
|
||||
result from a nested `p_parse_inner_XXX()` or `p_lex()` call.
|
||||
|
||||
Observe the following contract when returning a result code other than
|
||||
`P_SUCCESS`:
|
||||
|
||||
* Do not return `P_DROP`.
|
||||
This code is an internal lexer signal and is never returned to the parser by
|
||||
the generated `p_lex()` function.
|
||||
Returning it from a custom lex function would be reported as a spurious parse
|
||||
failure. If drop functionality is required, the custom lex function should
|
||||
loop and return the next non-drop token.
|
||||
* Do not return `P_EOF` to signal the end of the input.
|
||||
The end of input is communicated to the parser by returning `P_SUCCESS` with
|
||||
the `token` field set to the end-of-input token (`TOKEN___EOF`), which is what
|
||||
the generated `p_lex()` function does.
|
||||
Returning `P_EOF` aborts the parse rather than allowing it to complete.
|
||||
* The `p_token()` and `p_position()` accessors are populated by the parser only
|
||||
when the parser itself detects an unexpected token.
|
||||
When a custom lex function returns an error code, `p_token()` is not updated
|
||||
and may not reflect a meaningful token, and `p_position()` reflects the
|
||||
lexer's current text position rather than a parser-identified error location.
|
||||
A custom lex function that wants a specific reported position can set it with
|
||||
`p_set_position()` before returning.
|
||||
* Returning `P_USER_TERMINATED` does not populate the user terminate code
|
||||
returned by `p_user_terminate_code()`. This user terminate code is normally
|
||||
populated by the `$terminate()` function in the user code block.
|
||||
|
||||
##> `module` statement - specifying the generated parser module name
|
||||
|
||||
The `module` statement can be used to specify the module name for a generated
|
||||
@ -1367,6 +1402,42 @@ size_t result = p_parse_Statement(context);
|
||||
|
||||
In this case, the parser will start parsing with the `Statement` rule.
|
||||
|
||||
### `p_parse_inner_XXX`
|
||||
|
||||
For each start rule, a `p_parse_inner_XXX()` function is also generated.
|
||||
This variant of the parser entry point accepts a caller-provided array of
|
||||
"follow tokens" — tokens the caller allows to appear immediately after the
|
||||
start rule in some outer grammar context.
|
||||
It is useful when embedding a Propane-generated sub-parser within an outer
|
||||
parser and the outer parser knows which tokens naturally terminate the
|
||||
sub-parse.
|
||||
|
||||
For C targets, the signature is (example for a rule named `Statement`):
|
||||
|
||||
```
|
||||
size_t p_parse_inner_Statement(p_context_t * context,
|
||||
p_token_t const * follow_tokens, size_t n_follow_tokens);
|
||||
```
|
||||
|
||||
Passing a `NULL` pointer (or a count of zero) makes the function behave
|
||||
identically to `p_parse_Statement()`.
|
||||
|
||||
For D targets, the signature accepts a slice:
|
||||
|
||||
```
|
||||
size_t p_parse_inner_Statement(p_context_t * context,
|
||||
const(p_token_t)[] follow_tokens);
|
||||
```
|
||||
|
||||
Passing `null` for the slice makes the function behave identically to
|
||||
`p_parse_Statement()`.
|
||||
|
||||
When the parse is completed via a non-EOF follow token, that follow token is
|
||||
**not** consumed from the input stream.
|
||||
The parser rewinds the input index and text position to the start of the follow
|
||||
token so that a subsequent call to `p_lex()` or another parse function sees the
|
||||
same token.
|
||||
|
||||
### `p_position_valid`
|
||||
|
||||
The `p_position_valid()` function is only generated for C targets.
|
||||
@ -1438,6 +1509,41 @@ if (p_parse(context) == P_UNEXPECTED_TOKEN)
|
||||
}
|
||||
```
|
||||
|
||||
### `p_set_position`
|
||||
|
||||
The `p_set_position()` function sets the current text position stored in the
|
||||
context.
|
||||
This is useful when performing a nested parse operation over a slice of a
|
||||
larger enclosing document: by setting the initial text position to something
|
||||
other than the default `(1, 1)`, any token positions or error positions
|
||||
subsequently reported by the lexer/parser are relative to the enclosing
|
||||
document rather than the slice.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
p_context_t * context = p_context_new(input, input_length);
|
||||
p_position_t start = { .row = 5, .col = 20 };
|
||||
p_set_position(context, start);
|
||||
p_parse(context);
|
||||
```
|
||||
|
||||
### `p_input_index`
|
||||
|
||||
The `p_input_index()` function returns the current input text byte offset,
|
||||
measured from the start of the input text passed to `p_context_new()`.
|
||||
This is useful for slicing out the remaining input after a partial parse,
|
||||
or for locating tokens in the original input buffer.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
p_context_t * context = p_context_new(input, input_length);
|
||||
p_parse_inner_Statement(context, follow_tokens, n_follow_tokens);
|
||||
size_t offset = p_input_index(context);
|
||||
/* Remaining input starts at `input + offset`. */
|
||||
```
|
||||
|
||||
### `p_user_terminate_code`
|
||||
|
||||
The `p_user_terminate_code()` function can be used to retrieve the user
|
||||
|
||||
25
spec/macros.c.propane
Normal file
25
spec/macros.c.propane
Normal file
@ -0,0 +1,25 @@
|
||||
<<
|
||||
#include <stdlib.h>
|
||||
size_t mylexfn(p_context_t * context, p_token_info_t * out_token_info);
|
||||
void record(int v);
|
||||
>>
|
||||
|
||||
ptype int;
|
||||
|
||||
lex_fn mylexfn;
|
||||
|
||||
drop /\s+/;
|
||||
token lbrace /\{/;
|
||||
token rbrace /\}/;
|
||||
token plus /\+/;
|
||||
token macro;
|
||||
token macroname /@[a-zA-Z_]\w*/;
|
||||
token num /\d+/ << char b[100]; memcpy(b, match, match_length); b[match_length] = '\0'; $$ = atoi(b); >>
|
||||
|
||||
Start -> Statements;
|
||||
Statements -> ;
|
||||
Statements -> Statement Statements;
|
||||
Statement -> Add;
|
||||
Statement -> MacroStart;
|
||||
Add -> num plus num << $$ = $1 + $3; record($$); >>
|
||||
MacroStart -> macro macroname lbrace;
|
||||
31
spec/macros.d.propane
Normal file
31
spec/macros.d.propane
Normal file
@ -0,0 +1,31 @@
|
||||
<<
|
||||
import test_macros;
|
||||
>>
|
||||
|
||||
ptype int;
|
||||
|
||||
lex_fn mylexfn;
|
||||
|
||||
drop /\s+/;
|
||||
token lbrace /\{/;
|
||||
token rbrace /\}/;
|
||||
token plus /\+/;
|
||||
token macro;
|
||||
token macroname /@[a-zA-Z_]\w*/;
|
||||
token num /\d+/ <<
|
||||
int n = 0;
|
||||
foreach (c; match)
|
||||
{
|
||||
n *= 10;
|
||||
n += (c - '0');
|
||||
}
|
||||
$$ = n;
|
||||
>>
|
||||
|
||||
Start -> Statements;
|
||||
Statements -> ;
|
||||
Statements -> Statement Statements;
|
||||
Statement -> Add;
|
||||
Statement -> MacroStart;
|
||||
Add -> num plus num << $$ = $1 + $3; record($$); >>
|
||||
MacroStart -> macro macroname lbrace;
|
||||
19
spec/parse_inner_nested.c.propane
Normal file
19
spec/parse_inner_nested.c.propane
Normal file
@ -0,0 +1,19 @@
|
||||
<<
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
size_t mylexfn(p_context_t * context, p_token_info_t * out_token_info);
|
||||
>>
|
||||
|
||||
ptype int;
|
||||
|
||||
lex_fn mylexfn;
|
||||
|
||||
drop /\s+/;
|
||||
token lparen /\(/;
|
||||
token rparen /\)/;
|
||||
token plus /\+/;
|
||||
token num /\d+/ << char b[32]; memcpy(b, match, match_length); b[match_length] = '\0'; $$ = atoi(b); >>
|
||||
|
||||
Start -> Expr << $$ = $1; >>
|
||||
Expr -> num << $$ = $1; >>
|
||||
Expr -> Expr plus num << $$ = $1 + $3; >>
|
||||
17
spec/parse_inner_nested.d.propane
Normal file
17
spec/parse_inner_nested.d.propane
Normal file
@ -0,0 +1,17 @@
|
||||
<<
|
||||
import test_parse_inner_nested;
|
||||
>>
|
||||
|
||||
ptype int;
|
||||
|
||||
lex_fn mylexfn;
|
||||
|
||||
drop /\s+/;
|
||||
token lparen /\(/;
|
||||
token rparen /\)/;
|
||||
token plus /\+/;
|
||||
token num /\d+/ << int n = 0; foreach (ch; match) { n *= 10; n += (ch - '0'); } $$ = n; >>
|
||||
|
||||
Start -> Expr << $$ = $1; >>
|
||||
Expr -> num << $$ = $1; >>
|
||||
Expr -> Expr plus num << $$ = $1 + $3; >>
|
||||
17
spec/parse_inner_nested_tree.c.propane
Normal file
17
spec/parse_inner_nested_tree.c.propane
Normal file
@ -0,0 +1,17 @@
|
||||
<<
|
||||
size_t mylexfn(p_context_t * context, p_token_info_t * out_token_info);
|
||||
>>
|
||||
|
||||
tree;
|
||||
|
||||
lex_fn mylexfn;
|
||||
|
||||
drop /\s+/;
|
||||
token lparen /\(/;
|
||||
token rparen /\)/;
|
||||
token plus /\+/;
|
||||
token num /\d+/;
|
||||
|
||||
Start -> Expr;
|
||||
Expr -> num;
|
||||
Expr -> Expr plus num;
|
||||
17
spec/parse_inner_nested_tree.d.propane
Normal file
17
spec/parse_inner_nested_tree.d.propane
Normal file
@ -0,0 +1,17 @@
|
||||
<<
|
||||
import test_parse_inner_nested_tree;
|
||||
>>
|
||||
|
||||
tree;
|
||||
|
||||
lex_fn mylexfn;
|
||||
|
||||
drop /\s+/;
|
||||
token lparen /\(/;
|
||||
token rparen /\)/;
|
||||
token plus /\+/;
|
||||
token num /\d+/;
|
||||
|
||||
Start -> Expr;
|
||||
Expr -> num;
|
||||
Expr -> Expr plus num;
|
||||
@ -938,6 +938,34 @@ EOF
|
||||
expect(results.status).to eq 0
|
||||
end
|
||||
|
||||
it "allows setting the text position via p_set_position()" do
|
||||
write_grammar <<EOF
|
||||
token a;
|
||||
token b;
|
||||
Start -> a b;
|
||||
EOF
|
||||
run_propane(language: language)
|
||||
compile("spec/test_set_position.#{language}", language: language)
|
||||
results = run_test(language: language)
|
||||
expect(results.stderr).to eq ""
|
||||
expect(results.status).to eq 0
|
||||
end
|
||||
|
||||
it "exposes the current input byte offset via p_input_index()" do
|
||||
write_grammar <<EOF
|
||||
drop /\\s+/;
|
||||
token a;
|
||||
token b;
|
||||
start Start;
|
||||
Start -> a b;
|
||||
EOF
|
||||
run_propane(language: language)
|
||||
compile("spec/test_input_index.#{language}", language: language)
|
||||
results = run_test(language: language)
|
||||
expect(results.stderr).to eq ""
|
||||
expect(results.status).to eq 0
|
||||
end
|
||||
|
||||
it "allows creating a JSON parser" do
|
||||
ext = language == "cpp" ? "c" : language
|
||||
write_grammar(File.read("spec/json_parser.#{ext}.propane"))
|
||||
@ -1589,6 +1617,95 @@ EOF
|
||||
expect(results.status).to eq 0
|
||||
end
|
||||
|
||||
it "supports parse_inner APIs that treat provided tokens as follow tokens" do
|
||||
write_grammar <<EOF
|
||||
ptype int;
|
||||
token a << $$ = 1; >>
|
||||
token b << $$ = 2; >>
|
||||
Start -> Y << $$ = $1; >>
|
||||
Y -> a << $$ = $1; >>
|
||||
EOF
|
||||
run_propane(language: language)
|
||||
compile("spec/test_parse_inner.#{language}", language: language)
|
||||
results = run_test(language: language)
|
||||
expect(results.stderr).to eq ""
|
||||
expect(results.status).to eq 0
|
||||
end
|
||||
|
||||
it "parse_inner APIs block success when the outer rule is unfinished" do
|
||||
write_grammar <<EOF
|
||||
ptype int;
|
||||
token a << $$ = 1; >>
|
||||
token b << $$ = 2; >>
|
||||
token c << $$ = 3; >>
|
||||
Start -> a Start b << $$ = $2; >>
|
||||
Start -> c << $$ = $1; >>
|
||||
EOF
|
||||
run_propane(language: language)
|
||||
compile("spec/test_parse_inner_recursive.#{language}", language: language)
|
||||
results = run_test(language: language)
|
||||
expect(results.stderr).to eq ""
|
||||
expect(results.status).to eq 0
|
||||
end
|
||||
|
||||
it "parse_inner APIs work when the reduce state uses lookahead disambiguation" do
|
||||
write_grammar <<EOF
|
||||
ptype int;
|
||||
token a;
|
||||
token b;
|
||||
start Start;
|
||||
start R1;
|
||||
Start -> R1 a;
|
||||
Start -> R2 b;
|
||||
R1 -> a b << $$ = 11; >>
|
||||
R2 -> a b << $$ = 22; >>
|
||||
EOF
|
||||
run_propane(language: language)
|
||||
compile("spec/test_parse_inner_shared.#{language}", language: language)
|
||||
results = run_test(language: language)
|
||||
expect(results.stderr).to eq ""
|
||||
expect(results.status).to eq 0
|
||||
end
|
||||
|
||||
it "parse_inner APIs work in tree generation mode with follow tokens" do
|
||||
write_grammar <<EOF
|
||||
tree;
|
||||
token a;
|
||||
token b;
|
||||
start Start;
|
||||
start R1;
|
||||
Start -> R1 a;
|
||||
Start -> R2 b;
|
||||
R1 -> a b;
|
||||
R2 -> a b;
|
||||
EOF
|
||||
run_propane(language: language)
|
||||
compile("spec/test_parse_inner_tree.#{language}", language: language)
|
||||
results = run_test(language: language)
|
||||
expect(results.stderr).to eq ""
|
||||
expect(results.status).to eq 0
|
||||
end
|
||||
|
||||
it "supports a reentrant nested parse driven from a custom lex function" do
|
||||
ext = language == "cpp" ? "c" : language
|
||||
write_grammar(File.read("spec/parse_inner_nested.#{ext}.propane"))
|
||||
run_propane(language: language)
|
||||
compile("spec/test_parse_inner_nested.#{language}", language: language)
|
||||
results = run_test(language: language)
|
||||
expect(results.stderr).to eq ""
|
||||
expect(results.status).to eq 0
|
||||
end
|
||||
|
||||
it "tracks positions across a lex-function nested parse in tree mode" do
|
||||
ext = language == "cpp" ? "c" : language
|
||||
write_grammar(File.read("spec/parse_inner_nested_tree.#{ext}.propane"))
|
||||
run_propane(language: language)
|
||||
compile("spec/test_parse_inner_nested_tree.#{language}", language: language)
|
||||
results = run_test(language: language)
|
||||
expect(results.stderr).to eq ""
|
||||
expect(results.status).to eq 0
|
||||
end
|
||||
|
||||
it "allows multiple starting rules in tree mode" do
|
||||
write_grammar <<EOF
|
||||
tree;
|
||||
@ -2067,6 +2184,16 @@ StartR start: 1, 3
|
||||
StartR end: 1, 6
|
||||
EOF
|
||||
end
|
||||
|
||||
it "allows nested parses for macro expansions" do
|
||||
ext = language == "cpp" ? "c" : language
|
||||
write_grammar(File.read("spec/macros.#{ext}.propane"))
|
||||
run_propane(language: language)
|
||||
compile("spec/test_macros.#{language}", language: language)
|
||||
results = run_test(language: language)
|
||||
expect(results.stderr).to eq ""
|
||||
expect(results.status).to eq 0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
60
spec/test_input_index.c
Normal file
60
spec/test_input_index.c
Normal file
@ -0,0 +1,60 @@
|
||||
#include "testparser.h"
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "testutils.h"
|
||||
|
||||
int main()
|
||||
{
|
||||
/* Grammar (simple):
|
||||
* drop /\\s+/;
|
||||
* token a; token b;
|
||||
* Start -> a b;
|
||||
*
|
||||
* Verifies that p_input_index() reports the parser/lexer's current byte
|
||||
* offset into the input text. */
|
||||
|
||||
/* Fresh context: input_index starts at 0. */
|
||||
{
|
||||
char const * input = "ab";
|
||||
p_context_t * context = p_context_new((uint8_t const *)input, strlen(input));
|
||||
assert_eq(0u, p_input_index(context));
|
||||
p_context_delete(context);
|
||||
}
|
||||
|
||||
/* After each successful lex the byte offset advances past the token. */
|
||||
{
|
||||
char const * input = "a b";
|
||||
p_context_t * context = p_context_new((uint8_t const *)input, strlen(input));
|
||||
p_token_info_t token_info;
|
||||
assert(p_lex(context, &token_info) == P_SUCCESS);
|
||||
assert_eq((size_t)TOKEN_a, (size_t)token_info.token);
|
||||
assert_eq(1u, p_input_index(context));
|
||||
assert(p_lex(context, &token_info) == P_SUCCESS);
|
||||
assert_eq((size_t)TOKEN_b, (size_t)token_info.token);
|
||||
/* The dropped space between `a` and `b` advances input_index too. */
|
||||
assert_eq(3u, p_input_index(context));
|
||||
p_context_delete(context);
|
||||
}
|
||||
|
||||
/* After a full successful parse, input_index has reached the end. */
|
||||
{
|
||||
char const * input = "ab";
|
||||
p_context_t * context = p_context_new((uint8_t const *)input, strlen(input));
|
||||
assert(p_parse_Start(context) == P_SUCCESS);
|
||||
assert_eq(2u, p_input_index(context));
|
||||
p_context_delete(context);
|
||||
}
|
||||
|
||||
/* When parse_inner completes via a follow token, the follow token is not
|
||||
* consumed, so input_index points at the start of the follow token. */
|
||||
{
|
||||
char const * input = "abb";
|
||||
p_context_t * context = p_context_new((uint8_t const *)input, strlen(input));
|
||||
p_token_t follow_tokens[] = { TOKEN_b };
|
||||
assert(p_parse_inner_Start(context, follow_tokens, 1u) == P_SUCCESS);
|
||||
assert_eq(2u, p_input_index(context));
|
||||
p_context_delete(context);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
51
spec/test_input_index.d
Normal file
51
spec/test_input_index.d
Normal file
@ -0,0 +1,51 @@
|
||||
import testparser;
|
||||
import std.stdio;
|
||||
import testutils;
|
||||
|
||||
int main()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
/* See test_input_index.c for details on the grammar and cases. */
|
||||
|
||||
/* Fresh context: input_index starts at 0. */
|
||||
{
|
||||
string input = "ab";
|
||||
p_context_t * context = p_context_new(input);
|
||||
assert(p_input_index(context) == 0);
|
||||
}
|
||||
|
||||
/* After each successful lex the byte offset advances past the token. */
|
||||
{
|
||||
string input = "a b";
|
||||
p_context_t * context = p_context_new(input);
|
||||
p_token_info_t token_info;
|
||||
assert(p_lex(context, &token_info) == P_SUCCESS);
|
||||
assert(token_info.token == TOKEN_a);
|
||||
assert(p_input_index(context) == 1);
|
||||
assert(p_lex(context, &token_info) == P_SUCCESS);
|
||||
assert(token_info.token == TOKEN_b);
|
||||
assert(p_input_index(context) == 3);
|
||||
}
|
||||
|
||||
/* After a full successful parse, input_index has reached the end. */
|
||||
{
|
||||
string input = "ab";
|
||||
p_context_t * context = p_context_new(input);
|
||||
assert(p_parse_Start(context) == P_SUCCESS);
|
||||
assert(p_input_index(context) == 2);
|
||||
}
|
||||
|
||||
/* When parse_inner completes via a follow token, the follow token is not
|
||||
* consumed, so input_index points at the start of the follow token. */
|
||||
{
|
||||
string input = "abb";
|
||||
p_context_t * context = p_context_new(input);
|
||||
p_token_t[] follow_tokens = [TOKEN_b];
|
||||
assert(p_parse_inner_Start(context, follow_tokens) == P_SUCCESS);
|
||||
assert(p_input_index(context) == 2);
|
||||
}
|
||||
}
|
||||
117
spec/test_macros.c
Normal file
117
spec/test_macros.c
Normal file
@ -0,0 +1,117 @@
|
||||
#include "testparser.h"
|
||||
#include "testutils.h"
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <stddef.h>
|
||||
|
||||
static p_context_t * context;
|
||||
size_t n_tokens;
|
||||
p_token_info_t token_infos[10];
|
||||
|
||||
/* Capture the macro body tokens (everything up to the closing '}') into
|
||||
* token_infos[]. Called from mylexfn() right after the definition's '{' has
|
||||
* been lexed, so the input cursor is positioned at the first body token. */
|
||||
static void capture_macro_body(void)
|
||||
{
|
||||
n_tokens = 0u;
|
||||
for (;;)
|
||||
{
|
||||
size_t result = p_lex(context, &token_infos[n_tokens]);
|
||||
assert_eq(result, P_SUCCESS);
|
||||
if (token_infos[n_tokens].token == TOKEN_rbrace)
|
||||
{
|
||||
break;
|
||||
}
|
||||
n_tokens++;
|
||||
assert(n_tokens < sizeof(token_infos) / sizeof(token_infos[0]));
|
||||
}
|
||||
}
|
||||
|
||||
size_t mylexfn(p_context_t * context, p_token_info_t * out_token_info)
|
||||
{
|
||||
static bool defining;
|
||||
static bool expanding;
|
||||
static size_t expand_i;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (expanding)
|
||||
{
|
||||
size_t ei = expand_i++;
|
||||
if (expand_i >= n_tokens)
|
||||
{
|
||||
expanding = false;
|
||||
}
|
||||
*out_token_info = token_infos[ei];
|
||||
return P_SUCCESS;
|
||||
}
|
||||
|
||||
size_t lex_result = p_lex(context, out_token_info);
|
||||
if (lex_result != P_SUCCESS)
|
||||
{
|
||||
return lex_result;
|
||||
}
|
||||
|
||||
switch (out_token_info->token)
|
||||
{
|
||||
case TOKEN_macro:
|
||||
/* Start of a macro definition: "macro macroname { ... }". */
|
||||
defining = true;
|
||||
break;
|
||||
case TOKEN_macroname:
|
||||
if (!defining)
|
||||
{
|
||||
/* Use of a macro: replay its captured body tokens instead of
|
||||
* returning the macroname to the parser. */
|
||||
expanding = true;
|
||||
expand_i = 0u;
|
||||
continue;
|
||||
}
|
||||
/* Definition name: pass through and keep waiting for '{'. */
|
||||
break;
|
||||
case TOKEN_lbrace:
|
||||
if (defining)
|
||||
{
|
||||
/* Consume and store the macro body now, before the parser gets
|
||||
* a chance to read its lookahead token (which would otherwise
|
||||
* swallow the first body token). */
|
||||
capture_macro_body();
|
||||
defining = false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
defining = false;
|
||||
break;
|
||||
}
|
||||
return lex_result;
|
||||
}
|
||||
}
|
||||
|
||||
size_t n_nums;
|
||||
int nums[10];
|
||||
|
||||
void record(int v)
|
||||
{
|
||||
nums[n_nums++] = v;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
char const * input =
|
||||
"macro @m { 23 + 200 }\n"
|
||||
"66 + 100\n"
|
||||
"@m\n"
|
||||
"33 + 55\n"
|
||||
"@m\n";
|
||||
context = p_context_new((uint8_t const *)input, strlen(input));
|
||||
assert(p_parse(context) == P_SUCCESS);
|
||||
p_context_delete(context);
|
||||
|
||||
assert_eq(n_nums, 4);
|
||||
assert_eq(nums[0], 166);
|
||||
assert_eq(nums[1], 223);
|
||||
assert_eq(nums[2], 88);
|
||||
assert_eq(nums[3], 223);
|
||||
|
||||
return 0;
|
||||
}
|
||||
116
spec/test_macros.d
Normal file
116
spec/test_macros.d
Normal file
@ -0,0 +1,116 @@
|
||||
import testparser;
|
||||
import testutils;
|
||||
|
||||
size_t n_tokens;
|
||||
p_token_info_t[10] token_infos;
|
||||
|
||||
// Capture the macro body tokens (everything up to the closing '}') into
|
||||
// token_infos[]. Called from mylexfn() right after the definition's '{' has
|
||||
// been lexed, so the input cursor is positioned at the first body token.
|
||||
void capture_macro_body(p_context_t * context)
|
||||
{
|
||||
n_tokens = 0u;
|
||||
for (;;)
|
||||
{
|
||||
size_t result = p_lex(context, &token_infos[n_tokens]);
|
||||
assert(result == P_SUCCESS);
|
||||
if (token_infos[n_tokens].token == TOKEN_rbrace)
|
||||
{
|
||||
break;
|
||||
}
|
||||
n_tokens++;
|
||||
assert(n_tokens < token_infos.length);
|
||||
}
|
||||
}
|
||||
|
||||
size_t mylexfn(p_context_t * context, p_token_info_t * out_token_info)
|
||||
{
|
||||
static bool defining;
|
||||
static bool expanding;
|
||||
static size_t expand_i;
|
||||
|
||||
for (;;)
|
||||
{
|
||||
if (expanding)
|
||||
{
|
||||
size_t ei = expand_i++;
|
||||
if (expand_i >= n_tokens)
|
||||
{
|
||||
expanding = false;
|
||||
}
|
||||
*out_token_info = token_infos[ei];
|
||||
return P_SUCCESS;
|
||||
}
|
||||
|
||||
size_t lex_result = p_lex(context, out_token_info);
|
||||
if (lex_result != P_SUCCESS)
|
||||
{
|
||||
return lex_result;
|
||||
}
|
||||
|
||||
switch (out_token_info.token)
|
||||
{
|
||||
case TOKEN_macro:
|
||||
// Start of a macro definition: "macro macroname { ... }".
|
||||
defining = true;
|
||||
break;
|
||||
case TOKEN_macroname:
|
||||
if (!defining)
|
||||
{
|
||||
// Use of a macro: replay its captured body tokens instead of
|
||||
// returning the macroname to the parser.
|
||||
expanding = true;
|
||||
expand_i = 0u;
|
||||
continue;
|
||||
}
|
||||
// Definition name: pass through and keep waiting for '{'.
|
||||
break;
|
||||
case TOKEN_lbrace:
|
||||
if (defining)
|
||||
{
|
||||
// Consume and store the macro body now, before the parser gets
|
||||
// a chance to read its lookahead token (which would otherwise
|
||||
// swallow the first body token).
|
||||
capture_macro_body(context);
|
||||
defining = false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
defining = false;
|
||||
break;
|
||||
}
|
||||
return lex_result;
|
||||
}
|
||||
}
|
||||
|
||||
size_t n_nums;
|
||||
int[10] nums;
|
||||
|
||||
void record(int v)
|
||||
{
|
||||
nums[n_nums++] = v;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
string input =
|
||||
"macro @m { 23 + 200 }\n" ~
|
||||
"66 + 100\n" ~
|
||||
"@m\n" ~
|
||||
"33 + 55\n" ~
|
||||
"@m\n";
|
||||
p_context_t * context = p_context_new(input);
|
||||
assert(p_parse(context) == P_SUCCESS);
|
||||
p_context_delete(context);
|
||||
|
||||
assert(n_nums == 4);
|
||||
assert(nums[0] == 166);
|
||||
assert(nums[1] == 223);
|
||||
assert(nums[2] == 88);
|
||||
assert(nums[3] == 223);
|
||||
}
|
||||
73
spec/test_parse_inner.c
Normal file
73
spec/test_parse_inner.c
Normal file
@ -0,0 +1,73 @@
|
||||
#include "testparser.h"
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "testutils.h"
|
||||
|
||||
int main()
|
||||
{
|
||||
/* Grammar (chain reduce):
|
||||
* Start -> Y << $$ = $1; >>
|
||||
* Y -> a << $$ = $1; >>
|
||||
* token a << $$ = 1; >>
|
||||
*
|
||||
* The reduce lookahead for both `Y -> a` and `Start -> Y` is only $EOF,
|
||||
* so `p_parse_Start("ab")` fails at token `b`. p_parse_inner_Start with
|
||||
* `b` as a follow token should succeed via the reduce-side retry chain
|
||||
* (Y then Start) followed by the shift-side retry hitting $EOF at the
|
||||
* final state. */
|
||||
|
||||
/* Standard parse succeeds on complete input. */
|
||||
char const * input = "a";
|
||||
p_context_t * context = p_context_new((uint8_t const *)input, strlen(input));
|
||||
assert(p_parse_Start(context) == P_SUCCESS);
|
||||
assert_eq(1u, (size_t)p_result_Start(context));
|
||||
p_context_delete(context);
|
||||
|
||||
/* Standard parse fails when there's an unexpected trailing token. */
|
||||
input = "ab";
|
||||
context = p_context_new((uint8_t const *)input, strlen(input));
|
||||
assert(p_parse_Start(context) == P_UNEXPECTED_TOKEN);
|
||||
p_context_delete(context);
|
||||
|
||||
/* parse_inner succeeds via a chain of reduce retries (Y, then Start),
|
||||
* followed by the shift-side retry hitting $EOF at the final state. */
|
||||
{
|
||||
input = "ab";
|
||||
context = p_context_new((uint8_t const *)input, strlen(input));
|
||||
p_token_t follow_tokens[] = { TOKEN_b };
|
||||
assert(p_parse_inner_Start(context, follow_tokens, 1u) == P_SUCCESS);
|
||||
assert_eq(1u, (size_t)p_result_Start(context));
|
||||
p_context_delete(context);
|
||||
}
|
||||
|
||||
/* parse_inner with an empty (NULL) follow-token vector behaves like a
|
||||
* standard parse. */
|
||||
input = "ab";
|
||||
context = p_context_new((uint8_t const *)input, strlen(input));
|
||||
assert(p_parse_inner_Start(context, NULL, 0u) == P_UNEXPECTED_TOKEN);
|
||||
p_context_delete(context);
|
||||
|
||||
/* parse_inner behaves like a standard parse when the input matches the
|
||||
* grammar fully. */
|
||||
input = "a";
|
||||
context = p_context_new((uint8_t const *)input, strlen(input));
|
||||
{
|
||||
p_token_t follow_tokens[] = { TOKEN_b };
|
||||
assert(p_parse_inner_Start(context, follow_tokens, 1u) == P_SUCCESS);
|
||||
assert_eq(1u, (size_t)p_result_Start(context));
|
||||
}
|
||||
p_context_delete(context);
|
||||
|
||||
/* parse_inner with a non-matching follow token still fails. The grammar
|
||||
* can't consume `b` and it isn't listed as a follow token, so the retries
|
||||
* do not fire. */
|
||||
{
|
||||
input = "ab";
|
||||
context = p_context_new((uint8_t const *)input, strlen(input));
|
||||
p_token_t follow_tokens[] = { TOKEN___EOF };
|
||||
assert(p_parse_inner_Start(context, follow_tokens, 1u) == P_UNEXPECTED_TOKEN);
|
||||
p_context_delete(context);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
51
spec/test_parse_inner.d
Normal file
51
spec/test_parse_inner.d
Normal file
@ -0,0 +1,51 @@
|
||||
import testparser;
|
||||
import std.stdio;
|
||||
import testutils;
|
||||
|
||||
int main()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
/* See test_parse_inner.c for details on the grammar and cases. */
|
||||
|
||||
/* Standard parse succeeds on complete input. */
|
||||
string input = "a";
|
||||
p_context_t * context = p_context_new(input);
|
||||
assert(p_parse_Start(context) == P_SUCCESS);
|
||||
assert(p_result_Start(context) == 1);
|
||||
|
||||
/* Standard parse fails when there's an unexpected trailing token. */
|
||||
input = "ab";
|
||||
context = p_context_new(input);
|
||||
assert(p_parse_Start(context) == P_UNEXPECTED_TOKEN);
|
||||
|
||||
/* parse_inner succeeds via a chain of reduce retries (Y, then Start),
|
||||
* followed by the shift-side retry hitting $EOF at the final state. */
|
||||
input = "ab";
|
||||
context = p_context_new(input);
|
||||
p_token_t[] follow_tokens_b = [TOKEN_b];
|
||||
assert(p_parse_inner_Start(context, follow_tokens_b) == P_SUCCESS);
|
||||
assert(p_result_Start(context) == 1);
|
||||
|
||||
/* parse_inner with a null follow-token slice behaves like a standard
|
||||
* parse. */
|
||||
input = "ab";
|
||||
context = p_context_new(input);
|
||||
assert(p_parse_inner_Start(context, null) == P_UNEXPECTED_TOKEN);
|
||||
|
||||
/* parse_inner behaves like a standard parse when the input matches the
|
||||
* grammar fully. */
|
||||
input = "a";
|
||||
context = p_context_new(input);
|
||||
assert(p_parse_inner_Start(context, follow_tokens_b) == P_SUCCESS);
|
||||
assert(p_result_Start(context) == 1);
|
||||
|
||||
/* parse_inner with a non-matching follow token still fails. */
|
||||
input = "ab";
|
||||
context = p_context_new(input);
|
||||
p_token_t[] follow_tokens_eof = [TOKEN___EOF];
|
||||
assert(p_parse_inner_Start(context, follow_tokens_eof) == P_UNEXPECTED_TOKEN);
|
||||
}
|
||||
78
spec/test_parse_inner_nested.c
Normal file
78
spec/test_parse_inner_nested.c
Normal file
@ -0,0 +1,78 @@
|
||||
#include "testparser.h"
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "testutils.h"
|
||||
|
||||
/* Grammar (integer evaluator; parentheses handled by the lex function):
|
||||
* ptype int;
|
||||
* lex_fn mylexfn;
|
||||
* token lparen /\(/; token rparen /\)/; token plus /\+/;
|
||||
* token num /\d+/ << ... atoi ... >>
|
||||
* Start -> Expr << $$ = $1; >>
|
||||
* Expr -> num << $$ = $1; >>
|
||||
* Expr -> Expr plus num << $$ = $1 + $3; >>
|
||||
*
|
||||
* The tokens lparen and rparen appear in no grammar rule. Instead, when the
|
||||
* lex function lexes a '(', it performs a nested parse (p_parse_inner_Start)
|
||||
* of the parenthesized sub-expression -- reentrantly, while the outer parse is
|
||||
* still suspended in this callback -- reads the computed value with
|
||||
* p_result_Start, consumes the ')' that p_parse_inner deliberately left in the
|
||||
* input, and hands a single synthesized num token carrying that value back to
|
||||
* the outer parse. Nested groups recurse this process to arbitrary depth. */
|
||||
|
||||
size_t mylexfn(p_context_t * context, p_token_info_t * out_token_info)
|
||||
{
|
||||
size_t result = p_lex(context, out_token_info);
|
||||
if (result != P_SUCCESS)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
if (out_token_info->token == TOKEN_lparen)
|
||||
{
|
||||
/* Nested parse of the parenthesized sub-expression, stopping at the
|
||||
* closing ')' follow token. This re-enters the parser while the outer
|
||||
* parse is suspended in this lex callback. */
|
||||
p_token_t follow_tokens[] = { TOKEN_rparen };
|
||||
size_t inner_result = p_parse_inner_Start(context, follow_tokens, 1u);
|
||||
if (inner_result != P_SUCCESS)
|
||||
{
|
||||
return inner_result;
|
||||
}
|
||||
int value = p_result_Start(context);
|
||||
/* p_parse_inner rewound the input so that ')' was not consumed; consume
|
||||
* it now. */
|
||||
p_token_info_t rparen_info;
|
||||
size_t rparen_result = p_lex(context, &rparen_info);
|
||||
assert(rparen_result == P_SUCCESS);
|
||||
assert(rparen_info.token == TOKEN_rparen);
|
||||
/* Replace the '(' token with a synthesized num carrying the nested
|
||||
* parse result. */
|
||||
out_token_info->token = TOKEN_num;
|
||||
out_token_info->pvalue.v_default = value;
|
||||
}
|
||||
return P_SUCCESS;
|
||||
}
|
||||
|
||||
static int eval(char const * input)
|
||||
{
|
||||
p_context_t * context = p_context_new((uint8_t const *)input, strlen(input));
|
||||
assert(p_parse(context) == P_SUCCESS);
|
||||
int value = p_result(context);
|
||||
p_context_delete(context);
|
||||
return value;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
/* No parentheses: plain outer parse. */
|
||||
assert_eq(5u, (size_t)eval("2 + 3"));
|
||||
/* A single group evaluated by the nested parse. */
|
||||
assert_eq(3u, (size_t)eval("(1 + 2)"));
|
||||
/* A group in the middle of an outer expression. */
|
||||
assert_eq(14u, (size_t)eval("2 + (3 + 4) + 5"));
|
||||
/* Nested groups: the nested parse re-enters itself. */
|
||||
assert_eq(37u, (size_t)eval("2 + (10 + (20 + 5))"));
|
||||
assert_eq(15u, (size_t)eval("(1 + 2) + (3 + (4 + 5))"));
|
||||
|
||||
return 0;
|
||||
}
|
||||
64
spec/test_parse_inner_nested.d
Normal file
64
spec/test_parse_inner_nested.d
Normal file
@ -0,0 +1,64 @@
|
||||
import testparser;
|
||||
import testutils;
|
||||
|
||||
/* Grammar: see test_parse_inner_nested.c. */
|
||||
|
||||
size_t mylexfn(p_context_t * context, p_token_info_t * out_token_info)
|
||||
{
|
||||
size_t result = p_lex(context, out_token_info);
|
||||
if (result != P_SUCCESS)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
if (out_token_info.token == TOKEN_lparen)
|
||||
{
|
||||
/* Nested parse of the parenthesized sub-expression, stopping at the
|
||||
* closing ')' follow token. This re-enters the parser while the outer
|
||||
* parse is suspended in this lex callback. */
|
||||
p_token_t[] follow_tokens = [TOKEN_rparen];
|
||||
size_t inner_result = p_parse_inner_Start(context, follow_tokens);
|
||||
if (inner_result != P_SUCCESS)
|
||||
{
|
||||
return inner_result;
|
||||
}
|
||||
int value = p_result_Start(context);
|
||||
/* p_parse_inner rewound the input so that ')' was not consumed; consume
|
||||
* it now. */
|
||||
p_token_info_t rparen_info;
|
||||
size_t rparen_result = p_lex(context, &rparen_info);
|
||||
assert(rparen_result == P_SUCCESS);
|
||||
assert(rparen_info.token == TOKEN_rparen);
|
||||
/* Replace the '(' token with a synthesized num carrying the nested
|
||||
* parse result. */
|
||||
out_token_info.token = TOKEN_num;
|
||||
out_token_info.pvalue.v_default = value;
|
||||
}
|
||||
return P_SUCCESS;
|
||||
}
|
||||
|
||||
int eval(string input)
|
||||
{
|
||||
p_context_t * context = p_context_new(input);
|
||||
assert(p_parse(context) == P_SUCCESS);
|
||||
int value = p_result(context);
|
||||
p_context_delete(context);
|
||||
return value;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
/* No parentheses: plain outer parse. */
|
||||
assert_eq(5, eval("2 + 3"));
|
||||
/* A single group evaluated by the nested parse. */
|
||||
assert_eq(3, eval("(1 + 2)"));
|
||||
/* A group in the middle of an outer expression. */
|
||||
assert_eq(14, eval("2 + (3 + 4) + 5"));
|
||||
/* Nested groups: the nested parse re-enters itself. */
|
||||
assert_eq(37, eval("2 + (10 + (20 + 5))"));
|
||||
assert_eq(15, eval("(1 + 2) + (3 + (4 + 5))"));
|
||||
}
|
||||
104
spec/test_parse_inner_nested_tree.c
Normal file
104
spec/test_parse_inner_nested_tree.c
Normal file
@ -0,0 +1,104 @@
|
||||
#include "testparser.h"
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "testutils.h"
|
||||
|
||||
/* Grammar (tree generation mode; parentheses handled by the lex function):
|
||||
* tree;
|
||||
* lex_fn mylexfn;
|
||||
* token lparen /\(/; token rparen /\)/; token plus /\+/; token num /\d+/;
|
||||
* Start -> Expr;
|
||||
* Expr -> num;
|
||||
* Expr -> Expr plus num;
|
||||
*
|
||||
* The same lexer-driven nested parse as test_parse_inner_nested, but in tree
|
||||
* generation mode. Each "( ... )" group is parsed by a reentrant
|
||||
* p_parse_inner_Start() call from the lex function; the resulting subtree is
|
||||
* discarded and a single synthesized num token is handed to the outer parse.
|
||||
* The synthesized token's position is set to span the whole group ('(' start
|
||||
* through ')' end), so this verifies that positions survive the nested-parse
|
||||
* boundary and land correctly in the outer tree. */
|
||||
|
||||
size_t mylexfn(p_context_t * context, p_token_info_t * out_token_info)
|
||||
{
|
||||
size_t result = p_lex(context, out_token_info);
|
||||
if (result != P_SUCCESS)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
if (out_token_info->token == TOKEN_lparen)
|
||||
{
|
||||
p_position_t start_position = out_token_info->position;
|
||||
/* Reentrant nested parse of the parenthesized sub-expression. */
|
||||
p_token_t follow_tokens[] = { TOKEN_rparen };
|
||||
size_t inner_result = p_parse_inner_Start(context, follow_tokens, 1u);
|
||||
if (inner_result != P_SUCCESS)
|
||||
{
|
||||
return inner_result;
|
||||
}
|
||||
Start * inner = p_result_Start(context);
|
||||
assert_not_null(inner);
|
||||
/* p_parse_inner rewound the input so that ')' was not consumed; consume
|
||||
* it now. */
|
||||
p_token_info_t rparen_info;
|
||||
size_t rparen_result = p_lex(context, &rparen_info);
|
||||
assert(rparen_result == P_SUCCESS);
|
||||
assert(rparen_info.token == TOKEN_rparen);
|
||||
/* The subtree covers the region strictly between the parentheses. */
|
||||
assert_eq((size_t)(start_position.col + 1u), (size_t)inner->position.col);
|
||||
assert_eq((size_t)(rparen_info.position.col - 1u), (size_t)inner->end_position.col);
|
||||
p_tree_delete_Start(inner);
|
||||
/* Synthesize a num token spanning the entire "( ... )" group. */
|
||||
out_token_info->token = TOKEN_num;
|
||||
out_token_info->position = start_position;
|
||||
out_token_info->end_position = rparen_info.end_position;
|
||||
}
|
||||
return P_SUCCESS;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
/* "(3 + 4) + (5 + 6)": two parenthesized groups, each collapsed by the
|
||||
* lexer into a single num token spanning its group. */
|
||||
char const * input = "(3 + 4) + (5 + 6)";
|
||||
p_context_t * context = p_context_new((uint8_t const *)input, strlen(input));
|
||||
assert(p_parse(context) == P_SUCCESS);
|
||||
|
||||
Start * tree = p_result(context);
|
||||
assert_not_null(tree);
|
||||
|
||||
/* Start -> Expr, where the top Expr is "Expr plus num". */
|
||||
Expr * top = tree->pExpr;
|
||||
assert_not_null(top);
|
||||
assert_not_null(top->pExpr);
|
||||
assert_not_null(top->pToken2);
|
||||
assert_not_null(top->pToken3);
|
||||
|
||||
/* The '+' joining the two groups is at column 9. */
|
||||
assert_eq(1u, (size_t)top->pToken2->position.row);
|
||||
assert_eq(9u, (size_t)top->pToken2->position.col);
|
||||
|
||||
/* Right operand: synthesized num for "(5 + 6)", spanning columns 11..17. */
|
||||
assert_eq(1u, (size_t)top->pToken3->position.row);
|
||||
assert_eq(11u, (size_t)top->pToken3->position.col);
|
||||
assert_eq(1u, (size_t)top->pToken3->end_position.row);
|
||||
assert_eq(17u, (size_t)top->pToken3->end_position.col);
|
||||
|
||||
/* Left operand: Expr -> num, the synthesized num for "(3 + 4)", spanning
|
||||
* columns 1..7. */
|
||||
Expr * left = top->pExpr;
|
||||
assert_not_null(left->pToken1);
|
||||
assert_eq(1u, (size_t)left->pToken1->position.row);
|
||||
assert_eq(1u, (size_t)left->pToken1->position.col);
|
||||
assert_eq(1u, (size_t)left->pToken1->end_position.row);
|
||||
assert_eq(7u, (size_t)left->pToken1->end_position.col);
|
||||
|
||||
/* The whole tree spans columns 1..17. */
|
||||
assert_eq(1u, (size_t)tree->position.col);
|
||||
assert_eq(17u, (size_t)tree->end_position.col);
|
||||
|
||||
p_tree_delete(tree);
|
||||
p_context_delete(context);
|
||||
|
||||
return 0;
|
||||
}
|
||||
91
spec/test_parse_inner_nested_tree.d
Normal file
91
spec/test_parse_inner_nested_tree.d
Normal file
@ -0,0 +1,91 @@
|
||||
import testparser;
|
||||
import testutils;
|
||||
|
||||
/* Grammar: see test_parse_inner_nested_tree.c. */
|
||||
|
||||
size_t mylexfn(p_context_t * context, p_token_info_t * out_token_info)
|
||||
{
|
||||
size_t result = p_lex(context, out_token_info);
|
||||
if (result != P_SUCCESS)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
if (out_token_info.token == TOKEN_lparen)
|
||||
{
|
||||
p_position_t start_position = out_token_info.position;
|
||||
/* Reentrant nested parse of the parenthesized sub-expression. */
|
||||
p_token_t[] follow_tokens = [TOKEN_rparen];
|
||||
size_t inner_result = p_parse_inner_Start(context, follow_tokens);
|
||||
if (inner_result != P_SUCCESS)
|
||||
{
|
||||
return inner_result;
|
||||
}
|
||||
Start * inner = p_result_Start(context);
|
||||
assert(inner !is null);
|
||||
/* p_parse_inner rewound the input so that ')' was not consumed; consume
|
||||
* it now. */
|
||||
p_token_info_t rparen_info;
|
||||
size_t rparen_result = p_lex(context, &rparen_info);
|
||||
assert(rparen_result == P_SUCCESS);
|
||||
assert(rparen_info.token == TOKEN_rparen);
|
||||
/* The subtree covers the region strictly between the parentheses. */
|
||||
assert_eq(start_position.col + 1u, inner.position.col);
|
||||
assert_eq(rparen_info.position.col - 1u, inner.end_position.col);
|
||||
p_tree_delete_Start(inner);
|
||||
/* Synthesize a num token spanning the entire "( ... )" group. */
|
||||
out_token_info.token = TOKEN_num;
|
||||
out_token_info.position = start_position;
|
||||
out_token_info.end_position = rparen_info.end_position;
|
||||
}
|
||||
return P_SUCCESS;
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
/* "(3 + 4) + (5 + 6)": two parenthesized groups, each collapsed by the
|
||||
* lexer into a single num token spanning its group. */
|
||||
string input = "(3 + 4) + (5 + 6)";
|
||||
p_context_t * context = p_context_new(input);
|
||||
assert(p_parse(context) == P_SUCCESS);
|
||||
|
||||
Start * tree = p_result(context);
|
||||
assert(tree !is null);
|
||||
|
||||
/* Start -> Expr, where the top Expr is "Expr plus num". */
|
||||
Expr * top = tree.pExpr;
|
||||
assert(top !is null);
|
||||
assert(top.pExpr !is null);
|
||||
assert(top.pToken2 !is null);
|
||||
assert(top.pToken3 !is null);
|
||||
|
||||
/* The '+' joining the two groups is at column 9. */
|
||||
assert_eq(1u, top.pToken2.position.row);
|
||||
assert_eq(9u, top.pToken2.position.col);
|
||||
|
||||
/* Right operand: synthesized num for "(5 + 6)", spanning columns 11..17. */
|
||||
assert_eq(1u, top.pToken3.position.row);
|
||||
assert_eq(11u, top.pToken3.position.col);
|
||||
assert_eq(1u, top.pToken3.end_position.row);
|
||||
assert_eq(17u, top.pToken3.end_position.col);
|
||||
|
||||
/* Left operand: Expr -> num, the synthesized num for "(3 + 4)", spanning
|
||||
* columns 1..7. */
|
||||
Expr * left = top.pExpr;
|
||||
assert(left.pToken1 !is null);
|
||||
assert_eq(1u, left.pToken1.position.row);
|
||||
assert_eq(1u, left.pToken1.position.col);
|
||||
assert_eq(1u, left.pToken1.end_position.row);
|
||||
assert_eq(7u, left.pToken1.end_position.col);
|
||||
|
||||
/* The whole tree spans columns 1..17. */
|
||||
assert_eq(1u, tree.position.col);
|
||||
assert_eq(17u, tree.end_position.col);
|
||||
|
||||
p_tree_delete(tree);
|
||||
p_context_delete(context);
|
||||
}
|
||||
77
spec/test_parse_inner_recursive.c
Normal file
77
spec/test_parse_inner_recursive.c
Normal file
@ -0,0 +1,77 @@
|
||||
#include "testparser.h"
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "testutils.h"
|
||||
|
||||
int main()
|
||||
{
|
||||
/* Grammar (recursive):
|
||||
* Start -> a Start b << $$ = $2; >>
|
||||
* Start -> c << $$ = $1; >>
|
||||
* token a << $$ = 1; >>
|
||||
* token b << $$ = 2; >>
|
||||
* token c << $$ = 3; >>
|
||||
*
|
||||
* Here `Start` can appear in the middle of another `Start` rule, so the
|
||||
* inner-parse follow-token success must be blocked whenever an unfinished
|
||||
* outer `Start -> a Start b` remains on the parse stack (i.e. the parse
|
||||
* stack contains more than just the initial state and the reduced start
|
||||
* rule set). */
|
||||
|
||||
/* Standard parse of `c` succeeds. */
|
||||
char const * input = "c";
|
||||
p_context_t * context = p_context_new((uint8_t const *)input, strlen(input));
|
||||
assert(p_parse_Start(context) == P_SUCCESS);
|
||||
assert_eq(3u, (size_t)p_result_Start(context));
|
||||
p_context_delete(context);
|
||||
|
||||
/* Standard parse of `acb` succeeds (full outer rule). */
|
||||
input = "acb";
|
||||
context = p_context_new((uint8_t const *)input, strlen(input));
|
||||
assert(p_parse_Start(context) == P_SUCCESS);
|
||||
assert_eq(3u, (size_t)p_result_Start(context));
|
||||
p_context_delete(context);
|
||||
|
||||
/* Standard parse of `ac` fails (`b` missing). */
|
||||
input = "ac";
|
||||
context = p_context_new((uint8_t const *)input, strlen(input));
|
||||
assert(p_parse_Start(context) == P_UNEXPECTED_TOKEN);
|
||||
p_context_delete(context);
|
||||
|
||||
/* parse_inner with `ac` and follow token `b` also fails: even though the
|
||||
* inner `Start -> c` reduces and `Start` is shifted, the outer
|
||||
* `Start -> a Start . b` is still on the stack (stack length > 2), so the
|
||||
* "reduced start rule is the only thing on the parse stack" invariant
|
||||
* blocks the shift-side follow-token success. */
|
||||
{
|
||||
input = "ac";
|
||||
context = p_context_new((uint8_t const *)input, strlen(input));
|
||||
p_token_t follow_tokens[] = { TOKEN_b, TOKEN___EOF };
|
||||
assert(p_parse_inner_Start(context, follow_tokens, 2u) == P_UNEXPECTED_TOKEN);
|
||||
p_context_delete(context);
|
||||
}
|
||||
|
||||
/* parse_inner with `acb` (complete outer rule) succeeds via the standard
|
||||
* path. */
|
||||
{
|
||||
input = "acb";
|
||||
context = p_context_new((uint8_t const *)input, strlen(input));
|
||||
p_token_t follow_tokens[] = { TOKEN_b };
|
||||
assert(p_parse_inner_Start(context, follow_tokens, 1u) == P_SUCCESS);
|
||||
assert_eq(3u, (size_t)p_result_Start(context));
|
||||
p_context_delete(context);
|
||||
}
|
||||
|
||||
/* parse_inner with just `c` succeeds via the standard path even when a
|
||||
* follow-token vector is supplied. */
|
||||
{
|
||||
input = "c";
|
||||
context = p_context_new((uint8_t const *)input, strlen(input));
|
||||
p_token_t follow_tokens[] = { TOKEN_b };
|
||||
assert(p_parse_inner_Start(context, follow_tokens, 1u) == P_SUCCESS);
|
||||
assert_eq(3u, (size_t)p_result_Start(context));
|
||||
p_context_delete(context);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
49
spec/test_parse_inner_recursive.d
Normal file
49
spec/test_parse_inner_recursive.d
Normal file
@ -0,0 +1,49 @@
|
||||
import testparser;
|
||||
import std.stdio;
|
||||
import testutils;
|
||||
|
||||
int main()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
/* See test_parse_inner_recursive.c for details on the grammar. */
|
||||
|
||||
/* Standard parse of `c` succeeds. */
|
||||
string input = "c";
|
||||
p_context_t * context = p_context_new(input);
|
||||
assert(p_parse_Start(context) == P_SUCCESS);
|
||||
assert(p_result_Start(context) == 3);
|
||||
|
||||
/* Standard parse of `acb` succeeds. */
|
||||
input = "acb";
|
||||
context = p_context_new(input);
|
||||
assert(p_parse_Start(context) == P_SUCCESS);
|
||||
assert(p_result_Start(context) == 3);
|
||||
|
||||
/* Standard parse of `ac` fails. */
|
||||
input = "ac";
|
||||
context = p_context_new(input);
|
||||
assert(p_parse_Start(context) == P_UNEXPECTED_TOKEN);
|
||||
|
||||
/* parse_inner with `ac` fails: outer rule still on the stack. */
|
||||
input = "ac";
|
||||
context = p_context_new(input);
|
||||
p_token_t[] follow_tokens_bothway = [TOKEN_b, TOKEN___EOF];
|
||||
assert(p_parse_inner_Start(context, follow_tokens_bothway) == P_UNEXPECTED_TOKEN);
|
||||
|
||||
/* parse_inner with `acb` succeeds via the standard path. */
|
||||
input = "acb";
|
||||
context = p_context_new(input);
|
||||
p_token_t[] follow_tokens_b = [TOKEN_b];
|
||||
assert(p_parse_inner_Start(context, follow_tokens_b) == P_SUCCESS);
|
||||
assert(p_result_Start(context) == 3);
|
||||
|
||||
/* parse_inner with just `c` succeeds via the standard path. */
|
||||
input = "c";
|
||||
context = p_context_new(input);
|
||||
assert(p_parse_inner_Start(context, follow_tokens_b) == P_SUCCESS);
|
||||
assert(p_result_Start(context) == 3);
|
||||
}
|
||||
104
spec/test_parse_inner_shared.c
Normal file
104
spec/test_parse_inner_shared.c
Normal file
@ -0,0 +1,104 @@
|
||||
#include "testparser.h"
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "testutils.h"
|
||||
|
||||
int main()
|
||||
{
|
||||
/* Grammar:
|
||||
* start Start;
|
||||
* start R1;
|
||||
* Start -> R1 a;
|
||||
* Start -> R2 b;
|
||||
* R1 -> a b << $$ = 11; >>
|
||||
* R2 -> a b << $$ = 22; >>
|
||||
* token a; token b;
|
||||
*
|
||||
* The rules `R1 -> a b` and `R2 -> a b` produce identical input. Within
|
||||
* parse_Start, the generated parser differentiates the reduce by
|
||||
* lookahead: `a` selects R1 (because `Start -> R1 a`) and `b` selects R2
|
||||
* (because `Start -> R2 b`). Within parse_R1, the reduce is unconditional
|
||||
* on any lookahead. This test exercises p_parse_inner_R1() to confirm
|
||||
* that reductions to R1 succeed even when the incoming follow token is
|
||||
* not the natural lookahead used by parse_Start's disambiguation. */
|
||||
|
||||
/* Sanity-check that parse_Start resolves R1 vs R2 via lookahead in the
|
||||
* shared "a b" state. */
|
||||
char const * input = "aba";
|
||||
p_context_t * context = p_context_new((uint8_t const *)input, strlen(input));
|
||||
assert(p_parse_Start(context) == P_SUCCESS);
|
||||
p_context_delete(context);
|
||||
|
||||
input = "abb";
|
||||
context = p_context_new((uint8_t const *)input, strlen(input));
|
||||
assert(p_parse_Start(context) == P_SUCCESS);
|
||||
p_context_delete(context);
|
||||
|
||||
/* Standard parse of R1 succeeds on "ab". */
|
||||
input = "ab";
|
||||
context = p_context_new((uint8_t const *)input, strlen(input));
|
||||
assert(p_parse_R1(context) == P_SUCCESS);
|
||||
assert_eq(11u, (size_t)p_result_R1(context));
|
||||
p_context_delete(context);
|
||||
|
||||
/* Standard parse of R1 fails on "abb" (unexpected trailing token). */
|
||||
input = "abb";
|
||||
context = p_context_new((uint8_t const *)input, strlen(input));
|
||||
assert(p_parse_R1(context) == P_UNEXPECTED_TOKEN);
|
||||
p_context_delete(context);
|
||||
|
||||
/* parse_inner_R1("abb", [b]) succeeds: even though `b` is the lookahead
|
||||
* that parse_Start uses to select R2 over R1 in the ambiguous state, from
|
||||
* R1's start state the reduce to R1 is unconditional, and the follow-
|
||||
* token shift retry at the R1-accepting state completes the parse.
|
||||
*
|
||||
* The follow token that completed the parse must not be consumed from
|
||||
* the input: p_position() should point to the follow token, and a
|
||||
* subsequent p_lex() should return it. */
|
||||
{
|
||||
input = "abb";
|
||||
context = p_context_new((uint8_t const *)input, strlen(input));
|
||||
p_token_t follow_tokens[] = { TOKEN_b };
|
||||
assert(p_parse_inner_R1(context, follow_tokens, 1u) == P_SUCCESS);
|
||||
assert_eq(11u, (size_t)p_result_R1(context));
|
||||
/* Follow token `b` is at column 3 (1-based). */
|
||||
p_position_t pos = p_position(context);
|
||||
assert_eq(1u, (size_t)pos.row);
|
||||
assert_eq(3u, (size_t)pos.col);
|
||||
p_token_info_t token_info;
|
||||
assert(p_lex(context, &token_info) == P_SUCCESS);
|
||||
assert_eq((size_t)TOKEN_b, (size_t)token_info.token);
|
||||
assert_eq(1u, (size_t)token_info.position.row);
|
||||
assert_eq(3u, (size_t)token_info.position.col);
|
||||
p_context_delete(context);
|
||||
}
|
||||
|
||||
/* parse_inner_R1("aba", [a]) also succeeds: `a` is the follow token
|
||||
* parse_Start uses to select R1, and it works here as a follow token
|
||||
* too. */
|
||||
{
|
||||
input = "aba";
|
||||
context = p_context_new((uint8_t const *)input, strlen(input));
|
||||
p_token_t follow_tokens[] = { TOKEN_a };
|
||||
assert(p_parse_inner_R1(context, follow_tokens, 1u) == P_SUCCESS);
|
||||
assert_eq(11u, (size_t)p_result_R1(context));
|
||||
/* Follow token `a` is at column 3 (1-based) and remains in the
|
||||
* input. */
|
||||
p_position_t pos = p_position(context);
|
||||
assert_eq(1u, (size_t)pos.row);
|
||||
assert_eq(3u, (size_t)pos.col);
|
||||
p_token_info_t token_info;
|
||||
assert(p_lex(context, &token_info) == P_SUCCESS);
|
||||
assert_eq((size_t)TOKEN_a, (size_t)token_info.token);
|
||||
p_context_delete(context);
|
||||
}
|
||||
|
||||
/* parse_inner_R1("ab", NULL) behaves like p_parse_R1("ab"). */
|
||||
input = "ab";
|
||||
context = p_context_new((uint8_t const *)input, strlen(input));
|
||||
assert(p_parse_inner_R1(context, NULL, 0u) == P_SUCCESS);
|
||||
assert_eq(11u, (size_t)p_result_R1(context));
|
||||
p_context_delete(context);
|
||||
|
||||
return 0;
|
||||
}
|
||||
72
spec/test_parse_inner_shared.d
Normal file
72
spec/test_parse_inner_shared.d
Normal file
@ -0,0 +1,72 @@
|
||||
import testparser;
|
||||
import std.stdio;
|
||||
import testutils;
|
||||
|
||||
int main()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
/* See test_parse_inner_shared.c for details on the grammar. */
|
||||
|
||||
/* Sanity-check that parse_Start resolves R1 vs R2 via lookahead. */
|
||||
string input = "aba";
|
||||
p_context_t * context = p_context_new(input);
|
||||
assert(p_parse_Start(context) == P_SUCCESS);
|
||||
|
||||
input = "abb";
|
||||
context = p_context_new(input);
|
||||
assert(p_parse_Start(context) == P_SUCCESS);
|
||||
|
||||
/* Standard parse of R1 succeeds on "ab". */
|
||||
input = "ab";
|
||||
context = p_context_new(input);
|
||||
assert(p_parse_R1(context) == P_SUCCESS);
|
||||
assert(p_result_R1(context) == 11);
|
||||
|
||||
/* Standard parse of R1 fails on "abb". */
|
||||
input = "abb";
|
||||
context = p_context_new(input);
|
||||
assert(p_parse_R1(context) == P_UNEXPECTED_TOKEN);
|
||||
|
||||
/* parse_inner_R1("abb", [b]) succeeds: `b` is the lookahead that
|
||||
* parse_Start would use to select R2 over R1, but from R1's own start
|
||||
* state R1 reduces unconditionally, and the follow-token shift retry at
|
||||
* the R1-accepting state completes the parse.
|
||||
*
|
||||
* The follow token that completed the parse must not be consumed: a
|
||||
* subsequent p_lex() should return it. */
|
||||
input = "abb";
|
||||
context = p_context_new(input);
|
||||
p_token_t[] follow_tokens_b = [TOKEN_b];
|
||||
assert(p_parse_inner_R1(context, follow_tokens_b) == P_SUCCESS);
|
||||
assert(p_result_R1(context) == 11);
|
||||
p_position_t pos = p_position(context);
|
||||
assert(pos.row == 1);
|
||||
assert(pos.col == 3);
|
||||
p_token_info_t token_info;
|
||||
assert(p_lex(context, &token_info) == P_SUCCESS);
|
||||
assert(token_info.token == TOKEN_b);
|
||||
assert(token_info.position.row == 1);
|
||||
assert(token_info.position.col == 3);
|
||||
|
||||
/* parse_inner_R1("aba", [a]) also succeeds. */
|
||||
input = "aba";
|
||||
context = p_context_new(input);
|
||||
p_token_t[] follow_tokens_a = [TOKEN_a];
|
||||
assert(p_parse_inner_R1(context, follow_tokens_a) == P_SUCCESS);
|
||||
assert(p_result_R1(context) == 11);
|
||||
pos = p_position(context);
|
||||
assert(pos.row == 1);
|
||||
assert(pos.col == 3);
|
||||
assert(p_lex(context, &token_info) == P_SUCCESS);
|
||||
assert(token_info.token == TOKEN_a);
|
||||
|
||||
/* parse_inner_R1("ab", null) behaves like p_parse_R1("ab"). */
|
||||
input = "ab";
|
||||
context = p_context_new(input);
|
||||
assert(p_parse_inner_R1(context, null) == P_SUCCESS);
|
||||
assert(p_result_R1(context) == 11);
|
||||
}
|
||||
89
spec/test_parse_inner_tree.c
Normal file
89
spec/test_parse_inner_tree.c
Normal file
@ -0,0 +1,89 @@
|
||||
#include "testparser.h"
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "testutils.h"
|
||||
|
||||
int main()
|
||||
{
|
||||
/* Grammar (tree generation mode, shared reduce state):
|
||||
* tree;
|
||||
* token a; token b;
|
||||
* start Start;
|
||||
* start R1;
|
||||
* Start -> R1 a;
|
||||
* Start -> R2 b;
|
||||
* R1 -> a b;
|
||||
* R2 -> a b;
|
||||
*
|
||||
* Exercises p_parse_inner_R1() with a non-EOF follow token in tree
|
||||
* generation mode. Verifies:
|
||||
* * The reduced tree for R1 is well-formed after a follow-token
|
||||
* completion.
|
||||
* * The follow token is not consumed and remains available for a
|
||||
* subsequent p_lex() call.
|
||||
* * p_tree_delete_R1() cleans up the returned tree without leaks
|
||||
* (verified in CI via valgrind). */
|
||||
|
||||
/* Baseline: p_parse_R1 works on "ab" and the returned tree is
|
||||
* well-formed. */
|
||||
{
|
||||
char const * input = "ab";
|
||||
p_context_t * context = p_context_new((uint8_t const *)input, strlen(input));
|
||||
assert(p_parse_R1(context) == P_SUCCESS);
|
||||
R1 * tree = p_result_R1(context);
|
||||
assert_not_null(tree);
|
||||
assert_not_null(tree->pToken1);
|
||||
assert_eq((size_t)TOKEN_a, (size_t)tree->pToken1->token);
|
||||
assert_not_null(tree->pToken2);
|
||||
assert_eq((size_t)TOKEN_b, (size_t)tree->pToken2->token);
|
||||
p_tree_delete_R1(tree);
|
||||
p_context_delete(context);
|
||||
}
|
||||
|
||||
/* Primary case: p_parse_inner_R1 with a non-EOF follow token completes
|
||||
* the parse, returns a well-formed tree, and leaves the follow token
|
||||
* unconsumed. */
|
||||
{
|
||||
char const * input = "abb";
|
||||
p_context_t * context = p_context_new((uint8_t const *)input, strlen(input));
|
||||
p_token_t follow_tokens[] = { TOKEN_b };
|
||||
assert(p_parse_inner_R1(context, follow_tokens, 1u) == P_SUCCESS);
|
||||
|
||||
/* Tree is well-formed. */
|
||||
R1 * tree = p_result_R1(context);
|
||||
assert_not_null(tree);
|
||||
assert_not_null(tree->pToken1);
|
||||
assert_eq((size_t)TOKEN_a, (size_t)tree->pToken1->token);
|
||||
assert_eq(1u, (size_t)tree->pToken1->position.row);
|
||||
assert_eq(1u, (size_t)tree->pToken1->position.col);
|
||||
assert_not_null(tree->pToken2);
|
||||
assert_eq((size_t)TOKEN_b, (size_t)tree->pToken2->token);
|
||||
assert_eq(1u, (size_t)tree->pToken2->position.row);
|
||||
assert_eq(2u, (size_t)tree->pToken2->position.col);
|
||||
|
||||
/* The R1 tree covers positions 1..2 — the third `b` at column 3 is
|
||||
* the follow token and is not part of the tree. */
|
||||
assert_eq(1u, (size_t)tree->position.row);
|
||||
assert_eq(1u, (size_t)tree->position.col);
|
||||
assert_eq(1u, (size_t)tree->end_position.row);
|
||||
assert_eq(2u, (size_t)tree->end_position.col);
|
||||
|
||||
/* Follow token remains in the input. */
|
||||
p_position_t pos = p_position(context);
|
||||
assert_eq(1u, (size_t)pos.row);
|
||||
assert_eq(3u, (size_t)pos.col);
|
||||
p_token_info_t token_info;
|
||||
assert(p_lex(context, &token_info) == P_SUCCESS);
|
||||
assert_eq((size_t)TOKEN_b, (size_t)token_info.token);
|
||||
assert_eq(1u, (size_t)token_info.position.row);
|
||||
assert_eq(3u, (size_t)token_info.position.col);
|
||||
|
||||
/* p_tree_delete_R1 must free every node reachable from the tree
|
||||
* without leaking anything. valgrind (invoked by the spec runner on
|
||||
* Linux) will detect any missed frees. */
|
||||
p_tree_delete_R1(tree);
|
||||
p_context_delete(context);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
68
spec/test_parse_inner_tree.d
Normal file
68
spec/test_parse_inner_tree.d
Normal file
@ -0,0 +1,68 @@
|
||||
import testparser;
|
||||
import std.stdio;
|
||||
import testutils;
|
||||
|
||||
int main()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
/* See test_parse_inner_tree.c for details on the grammar and cases. */
|
||||
|
||||
/* Baseline: p_parse_R1 works on "ab". */
|
||||
{
|
||||
string input = "ab";
|
||||
p_context_t * context = p_context_new(input);
|
||||
assert(p_parse_R1(context) == P_SUCCESS);
|
||||
R1 * tree = p_result_R1(context);
|
||||
assert(tree !is null);
|
||||
assert(tree.pToken1 !is null);
|
||||
assert(tree.pToken1.token == TOKEN_a);
|
||||
assert(tree.pToken2 !is null);
|
||||
assert(tree.pToken2.token == TOKEN_b);
|
||||
p_tree_delete_R1(tree);
|
||||
}
|
||||
|
||||
/* Primary case: p_parse_inner_R1 with a non-EOF follow token completes
|
||||
* the parse, returns a well-formed tree, and leaves the follow token
|
||||
* unconsumed. */
|
||||
{
|
||||
string input = "abb";
|
||||
p_context_t * context = p_context_new(input);
|
||||
p_token_t[] follow_tokens = [TOKEN_b];
|
||||
assert(p_parse_inner_R1(context, follow_tokens) == P_SUCCESS);
|
||||
|
||||
/* Tree is well-formed. */
|
||||
R1 * tree = p_result_R1(context);
|
||||
assert(tree !is null);
|
||||
assert(tree.pToken1 !is null);
|
||||
assert(tree.pToken1.token == TOKEN_a);
|
||||
assert(tree.pToken1.position.row == 1);
|
||||
assert(tree.pToken1.position.col == 1);
|
||||
assert(tree.pToken2 !is null);
|
||||
assert(tree.pToken2.token == TOKEN_b);
|
||||
assert(tree.pToken2.position.row == 1);
|
||||
assert(tree.pToken2.position.col == 2);
|
||||
|
||||
/* The R1 tree covers positions 1..2. The third `b` at column 3 is
|
||||
* the follow token and is not part of the tree. */
|
||||
assert(tree.position.row == 1);
|
||||
assert(tree.position.col == 1);
|
||||
assert(tree.end_position.row == 1);
|
||||
assert(tree.end_position.col == 2);
|
||||
|
||||
/* Follow token remains in the input. */
|
||||
p_position_t pos = p_position(context);
|
||||
assert(pos.row == 1);
|
||||
assert(pos.col == 3);
|
||||
p_token_info_t token_info;
|
||||
assert(p_lex(context, &token_info) == P_SUCCESS);
|
||||
assert(token_info.token == TOKEN_b);
|
||||
assert(token_info.position.row == 1);
|
||||
assert(token_info.position.col == 3);
|
||||
|
||||
p_tree_delete_R1(tree);
|
||||
}
|
||||
}
|
||||
81
spec/test_set_position.c
Normal file
81
spec/test_set_position.c
Normal file
@ -0,0 +1,81 @@
|
||||
#include "testparser.h"
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "testutils.h"
|
||||
|
||||
int main()
|
||||
{
|
||||
/* Grammar (simple):
|
||||
* token a; token b;
|
||||
* Start -> a b;
|
||||
*
|
||||
* Verifies that p_set_position() overrides the default (1, 1) starting
|
||||
* position so that lexed tokens and error positions are reported
|
||||
* relative to the caller-supplied position. */
|
||||
|
||||
/* Baseline: without p_set_position(), positions start at (1, 1). */
|
||||
{
|
||||
char const * input = "ab";
|
||||
p_context_t * context = p_context_new((uint8_t const *)input, strlen(input));
|
||||
p_position_t pos = p_position(context);
|
||||
assert_eq(1u, (size_t)pos.row);
|
||||
assert_eq(1u, (size_t)pos.col);
|
||||
p_token_info_t token_info;
|
||||
assert(p_lex(context, &token_info) == P_SUCCESS);
|
||||
assert_eq((size_t)TOKEN_a, (size_t)token_info.token);
|
||||
assert_eq(1u, (size_t)token_info.position.row);
|
||||
assert_eq(1u, (size_t)token_info.position.col);
|
||||
p_context_delete(context);
|
||||
}
|
||||
|
||||
/* p_set_position() overrides the initial position; subsequent lex calls
|
||||
* report token positions relative to the set position. */
|
||||
{
|
||||
char const * input = "ab";
|
||||
p_context_t * context = p_context_new((uint8_t const *)input, strlen(input));
|
||||
p_position_t initial = {5u, 20u};
|
||||
p_set_position(context, initial);
|
||||
p_position_t pos = p_position(context);
|
||||
assert_eq(5u, (size_t)pos.row);
|
||||
assert_eq(20u, (size_t)pos.col);
|
||||
p_token_info_t token_info;
|
||||
assert(p_lex(context, &token_info) == P_SUCCESS);
|
||||
assert_eq((size_t)TOKEN_a, (size_t)token_info.token);
|
||||
assert_eq(5u, (size_t)token_info.position.row);
|
||||
assert_eq(20u, (size_t)token_info.position.col);
|
||||
assert(p_lex(context, &token_info) == P_SUCCESS);
|
||||
assert_eq((size_t)TOKEN_b, (size_t)token_info.token);
|
||||
assert_eq(5u, (size_t)token_info.position.row);
|
||||
assert_eq(21u, (size_t)token_info.position.col);
|
||||
p_context_delete(context);
|
||||
}
|
||||
|
||||
/* p_set_position() before a full parse: successful parse still works and
|
||||
* text_position tracking is relative to the set starting point. */
|
||||
{
|
||||
char const * input = "ab";
|
||||
p_context_t * context = p_context_new((uint8_t const *)input, strlen(input));
|
||||
p_position_t initial = {3u, 7u};
|
||||
p_set_position(context, initial);
|
||||
assert(p_parse_Start(context) == P_SUCCESS);
|
||||
p_context_delete(context);
|
||||
}
|
||||
|
||||
/* p_set_position() before a parse that fails: the reported error
|
||||
* position is relative to the set starting point. */
|
||||
{
|
||||
char const * input = "aa";
|
||||
p_context_t * context = p_context_new((uint8_t const *)input, strlen(input));
|
||||
p_position_t initial = {10u, 2u};
|
||||
p_set_position(context, initial);
|
||||
assert(p_parse_Start(context) == P_UNEXPECTED_TOKEN);
|
||||
p_position_t err_pos = p_position(context);
|
||||
/* Error is at the second `a`, which is one column past the initial
|
||||
* column. */
|
||||
assert_eq(10u, (size_t)err_pos.row);
|
||||
assert_eq(3u, (size_t)err_pos.col);
|
||||
p_context_delete(context);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
66
spec/test_set_position.d
Normal file
66
spec/test_set_position.d
Normal file
@ -0,0 +1,66 @@
|
||||
import testparser;
|
||||
import std.stdio;
|
||||
import testutils;
|
||||
|
||||
int main()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
/* See test_set_position.c for details. */
|
||||
|
||||
/* Baseline: without p_set_position(), positions start at (1, 1). */
|
||||
{
|
||||
string input = "ab";
|
||||
p_context_t * context = p_context_new(input);
|
||||
p_position_t pos = p_position(context);
|
||||
assert(pos.row == 1);
|
||||
assert(pos.col == 1);
|
||||
p_token_info_t token_info;
|
||||
assert(p_lex(context, &token_info) == P_SUCCESS);
|
||||
assert(token_info.token == TOKEN_a);
|
||||
assert(token_info.position.row == 1);
|
||||
assert(token_info.position.col == 1);
|
||||
}
|
||||
|
||||
/* p_set_position() overrides the initial position. */
|
||||
{
|
||||
string input = "ab";
|
||||
p_context_t * context = p_context_new(input);
|
||||
p_set_position(context, p_position_t(5u, 20u));
|
||||
p_position_t pos = p_position(context);
|
||||
assert(pos.row == 5);
|
||||
assert(pos.col == 20);
|
||||
p_token_info_t token_info;
|
||||
assert(p_lex(context, &token_info) == P_SUCCESS);
|
||||
assert(token_info.token == TOKEN_a);
|
||||
assert(token_info.position.row == 5);
|
||||
assert(token_info.position.col == 20);
|
||||
assert(p_lex(context, &token_info) == P_SUCCESS);
|
||||
assert(token_info.token == TOKEN_b);
|
||||
assert(token_info.position.row == 5);
|
||||
assert(token_info.position.col == 21);
|
||||
}
|
||||
|
||||
/* p_set_position() before a full parse still parses successfully. */
|
||||
{
|
||||
string input = "ab";
|
||||
p_context_t * context = p_context_new(input);
|
||||
p_set_position(context, p_position_t(3u, 7u));
|
||||
assert(p_parse_Start(context) == P_SUCCESS);
|
||||
}
|
||||
|
||||
/* p_set_position() before a parse that fails: error position is
|
||||
* relative to the set starting point. */
|
||||
{
|
||||
string input = "aa";
|
||||
p_context_t * context = p_context_new(input);
|
||||
p_set_position(context, p_position_t(10u, 2u));
|
||||
assert(p_parse_Start(context) == P_UNEXPECTED_TOKEN);
|
||||
p_position_t err_pos = p_position(context);
|
||||
assert(err_pos.row == 10);
|
||||
assert(err_pos.col == 3);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user