6

I'm only beginning to study classes, so I don't understand the basics.

I want a method to construct regex using attributes of the object:

class TEST {
    has Str $.str;

    method reg {
        return 
            rx/
               <<
               <[abc]> *
               $!str
               <!before foo>
              /;
    }         
}   

my $var = TEST.new(str => 'baz');
say $var.reg;

When trying to run this program, I get the following error message:

===SORRY!=== Error while compiling /home/evb/Desktop/p6/e.p6
Attribute $!str not available inside of a regex, since regexes are methods on Cursor.
Consider storing the attribute in a lexical, and using that in the regex.
at /home/evb/Desktop/p6/e.p6:11
------>                <!before foo>⏏<EOL>
    expecting any of:
        infix stopper

So, what's the right way to do that?

jjmerelo
  • 22,578
  • 8
  • 40
  • 86
Eugene Barsky
  • 5,780
  • 3
  • 17
  • 40
  • 2
    You probably need to build a closure with a lexical variable. See [Method returning a regex in Perl 6?](https://stackoverflow.com/q/40883160/2173773) for more information – Håkon Hægland Apr 22 '18 at 08:34
  • 1
    @HåkonHægland Thanks. I'd rather use `EVAL`. :) – Eugene Barsky Apr 22 '18 at 08:55
  • 2
    In case the error message isn't clear... A regex is a method. Declaring a regex as you do above does the equivalent of attempting to add an anonymous method to a [`Match`](https://docs.perl6.org/type/Match) object. (The mention of `Cursor` is outdated -- `Cursor` =:= `Match`). So `$!str` refers to an attribute in `Match` -- but `Match` has no attributes. So you get a compile-time failure. – raiph Apr 22 '18 at 10:06

2 Answers2

9

Looks like this would work:

class TEST {
    has Str $.str;

    method reg {
        my $str = $.str;
        return 
            regex {
               <<
               <[abc]> *
               $str
               <!before foo>
               }
    }         
}   

my $var = TEST.new(str => 'baz');
say $var.reg;
say "foo" ~~ $var.reg;
say "<<abaz" ~~ $var.reg

You are returning an anonymous regex, which can be used as an actual regex, as it's done in the last two sentences.

jjmerelo
  • 22,578
  • 8
  • 40
  • 86
  • 1
    Thanks, it works! Is there any advantage of using an anonymous regex `regex {}` over regex `rx//`? – Eugene Barsky Apr 22 '18 at 15:41
  • 1
    It's a bit more flexible, since a `regex` is actually a `sub`. You might want to have it using an argument, for instance. In fact, you could create just a function and *curry* it for returning regexes that have different parameters. – jjmerelo Apr 22 '18 at 15:46
  • 2
    The key point in this answer is the use of a lexical alias `my $str = $!str`. This is because a regex is really a *method* that is called on the cursor (an object that belongs to the regex engine, basically), so there is no `self` available that could resolve the `$!str` attribute. Also, attributes are not part of closures, so you need the lexical alias. – moritz Apr 22 '18 at 20:42
2

Using EVAL solved my problem. So, I wonder, whether there are any drawbacks in this method.

class TEST {
    has Str $.str;

    method reg {
        return
            "rx/
               <<
               <[abc]> * 
               $!str
               <!before foo>
              /".EVAL;
    }
}

my $var = TEST.new(str => 'baz');
say "abaz" ~~ $var.reg;    # abaz
say "cbazfoo" ~~ $var.reg; # Nil
Eugene Barsky
  • 5,780
  • 3
  • 17
  • 40
  • 2
    That's a natural *dynamic* (run-time) solution. The obvious potential downside is it's dynamic, so slower at run-time than a compile-time solution. But imo it doesn't matter if it's understandable and fast enough for your use case. – raiph Apr 22 '18 at 09:15
  • 1
    Thanks! In my case it's OK, It'll have to produce only about 200 such `regex`es. – Eugene Barsky Apr 22 '18 at 09:31
  • 6
    N.B. While many uses of `EVAL` are safe, such as your use in this solution, others are not, inasmuch as they can introduce vulnerability to [code injection](https://en.wikipedia.org/wiki/Code_injection). Fortunately Perl 6 is designed and specified in such a way that a compiler can and does detect **at compile-time** when usage of `EVAL` is dangerous in this way. If so, the compiler will refuse to compile unless you add another line to tell it to go ahead anyway. Never add or accept that line (`use MONKEY-SEE-NO-EVAL;`) unless you are willing to accept the challenges/consequences of doing so. – raiph Apr 22 '18 at 10:47