micsza
micsza

Reputation: 819

Iteratively capture regex subgroups in Scala

I do not know how to capture regex subgroups in Scala.

Say potions are either basic or made of other potions and they are named as <adjective> <noun>. There are recipe formulas as follows:

val recipe = "white phial potion is 3 fortify health potions, 1 vigorous stamina potion, 5 plentiful magicka potions."

I want to parse the formulas into the following domain:

case class Potion(name: String)
case class Formula(potion: Potion, ingredients: Option[List[(Potion, Int)]])

So far I have come up with the pattern

val recipePattern: Regex = """(\w+ \w+) potion is( (\d+) (\w+ \w+) (?:potion|potions)[,.])+""".r

but I have no idea how to proceed further and grab the iterative subgroups.

Upvotes: 0

Views: 70

Answers (1)

Kolmar
Kolmar

Reputation: 14224

It is not possible to capture repeated groups with regular expressions. The last match of a specific group will overwrite all the previous matches of the same group.

I recommend using a proper parsing library to parse that kind of strings. For example, using fastparse:

import scala.util.{Try, Success, Failure}

case class Potion(name: String)
object Potion {
  def apply(adjective: String, noun: String): Potion = Potion(s"$adjective $noun")
}

case class Formula(potion: Potion, ingredients: List[(Potion, Int)])
object Formula {
  import fastparse._, SingleLineWhitespace._

  object Parser {
    def number[_: P] = P(CharsWhileIn("0-9").!).map(_.toInt)
    def word[_: P] = P(CharsWhileIn("a-z").!)
    def ingredient[_: P] = P(number ~ word ~ word ~ "potion"~~"s".?).map {
      case (count, adj, noun) => (Potion(adj, noun), count)
    }
    def formula[_: P] = P(word ~ word ~ "potion" ~ "is" ~ ingredient.rep(sep = ",") ~ "." ~ End).map {
      case (adj, noun, ingredients) => Formula(Potion(adj, noun), ingredients.toList)
    }
  }

  def parse(formula: String): Try[Formula] = {
    fastparse.parse(formula, Parser.formula(_)) match {
      case Parsed.Success(result, _) => Success(result)
      case failure: Parsed.Failure => Failure(new RuntimeException(failure.trace().longMsg))
    }
  }
}

The result is:

scala> Formula.parse("white phial potion is 3 fortify health potions, 1 vigorous stamina potion, 5 plentiful magicka potions.")
res0: scala.util.Try[core.Test.Formula] = Success(Formula(Potion(white phial),List((Potion(fortify health),3), (Potion(vigorous stamina),1), (Potion(plentiful magicka),5))))

Upvotes: 2

Related Questions