Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Left Recursion #113

Merged
merged 5 commits into from Aug 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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