Day 12 – Building a flexible grammar

Mrs Santa has written a basic grammar to match the simple lists that GDPR ignorant elves are collecting from around the world about who has been naughty or nice this year.

Each record is a name, followed by a tab, followed by an address, followed by a tab, followed by an assessment of naughty or nice and then finishes with a newline.

Batman 1 Batman Street, Gotham Nice
Joker 5 Joker Street, Arkham Naughty
Riddler 12 Riddler Street, Arkham Naughty
Robin 2 Robin Street, Gotham Nice

She wants to filter off the Naughty people into one list and the Nice people into another, as Krampus is going to deal with all the naughty people this year.

Mrs S. starts with a grammar like this:

grammar naughty-nice-list {
     #|{ Find one or more records made up of name,
     address, assessment (defined below)}
     token TOP { <details>+ }
    
     #| Find the elements from below, in this order
     token details { <name> <address> <assessment> }
    
     #| Find any characters up to the earliest tab
     token name { .*? \t }
    
     #| Find any characters up to the earliest tab
     token address { .*? \t }
    
     #|{ Find either 'Naughty' or 'Nice' followed
     by a newline}
     token assessment { Naughty|Nice \n }
}

And calls it on the list like this:

naughty-nice-list.parsefile("./list.txt");

But, of course, she has to do something to put the details into separate lists.

For this, she creates an action class:

class santa-list-actions {
  #| Create a private hash for this class
  has %!filtered-lists;
  
  #| Create a method to return our hash to the user
  method show { return %!filtered-lists } 

  #|{ This method is automatically called when the token
  with the same name makes a match}
  method details ($/) {
    
      #|{ Create an array of just the name and address matches
      converted to strings}
      my @details.push($<name>.Str, $<address>.Str);
	
      #|{ Push the @details array into an array accessed
      with the 'Naughty' or 'Nice' key
      Note the curly braces to interpolate { $<assessment> }
      instead of < $<assessment> >
      Otherwise we would get literally
      what we typed for the hash key.}
      %!filtered-lists{ $<assessment>.Str }.push(@details);

  };
      
};

She uses it like this:

    my $actions = santa-list-actions.new;

    #|{ As Mrs S. called the object 'actions', the same as the keyword,
    she could write :$actions instead of actions=>$actions}
    naughty-nice-list.parsefile("./list.txt", actions=>$actions); 
  
    my %hash-naughty-nice = $actions.show;

Mrs Santa is quite cheerful that she now has a hash table that has keys of ‘Naughty’ and ‘Nice’ each containing an array of the arrays of everyone’s details.

But there’s always a polar bear paw in the fishing hole and, despite Santa’s assurances, the elves from around the world don’t put just ‘Naughty’ or ‘Nice’. They put it in their own language!

And Mrs Santa had specifically asked, but Santa had been adamant. Only ‘Naughty’ or ‘Nice’. But there are lists looking like this.

Batman 1 Bat Street, Gotham Nice
Joker 5 Joker Street, Arkham Naughty
Riddler 12 Riddler Street, Arkham Naughty
Robin 2 Robin Street, Gotham Nice
El Gato Negro 1 Gato Street, South Texas Bueno
Victor Mancho 3 Mancho Street, New York City Malo

Mrs Santa briefly considers just hard-coding the new words in, but she knows this is not the time for laziness. There are elves in every corner of the globe and she needs things to be able to evolve.

So, to call her script now, she creates two arrays and passes them into the grammar:

    my @nice = ['Nice', 'Bueno'];
    
    my @naughty = ['Naughty', 'Malo'];
    
    naughty-nice-list.parsefile("./list.txt", args=>(@nice, @naughty), actions=>$actions);

She changes the grammar like this to use the new arrays:


    grammar naughty-nice-list {
    
    #|{ Create dynamic arrays with the passed in arrays,
    available throughout the grammar}
    token TOP (@*nice-words, @*naughty-words) { <details>+ } 
    
    token details { <name> <address> <assessment> }
	  
    token name { .*? \t }

    token address { .*? \t }
	  
    #|{ Find either a word from the naughty-words array
    or from the nice-words array followed by a newline}
    token assessment { @*naughty-words|@*nice-words \n } 
}

But Mrs S. realises that she is going to end up creating lots of different keys in her hash table in her actions class now. Keys will be ‘Nice‘, ‘Naughty‘, ‘Bueno‘ or ‘Malo‘ as these will be the matching words that $<assessment>.Str might return (with more coming in the future, in all probability).

So she makes another change, naming the potential matches in the grammar for the assessment token:

    #| Mrs S. has now added names to the potential matches
    token assessment { $<Naughty>=@*naughty-words|$<Nice>=@*nice-words \n } 

Inside the action class there have to be changes to accommodate this. Using make and made, Mrs Santa will store the name of the appropriate match:


    class santa-list-actions {

        has %!filtered-lists;

	method show { return %!filtered-lists };

	method details ($/) {

	    my @details.push($<name>.Str, $<address>.Str);
    
	    #|{ This will now use the value from 'assessment.made' as the key,
	    rather than the match in 'assessment.Str'}
	    %filtered-lists{ $<assessment>.made }.push(@details); 

	};

	method assessment ($/) {

 	    #| If the named pattern 'Naughty' matched...
            if $<Naughty> { 

	      #| ... set assessment.made to "Naughty"
	      make "Naughty" 

	    }

	    #| Or if the named pattern 'Nice' matched...
	    elsif $<Nice> { 

	      #| ... set assessment.made to "Nice"
	      make "Nice" 

	    };

	};
	    
};

Once Mrs Santa captures the data into her own hash, she can easily check that Victor Mancho, who has been Malo this year, has made it to the right list:

    #| Produces the output 'Victor Mancho'
    say %hash-naughty-nice<Naughty>[2][0];

So now Mrs Santa can add any new translations of ‘Naughty’ or ‘Nice’ to the relevant array without touching the grammar at all.

Mrs Santa finds herself quite pleased with the flexibility of Perl 6 grammars. Less so with Santa’s research of the problem in the first place…but she knows she’s well on the way to making sure everyone either gets a present or has eggs thrown against their windows this yuletide.

One thought on “Day 12 – Building a flexible grammar

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.