Regex, default capture group value?

Posted by j0nas on Wed 05 Jan 2011 04:40 PM — 5 posts, 23,151 views.

#0
This is a little bit hard to explain, so I'll skip the circumstances that led me to needing this particular solution and just skip straight ahead to my problem.

I'm using regex triggers with named groups, and there are some special cases in which it would be very convenient if I could give one or several of the named groups in the regex default values, even if they are never present in the pattern as actual catching groups. Is this possible?

For example, input normally looks like this:
You crave bread!
You crave wine!
..so I use a pattern like '^You crave (?<craving>.*?)!$' and things work just fine.

But in some special cases, it looks like this:
You crave OMNOMNOM crumbly goodness!
..in which case I would like to be able to use a trigger like '^You crave OMNOMNOM crumbly goodness!$' BUT have the rest of the trigger evaluate as if the capture-group 'craving' had matched 'cookie'. This seems like something an advanced feature of regex might be able to pull off, but how? Am I the only one to ever have had this need?
To further clarify, I imagine such a pattern might look something like '(?assign:<craving>=cookie)^You crave OMNOMNOM crumbly goodness!$', giving the capture group a initial value to be used in case there are no other matches, or something like that.


( Of course I'm aware that there are many other possible workarounds I could use, but this is just a simplified example. In my real scenario it would simply be a lot more convenient if it were possible to do things as described above, but is that even possible? )
#1
Keep the same regex, but have a table that you can substitute with if need be.


replacestuff = {
 "OMNOMNOM crumbly goodness" = "cookie",
 "Something else here" = "bread",
}


Then at the top of your function for the trigger do something like this:

if replacestuff[wildcards.craving] then
  wildcards.craving = replacestuff[wildcards.craving]
end


and then go about doing whatever is you wanted to do.

When you figure out something else you need to replace, just add it to the table. No new triggers.

Bast
#2
Bast said:

Keep the same regex, but have a table that you can substitute with if need be.


replacestuff = {
 "OMNOMNOM crumbly goodness" = "cookie",
 "Something else here" = "bread",
}


Then at the top of your function for the trigger do something like this:

if replacestuff[wildcards.craving] then
  wildcards.craving = replacestuff[wildcards.craving]
end


and then go about doing whatever is you wanted to do.

When you figure out something else you need to replace, just add it to the table. No new triggers.

Bast
Yes, you're right, that would work just fine for the example, but like I said the actual case I have is a lot more complicated than just the example and so that approach would not be nearly as neat. I guess my example was poorly thought out, but I didn't want to over-complicate things.

So I'm still looking for a solution that would 'keep it all in the regex', as it were.
Australia Forum Administrator #3
j0nas said:

I'm using regex triggers with named groups, and there are some special cases in which it would be very convenient if I could give one or several of the named groups in the regex default values, even if they are never present in the pattern as actual catching groups. Is this possible?


No, I don't think so. I didn't write the regexp stuff, and I don't believe that feature is in the library.

Basically you are wanting it to return a capture which doesn't even exist in the source text. I doubt it will ever do that. In your example you want it to "capture" the word "cookie" which isn't present in the text.

All I can really do is agree with Bast, and recommend some sort of post-processing. Indeed when regexps start getting really complicated it can be time to simply capture a simple wildcard, and then drop into scripting to make those sort of decisions.

Here's one suggestion ... assuming you have some sort of way of identifying the problem triggers (eg. a trigger group) you could simply say something like this:


if wildcards.craving == "" then
  wildcards.craving = "cookie"
end -- if


Since the ordinary case will have the craving type supplied, it will be untouched, but the nomnomnom case will have no craving, and you put the "default" in after the event.

Here's an example. Two triggers:


<triggers>
  <trigger
   custom_colour="2"
   enabled="y"
   match="^You crave OMNOMNOM crumbly goodness!(?&lt;craving&gt;)$"
   regexp="y"
   script="mytrigger"
   sequence="90"
  >
  </trigger>

  <trigger
   custom_colour="2"
   enabled="y"
   match="^You crave (?&lt;craving&gt;.*?)!$"
   regexp="y"
   script="mytrigger"
   sequence="100"
  >
  </trigger>
</triggers>


Both have the "craving" group, but in the case of OMNOMNOM crumbly goodness it just matches an empty string. However this means the group "craving" will exist in both cases.

Now your script looks like this:


function mytrigger (name, line, wildcards, styles)

  if wildcards.craving then  -- group exists?
    if wildcards.craving == "" then 
      wildcards.craving = "cookie"  -- take default
    end -- if
  end -- if
  
  print ("craving=", wildcards.craving)
end -- mytrigger



The first test is whether or not the craving group exists (it does in both cases). The second test says that if the group exists, but is empty, we assume cookie. That would seem to address your issue.

Example output:


You crave OMNOMNOM crumbly goodness!
craving= cookie
You crave fish!
craving= fish

#4
Again that solution works well for the given example, but for my actual usage that doesn't work out very well at all. At least I can be sure that there's no near-magical regex trick I can use to pull a rabbit out of my hat, thanks Nick, Bast!

If I manage to find some solution that ends up working out neater than the ways already suggested, I'll come back and write it up, but I doubt I can find one.