Allow specifying the start rule name

This commit is contained in:
Josh Holtrop 2024-05-05 12:39:00 -04:00
parent 508dabe760
commit 494afb7307
12 changed files with 119 additions and 18 deletions

View File

@ -3,6 +3,7 @@
### New Features ### New Features
- Allow user to specify AST node name prefix or suffix - Allow user to specify AST node name prefix or suffix
- Allow specifying the start rule name
## v1.3.0 ## v1.3.0

View File

@ -924,7 +924,7 @@ size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context)
{ {
/* Successful parse. */ /* Successful parse. */
<% if @grammar.ast %> <% if @grammar.ast %>
context->parse_result = (<%= @grammar.ast_prefix %>Start<%= @grammar.ast_suffix %> *)state_values_stack_index(&statevalues, -1)->ast_node; context->parse_result = (<%= @grammar.ast_prefix %><%= @grammar.start_rule %><%= @grammar.ast_suffix %> *)state_values_stack_index(&statevalues, -1)->ast_node;
<% else %> <% else %>
context->parse_result = state_values_stack_index(&statevalues, -1)->pvalue; context->parse_result = state_values_stack_index(&statevalues, -1)->pvalue;
<% end %> <% end %>
@ -1029,7 +1029,7 @@ size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context)
* @return Parse result value. * @return Parse result value.
*/ */
<% if @grammar.ast %> <% if @grammar.ast %>
<%= @grammar.ast_prefix %>Start<%= @grammar.ast_suffix %> * <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context) <%= @grammar.ast_prefix %><%= @grammar.start_rule %><%= @grammar.ast_suffix %> * <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context)
<% else %> <% else %>
<%= start_rule_type[1] %> <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context) <%= start_rule_type[1] %> <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context)
<% end %> <% end %>

View File

@ -144,7 +144,7 @@ public struct <%= @grammar.prefix %>context_t
/** Parse result value. */ /** Parse result value. */
<% if @grammar.ast %> <% if @grammar.ast %>
<%= @grammar.ast_prefix %>Start<%= @grammar.ast_suffix %> * parse_result; <%= @grammar.ast_prefix %><%= @grammar.start_rule %><%= @grammar.ast_suffix %> * parse_result;
<% else %> <% else %>
<%= @grammar.prefix %>value_t parse_result; <%= @grammar.prefix %>value_t parse_result;
<% end %> <% end %>
@ -973,7 +973,7 @@ public size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * cont
{ {
/* Successful parse. */ /* Successful parse. */
<% if @grammar.ast %> <% if @grammar.ast %>
context.parse_result = cast(<%= @grammar.ast_prefix %>Start<%= @grammar.ast_suffix %> *)statevalues[$-1].ast_node; context.parse_result = cast(<%= @grammar.ast_prefix %><%= @grammar.start_rule %><%= @grammar.ast_suffix %> *)statevalues[$-1].ast_node;
<% else %> <% else %>
context.parse_result = statevalues[$-1].pvalue; context.parse_result = statevalues[$-1].pvalue;
<% end %> <% end %>
@ -1075,7 +1075,7 @@ public size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * cont
* @return Parse result value. * @return Parse result value.
*/ */
<% if @grammar.ast %> <% if @grammar.ast %>
public <%= @grammar.ast_prefix %>Start<%= @grammar.ast_suffix %> * <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context) public <%= @grammar.ast_prefix %><%= @grammar.start_rule %><%= @grammar.ast_suffix %> * <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context)
<% else %> <% else %>
public <%= start_rule_type[1] %> <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context) public <%= start_rule_type[1] %> <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context)
<% end %> <% end %>

View File

@ -144,7 +144,7 @@ typedef struct
/** Parse result value. */ /** Parse result value. */
<% if @grammar.ast %> <% if @grammar.ast %>
<%= @grammar.ast_prefix %>Start<%= @grammar.ast_suffix %> * parse_result; <%= @grammar.ast_prefix %><%= @grammar.start_rule %><%= @grammar.ast_suffix %> * parse_result;
<% else %> <% else %>
<%= @grammar.prefix %>value_t parse_result; <%= @grammar.prefix %>value_t parse_result;
<% end %> <% end %>
@ -173,7 +173,7 @@ size_t <%= @grammar.prefix %>lex(<%= @grammar.prefix %>context_t * context, <%=
size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context); size_t <%= @grammar.prefix %>parse(<%= @grammar.prefix %>context_t * context);
<% if @grammar.ast %> <% if @grammar.ast %>
<%= @grammar.ast_prefix %>Start<%= @grammar.ast_suffix %> * <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context); <%= @grammar.ast_prefix %><%= @grammar.start_rule %><%= @grammar.ast_suffix %> * <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context);
<% else %> <% else %>
<%= start_rule_type[1] %> <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context); <%= start_rule_type[1] %> <%= @grammar.prefix %>result(<%= @grammar.prefix %>context_t * context);
<% end %> <% end %>

View File

@ -203,8 +203,10 @@ In AST generation mode various aspects of propane's behavior are changed:
* Parser user code blocks are not supported. * Parser user code blocks are not supported.
* Structure types are generated to represent the parsed tokens and rules as * Structure types are generated to represent the parsed tokens and rules as
defined in the grammar. defined in the grammar.
* The parse result from `p_result()` points to a `Start` structure containing * The parse result from `p_result()` points to a `Start` struct containing
the entire parse tree for the input. the entire parse tree for the input. If the user has changed the start rule
with the `start` grammar statement, the name of the start struct will be
given by the user-specified start rule instead of `Start`.
Example AST generation grammar: Example AST generation grammar:
@ -594,21 +596,24 @@ Rules with the same name define a rule set for that name and act as
alternatives that the parser can accept when attempting to match a reference to alternatives that the parser can accept when attempting to match a reference to
that rule. that rule.
The grammar file must define a rule with the name `Start` which will be used as The default start rule name is `Start`.
the top-level starting rule that the parser attempts to reduce. This can be changed with the `start` statement.
The grammar file must define a rule with the name of the start rule name which
will be used as the top-level starting rule that the parser attempts to reduce.
Example: Example:
``` ```
ptype ulong; ptype ulong;
start Top;
token word /[a-z]+/ << $$ = match.length; >> token word /[a-z]+/ << $$ = match.length; >>
Start -> word << $$ = $1; >> Top -> word << $$ = $1; >>
``` ```
In the above example the `Start` rule is defined to match a single `word` In the above example the `Top` rule is defined to match a single `word`
token. token.
Example: Another example:
``` ```
Start -> E1 << $$ = $1; >> Start -> E1 << $$ = $1; >>
@ -622,6 +627,8 @@ E4 -> integer << $$ = $1; >>
E4 -> lparen E1 rparen << $$ = $2; >> E4 -> lparen E1 rparen << $$ = $2; >>
``` ```
This example uses the default start rule name of `Start`.
A parser rule has zero or more terms on the right side of its definition. A parser rule has zero or more terms on the right side of its definition.
Each of these terms is either a token name or a rule name. Each of these terms is either a token name or a rule name.
@ -635,6 +642,16 @@ can be used to produce the parser value for the accepted rule.
Parser rule code blocks are not allowed and not used when AST generation mode Parser rule code blocks are not allowed and not used when AST generation mode
is active. is active.
##> Specifying the parser start rule name - the `start` statement
The start rule can be changed from the default of `Start` by using the `start`
statement.
Example:
```
start MyStartRule;
```
##> Specifying the parser module name - the `module` statement ##> Specifying the parser module name - the `module` statement
The `module` statement can be used to specify the module name for a generated The `module` statement can be used to specify the module name for a generated

View File

@ -66,11 +66,11 @@ class Propane
tokens_by_name[token.name] = token tokens_by_name[token.name] = token
end end
# Check for user start rule. # Check for user start rule.
unless @grammar.rules.find {|rule| rule.name == "Start"} unless @grammar.rules.find {|rule| rule.name == @grammar.start_rule}
raise Error.new("Start rule not found") raise Error.new("Start rule `#{@grammar.start_rule}` not found")
end end
# Add "real" start rule. # Add "real" start rule.
@grammar.rules.unshift(Rule.new("$Start", ["Start", "$EOF"], nil, nil, nil)) @grammar.rules.unshift(Rule.new("$Start", [@grammar.start_rule, "$EOF"], nil, nil, nil))
rule_sets = {} rule_sets = {}
rule_set_id = @grammar.tokens.size rule_set_id = @grammar.tokens.size
@grammar.rules.each_with_index do |rule, rule_id| @grammar.rules.each_with_index do |rule, rule_id|
@ -270,7 +270,7 @@ class Propane
# Start rule parser value type name and type string. # Start rule parser value type name and type string.
def start_rule_type def start_rule_type
start_rule = @grammar.rules.find do |rule| start_rule = @grammar.rules.find do |rule|
rule.name == "Start" rule.name == @grammar.start_rule
end end
[start_rule.ptypename, @grammar.ptypes[start_rule.ptypename]] [start_rule.ptypename, @grammar.ptypes[start_rule.ptypename]]
end end

View File

@ -11,6 +11,7 @@ class Propane
attr_reader :modulename attr_reader :modulename
attr_reader :patterns attr_reader :patterns
attr_reader :rules attr_reader :rules
attr_reader :start_rule
attr_reader :tokens attr_reader :tokens
attr_reader :code_blocks attr_reader :code_blocks
attr_reader :ptypes attr_reader :ptypes
@ -18,6 +19,7 @@ class Propane
def initialize(input) def initialize(input)
@patterns = [] @patterns = []
@start_rule = "Start"
@tokens = [] @tokens = []
@rules = [] @rules = []
@code_blocks = {} @code_blocks = {}
@ -63,6 +65,7 @@ class Propane
elsif parse_module_statement! elsif parse_module_statement!
elsif parse_ptype_statement! elsif parse_ptype_statement!
elsif parse_pattern_statement! elsif parse_pattern_statement!
elsif parse_start_statement!
elsif parse_token_statement! elsif parse_token_statement!
elsif parse_tokenid_statement! elsif parse_tokenid_statement!
elsif parse_drop_statement! elsif parse_drop_statement!
@ -228,6 +231,12 @@ class Propane
end end
end end
def parse_start_statement!
if md = consume!(/start\s+(\w+)\s*;/)
@start_rule = md[1]
end
end
def parse_code_block_statement! def parse_code_block_statement!
if md = consume!(/<<([a-z]*)(.*?)>>\n/m) if md = consume!(/<<([a-z]*)(.*?)>>\n/m)
name, code = md[1..2] name, code = md[1..2]

View File

@ -889,6 +889,27 @@ EOF
expect(results.stderr).to eq "" expect(results.stderr).to eq ""
expect(results.status).to eq 0 expect(results.status).to eq 0
end end
it "allows specifying a different start rule" do
write_grammar <<EOF
token hi;
start Top;
Top -> hi;
EOF
run_propane(language: language)
compile("spec/test_start_rule.#{language}", language: language)
end
it "allows specifying a different start rule with AST generation" do
write_grammar <<EOF
ast;
token hi;
start Top;
Top -> hi;
EOF
run_propane(language: language)
compile("spec/test_start_rule_ast.#{language}", language: language)
end
end end
end end
end end

9
spec/test_start_rule.c Normal file
View File

@ -0,0 +1,9 @@
#include "testparser.h"
#include <assert.h>
#include <string.h>
#include "testutils.h"
int main()
{
return 0;
}

8
spec/test_start_rule.d Normal file
View File

@ -0,0 +1,8 @@
import testparser;
import std.stdio;
import testutils;
int main()
{
return 0;
}

View File

@ -0,0 +1,17 @@
#include "testparser.h"
#include <assert.h>
#include <string.h>
#include "testutils.h"
int main()
{
char const * input = "hi";
p_context_t context;
p_context_init(&context, (uint8_t const *)input, strlen(input));
assert_eq(P_SUCCESS, p_parse(&context));
Top * top = p_result(&context);
assert(top->pToken != NULL);
assert_eq(TOKEN_hi, top->pToken->token);
return 0;
}

View File

@ -0,0 +1,19 @@
import testparser;
import std.stdio;
import testutils;
int main()
{
return 0;
}
unittest
{
string input = "hi";
p_context_t context;
p_context_init(&context, input);
assert_eq(P_SUCCESS, p_parse(&context));
Top * top = p_result(&context);
assert(top.pToken !is null);
assert_eq(TOKEN_hi, top.pToken.token);
}