Skip to content

Commit

Permalink
Merge pull request #113 from soasme/leftrecur
Browse files Browse the repository at this point in the history
Left Recursion
  • Loading branch information
soasme committed Aug 26, 2021
2 parents 2e35ede + 5c06974 commit dfe811a
Show file tree
Hide file tree
Showing 7 changed files with 823 additions and 7 deletions.
62 changes: 60 additions & 2 deletions peppa.c
Expand Up @@ -617,6 +617,7 @@ struct P4_Expression {
struct {
P4_Expression* lhs;
P4_Expression* rhs;
P4_String recur_ref;
};
};
};
Expand Down Expand Up @@ -881,6 +882,7 @@ P4_PRIVATE(P4_Error) P4_PegEvalRange(P4_Node* node, P4_Result* result
P4_PRIVATE(P4_Error) P4_PegEvalMembers(P4_Node* node, P4_Expression* expr, P4_Result* result);
P4_PRIVATE(P4_Error) P4_PegEvalSequence(P4_Node* node, P4_Result* result);
P4_PRIVATE(P4_Error) P4_PegEvalChoice(P4_Node* node, P4_Result* result);
P4_PRIVATE(P4_Error) P4_PegEvalLeftRecursion(P4_Node* node, P4_Result* result);
P4_PRIVATE(P4_Error) P4_PegEvalPositive(P4_Node* node, P4_Result* result);
P4_PRIVATE(P4_Error) P4_PegEvalNegative(P4_Node* node, P4_Result* result);
P4_PRIVATE(P4_Error) P4_PegEvalRepeat(P4_Node* node, P4_Result* result);
Expand Down Expand Up @@ -2256,6 +2258,7 @@ match_left_recursion(P4_Source* s, P4_Expression* e) {
/* if no match, puke the parsed whitespace, if any. */
if (no_match(s)) {
if (space) { set_position(s, whitespace_startpos); }
rescue_error(s);
break;
}

Expand Down Expand Up @@ -2684,6 +2687,7 @@ P4_CreateLeftRecursion(P4_Expression* lhs, P4_Expression* rhs) {
expr->name = NULL;
expr->lhs = lhs;
expr->rhs = rhs;
expr->recur_ref = NULL;
return expr;
}

Expand Down Expand Up @@ -3417,6 +3421,7 @@ P4_DeleteExpression(P4_Expression* expr) {
case P4_LeftRecursion:
P4_DeleteExpression(expr->lhs);
P4_DeleteExpression(expr->rhs);
if (expr->recur_ref) P4_FREE(expr->recur_ref);
default:
break;
}
Expand Down Expand Up @@ -3938,13 +3943,24 @@ P4_Grammar* P4_CreatePegGrammar () {
));
catch_err(P4_SetGrammarRuleFlag(grammar, "insensitive", P4_FLAG_TIGHT));

catch_err(P4_AddSequenceWithMembers(grammar, "left_recursion", 2,
P4_CreateReference("choice"),
P4_CreateZeroOrOnce(P4_CreateSequenceWithMembers(4,
P4_CreateLiteral("|", true),
P4_CreateCut(),
P4_CreateReference("reference"),
P4_CreateReference("choice")
))
));
catch_err(P4_SetGrammarRuleFlag(grammar, "left_recursion", P4_FLAG_NON_TERMINAL));

catch_err(P4_AddJoin(grammar, "choice", "/", "sequence"));
catch_err(P4_SetGrammarRuleFlag(grammar, "choice", P4_FLAG_NON_TERMINAL));

catch_err(P4_AddOnceOrMore(grammar, "sequence", P4_CreateReference("repeat")));
catch_err(P4_SetGrammarRuleFlag(grammar, "sequence", P4_FLAG_NON_TERMINAL));

catch_err(P4_AddReference(grammar, "expression", "choice"));
catch_err(P4_AddReference(grammar, "expression", "left_recursion"));
catch_err(P4_SetGrammarRuleFlag(grammar, "expression", P4_FLAG_LIFTED));

catch_err(P4_AddReference(grammar, "name", "reference"));
Expand Down Expand Up @@ -4304,6 +4320,36 @@ P4_PegEvalChoice(P4_Node* node, P4_Result* result) {
return err;
}

P4_PRIVATE(P4_Error)
P4_PegEvalLeftRecursion(P4_Node* node, P4_Result* result) {
P4_Error err = P4_Ok;
P4_Expression* lhs = NULL;
P4_Expression* rhs = NULL;
P4_String ref = NULL;

catch_err(P4_PegEvalExpression(node->head, result));
lhs = unwrap_expr(result);

if (node->head->next != NULL) {
catch_err(P4_PegEvalRuleName(node->head->next, &ref));
catch_err(P4_PegEvalExpression(node->tail, result));
rhs = unwrap_expr(result);

catch_oom(result->expr = P4_CreateLeftRecursion(lhs, rhs));
result->expr->recur_ref = ref;
} else {
result->expr = lhs;
}

return P4_Ok;

finalize:
P4_DeleteExpression(lhs);
P4_DeleteExpression(rhs);
P4_FREE(ref);
return err;
}

P4_PRIVATE(P4_Error)
P4_PegEvalPositive(P4_Node* node, P4_Result* result) {
P4_Error err = P4_Ok;
Expand Down Expand Up @@ -4492,6 +4538,15 @@ P4_PegEvalGrammarRule(P4_Node* node, P4_Result* result) {

expr = unwrap_expr(result);
assert(expr != NULL, "failed to eval grammar rule expression");

/* special case: for left recursion, rule name must be identical to recursion referece. */
if (expr->kind == P4_LeftRecursion && strcmp(expr->recur_ref, rule_name) != 0) {
catch_err(
P4_PegError,
P4_EvalRaisef(result, "unmatched left recursion rule name: %s, %s", rule_name, expr->recur_ref)
);
}

expr->name = rule_name;
expr->flag = rule_flag;

Expand Down Expand Up @@ -4611,6 +4666,9 @@ P4_PegEvalExpression(P4_Node* node, P4_Result* result) {
if (strcmp(node->rule_name, "choice") == 0)
return P4_PegEvalChoice(node, result);

if (strcmp(node->rule_name, "left_recursion") == 0)
return P4_PegEvalLeftRecursion(node, result);

if (strcmp(node->rule_name, "positive") == 0)
return P4_PegEvalPositive(node, result);

Expand All @@ -4633,7 +4691,7 @@ P4_PegEvalExpression(P4_Node* node, P4_Result* result) {
return P4_PegEvalBackReference(node, result);

UNREACHABLE();
panicf("Unreachable: node %p is not a peg expression", node);
panicf("Unreachable: node %p (%s) is not a peg expression", node, node->rule_name);
}

P4_PUBLIC P4_Error
Expand Down
8 changes: 5 additions & 3 deletions scripts/check_spec.py
Expand Up @@ -14,7 +14,7 @@ def test_spec():
print("invalid json spec")
exit(1)

failed, total = 0, 0
failed, ignored, total = 0, 0, 0
for spec in specs:
for test in spec['tests']:
total += 1
Expand Down Expand Up @@ -56,7 +56,7 @@ def test_spec():
f"GOT:\n{proc.stderr.decode('utf-8')}\n"
)
failed += 1
else:
elif 'E' in test:
assert proc.returncode != 0, proc.stderr
if proc.stderr.decode('utf-8').strip() != test['E']:
print(
Expand All @@ -66,8 +66,10 @@ def test_spec():
f"GOT:\n{proc.stderr.decode('utf-8')}"
)
failed += 1
else:
ignored += 1

print("total: %d, failed: %d" % (total, failed))
print("total: %d, failed: %d, ignored: %d" % (total, failed, ignored))
if failed:
exit(1)

Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Expand Up @@ -75,6 +75,7 @@ set(
test_insensitive_spec
test_sequence_spec
test_choice_spec
test_left_recursion_spec
test_lookahead_spec
test_cut_spec
test_repeat_spec
Expand Down
5 changes: 4 additions & 1 deletion tests/peppa.peg
Expand Up @@ -19,7 +19,10 @@ decorator = "@" @cut (
name = reference;

@lifted
expression = choice;
expression = left_recursion;

@nonterminal
left_recursion = choice ("|" @cut reference choice)?;

@nonterminal
choice = sequence ("/" sequence)*;
Expand Down

0 comments on commit dfe811a

Please sign in to comment.