diff --git a/doc/user_guide.md b/doc/user_guide.md index dba5050..d0ef0ac 100644 --- a/doc/user_guide.md +++ b/doc/user_guide.md @@ -641,13 +641,14 @@ This example uses the default start rule name of `Start`. A parser rule has zero or more fields on the right side of its definition. Each of these fields is either a token name or a rule name. +A field can be immediately followed by a `?` character to signify that it is +optional. A field can optionally be followed by a `:` and then a field alias name. If present, the field alias name is used to refer to the field value in user code blocks, or if AST mode is active, the field alias name is used as the field name in the generated AST node structure. -A field can be immediately followed by a `?` character to signify that it is -optional. -Another example: +An optional and named field must use the format `field?:name`. +Example: ``` token public; @@ -655,7 +656,7 @@ token private; token int; token ident /[a-zA-Z_][a-zA-Z_0-9]*/; token semicolon /;/; -IntegerDeclaration -> Visibility? int ident:name semicolon; +IntegerDeclaration -> Visibility?:visibility int ident:name semicolon; Visibility -> public; Visibility -> private; ``` @@ -663,7 +664,7 @@ Visibility -> private; In a parser rule code block, parser values for the right side fields are accessible as `$1` for the first field's parser value, `$2` for the second field's parser value, etc... -For the `IntegerDeclaration` rule, the third field value can also be referred +For the `IntegerDeclaration` rule, the first field value can also be referred to as `${visibility}` and the third field value can also be referred to as `${name}`. The `$$` symbol accesses the output parser value for this rule. The above examples demonstrate how the parser values for the rule components diff --git a/lib/propane/grammar.rb b/lib/propane/grammar.rb index 0e8f090..a6e6469 100644 --- a/lib/propane/grammar.rb +++ b/lib/propane/grammar.rb @@ -198,7 +198,7 @@ class Propane if @ast && ptypename raise Error.new("Multiple ptypes are unsupported in AST mode") end - md = consume!(/((?:#{IDENTIFIER_REGEX}(?::#{IDENTIFIER_REGEX})?\??\s*)*)\s*/, "expected rule component list") + md = consume!(/((?:#{IDENTIFIER_REGEX}\??(?::#{IDENTIFIER_REGEX})?\s*)*)\s*/, "expected rule component list") components = md[1].strip.split(/\s+/) if @ast consume!(/;/, "expected `;'") diff --git a/spec/propane_spec.rb b/spec/propane_spec.rb index 3f056e9..6964bac 100644 --- a/spec/propane_spec.rb +++ b/spec/propane_spec.rb @@ -1118,6 +1118,47 @@ EOF expect(results.status).to eq 0 end + it "allows naming an optional rule component in AST generation mode" do + if language == "d" + write_grammar <> + +token a; +token b; +token c; +token d; +Start -> a?:a b R?:r; +R -> c d; +R -> d c; +EOF + else + write_grammar < +>> + +token a; +token b; +token c; +token d; +Start -> a?:a b R?:r; +R -> c d; +R -> d c; +EOF + end + run_propane(language: language) + compile("spec/test_named_optional_rule_component_ast.#{language}", language: language) + results = run_test + expect(results.stderr).to eq "" + expect(results.status).to eq 0 + end + it "stores token and rule positions in AST nodes" do write_grammar < +#include +#include "testutils.h" + +int main() +{ + char const * input = "b"; + p_context_t context; + p_context_init(&context, (uint8_t const *)input, strlen(input)); + assert(p_parse(&context) == P_SUCCESS); + Start * start = p_result(&context); + assert(start->a == NULL); + assert(start->pToken2 != NULL); + assert_eq(TOKEN_b, start->pToken2->token); + assert(start->pR3 == NULL); + assert(start->pR == NULL); + assert(start->r == NULL); + + input = "abcd"; + p_context_init(&context, (uint8_t const *)input, strlen(input)); + assert(p_parse(&context) == P_SUCCESS); + start = p_result(&context); + assert(start->a != NULL); + assert_eq(TOKEN_a, start->pToken1->token); + assert(start->pToken2 != NULL); + assert(start->pR3 != NULL); + assert(start->pR != NULL); + assert(start->r != NULL); + assert(start->pR == start->pR3); + assert(start->pR == start->r); + assert_eq(TOKEN_c, start->pR->pToken1->token); + + input = "bdc"; + p_context_init(&context, (uint8_t const *)input, strlen(input)); + assert(p_parse(&context) == P_SUCCESS); + start = p_result(&context); + assert(start->a == NULL); + assert(start->pToken2 != NULL); + assert(start->r != NULL); + assert_eq(TOKEN_d, start->pR->pToken1->token); + + return 0; +} + diff --git a/spec/test_named_optional_rule_component_ast.d b/spec/test_named_optional_rule_component_ast.d new file mode 100644 index 0000000..d0658d3 --- /dev/null +++ b/spec/test_named_optional_rule_component_ast.d @@ -0,0 +1,46 @@ +import testparser; +import std.stdio; +import testutils; + +int main() +{ + return 0; +} + +unittest +{ + string input = "b"; + p_context_t context; + p_context_init(&context, input); + assert(p_parse(&context) == P_SUCCESS); + Start * start = p_result(&context); + assert(start.pToken1 is null); + assert(start.pToken2 !is null); + assert_eq(TOKEN_b, start.pToken2.token); + assert(start.pR3 is null); + assert(start.pR is null); + assert(start.r is null); + + input = "abcd"; + p_context_init(&context, input); + assert(p_parse(&context) == P_SUCCESS); + start = p_result(&context); + assert(start.pToken1 != null); + assert_eq(TOKEN_a, start.pToken1.token); + assert(start.pToken2 != null); + assert(start.pR3 != null); + assert(start.pR != null); + assert(start.r != null); + assert(start.pR == start.pR3); + assert(start.pR == start.r); + assert_eq(TOKEN_c, start.pR.pToken1.token); + + input = "bdc"; + p_context_init(&context, input); + assert(p_parse(&context) == P_SUCCESS); + start = p_result(&context); + assert(start.pToken1 is null); + assert(start.pToken2 !is null); + assert(start.pR !is null); + assert_eq(TOKEN_d, start.pR.pToken1.token); +}