Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6c847c05b1 | ||
|
|
a5800575c8 | ||
|
|
24af3590d1 | ||
|
|
92c76b74c8 | ||
|
|
a032ac027c |
No files matched your search
+1
-1
@@ -1,6 +1,6 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2010-2023 Josh Holtrop
|
||||
Copyright (c) 2010-2024 Josh Holtrop
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
||||
+26
-7
@@ -21,6 +21,7 @@
|
||||
#define P_UNEXPECTED_TOKEN 3u
|
||||
#define P_DROP 4u
|
||||
#define P_EOF 5u
|
||||
#define P_USER_TERMINATED 6u
|
||||
<% end %>
|
||||
|
||||
/* An invalid ID value. */
|
||||
@@ -390,7 +391,6 @@ static size_t find_longest_match(<%= @grammar.prefix %>context_t * context,
|
||||
/* Valid EOF return. */
|
||||
return P_EOF;
|
||||
}
|
||||
break;
|
||||
|
||||
case P_DECODE_ERROR:
|
||||
/* If we see a decode error, we may be partially in the middle of
|
||||
@@ -738,12 +738,13 @@ static void state_values_stack_free(state_values_stack_t * stack)
|
||||
*
|
||||
* @param rule The ID of the rule.
|
||||
*
|
||||
* @return Parse value.
|
||||
* @retval P_SUCCESS
|
||||
* Continue parsing.
|
||||
* @retval P_USER_TERMINATED
|
||||
* User requested to terminate parsing.
|
||||
*/
|
||||
static <%= @grammar.prefix %>value_t parser_user_code(uint32_t rule, state_values_stack_t * statevalues, uint32_t n_states)
|
||||
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)
|
||||
{
|
||||
<%= @grammar.prefix %>value_t _pvalue = {0};
|
||||
|
||||
switch (rule)
|
||||
{
|
||||
<% @grammar.rules.each do |rule| %>
|
||||
@@ -756,7 +757,7 @@ static <%= @grammar.prefix %>value_t parser_user_code(uint32_t rule, state_value
|
||||
default: break;
|
||||
}
|
||||
|
||||
return _pvalue;
|
||||
return P_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -889,7 +890,12 @@ size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context)
|
||||
if (reduce_index != INVALID_ID)
|
||||
{
|
||||
/* We have something to reduce. */
|
||||
reduced_parser_value = parser_user_code(parser_reduce_table[reduce_index].rule, &statevalues, parser_reduce_table[reduce_index].n_states);
|
||||
<%= @grammar.prefix %>value_t reduced_parser_value2 = {0};
|
||||
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)
|
||||
{
|
||||
return P_USER_TERMINATED;
|
||||
}
|
||||
reduced_parser_value = reduced_parser_value2;
|
||||
reduced_rule_set = parser_reduce_table[reduce_index].rule_set;
|
||||
state_values_stack_pop(&statevalues, parser_reduce_table[reduce_index].n_states);
|
||||
continue;
|
||||
@@ -934,3 +940,16 @@ size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context)
|
||||
{
|
||||
return context->text_position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user terminate code.
|
||||
*
|
||||
* @param context
|
||||
* Lexer/parser context structure.
|
||||
*
|
||||
* @return User terminate code.
|
||||
*/
|
||||
size_t <%= @grammar.prefix %>user_terminate_code(<%= @grammar.prefix %>context_t * context)
|
||||
{
|
||||
return context->user_terminate_code;
|
||||
}
|
||||
+30
-7
@@ -27,6 +27,7 @@ public enum : size_t
|
||||
<%= @grammar.prefix.upcase %>UNEXPECTED_TOKEN,
|
||||
<%= @grammar.prefix.upcase %>DROP,
|
||||
<%= @grammar.prefix.upcase %>EOF,
|
||||
<%= @grammar.prefix.upcase %>USER_TERMINATED,
|
||||
}
|
||||
|
||||
/** Token type. */
|
||||
@@ -114,6 +115,9 @@ public struct <%= @grammar.prefix %>context_t
|
||||
|
||||
/** Unexpected token received. */
|
||||
<%= @grammar.prefix %>token_t token;
|
||||
|
||||
/** User terminate code. */
|
||||
size_t user_terminate_code;
|
||||
}
|
||||
|
||||
/**************************************************************************
|
||||
@@ -141,6 +145,7 @@ private enum : size_t
|
||||
P_UNEXPECTED_TOKEN,
|
||||
P_DROP,
|
||||
P_EOF,
|
||||
P_USER_TERMINATED,
|
||||
}
|
||||
<% end %>
|
||||
|
||||
@@ -502,7 +507,6 @@ private size_t find_longest_match(<%= @grammar.prefix %>context_t * context,
|
||||
/* Valid EOF return. */
|
||||
return P_EOF;
|
||||
}
|
||||
break;
|
||||
|
||||
case P_DECODE_ERROR:
|
||||
/* If we see a decode error, we may be partially in the middle of
|
||||
@@ -762,12 +766,13 @@ private immutable parser_state_t[] parser_state_table = [
|
||||
*
|
||||
* @param rule The ID of the rule.
|
||||
*
|
||||
* @return Parse value.
|
||||
* @retval P_SUCCESS
|
||||
* Continue parsing.
|
||||
* @retval P_USER_TERMINATED
|
||||
* User requested to terminate parsing.
|
||||
*/
|
||||
private <%= @grammar.prefix %>value_t parser_user_code(uint rule, state_value_t[] statevalues, uint n_states)
|
||||
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)
|
||||
{
|
||||
<%= @grammar.prefix %>value_t _pvalue;
|
||||
|
||||
switch (rule)
|
||||
{
|
||||
<% @grammar.rules.each do |rule| %>
|
||||
@@ -780,7 +785,7 @@ private <%= @grammar.prefix %>value_t parser_user_code(uint rule, state_value_t[
|
||||
default: break;
|
||||
}
|
||||
|
||||
return _pvalue;
|
||||
return P_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -907,7 +912,12 @@ public size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * cont
|
||||
if (reduce_index != INVALID_ID)
|
||||
{
|
||||
/* We have something to reduce. */
|
||||
reduced_parser_value = parser_user_code(parser_reduce_table[reduce_index].rule, statevalues, parser_reduce_table[reduce_index].n_states);
|
||||
<%= @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)
|
||||
{
|
||||
return P_USER_TERMINATED;
|
||||
}
|
||||
reduced_parser_value = reduced_parser_value2;
|
||||
reduced_rule_set = parser_reduce_table[reduce_index].rule_set;
|
||||
statevalues.length -= parser_reduce_table[reduce_index].n_states;
|
||||
continue;
|
||||
@@ -949,3 +959,16 @@ public <%= @grammar.prefix %>position_t <%= @grammar.prefix %>position(<%= @gram
|
||||
{
|
||||
return context.text_position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user terminate code.
|
||||
*
|
||||
* @param context
|
||||
* Lexer/parser context structure.
|
||||
*
|
||||
* @return User terminate code.
|
||||
*/
|
||||
public size_t <%= @grammar.prefix %>user_terminate_code(<%= @grammar.prefix %>context_t * context)
|
||||
{
|
||||
return context.user_terminate_code;
|
||||
}
|
||||
@@ -20,6 +20,7 @@
|
||||
#define <%= @grammar.prefix.upcase %>UNEXPECTED_TOKEN 3u
|
||||
#define <%= @grammar.prefix.upcase %>DROP 4u
|
||||
#define <%= @grammar.prefix.upcase %>EOF 5u
|
||||
#define <%= @grammar.prefix.upcase %>USER_TERMINATED 6u
|
||||
|
||||
/** Token type. */
|
||||
typedef <%= get_type_for(@grammar.invalid_token_id) %> <%= @grammar.prefix %>token_t;
|
||||
@@ -109,6 +110,9 @@ typedef struct
|
||||
|
||||
/** Unexpected token received. */
|
||||
<%= @grammar.prefix %>token_t token;
|
||||
|
||||
/** User terminate code. */
|
||||
size_t user_terminate_code;
|
||||
} <%= @grammar.prefix %>context_t;
|
||||
|
||||
void <%= @grammar.prefix %>context_init(<%= @grammar.prefix %>context_t * context, uint8_t const * input, size_t input_length);
|
||||
@@ -123,3 +127,5 @@ size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context);
|
||||
<%= start_rule_type[1] %> <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context);
|
||||
|
||||
<%= @grammar.prefix %>position_t <%= @grammar.prefix %>position(<%= @grammar.prefix %>context_t * context);
|
||||
|
||||
size_t <%= @grammar.prefix %>user_terminate_code(<%= @grammar.prefix %>context_t * context);
|
||||
+145
-1
@@ -13,7 +13,7 @@ Propane is a LALR Parser Generator (LPG) which:
|
||||
* generates a built-in lexer to tokenize input
|
||||
* supports UTF-8 lexer inputs
|
||||
* generates a table-driven shift/reduce parser to parse input in linear time
|
||||
* target C or D language outputs
|
||||
* targets C or D language outputs
|
||||
* is MIT-licensed
|
||||
* is distributable as a standalone Ruby script
|
||||
|
||||
@@ -574,6 +574,150 @@ default.
|
||||
It can also be used when generating multiple lexers/parsers to be used in the
|
||||
same program to avoid symbol collisions.
|
||||
|
||||
##> User termination of the parser
|
||||
|
||||
Propane supports allowing parser user code blocks to terminate execution of the
|
||||
parser.
|
||||
One example use of this functionality is to detect and report an error before
|
||||
the parser continues parsing the remainder of the input.
|
||||
Another use of this features is to begin parsing input and determine whether a
|
||||
different parser should be used instead.
|
||||
|
||||
To terminate parsing from a parser user code block, use the `$terminate(code)`
|
||||
function, passing an integer expression argument.
|
||||
For example:
|
||||
|
||||
```
|
||||
NewExpression -> new Expression <<
|
||||
$terminate(42);
|
||||
>>
|
||||
```
|
||||
|
||||
The value passed to the `$terminate()` function is known as the "user terminate
|
||||
code".
|
||||
If the parser returns a `P_USER_TERMINATED` result code, then the user
|
||||
terminate code can be accessed using the `p_user_terminate_code()` API
|
||||
function.
|
||||
|
||||
#> Propane generated API
|
||||
|
||||
By default, Propane uses a prefix of `p_` when generating a lexer/parser.
|
||||
This prefix is used for all publicly declared types and functions.
|
||||
The uppercase version of the prefix is used for all constant values.
|
||||
|
||||
This section documents the generated API using the default `p_` or `P_` names.
|
||||
|
||||
##> Constants
|
||||
|
||||
Propane generates the following result code constants:
|
||||
|
||||
* `P_SUCCESS`: A successful decode/lex/parse operation has taken place.
|
||||
* `P_DECODE_ERROR`: An error occurred when decoding UTF-8 input.
|
||||
* `P_UNEXPECTED_INPUT`: Input was received by the lexer that does not match any lexer pattern.
|
||||
* `P_UNEXPECTED_TOKEN`: A token was seen in a location that does not match any parser rule.
|
||||
* `P_DROP`: The lexer matched a drop pattern.
|
||||
* `P_EOF`: The lexer reached the end of the input string.
|
||||
* `P_USER_TERMINATED`: A parser user code block has requested to terminate the parser.
|
||||
|
||||
Result codes are returned by the functions `p_decode_input()`, `p_lex()`, and `p_parse()`.
|
||||
|
||||
##> Types
|
||||
|
||||
### `p_context_t`
|
||||
|
||||
Propane defines a `p_context_t` structure type.
|
||||
The structure is intended to be used opaquely and stores information related to
|
||||
the state of the lexer and parser.
|
||||
Integrating code must define an instance of the `p_context_t` structure.
|
||||
A pointer to this instance is passed to the generated functions.
|
||||
|
||||
### `p_position_t`
|
||||
|
||||
The `p_position_t` structure contains two fields `row` and `col`.
|
||||
These fields contain the 0-based row and column describing a parser position.
|
||||
|
||||
##> Functions
|
||||
|
||||
### `p_context_init`
|
||||
|
||||
The `p_context_init()` function must be called to initialize the context
|
||||
structure.
|
||||
The input to be used for lexing/parsing is passed in when initializing the
|
||||
context structure.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
p_context_t context;
|
||||
p_context_init(&context, input, input_length);
|
||||
```
|
||||
|
||||
### `p_parse`
|
||||
|
||||
The `p_parse()` function is the main entry point to the parser.
|
||||
It must be passed a pointer to an initialized context structure.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
p_context_t context;
|
||||
p_context_init(&context, input, input_length);
|
||||
size_t result = p_parse(&context);
|
||||
```
|
||||
|
||||
### `p_result`
|
||||
|
||||
The `p_result()` function can be used to retrieve the final parse value after
|
||||
`p_parse()` returns a `P_SUCCESS` value.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
p_context_t context;
|
||||
p_context_init(&context, input, input_length);
|
||||
size_t result = p_parse(&context);
|
||||
if (p_parse(&context) == P_SUCCESS)
|
||||
{
|
||||
result = p_result(&context);
|
||||
}
|
||||
```
|
||||
|
||||
### `p_position`
|
||||
|
||||
The `p_position()` function can be used to retrieve the parser position where
|
||||
an error occurred.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
p_context_t context;
|
||||
p_context_init(&context, input, input_length);
|
||||
size_t result = p_parse(&context);
|
||||
if (p_parse(&context) == P_UNEXPECTED_TOKEN)
|
||||
{
|
||||
p_position_t error_position = p_position(&context);
|
||||
fprintf(stderr, "Error: unexpected token at row %u column %u\n",
|
||||
error_position.row + 1, error_position.col + 1);
|
||||
}
|
||||
```
|
||||
|
||||
### `p_user_terminate_code`
|
||||
|
||||
The `p_user_terminate_code()` function can be used to retrieve the user
|
||||
terminate code after `p_parse()` returns a `P_USER_TERMINATED` value.
|
||||
User terminate codes are arbitrary values that can be defined by the user to
|
||||
be returned when the user requests to terminate parsing.
|
||||
They have no particular meaning to Propane.
|
||||
|
||||
Example:
|
||||
|
||||
```
|
||||
if (p_parse(&context) == P_USER_TERMINATED)
|
||||
{
|
||||
size_t user_terminate_code = p_user_terminate_code(&context);
|
||||
}
|
||||
```
|
||||
|
||||
#> License
|
||||
|
||||
Propane is licensed under the terms of the MIT License:
|
||||
|
||||
@@ -200,7 +200,12 @@ class Propane
|
||||
end
|
||||
if parser
|
||||
code = code.gsub(/\$\$/) do |match|
|
||||
"_pvalue.v_#{rule.ptypename}"
|
||||
case @language
|
||||
when "c"
|
||||
"_pvalue->v_#{rule.ptypename}"
|
||||
when "d"
|
||||
"_pvalue.v_#{rule.ptypename}"
|
||||
end
|
||||
end
|
||||
code = code.gsub(/\$(\d+)/) do |match|
|
||||
index = $1.to_i
|
||||
@@ -211,6 +216,15 @@ class Propane
|
||||
"statevalues[$-1-n_states+#{index}].pvalue.v_#{rule.components[index - 1].ptypename}"
|
||||
end
|
||||
end
|
||||
code = code.gsub(/\$terminate\((.*)\);/) do |match|
|
||||
user_terminate_code = $1
|
||||
case @language
|
||||
when "c"
|
||||
"context->user_terminate_code = (#{user_terminate_code}); return P_USER_TERMINATED;"
|
||||
when "d"
|
||||
"context.user_terminate_code = (#{user_terminate_code}); return P_USER_TERMINATED;"
|
||||
end
|
||||
end
|
||||
else
|
||||
code = code.gsub(/\$\$/) do |match|
|
||||
case @language
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
class Propane
|
||||
VERSION = "1.0.0"
|
||||
VERSION = "1.1.0"
|
||||
end
|
||||
@@ -729,6 +729,26 @@ EOF
|
||||
expect(results.stderr).to eq ""
|
||||
expect(results.status).to eq 0
|
||||
end
|
||||
|
||||
it "allows the user to terminate the parser" do
|
||||
write_grammar <<EOF
|
||||
token a;
|
||||
token b;
|
||||
token c;
|
||||
Start -> Any;
|
||||
Any -> a Any;
|
||||
Any -> b Any <<
|
||||
$terminate(4200);
|
||||
>>
|
||||
Any -> c Any;
|
||||
Any -> ;
|
||||
EOF
|
||||
build_parser(language: language)
|
||||
compile("spec/test_user_terminate.#{language}", language: language)
|
||||
results = run
|
||||
expect(results.stderr).to eq ""
|
||||
expect(results.status).to eq 0
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,19 @@
|
||||
#include "testparser.h"
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
int main()
|
||||
{
|
||||
char const * input = "aacc";
|
||||
p_context_t context;
|
||||
p_context_init(&context, (uint8_t const *)input, strlen(input));
|
||||
assert(p_parse(&context) == P_SUCCESS);
|
||||
|
||||
input = "abc";
|
||||
p_context_init(&context, (uint8_t const *)input, strlen(input));
|
||||
assert(p_parse(&context) == P_USER_TERMINATED);
|
||||
assert(p_user_terminate_code(&context) == 4200);
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import testparser;
|
||||
import std.stdio;
|
||||
|
||||
int main()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
unittest
|
||||
{
|
||||
string input = "aacc";
|
||||
p_context_t context;
|
||||
p_context_init(&context, input);
|
||||
assert(p_parse(&context) == P_SUCCESS);
|
||||
|
||||
input = "abc";
|
||||
p_context_init(&context, input);
|
||||
assert(p_parse(&context) == P_USER_TERMINATED);
|
||||
assert(p_user_terminate_code(&context) == 4200);
|
||||
}
|
||||
Reference in new issue
Block a user