http://www.perlmonks.org?node_id=1015645


in reply to Re: Why won't this basic Parse::RecDescent example work?
in thread Why won't this basic Parse::RecDescent example work?

I’m not familiar with Parse::RecDescent, but by reference to the docs plus a bit of trial-and-error I got this to work by adjusting the regex and assigning $1 to a local variable (see the section “Start-up Actions” in Parse::RecDescent):

#! perl use strict; use warnings; use 5.012; use Data::Dumper; use Parse::RecDescent; $::RD_ERRORS = 1; # Parser dies when it encounters an error $::RD_WARN = 1; # Enable warnings- warn on unused rules &c. $::RD_HINT = 1; # Give out hints to help fix problems. #$::RD_TRACE = 1; # if defined, also trace parsers' behaviour our %HASH; my $grammar = <<'END_OF_GRAMMAR'; { my $directory; } startrule: from_clause from_clause: 'from' dir(s) { print "-->$directory<--\n"; $main::HASH{dir} = $directory; } dir: m{ ^ ( .*? / .* ) $ }x { $directory = $1; } END_OF_GRAMMAR my $parser = Parse::RecDescent->new($grammar) or die "Bad grammar!\n +"; defined $parser->startrule("from ./foo") or print "Bad text!\n"; say Dumper(\%HASH);

Output:

18:38 >perl 505_SoPW.pl -->./foo<-- $VAR1 = { 'dir' => './foo' }; 18:51 >

Note that setting $::RD_TRACE = 1; is useful for understanding what the parser is doing.

Hope that helps,

Athanasius <°(((><contra mundum Iustus alius egestas vitae, eros Piratica,

Replies are listed 'Best First'.
Re^3: Why won't this basic Parse::RecDescent example work?
by 7stud (Deacon) on Jan 28, 2013 at 19:55 UTC

    I’m not familiar with Parse::RecDescent,

    Then thanks for being brave enough to take a look!

    by reference to the docs plus a bit of trial-and-error I got this to work by adjusting the regex and assigning $1 to a local variable.

    Nice going! After reading the "Start up Action" section in the docs, I reread the "Action" section, and I noticed this statement:

    The results of named subrules are stored in the hash under each subrule's name (including the repetition specifier, if any)

    So the key I was using in the %item hash, 'dir', was wrong. The key should be 'dir(s)'. So now I can get this output:

    use strict; use warnings; use 5.012; use Parse::RecDescent; $::RD_ERRORS = 1; #Parser dies when it encounters an error $::RD_WARN = 1; #Enable warnings - warn on unused rules &c. $::RD_HINT = 1; # Give out hints to help fix problems. our %HASH; my $grammar = <<'END_OF_GRAMMAR'; startrule : from_clause from_clause : 'from' dir(s) { print "-->@{$item{'dir(s)'}}<-- \n"; $main::HASH{dirs} = @{$item{'dir(s)'}}; } dir : 'hello' END_OF_GRAMMAR my $parser = Parse::RecDescent->new($grammar); $parser->startrule("from hello world"); use Data::Dumper; say Dumper(\%HASH); --output:-- -->hello<-- $VAR1 = { 'dirs' => 1 };

    Partial success! Note the dereference of $item{'dir(s)'}. Now, what is that '1'? The return value from print()? But print() isn't the last statement of the action. If I change the dir rule to:

    dir : 'hello' | 'world'

    I get this output:

    -->hello world<-- $VAR1 = { 'dirs' => 2 };

    Is 2 the count of the words matched? What is going on? I am using the exact same array in each of these lines:

    print "-->@{$item{'dir(s)'}}<-- \n"; $main::HASH{dirs} = @{$item{'dir(s)'}};

    ...yet I am getting different results 'hello world' v. 2! How is that possible? Argghh, of course! Perl doesn't care about giving you the exact same results for any expression you use--because perl determines the result by the context in which the expression appears. In my case, the print() statement supplies list context for the array, and "$main::... =" provides scalar context for the array--and an array provides its length in scalar context.

    So now I can get the expected output:

    use strict; use warnings; use 5.012; use Parse::RecDescent; $::RD_ERRORS = 1; #Parser dies when it encounters an error $::RD_WARN = 1; #Enable warnings - warn on unused rules &c. $::RD_HINT = 1; # Give out hints to help fix problems. our %HASH; my $grammar = <<'END_OF_GRAMMAR'; startrule : from_clause from_clause : 'from' dir(s) { print "-->@{$item{'dir(s)'}}<-- \n"; $main::HASH{dirs} = $item{'dir(s)'}; } dir : 'hello' | 'world' END_OF_GRAMMAR my $parser = Parse::RecDescent->new($grammar); $parser->startrule("from hello world hello"); use Data::Dumper; say Dumper(\%HASH); --output:-- -->hello world<-- $VAR1 = { 'dirs' => [ 'hello', 'world' ] };

    Next up, the regex problem. This doesn't work:

    my $grammar = <<'END_OF_GRAMMAR'; #Start up action(executed in parser namespace): { use 5.012; #So I can use say() } startrule : from_clause from_clause : 'from' dir(s) { say "-->$item[0]<--"; say "-->@{$item[-1]}<--"; } dir : m{/} END_OF_GRAMMAR my $parser = Parse::RecDescent->new($grammar); $parser->startrule("from ./hello"); --output:-- (blank)

    Note that I tried using the @item array this time. The first item in @item is the rule name, "from_clause", and the next items should be the matches for the subrules, so $item[2], or equivalently $item[-1], should be the matches for dir(s). But because I am not even seeing the arrows in my print statement, that means the parser isn't finding a match for my rule.

    I also notice there are weird rules the parser follows for comments. This does not cause an error:

    my $grammar = <<'END_OF_GRAMMAR'; #Start up action(executed in parser namespace): { use 5.012; #So I can use say() } ...

    ...but this does cause an error:

    my $grammar = <<'END_OF_GRAMMAR'; #Start up action(executed in parser namespace): { use 5.012; #So I can use say() } ... --output:-- Unknown starting rule (Parse::RecDescent::namespace000001::startrule) +called at 3.pl line 76.

    Back to the regex problem. It seems that Parse::RecDescent takes the regex pattern and adds a ^ to the beginning of the pattern and adds $ to the end of the pattern. In other words, the regex you specify has to match all of the text you are interested in examining.

    my $grammar = <<'END_OF_GRAMMAR'; #Start up action(executed in parser namespace): { use 5.012; #So I can use say() } startrule : from_clause from_clause : 'from' dir(s) { say "-->$_<--" for @{ $item{'dir(s)'} }; } dir : m{\S* / \S*}xms END_OF_GRAMMAR my $parser = Parse::RecDescent->new($grammar); $parser->startrule("from ./hello hello/world"); --output:-- -->./hello<-- -->hello/world<--

    And reworking my original example:

    use strict; use warnings; use 5.012; use Parse::RecDescent; $::RD_ERRORS = 1; #Parser dies when it encounters an error $::RD_WARN = 1; #Enable warnings - warn on unused rules &c. $::RD_HINT = 1; # Give out hints to help fix problems. our %HASH; my $grammar = <<'END_OF_GRAMMAR'; #Start up action(executed in parser namespace): { use 5.012; #So I can use say() } startrule : from_clause from_clause : 'from' dir(s) { say "-->$_<--" for @{ $item{'dir(s)'} }; $main::HASH{target_dirs} = $item{'dir(s)'}; } dir : m{\S* / \S*}xms END_OF_GRAMMAR my $parser = Parse::RecDescent->new($grammar); $parser->startrule("from ./hello hello/world"); use Data::Dumper; say Dumper(\%HASH); --output:-- -->./hello<-- -->hello/world<-- $VAR1 = { 'target_dirs' => [ './hello', 'hello/world' ] };

    Success! Thanks.