Hi P::RD lovers and FAQ maintainer(s),


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



Reply via email to