Beefy Boxes and Bandwidth Generously Provided by pair Networks
go ahead... be a heretic
 
PerlMonks  

Re: First steps with Marpa::R2 and BNF

by duelafn (Vicar)
on Jan 13, 2021 at 15:01 UTC ( #11126849=note: print w/replies, xml ) Need Help??


in reply to First steps with Marpa::R2 and BNF

I'd suggest separating parsing from evaluation. The first argument to the action subroutines is a hash that you can use for any purpose. In this case, I'd store parts of the specification in there, then return that hash (which makes the convenient default ::= action => ::first work for the Dice_Expression rule). Also, I split modifier_r_comp from modifier_r action, but you could equally merge them and count the number of arguments.

use Marpa::R2; use Data::Dump; my $dsl = <<'END_OF_DSL'; :default ::= action => ::first lexeme default = latm => 1 Dice_Expression ::= Dice_Expression1 | Dice_Expression1 add_modifier Dice_Expression1 ::= Simple_Dice | Simple_Dice x_modifier | Simple_Dice r_modifier Simple_Dice ::= Rolls 'd' Sides action => sim +ple_roll add_modifier ::= '+' Die_Modifier_Val action => mod +ifier_add | '-' Die_Modifier_Val action => mod +ifier_add x_modifier ::= 'x' Die_Modifier_Val action => mod +ifier_x r_modifier ::= 'r' Die_Modifier_Val action => mod +ifier_r | 'r' Die_Modifier_Comp Die_Modifier_Val action => mod +ifier_r_comp Die_Modifier_Val ~ digits Die_Modifier_Comp ~ 'gt' | 'lt' Rolls ~ digits Sides ~ digits digits ~ [\d]+ :discard ~ whitespace whitespace ~ [\s]+ END_OF_DSL my $grammar = Marpa::R2::Scanless::G->new( { source => \$dsl } ); my $input = $ARGV[0] // '6d4x1'; my $parsed = $grammar->parse( \$input, 'My_Actions' ); print "\n\nParsed result: ";dd $parsed; # print "\n\nFinal result: ";dd evaluate_rolls($parsed); sub evaluate_rolls { my $spec = shift; # TODO... } sub My_Actions::modifier_add { my ( $self, $sign, $val ) = @_; $$self{add} = 0 + "$sign$val"; $self; } sub My_Actions::modifier_r { my ( $self, undef, $reroll ) = @_; $$self{r} = $reroll; return $self; } sub My_Actions::modifier_r_comp { my ( $self, undef, $comp, $reroll ) = @_; $$self{comp} = $comp; $$self{r} = $reroll; return $self; } sub My_Actions::simple_roll { my ( $self, $rolls, undef, $sides ) = @_; $$self{rolls} = $rolls; $$self{sides} = $sides; return $self; } sub My_Actions::modifier_x { my ( $self, $modifier, $modifier_val ) = @_; $$self{x} = $modifier_val; return $self; }

Update: Added +/- modifiers

Good Day,
    Dean

Replies are listed 'Best First'.
Re^2: First steps with Marpa::R2 and BNF
by Discipulus (Abbot) on Jan 15, 2021 at 12:55 UTC
    Thanks duelafn,

    I really appreciate your code example as it seems to me a good starting point. I really like the use of $self you exploit to add optional terms to the expression. Now prehaps I understand it better.

    I still do not fully understand your:

    > which makes the convenient default ::= action => ::first work for the Dice_Expression rule

    If you have the patience to expand this further it will help me in the understanding the ::first (see also below my answer to GrandFather).

    Again about optional sub elements:

    So the only way to specify somenthing optional is:

    Dice_with_modifier_r ::= Simple_Dice 'r' Die_Modifier_Val action => mo +difier_r |Simple_Dice 'r' Die_Modifier_Comp Die_Modifie +r_Val action => modifier_r

    Right? there is no  <Optional> syntax to play with?

    I tried a single rule like:

    Dice_with_modifier_r ::= Simple_Dice 'r' <Die_Modifier_Comp>* Die_Modifier_Val action => modifier_r

    as found at the end of this post but it throws the error:

    Parse of BNF/Scanless source failed Error in SLIF parse: No lexeme found at line 11, column 61 * String before error: modifier_r ::= Simple_Dice 'r' <Die_Modifier_Co +mp> * The error was at line 11, column 61, and at character 0x002a '*', .. +. * here: * Die_Modifier_Val action => modifier_r\n\n\nSimpl Marpa::R2 exception at marpa07.pl line 41. Marpa::R2 exception at marpa07.pl line 41.

    Why on marpa-for-building-parsers there is: declaration ::= assignment* action => doResult

    and I cannot do

    Dice_with_modifier_r ::= Simple_Dice 'r' Die_Modifier_Comp* Die_Modifi +er_Val action => modifier_r Die_Modifier_Comp ~ 'gt' | 'lt'

    L*

    There are no rules, there are no thumbs..
    Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.

      Late responding, so you may have some of this figured out, but...

      default ::first: First is just a subroutine which returns the result of the first token. That is: sub ::first { return $_[1] }. Thus, Dice_Expression returns whatever Dice_Expression1 returns which returns whatever Simple_Dice returns which happens to be our "$self". After posting, I decided that if I were doing it, I would probably avoid using the default action and instead do something like:

      root ::= Dice_Expression action => finish Dice_Expression ::= ... ... same as above ... sub My_Actions::finish { my $self = shift; # ... additional cleanup or else change whole sub to just return $ +_[0] return $self }

      The advantage being that this is explicit and gives a nice hook for modifying the final result just before returning it.

      Optionals For the most part, yes, I spell it out. The "*" syntax has a big limitation: The RHS alternative must consist of a single RHS primary. which means only "NAME ::= ITEM*" rules. You can't have multiple things on the right hand side. So, it would look like:

      r_modifier ::= 'r' Die_Modifier_Comp Die_Modifier_Val action +=> modifier_r_comp Die_Modifier_Comp ::= Die_Modifier_Comp_Toke* action +=> ::first Die_Modifier_Comp_Toke ~ 'gt' | 'lt'

      The star has to move to its own rule by itself (and then we renamed the token rule). Of course, that rule won't do what you want since it doesn't limit the number of "Die_Modifier_Comp_Toke". You could instead, use an empty rule to achieve a 0-or-1 match:

      r_modifier ::= 'r' Die_Modifier_Comp Die_Modifier_Val action = +> modifier_r_comp Die_Modifier_Comp ::= Die_Modifier_Comp_Toke action = +> ::first | None Die_Modifier_Comp_Toke ~ 'gt' | 'lt' None ::=

      That seems to work, but requires "None" to be a "::=" rule, a "~" rule throws an error. I'm not sure if that means it is an abuse of syntax to do that or not, but if it works and doesn't seem to slow down the parsing, I'd say go for it. It will leave an undef in the Die_Modifier_Comp slot.

      Good Day,
          Dean

Log In?
Username:
Password:

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

How do I use this? | Other CB clients
Other Users?
Others contemplating the Monastery: (8)
As of 2021-02-26 17:05 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found

    Notices?