I stumbled over a piece of code in the FAQ belonging to this topic:
Commit in subrule which is optional in rule ============================================
The question was, how to fail a parent rule when an optional subrule has already commited but fails after commitment.
Damian suggests a negative look-ahead following the optional subrule production, which works if you have an { action } and not just the default return result.
Later on in this chapter there is an optimization example by Marcel Grunaer which isn't working correctly IMHO:
Marcel went on to point out an optimization:
another option would be the use of a rulevar:
myrule : <rulevar: local $failed> myrule : 'stuff' mysubrule(?) <reject:$failed>
mysubrule: ID <commit> '[' ']' | <error?> { $failed++ }
the rule 'myrule' should fail if the subrule 'mysubrule' has already commited. This will not happen, let me explain:
Case 1: the subrule 'mysubrule' fails before <commit>
the production '| <error?> { $failed++ }' returns 0 (not undef!) for the following reasons:
<error?> returns 0, since it wasn't commited (see below) { $failed++ } returns 0, since it's a postincrement of an formerly undefined value.
The subrule matches, but the parent rule isn't successful the <reject: $failed> matches. This is not the intended behavior, the myrule should match as the subrule didn't <commit>
Case 2: the subrule 'mysubrule' fails after <commit>
the production '| <error?> { $failed++ }' comes to the directive <error?>, this directive matches and returns undef as a <error..> directive should do. This means, you will never come to the { $failed++ } action. The subrule fails, the rule is successful since we have the optional (?) and $failed is still not set.
The usual ' | <error?> <reject> ' pattern will have misled Marcel and all other FAQ readers until now, because this pretends that after a successful <error?> directive the subrule is continued. But this isn't correct, the <reject> directive is needed for uncommited errors.
Hmmm, you will ask, why that, we just hit this production only if we are commited since the first directive is <error?>.
No, when an <error> OR <error?> is the first directive in a production, an implicit <uncommit> is fired.
Sure, it's difficult but useful and don't forget it's from Damian ;-)
Just in case, see my attached code, this is the last source of truth if Damian has no time to follow this mailinglist:
<<<<<<<<<<<<<<<<<<<<<<<< snip >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> #!/usr/local/bin/perl
use strict; use warnings; use Parse::RecDescent; $::RD_TRACE = 1; use Data::Dumper;
my $grammar = <<'EOG'; myrule : <rulevar: local $failed> myrule : mysubrule(?) <reject:$failed> { $return = 'success!' } mysubrule : 'ID' <commit> '[' ']' | <error?> { $failed++ } EOG
my $parser = Parse::RecDescent->new($grammar) or die "can't create parser,";
my $text = join '', <>; print Dumper($parser->myrule($text)); <<<<<<<<<<<<<<<<<<<<<<<< snip >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
Best Regards Charly
-- Karl Gaissmaier KIZ/Infrastructure, University of Ulm, Germany Email:[EMAIL PROTECTED] Service Group Network Tel.: ++49 731 50-22499