From 793b5589faadfbe8239a82c13e7517ba5e100b94 Mon Sep 17 00:00:00 2001 From: soasme Date: Thu, 26 Aug 2021 17:04:36 +1200 Subject: [PATCH 1/5] api: add new kind: P4_LeftRecursion. --- peppa.h | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/peppa.h b/peppa.h index 4d13b00..411513b 100644 --- a/peppa.h +++ b/peppa.h @@ -215,7 +215,9 @@ typedef enum { */ P4_Repeat, /** Rule: Cut. */ - P4_Cut + P4_Cut, + /** Rule: Left Recursion. */ + P4_LeftRecursion } P4_ExpressionKind; /** @@ -694,6 +696,26 @@ P4_Error P4_AddNegative(P4_Grammar* grammar, P4_String name, P4_Expression */ P4_Expression* P4_CreateCut(); +/** + * Create a P4_LeftRecursion expression. + * + * @param lhs Left-hand side of left recursion. + * @param rhs Right-hand side of left recursion. + * @return A P4_Expression. + */ +P4_Expression* P4_CreateLeftRecursion(P4_Expression* lhs, P4_Expression* rhs); + +/** + * Add a P4_LeftRecursion expression as grammar rule. + * + * @param grammar The grammar. + * @param name The grammar rule name. + * @param lhs Left-hand side of left recursion. + * @param rhs Right-hand side of left recursion. + * @return The error code. + */ +P4_Error P4_AddLeftRecursion(P4_Grammar* grammar, P4_String name, P4_Expression* lhs, P4_Expression* rhs); + /** * Create a P4_Sequence expression. * From 0b74e53b645dac013de61b4c31fdc98fb4c8835c Mon Sep 17 00:00:00 2001 From: soasme Date: Thu, 26 Aug 2021 17:06:13 +1200 Subject: [PATCH 2/5] match: initial commit for left recursion implementation. --- peppa.c | 104 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 100 insertions(+), 4 deletions(-) diff --git a/peppa.c b/peppa.c index 3ee6f9d..9130fd1 100644 --- a/peppa.c +++ b/peppa.c @@ -612,6 +612,12 @@ struct P4_Expression { size_t repeat_min; size_t repeat_max; }; + + /** Used by P4_LeftRecursion. */ + struct { + P4_Expression* lhs; + P4_Expression* rhs; + }; }; }; @@ -851,6 +857,7 @@ P4_PRIVATE(P4_Node*) match_cut(P4_Source*, P4_Expression*); P4_PRIVATE(P4_Node*) match_sequence(P4_Source*, P4_Expression*); P4_PRIVATE(P4_Node*) match_choice(P4_Source*, P4_Expression*); P4_PRIVATE(P4_Node*) match_repeat(P4_Source*, P4_Expression*); +P4_PRIVATE(P4_Node*) match_left_recursion(P4_Source*, P4_Expression*); P4_PRIVATE(P4_Node*) match_spaced_rules(P4_Source*, P4_Expression*); P4_PRIVATE(P4_Node*) match_back_reference(P4_Source*, P4_Expression*, P4_Slice*, P4_Expression*); @@ -2207,6 +2214,66 @@ match_repeat(P4_Source* s, P4_Expression* e) { return NULL; } +P4_PRIVATE(P4_Node*) +match_left_recursion(P4_Source* s, P4_Expression* e) { + assert(no_error(s), "can't proceed due to a failed match"); + + mark_position(s, startpos); + + P4_Node* toklhs = match_expression(s, e->lhs); + P4_Node* tokrhs = NULL; + P4_Node* toktmp = NULL; + + if (need_lift(s, e)) { + P4_MatchRaisef(s, P4_PegError, + "rule %s is left recursion and can't be lifted", peek_rule_name(s)); + return NULL; + } + + if (!no_error(s)) { + P4_MatchRaisef(s, P4_MatchError, "expect %s", peek_rule_name(s)); + goto finalize; + } + + mark_position(s, lhs_endpos); + toklhs = P4_CreateNode(s->content, startpos, lhs_endpos, e->name); + + do { + if (is_end(s)) + break; + + mark_position(s, rhs_startpos); + + tokrhs = match_expression(s, e->rhs); + + if (no_match(s)) + break; + + if (!no_error(s)) { + P4_MatchRaisef(s, P4_MatchError, "expect %s", peek_rule_name(s)); + goto finalize; + } + + mark_position(s, endpos); + + if (tokrhs == NULL) { + tokrhs = P4_CreateNode(s->content, rhs_startpos, endpos, e->name); + } + + toktmp = P4_CreateNode(s->content, startpos, endpos, e->name); + P4_AdoptNode(toktmp->head, toktmp->tail, toklhs); + P4_AdoptNode(toktmp->head, toktmp->tail, tokrhs); + + toklhs = toktmp; + } while (true); + + return toklhs; + +finalize: + P4_DeleteNode(s->grammar, toklhs); + return NULL; +} + P4_PRIVATE(P4_Node*) match_positive(P4_Source* s, P4_Expression* e) { assert(no_error(s) && e->ref_expr != NULL, "expression should not be null"); @@ -2284,6 +2351,7 @@ match_expression(P4_Source* s, P4_Expression* e) { case P4_Positive: result = match_positive(s, e); break; case P4_Negative: result = match_negative(s, e); break; case P4_Repeat: result = match_repeat(s, e); break; + case P4_LeftRecursion: result = match_left_recursion(s, e); break; case P4_Cut: panic("cut can be applied only in sequence."); case P4_BackReference: panic("backreference can be applied only in sequence."); default: panicf("invalid dispatch kind: %zu.", e->kind); @@ -2587,6 +2655,17 @@ P4_CreateCut() { return expr; } +P4_PUBLIC P4_Expression* +P4_CreateLeftRecursion(P4_Expression* lhs, P4_Expression* rhs) { + P4_Expression* expr = P4_MALLOC(sizeof(P4_Expression)); + expr->kind = P4_LeftRecursion; + expr->flag = 0; + expr->name = NULL; + expr->lhs = lhs; + expr->rhs = rhs; + return expr; +} + P4_PRIVATE(P4_Expression*) P4_CreateContainer(size_t count) { if (count == 0) @@ -3268,6 +3347,12 @@ P4_Expression* P4_CreateJoin(const P4_String joiner, P4_String reference) { ); } +P4_PUBLIC P4_Error +P4_AddLeftRecursion(P4_Grammar* grammar, P4_String name, P4_Expression* lhs, P4_Expression* rhs) { + P4_AddSomeGrammarRule(grammar, name, P4_CreateLeftRecursion(lhs, rhs)); + return P4_Ok; +} + P4_PUBLIC void P4_DeleteExpression(P4_Expression* expr) { if (expr == NULL) @@ -3308,6 +3393,9 @@ P4_DeleteExpression(P4_Expression* expr) { if (expr->repeat_expr) P4_DeleteExpression(expr->repeat_expr); break; + case P4_LeftRecursion: + P4_DeleteExpression(expr->lhs); + P4_DeleteExpression(expr->rhs); default: break; } @@ -3560,6 +3648,10 @@ P4_RefreshReference(P4_Expression* expr, P4_String name) { case P4_Repeat: catch_err(P4_RefreshReference(expr->repeat_expr, name)); break; + case P4_LeftRecursion: + catch_err(P4_RefreshReference(expr->lhs, name)); + catch_err(P4_RefreshReference(expr->rhs, name)); + break; default: break; } @@ -4396,7 +4488,7 @@ P4_PegEvalGrammarReferences( P4_Grammar* grammar, P4_Expression* expr, P4_Result* result) { -# define recursive(e) \ +# define recursively_eval_reference(e) \ if ((e)) \ return P4_PegEvalGrammarReferences(grammar, (e), result); @@ -4421,15 +4513,19 @@ P4_PegEvalGrammarReferences( /* recursively check non-reference expressions. */ case P4_Positive: case P4_Negative: - recursive(expr->ref_expr); + recursively_eval_reference(expr->ref_expr); break; case P4_Sequence: case P4_Choice: for (i = 0; i < expr->count; i++) - recursive(expr->members[i]) + recursively_eval_reference(expr->members[i]) break; case P4_Repeat: - recursive(expr->repeat_expr); + recursively_eval_reference(expr->repeat_expr); + break; + case P4_LeftRecursion: + recursively_eval_reference(expr->lhs); + recursively_eval_reference(expr->rhs); break; default: break; } From e2fac463186ba726242097455b6338526dc45d64 Mon Sep 17 00:00:00 2001 From: soasme Date: Thu, 26 Aug 2021 17:42:01 +1200 Subject: [PATCH 3/5] tests: add test cases for left recursion. --- tests/CMakeLists.txt | 1 + tests/test_left_recursion.c | 286 ++++++++++++++++++++++++++++++++++++ 2 files changed, 287 insertions(+) create mode 100644 tests/test_left_recursion.c diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d2fe4bb..9f464bf 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -16,6 +16,7 @@ set( test_flags test_cut test_back_reference + test_left_recursion test_misc test_peg test_example_mustache diff --git a/tests/test_left_recursion.c b/tests/test_left_recursion.c new file mode 100644 index 0000000..9d77354 --- /dev/null +++ b/tests/test_left_recursion.c @@ -0,0 +1,286 @@ +#include "unity/src/unity.h" +#include "common.h" + +/** + * Grammar + * entry = "a" | entry "b"; + * Input + * abb + */ +void test_match_left_recursion_basic(void) { + P4_Grammar* grammar = P4_CreateGrammar(); + TEST_ASSERT_NOT_NULL(grammar); + TEST_ASSERT_EQUAL( + P4_Ok, + P4_AddLeftRecursion(grammar, "entry", + P4_CreateLiteral("a", true), + P4_CreateLiteral("b", true) + ) + ); + P4_Source* source = P4_CreateSource("abb", "entry"); + TEST_ASSERT_NOT_NULL(source); + TEST_ASSERT_EQUAL( + P4_Ok, + P4_Parse(grammar, source) + ); + + P4_Node* node = P4_GetSourceAst(source); + TEST_ASSERT_NOT_NULL(node); + ASSERT_EQUAL_NODE_STRING("abb", node); + + /** + * (abb) + * (ab) + */ + + TEST_ASSERT_NOT_NULL(node->head); + TEST_ASSERT_EQUAL(node->head, node->tail); + ASSERT_EQUAL_NODE_STRING("ab", node->head); + + TEST_ASSERT_NULL(node->head->head); + TEST_ASSERT_NULL(node->head->tail); + + P4_DeleteSource(source); + P4_DeleteGrammar(grammar); +} + +/** + * Grammar: + * entry = A | entry B; + * A = "a"; + * B = "b"; + * Input: + * abb + */ +void test_match_left_recursion_references(void) { + P4_Grammar* grammar = P4_CreateGrammar(); + TEST_ASSERT_NOT_NULL(grammar); + TEST_ASSERT_EQUAL( + P4_Ok, + P4_AddLeftRecursion(grammar, "entry", + P4_CreateReference("A"), + P4_CreateReference("B") + ) + ); + TEST_ASSERT_EQUAL( + P4_Ok, + P4_AddLiteral(grammar, "A", "a", true) + ); + TEST_ASSERT_EQUAL( + P4_Ok, + P4_AddLiteral(grammar, "B", "b", true) + ); + P4_Source* source = P4_CreateSource("abb", "entry"); + TEST_ASSERT_NOT_NULL(source); + TEST_ASSERT_EQUAL( + P4_Ok, + P4_Parse(grammar, source) + ); + + P4_Node* node = P4_GetSourceAst(source); + TEST_ASSERT_NOT_NULL(node); + ASSERT_EQUAL_NODE_STRING("abb", node); + + /** + * (abb) + * (ab) (b) + * (a) (b) + */ + + TEST_ASSERT_NOT_NULL(node->head); + ASSERT_EQUAL_NODE_STRING("ab", node->head); + + TEST_ASSERT_NOT_NULL(node->tail); + ASSERT_EQUAL_NODE_STRING("b", node->tail); + + TEST_ASSERT_NOT_NULL(node->head->head); + ASSERT_EQUAL_NODE_STRING("a", node->head->head); + + TEST_ASSERT_NOT_NULL(node->head->tail); + ASSERT_EQUAL_NODE_STRING("b", node->head->tail); + + P4_DeleteSource(source); + P4_DeleteGrammar(grammar); +} + +/** + * Grammar: + * S = E | S op E; + * op = "+" / "-"; + * E = [0-9]; + * Input: + * abb + */ +void test_match_left_recursion_sequence_on_rhs(void) { + P4_Grammar* grammar = P4_CreateGrammar(); + TEST_ASSERT_NOT_NULL(grammar); + TEST_ASSERT_EQUAL( + P4_Ok, + P4_AddLeftRecursion(grammar, "S", + P4_CreateReference("E"), + P4_CreateSequenceWithMembers(2, + P4_CreateReference("op"), + P4_CreateReference("E") + ) + ) + ); + TEST_ASSERT_EQUAL( + P4_Ok, + P4_AddRange(grammar, "E", '0', '9', 1) + ); + TEST_ASSERT_EQUAL( + P4_Ok, + P4_AddChoiceWithMembers(grammar, "op", 2, + P4_CreateLiteral("+", true), + P4_CreateLiteral("-", true) + ) + ); + P4_Source* source = P4_CreateSource("1+2+3", "S"); + TEST_ASSERT_NOT_NULL(source); + TEST_ASSERT_EQUAL( + P4_Ok, + P4_Parse(grammar, source) + ); + + P4_Node* node = P4_GetSourceAst(source); + TEST_ASSERT_NOT_NULL(node); + ASSERT_EQUAL_NODE_STRING("1+2+3", node); + + /** + * (1+2+3) + * (1+2) (+) (3) + * (1) (+) (2) + */ + + TEST_ASSERT_NOT_NULL(node->head); + ASSERT_EQUAL_NODE_STRING("1+2", node->head); + + TEST_ASSERT_NOT_NULL(node->head->next); + ASSERT_EQUAL_NODE_STRING("+", node->head->next); + + TEST_ASSERT_NOT_NULL(node->tail); + ASSERT_EQUAL_NODE_STRING("3", node->tail); + + TEST_ASSERT_NOT_NULL(node->head->head); + ASSERT_EQUAL_NODE_STRING("1", node->head->head); + + TEST_ASSERT_NOT_NULL(node->head->head->next); + ASSERT_EQUAL_NODE_STRING("+", node->head->head->next); + + TEST_ASSERT_NOT_NULL(node->head->tail); + ASSERT_EQUAL_NODE_STRING("2", node->head->tail); + + P4_DeleteSource(source); + P4_DeleteGrammar(grammar); +} + +/** + * Grammar: + * @spaced ws = " " / "\t" / "\n"; + * S = E | S op E; + * op = "+" / "-"; + * E = [0-9]; + * Input: + * 1 + 2 + 3 + */ +void test_match_left_recursion_spaced(void) { + P4_Grammar* grammar = P4_CreateGrammar(); + TEST_ASSERT_NOT_NULL(grammar); + TEST_ADD_WHITESPACE(grammar, "ws"); + TEST_ASSERT_EQUAL( + P4_Ok, + P4_AddLeftRecursion(grammar, "S", + P4_CreateReference("E"), + P4_CreateSequenceWithMembers(2, + P4_CreateReference("op"), + P4_CreateReference("E") + ) + ) + ); + TEST_ASSERT_EQUAL( + P4_Ok, + P4_AddRange(grammar, "E", '0', '9', 1) + ); + TEST_ASSERT_EQUAL( + P4_Ok, + P4_AddChoiceWithMembers(grammar, "op", 2, + P4_CreateLiteral("+", true), + P4_CreateLiteral("-", true) + ) + ); + P4_Source* source = P4_CreateSource("1 + 2 + 3", "S"); + TEST_ASSERT_NOT_NULL(source); + TEST_ASSERT_EQUAL( + P4_Ok, + P4_Parse(grammar, source) + ); + + P4_Node* node = P4_GetSourceAst(source); + TEST_ASSERT_NOT_NULL(node); + ASSERT_EQUAL_NODE_STRING("1 + 2 + 3", node); + + /** + * (1 + 2 + 3) + * (1 + 2) (+) (3) + * (1) (+) (2) + */ + + TEST_ASSERT_NOT_NULL(node->head); + ASSERT_EQUAL_NODE_STRING("1 + 2", node->head); + + TEST_ASSERT_NOT_NULL(node->head->next); + ASSERT_EQUAL_NODE_STRING("+", node->head->next); + + TEST_ASSERT_NOT_NULL(node->tail); + ASSERT_EQUAL_NODE_STRING("3", node->tail); + + TEST_ASSERT_NOT_NULL(node->head->head); + ASSERT_EQUAL_NODE_STRING("1", node->head->head); + + TEST_ASSERT_NOT_NULL(node->head->head->next); + ASSERT_EQUAL_NODE_STRING("+", node->head->head->next); + + TEST_ASSERT_NOT_NULL(node->head->tail); + ASSERT_EQUAL_NODE_STRING("2", node->head->tail); + + P4_DeleteSource(source); + P4_DeleteGrammar(grammar); +} + +/** Because lifted eliminates the hierarchy of left recursion. + * which in essence makes the expression become (lhs rhs*). + * */ +void test_left_recursion_cannot_use_with_lifted(void) { + P4_Grammar* grammar = P4_CreateGrammar(); + TEST_ASSERT_NOT_NULL(grammar); + TEST_ASSERT_EQUAL( + P4_Ok, + P4_AddLeftRecursion(grammar, "entry", + P4_CreateLiteral("a", true), + P4_CreateLiteral("b", true) + ) + ); + TEST_ASSERT_EQUAL(P4_Ok, P4_SetGrammarRuleFlag(grammar, "entry", P4_FLAG_LIFTED)); + + P4_Source* source = P4_CreateSource("abbb", "entry"); + TEST_ASSERT_NOT_NULL(source); + TEST_ASSERT_EQUAL( + P4_PegError, + P4_Parse(grammar, source) + ); + TEST_ASSERT_EQUAL_STRING("line 1:2, left recursion rule entry cannot be lifted", P4_GetErrorMessage(source)); + P4_DeleteSource(source); + P4_DeleteGrammar(grammar); +} + +int main(void) { + UNITY_BEGIN(); + + RUN_TEST(test_match_left_recursion_basic); + RUN_TEST(test_match_left_recursion_references); + RUN_TEST(test_match_left_recursion_sequence_on_rhs); + RUN_TEST(test_match_left_recursion_spaced); + RUN_TEST(test_left_recursion_cannot_use_with_lifted); + + return UNITY_END(); +} From c404d3366ce95a5e043180774de4cc3fd9e3b6f6 Mon Sep 17 00:00:00 2001 From: soasme Date: Thu, 26 Aug 2021 17:42:20 +1200 Subject: [PATCH 4/5] match: add implicit whitespace & nonterminal for left recursion. --- peppa.c | 40 ++++++++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 10 deletions(-) diff --git a/peppa.c b/peppa.c index 9130fd1..177a7aa 100644 --- a/peppa.c +++ b/peppa.c @@ -2223,10 +2223,11 @@ match_left_recursion(P4_Source* s, P4_Expression* e) { P4_Node* toklhs = match_expression(s, e->lhs); P4_Node* tokrhs = NULL; P4_Node* toktmp = NULL; + P4_Node* whitespace = NULL; if (need_lift(s, e)) { P4_MatchRaisef(s, P4_PegError, - "rule %s is left recursion and can't be lifted", peek_rule_name(s)); + "left recursion rule %s cannot be lifted", peek_rule_name(s)); return NULL; } @@ -2235,19 +2236,28 @@ match_left_recursion(P4_Source* s, P4_Expression* e) { goto finalize; } - mark_position(s, lhs_endpos); - toklhs = P4_CreateNode(s->content, startpos, lhs_endpos, e->name); + bool space = need_whitespace(s); do { + /* left recursion until the end of input. */ if (is_end(s)) break; - mark_position(s, rhs_startpos); + /* match implicit whitespace. */ + mark_position(s, whitespace_startpos); + if (space) { + whitespace = match_spaced_rules(s, NULL); + if (!no_error(s)) goto finalize; + } + /* attempt to match rhs. */ tokrhs = match_expression(s, e->rhs); - if (no_match(s)) + /* if no match, puke the parsed whitespace, if any. */ + if (no_match(s)) { + if (space) { set_position(s, whitespace_startpos); } break; + } if (!no_error(s)) { P4_MatchRaisef(s, P4_MatchError, "expect %s", peek_rule_name(s)); @@ -2256,14 +2266,22 @@ match_left_recursion(P4_Source* s, P4_Expression* e) { mark_position(s, endpos); - if (tokrhs == NULL) { - tokrhs = P4_CreateNode(s->content, rhs_startpos, endpos, e->name); - } - - toktmp = P4_CreateNode(s->content, startpos, endpos, e->name); + /* adopt all lhs,ws,rhs tokens under the hierarchy of e. */ + catch_oom(toktmp = P4_CreateNode(s->content, startpos, endpos, e->name)); P4_AdoptNode(toktmp->head, toktmp->tail, toklhs); + P4_AdoptNode(toktmp->head, toktmp->tail, whitespace); P4_AdoptNode(toktmp->head, toktmp->tail, tokrhs); + whitespace = tokrhs = NULL; + + /* if only one child and e is nonterminal, keep the child. */ + if (is_non_terminal(e) && toktmp->head == toktmp->tail && toktmp->head != NULL) { + toklhs = toktmp->head; + toktmp->head = NULL; + P4_DeleteNode(s->grammar, toktmp); + } + /* on next loop, we will use the parsed inputs as lhs and + * attempt to match rhs, if any. */ toklhs = toktmp; } while (true); @@ -2271,6 +2289,8 @@ match_left_recursion(P4_Source* s, P4_Expression* e) { finalize: P4_DeleteNode(s->grammar, toklhs); + P4_DeleteNode(s->grammar, whitespace); + P4_DeleteNode(s->grammar, tokrhs); return NULL; } From 01f94c29673aebb940dab9d63f96ada86bb4913c Mon Sep 17 00:00:00 2001 From: soasme Date: Thu, 26 Aug 2021 21:07:25 +1200 Subject: [PATCH 5/5] match: support nonterminal for left_recursion. --- peppa.c | 9 +++--- tests/test_left_recursion.c | 58 +++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/peppa.c b/peppa.c index 177a7aa..8d3178b 100644 --- a/peppa.c +++ b/peppa.c @@ -2277,12 +2277,13 @@ match_left_recursion(P4_Source* s, P4_Expression* e) { if (is_non_terminal(e) && toktmp->head == toktmp->tail && toktmp->head != NULL) { toklhs = toktmp->head; toktmp->head = NULL; + toktmp->tail = NULL; P4_DeleteNode(s->grammar, toktmp); + } else { + /* on next loop, we will use the parsed inputs as lhs and + * attempt to match rhs, if any. */ + toklhs = toktmp; } - - /* on next loop, we will use the parsed inputs as lhs and - * attempt to match rhs, if any. */ - toklhs = toktmp; } while (true); return toklhs; diff --git a/tests/test_left_recursion.c b/tests/test_left_recursion.c index 9d77354..aa78ef8 100644 --- a/tests/test_left_recursion.c +++ b/tests/test_left_recursion.c @@ -247,6 +247,63 @@ void test_match_left_recursion_spaced(void) { P4_DeleteGrammar(grammar); } +/** + * Grammar: + * @nonterminal entry = A | entry ("++" / minus A); + * A = "a"; + * minus = "-"; + * Input: + * a++ + */ +void test_match_left_recursion_nonterminal(void) { + P4_Grammar* grammar = P4_CreateGrammar(); + TEST_ASSERT_NOT_NULL(grammar); + TEST_ASSERT_EQUAL( + P4_Ok, + P4_AddLeftRecursion(grammar, "entry", + P4_CreateReference("A"), + P4_CreateChoiceWithMembers(2, + P4_CreateLiteral("++", true), + P4_CreateSequenceWithMembers(2, + P4_CreateReference("minus"), + P4_CreateReference("A") + ) + ) + ) + ); + TEST_ASSERT_EQUAL(P4_Ok, P4_SetGrammarRuleFlag(grammar, "entry", P4_FLAG_NON_TERMINAL)); + TEST_ASSERT_EQUAL( + P4_Ok, + P4_AddLiteral(grammar, "A", "a", true) + ); + TEST_ASSERT_EQUAL( + P4_Ok, + P4_AddLiteral(grammar, "minus", "-", true) + ); + P4_Source* source = P4_CreateSource("a++-a", "entry"); + TEST_ASSERT_NOT_NULL(source); + TEST_ASSERT_EQUAL( + P4_Ok, + P4_Parse(grammar, source) + ); + + P4_Node* node = P4_GetSourceAst(source); + TEST_ASSERT_NOT_NULL(node); + ASSERT_EQUAL_NODE_STRING("a++-a", node); + + TEST_ASSERT_NOT_NULL(node->head); + ASSERT_EQUAL_NODE_STRING("a", node->head); /* ++ is dropped by nonterminal. */ + + TEST_ASSERT_NOT_NULL(node->head->next); + ASSERT_EQUAL_NODE_STRING("-", node->head->next); + + TEST_ASSERT_NOT_NULL(node->tail); + ASSERT_EQUAL_NODE_STRING("a", node->tail); + + P4_DeleteSource(source); + P4_DeleteGrammar(grammar); +} + /** Because lifted eliminates the hierarchy of left recursion. * which in essence makes the expression become (lhs rhs*). * */ @@ -280,6 +337,7 @@ int main(void) { RUN_TEST(test_match_left_recursion_references); RUN_TEST(test_match_left_recursion_sequence_on_rhs); RUN_TEST(test_match_left_recursion_spaced); + RUN_TEST(test_match_left_recursion_nonterminal); RUN_TEST(test_left_recursion_cannot_use_with_lifted); return UNITY_END();