Search code examples
regexmatchingcrystal-lang

How to access the results of .match as string value in Crystal lang


In many other programming languages, there is a function which takes as a parameter a regular expression and returns an array of string values. This is true of Javascript and Ruby. The .match in crystal, however, does 1) not seem to accept the global flag and 2) it does not return an array but rather a struct of type Regex::MatchData. (https://crystal-lang.org/api/0.25.1/Regex/MatchData.html)

As an example the following code:

str = "Happy days"
re = /[a-z]+/i
matches = str.match(re)
puts matches

returns Regex::MatchData("Happy")

I am unsure how to convert this result into a string or why this is not the default as it is in the inspiration language (Ruby). I understand this question probably results from my inexperience dealing with structs and compiled languages but I would appreciate an answer in hopes that it might also help someone else coming from a JS/Ruby background.


Solution

  • What if I want to convert to a string merely the first match?

    puts "Happy days"[/[a-z]+/i]?
    puts "Happy days".match(/[a-z]+/i).try &.[0]
    

    It will try to match a string against /[a-z]+/i regex and if there is a match, Group 0, i.e. the whole match, will be output. Note that the ? after [...] will make it fail gracefully if there is no match found. If you just use puts "??!!"[/[a-z]+/i], an exception will be thrown.

    See this online demo.

    If you want the functionality similar to String#scan that returns all matches found in the input, you may use (shortened version only left as per @Amadan's remark):

    str = "Happy days"
    re = /[a-z]+/i
    matches = [] of String
    str.scan(re) do |match|
          matches << match[0]
        end
    puts matches
    

    Output of the code above:

    ["Happy", "days"]
    

    Note that String::scan will return an array of Regex::MatchData for each match. To get the text of the match, just access the first item in the match object.

    A shorter way is to use

    matches = str.scan(re).map(&.to_a).flatten
    

    See the online demo.