Today's example constructs a grammar for tracking playing cards in a single deal. We'll say it's poker with one or more players and that each player is being dealt a hand that contains exactly five cards.
The "almost" part is the need to track cards and detect duplicates. We want to check for repeated cards both within each card-hand and between hands.
To start with, here's the basic grammar (no duplicate checks yet):
grammar CardGame {
rule TOP { ^ <deal> $ }
rule deal {
<hand>+ % ';'
}
rule hand { [ <card> ]**5 }
token card {<face><suit>}
proto token suit {*}
token suit:sym<♥> {<sym>}
token suit:sym<♦> {<sym>}
token suit:sym<♠> {<sym>}
token suit:sym<♣> {<sym>}
token face {:i <[2..9]> | 10 | j | q | k | a }
}
say CardGame.parse("2♥ 5♥ 7♦ 8♣ 9♠");
say CardGame.parse("2♥ a♥ 7♦ 8♣ j♥");
Our top-level rule is a deal
. The deal
consists of one or more hand
s separated by ';'. Each hand
consists of 5 playing card
s.
Each card is represented by a face
, one of: a
(ace), j
(jack), q
(queen) or k
(king), or 2
to 10
followed by a suite: ♥ (hearts) ♦ (diamonds) ♣ (clubs) or ♠ (spades). [We could have used the playing cards characters, newly introduced in Unicode 6.0, but these aren't widely supported yet].
As expected, the first cut of the grammar cheerily parses hands and games with duplicate cards.
say CardGame.parse("a♥ a♥ 7♦ 8♣ j♥"); # one hand, duplicate a♥
say CardGame.parse("a♥ 7♥ 7♦ 8♣ j♥; 10♥ j♥ q♥ k♥ a♥"); # two hands, duplicate j♥
Now to add the duplicate check. We start by adding a Perl 6 variable declaration to the grammar:
rule deal {
:my %*PLAYED = ();
<hand>+ % ';'
}
This declares %*PLAYED
[1]. It's %*
twigil indicates declared it as a hash (%
) that's dynamically scoped (*
).
Dynamic scoping is not only for subroutine and method calls [1]. It also works seamlessly with grammar rules and tokens . %*PLAYED
is available to the hand
rule called from the game
and therefore to the card
token, which it calls.
This includes any actions being called. So we can track and report on duplicates by adding an action method on card
:
class CardGame::Actions {
method card($/) {
my $card = $/.lc;
say "Hey, there's an extra $card"
if %*PLAYED{$card}++;
}
}
my $a = CardGame::Actions.new;
say CardGame.parse("a♥ a♥ 7♦ 8♣ j♥", :actions($a)); # one hand, duplicate a♥
# 'Hey there's an extra a♥'
say CardGame.parse("a♥ 7♥ 7♦ 8♣ j♥; 10♥ j♥ q♥ k♥ a♦", :actions($a)); # two hands, duplicate j♥
# 'Hey there's an extra j♥'
And that might be all that's needed most of the time for tracking and reporting on duplicates. There's a pretty good separation between the declarative grammar and procedural actions, with just one dynamic variable for tracking duplicates.
I had a situation where I wanted duplicate checking to be a parsing constraint. Parsing needed to fail when duplicates were encountered.
This was achieved by moving the duplicate check grammar side:
token card {<face><suit>
<?{
# only allow each card to appear once
my $card = $/.lc;
say "Hey, there's an extra $card"
if %*PLAYED{$card};
! %*PLAYED{$card}++;
}>
}
We've introduced a code assertion delimited by <?{
and }>
[2]. The rule succeeds when the code evaluates to a True value. The card
token thus fails when the same card is detected more than once in a single deal.
say CardGame.parse("2♥ 7♥ 2♦ 3♣ 3♦"); # legitimate
# parses
say CardGame.parse("a♥ a♥ 7♦ 8♣ j♥"); # one hand, duplicate a♥
# fails with message: Hey, there's an extra a♥
say CardGame.parse("a♥ 7♥ 7♦ 8♣ j♥; 10♥ j♥ q♥ k♥ a♦"); # two hands, duplicate j♥
# fails with message: Hey, there's an extra j♥
This last technique works fully on rakudo/parrot today and will hopefully be available on the jvm in the near future.
There is one thing to be careful of with this type of technique, back-tracking (trying of alternatives). Ideally grammars should be as simple as possible and minimise back-tracking.
If in any doubt, I recommend using on or more of the Grammar::Debugger, Grammar::Tracer [3] or the Rakudo::Debugger [4] modules [5] to track what's happening.
That concludes the exercise for today; a simple Perl 6 Grammar to parse playing-cards in a card-game, but with duplicate checks using either actions or code assertions.
Well say ->
We'll say ?
The deal consists of one or hands ->
The deal consists of one or more hands
♥ (hearts) ♦ (diamonds) ♣ (clubs) and ♠ (spades). ->
♥ (hearts) ♦ (diamonds) ♣ (clubs) or ♠ (spades).
token thus fails when their are any ->
token thus fails when there are any
If in any doubt, I recommed ->
If in any doubt, I recommend
using the Grammar::Debugger or Grammar::Tracer module [3] [4] to track what's happening.
Or the debugger?