Search code examples
parsinggrammarraku

Is it possible to lift a made value to Grammar TOP?


I have this raku Grammar:

#!/usr/bin/env raku
use v6.d;

use Grammar::Tracer;

grammar Grammar {
    token TOP {
          <city>      \v
          <state-zip> \v?
    }
    token city {
        ^^ \V* $$
    }
    token state-zip {
        ^^ <state> <.ws>? <zipcode> $$    #<.ws> is [\h* | \v]
    }
    token state {
        \w \w
    }
    token zipcode {
        \d ** 5
    }
}

class Actions {
    method TOP($/) {
        #works
        say "city is ",    $_ with $<city>.made;
        say "state is ",   $_ with $<state-zip><state>.made;
        say "zipcode is ", $_ with $<state-zip><zipcode>.made;

        #doesn't work
        say "state2 is ",   $_ with $<state>.made;
        say "zipcode2 is ", $_ with $<zipcode>.made;
    }

    method city($/)     { make ~$/ }
    method state($/)    { make ~$/ }
    method zipcode($/)  { make ~$/ }
}

my $address = q:to/END/;
Springfield,
IL 62704
END

Grammar.parse($address, :actions(Actions));

It works great:

TOP
|  city
|  * MATCH "Springfield,"
|  state-zip
|  |  state
|  |  * MATCH "IL"
|  |  zipcode
|  |  * MATCH "62704"
|  * MATCH "IL 62704"
city is Springfield,
state is IL
zipcode is 62704
* MATCH "Springfield,\nIL 62704\n"

token <state-zip> is there to let the state and zip co-reside on a single line or to span multiple lines. Bear in mind that this is an MRE so I am looking for answers that do not alter the Grammar, but the make / made aspects of the Actions.

BUT - to streamline my code, I would like to be able to access the state and zipcode at the top level from the method TOP($/) {} action per my examples state2 and zipcode2. I have been trying to "lift" the made values of state and token up the match tree - for example maybe something like:

# doesn't work
method TOP($/) {
    make $<state>.made;
    make $<zip>.made;
    ...
}
method state-zip($/) {
    make $<state>.made;
    make $<zip>.made;
}

Is this possible?


Solution

  • TL;DR You can't modify a Match object's captures. There are three things I can think of that you might be able to do instead: Ⓐ Modify the match object's .made attribute and/or Ⓑ Modify $/ to use its shortcuts and/or Ⓒ Clean it up?

    You can't modify a Match object's captures

    What you're asking about is a natural thing to want to do. I recall wanting it myself.

    In my answer to How do I mutate captures in a grammar?), I implied that you can't mutate a Match object's captures. And that is true currently in Raku(do). But I now wonder if something could be done. I'll return to that in the last section of this answer.

    Modify the match object's .made attribute

    What I suggest for now is that you use the match object's .made attribute in your method TOP as follows.

    Keep your original code:

            #works
            say "city is ",    $_ with $<city>.made;
            say "state is ",   $_ with $<state-zip><state>.made;
            say "zipcode is ", $_ with $<state-zip><zipcode>.made;
    

    Insert code to create dictionary and then make it:

            my %hash;        
            %hash<state> = $_ with $<state-zip><state>.made;
            %hash<zipcode> = $_ with $<state-zip><zipcode>.made;
            make %hash if %hash;
    

    Keep your original code but draw from match object's .made:

            #works
            say "state2 is ",   $_ with $/.made<state>;
            say "zipcode2 is ", $_ with $/.made<zipcode>;
    

    Modify $/ to use its shortcuts

    NB. This section is about modifying (assigning/binding) the symbol (variable) $/ so that it refers to a different object/value. It is not about modifying an object/value that has been assigned or bound to $/.

    The following is a combination of the code from the first section above with a couple tweaks. This way your code can still make use of $/'s usual shortcuts.¹

    Add an is copy trait so the $/ parameter can be reassigned:

        method TOP($/ is copy) {
            ...
    

    Keep the rest of the code the same up until the make line:

            ...
            make %hash if %hash;
    

    Insert a reassignment (or binding) of $/ to its .made payload:

            $/ .= made;
    

    Now your code can switch back to the usual shortcuts:

            say "state2 is ",   $_ with $<state>;
    

    NB. When you reassign/bind $/ you override the original binding to the original match object. So this stops working:

            say "city is ",    $_ with $<city>;
    

    In the last section of this answer I sketch out possible solutions for this problem.

    Clean it up?

    Perhaps Match objects could be furnished with new methods that allow users to add aliases to existing captures in match objects to other match objects provided they were constructed based on the same input string?

    A more hackish idea is creating new class that's like an empty mutable Hash (so can have keys added to it) but is initialized with an existing Match object that is bound to a delegating attribute (has Match $original handles **;). The latter would handle any lookups that fail to match a key in the hash by failing over to do the lookup on the existing Match object. (I'm not quite sure that's the right handles expression but want to post this update so I can go to sleep.)

    Footnotes

    ¹ Any data can be assigned or bound to $/, not just Match objects. It's always the case that $0 is aliased to $/[0], $1 to $/[1] and $<foo> to $/<foo> and $<bar> to $/<bar> and so on, no matter what has been assigned or bound to $/. Indeed, for many years I have been using $/ for what I refer to in my head as "the data variable", short for "data (de)structure" variable, such that "current match object" is just one of "the data variable"'s many uses.