Beefy Boxes and Bandwidth Generously Provided by pair Networks
good chemistry is complicated,
and a little bit messy -LW
 
PerlMonks  

Re^2: How to grab Parse::RecDescent error output in a variable?

by w-ber (Hermit)
on Sep 15, 2008 at 08:07 UTC ( #711402=note: print w/ replies, xml ) Need Help??


in reply to Re: How to grab Parse::RecDescent error output in a variable?
in thread How to grab Parse::RecDescent error output in a variable?

About the use of require in this example... Here is a complete working example that captures Parse::RecDescent output in a dup'd anonymous filehandle, which shows that use and require are not equivalent in this case.

use strict; my $ParseErrorFh; BEGIN { open(my $olderr, '>&STDERR') or die "Cannot dup STDERR: $!"; close STDERR or die "Cannot close STDERR: $!"; open(STDERR, '+>', undef) or die "Cannot open anonymous file: $!"; select STDERR; $| = 1; open($ParseErrorFh, '>&STDERR') or die "Cannot dup anonymous file: + $!"; require Parse::RecDescent; close STDERR or die "Cannot close STDERR: $!"; open(STDERR, '>&', $olderr) or die "Cannot restore STDERR: $!"; } sub parse { my ($grammar, $str) = @_; local $::RD_ERROR = 1; local $::RD_WARN = 2; seek($ParseErrorFh, 0, 0); my $p = Parse::RecDescent->new($grammar) or die "Grammar is invalid"; my $x = $p->start($str); if (not defined $x) { seek($ParseErrorFh, 0, 0); die join '', grep { $_ !~ m/^\s*$/ } <$ParseErrorFh>; } return $x; } print parse('start: /foo/ | <error>', 'fo'), "\n";

Running this you will get the output

$ perl rectest.pl ERROR (line 1): Invalid start: Was expecting /foo/

Now, if you change require at line 13 to use,

$ perl rectest2.pl ERROR (line 1): Invalid start: Was expecting /foo/ Died at recdescent.pl line 34.

Here, the first two lines are printed to STDERR, while the last one indicates that nothing is captured in the dup'd filehandle. Regardless of what the documentation says about use being equivalent to require Module; import Module;, this example shows that it is not the case this time.

Anyway, it would be cleaner to re-open Parse::RecDescent::ERROR.

Yes, it definitely would be. This is what I tried first. Here is a complete example (did you try to run your own example code?):

use strict; use Parse::RecDescent; sub parse { my ($grammar, $str) = @_; open(local *Parse::RecDescent::ERROR, '>', \my $error) or die "Cannot open in-memory filehandle: $!"; local $::RD_ERROR = 1; local $::RD_WARN = 2; my $p = Parse::RecDescent->new($grammar) or die "Grammar is invalid"; my $x = $p->start($str); defined $x or die $error; return $x; } print parse('start: /foo/ | <error>', 'fo'), "\n";

Result:

$ perl recdescent3.pl Undefined format "Parse::RecDescent::ERROR" called at /usr/share/perl5 +/Parse/RecDescent.pm line 2910.

Clearly format has a side-effect that prevents the use of the nice solution this time. Besides that, your re-open of ERROR requires knowledge of the package internals, while redirecting STDERR requires arguably less knowledge, and certainly not the name of a private (albeit package global) variable.

--
say "Just Another Perl Hacker";


Comment on Re^2: How to grab Parse::RecDescent error output in a variable?
Select or Download Code
Re^3: How to grab Parse::RecDescent error output in a variable?
by ikegami (Pope) on Sep 15, 2008 at 12:00 UTC

    Regardless of what the documentation says about use being equivalent to require Module; import Module;, this example shows that it is not the case this time.

    Not true. The documentation doesn't say that. Both the documentation and I said use Module; is equivalent to

    BEGIN { require Module; import Module; }

    And that is clearly the case this time.

    Besides that, your re-open of ERROR requires knowledge of the package internals

    So does knowing STDERR is duped at execution time. Besides, the benefits far outweigh the drawbacks. Well, if it had worked.

    Benefits
    • Works with scalar handles.
    • Much cleaner code in the caller.
    • In fact, one can arrange to have no extra code in the caller since it can be placed in the "grammar".
    • Works even if use Parse::RecDescent; is executed twice.
    • Works with threads.
    Drawbacks
    • Minor reliance on stable PRD guts.
    • Doesn't work. Oops!

    Clearly format has a side-effect that prevents the use of the nice solution this time.

    Ah dang! You could issue the format on the new handle, but that's going pretty far into the inards.

Re^3: How to grab Parse::RecDescent error output in a variable?
by tfrayner (Curate) on Sep 15, 2008 at 13:09 UTC
    Curious; as it happens I had to do this for a project I was working on some months back, and came to the same conclusion as ikegami. My code reads thusly (copied and pasted):
    open( *Parse::RecDescent::ERROR, '>', \(my $parse_error) ) or croak("Error: unable to redirect SDTERR."); $Parse::RecDescent::skip = ' *\x{0} *'; $::RD_ERRORS++; $::RD_WARN++; $::RD_HINT++; my $parser = Parse::RecDescent->new($grammar) or die("Bad grammar! +");
    I can assure you that does actually work for me (perl 5.8.8, P::RD 1.94). My best guess is that your use of local on that open call is maybe creating problems?

    The dependency on P::RD internals always bothered me as well, but I never got around to figuring out a better way to do this.

    Cheers, Tim

    Update: Yup, turns out if you remove that local then it works:
    use strict; use Parse::RecDescent; sub parse { my ($grammar, $str) = @_; open(*Parse::RecDescent::ERROR, '>', \my $error) or die "Cannot open in-memory filehandle: $!"; local $::RD_ERROR = 1; local $::RD_WARN = 2; my $p = Parse::RecDescent->new($grammar) or die "Grammar is invalid"; my $x = $p->start($str); defined $x or die "CAPTURED: $error"; return $x; } print parse('start: /foo/ | <error>', 'fo'), "\n";

      The reason local breaks it is that local replaces the variable in that lexical scope, and the references to P::RD::ERROR in P/RD.pm are outside the lexical scope of the local invocation (I think).

      It's an interesting question though, and IMO the right solution would be to get a patch committed for P::RD that allows the user to specify an alternative filehandle for error output.

        Done, and also received feedback already! Here's the bug report: https://rt.cpan.org/Ticket/Display.html?id=39323

        And here's the solution I was seeking, based on demo/demo_errors.pl:

        use strict; use Data::Dumper; use Parse::RecDescent; # Simple Lisp S-expressions (as an example) my $grammar = q# start: expression(s?) | { die join("\n", map { $_->[0] } @{ $thisparser->{errors} } +)."\n" } expression: /\w+/ | '(' expression(s?) ')' { $item[2] } | <error> #; my $p = Parse::RecDescent->new($grammar); # This will die if there is an error. print Dumper($p->start('(foo (bar))')); # Like so: $p->start('(foo');

        --
        say "Just Another Perl Hacker";

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: note [id://711402]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others surveying the Monastery: (9)
As of 2014-12-21 16:48 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    Is guessing a good strategy for surviving in the IT business?





    Results (106 votes), past polls