Enhanced String Interpolator

You can read some background motivation for this project in my blog post.

Basic usage

Currently all functionality is accessed with one import:

import stringmatching.regex.Interpolators.r

Example 1

A simple example can be illustrated here, where we parse some basic text representing a sequence of integers, delimited by "[" and "]", and separated by ", ". In one pattern, you can extract a typed value xs: IndexedSeq[Int] as follows:

"[23, 56, 71]" match
  case r"[${r"$xs%d"}...(, )]" => xs.sum // 150

The get an intuition for how r works, it works like the simple string matcher from the standard library (i.e. case s"$foo $bar"), except that after each splice you can append an optional format string, such as %d.

Example 2

The format pattern is stripped before matching against the string, meaning that it only provides a directive of how to interpret the splice.

To illustrate, take the following example:

"age: 23, year: 2019" match
  case r"age: $n%d, year: $y%d" => n + y

The two %d format strings will be removed before matching, and tell the interpolator to treat n and d as integer patterns. This means that the behavior of above snippet is equivalent to the following:

"age: 23, year: 2019" match
  case str @ s"age: $n0, year: $y0" =>
    (n0.toIntOption, y0.toIntOption) match
      case (Some(n), Some(y)) => n + y
      _                       => throw MatchError(str)

Possible Formats

String Pattern

e.g. $foo, which extracts val foo: String.

Int Pattern

e.g. $foo%d, which extracts val foo: Int.

Long Pattern

e.g. $foo%L, which extracts val foo: Long.

Float Pattern

e.g. $foo%f, which extracts val foo: Float.

Double Pattern

e.g. $foo%g, which extracts val foo: Double.

Split Pattern

e.g. $foo...(<regex>), which extracts val foo: IndexedSeq[String].

This is equivalent to extracting with $foo and then performingfoo.split(raw"<regex>").toIndexedSeq.

This means that inside the <regex> you may put any valid regex accepted by scala.util.matching.Regex. String escape characters are also not processed within the regex.

There is also a special case where if the first element of the sequence is expected to be empty you can drop it with the $foo..!(<regex>) pattern.

Putting this all together, you could split Windows style paths with the following pattern:

raw"C:\foo\bar\baz.pdf" match
  case r"C:$elems..!(\\)" => elems.mkString("/")
// yields "foo/bar/baz.pdf"

Nested Patterns

The r interpolator can also match on Seq of strings, arbitrarily nested.

For example

val strings: Seq[String] = ???

val foo: Seq[Int] = strings match
  case r"$foo%d" => foo

or even

val stringss: Seq[Seq[String]] = ???

val foo: Seq[Seq[Int]] = stringss match
  case r"$foo%d" => foo