Search code examples
actiongrammarraku

Raku grammar action throwing "Cannot bind attributes in a Nil type object. Did you forget a '.new'?" error when using "make"


I have this method in a class that's throwing a Cannot bind attributes in a Nil type object. Did you forget a '.new'?

method parse() {
    grammar FindHeaders {
        token TOP { [<not-header> | <header>]+ $ }
        token not-header { ^^ <![#]> \N* \n }
        token header { ^^ '#'{ 1 .. 6 } <content> \n }
        token content { \N+ }
    }
    class HeaderActions {
        method content($match) {
            return if $match ~~ m/^^\#\s+<[A..Z]>+e*s/ || $match !~~ m/<[a..z]>/;
            return if $match ~~ m/\|/ && ( $match ~~ m:i/project/ || $match ~~ m:i/\+\w/ );
            my $tc = Lingua::EN::Titlecase.new($match);
            my $new_title = $tc.title;
            make($new_title);
        }
    }

    my $t = $!content;
    FindHeaders.parse($t, actions => HeaderActions.new);
}

As far as I can tell, this code matches what's in the official documentation. So not sure why I'm getting this error. I have no idea what attribute or Nil object the compiler is referring to. If I comment out the line with the make method, everything works fine.


Solution

  • method content($match) {

    There's a reason that action methods typically use $/ as the argument name: because the make function looks for $/ in order to associate the provided object to it. You can use $match, but then need to call the make method on that instead:

    $match.make($new_title);
    

    The mention of Nil is because the failed match earlier in the action method resulted in $/ being set to Nil.

    I guess you avoided the more idiomatic $/ as the parameter of the action method because it gets in the way of doing further matching in the action method. Doing further matching in action methods means that the text is being parsed twice (once in the grammar and again the action), which is not so efficient, and usually best avoided (by moving the parsing work into the grammar).

    As a final style point, declaring grammars and action classes in a method is neat encapsulation if they are only used there, but it would be wise to my scope them (my grammar FindHeaders { ... }), otherwise they shall end up installed in the nearest enclosing package anyway.