Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I interpret escape sequences in a multiline scala string?

In a nutshell:

"""I want to be able to
  |have the convenient formatting of a multiline string,
  |while using inline escape sequences\r\r\b\\
  |
  |How can this be done?""".stripMargin
like image 388
root Avatar asked Jul 25 '13 21:07

root


3 Answers

Two options that I can think of:

  1. You can use StringContext.treatEscapes directly:

    StringContext.treatEscapes("""I want to be able to
            |have the convenient formatting of a multiline string,
            |while using inline escape sequences\r\r\b\\
            |
            |How can this be done?""".stripMargin)
    
  2. If the variable substitution feature of the "simple interpolator" (s) isn't disruptive to your needs, then try combining string interpolation (which converts escaped characters) with """-quotes (which doesn't escape ...):

    println("""he\\lo\nworld""")
    println(s"""he\\lo\nworld""")
    

    outputs

    he\\lo\nworld
    he\lo
    world
    

    For details, see the relevant SIP and this earlier question.

like image 156
Richard Sitze Avatar answered Oct 13 '22 13:10

Richard Sitze


In addition to the other answers - what about this idea?

s"""I want to be able to
  |have the convenient formatting of a multiline string,
  |while using inline escape sequences${"\r\r\b\"}\
  |
  |How can this be done?""".stripMargin

The only thing that won't work that way is the \ at the end of the line. In my example, you embed the escaped characters as a normal string, and then use string interpolation to insert them. (Mind the s before the opening triple quotes.)

like image 38
Madoc Avatar answered Oct 13 '22 11:10

Madoc


There is a convenient example hidden in the standard library. It's easy to tweak in a couple of ways to add standard processing. It's not obvious how embedded \r is intended, however, so caveat interpolator.

Update: for the record, the hard part was forgetting the sequence arg _*. Because it's Any*, there's no type error; the underlying interpolator just throws an error that the parts don't match the args.

Update: fixed underscore star so it doesn't italicize.

Example:

import reflect.internal.util.StripMarginInterpolator

object Test extends App {

  trait ZipMarginator extends StripMarginInterpolator {
    def zm(args: Any*): String = StringContext treatEscapes sm(args: _*)
  }
  implicit class ZipMarginOps(val stringContext: StringContext) extends ZipMarginator
  val sample =
zm"""I want to be able to
    |have the convenient formatting of a multiline string,
    |while using inline escape sequences
    |like\t\ttabs and \\Program Files\\backslashes.
    |
    |How can this be done?"""
  Console println sample

  implicit class ZipMarginOps2(val stringContext: StringContext) extends SStripMarginInterpolator {
    def sz(args: Any*): String = ssm(args: _*)
  }
  Console println sz"""
    |Another\t\texample."""
  Console println sz"""
    |Another\r\tex\nample.
    |Huh?"""

}

Here is the StripMargin..or with name changes to protect one's sanity, note the caveat about raw:

trait SStripMarginInterpolator {
  def stringContext: StringContext

  /**
   * A safe combination of [[scala.collection.immutable.StringLike#stripMargin]]
   * and [[scala.StringContext#raw]].
   *
   * The margin of each line is defined by whitespace leading up to a '|' character.
   * This margin is stripped '''before''' the arguments are interpolated into to string.
   *
   * String escape sequences are '''not''' processed; this interpolater is designed to
   * be used with triple quoted Strings.
   *
   * {{{
   * scala> val foo = "f|o|o"
   * foo: String = f|o|o
   * scala> sm"""|${foo}
   *             |"""
   * res0: String =
   * "f|o|o
   * "
   * }}}
   */
  final def ssm(args: Any*): String = {
    def isLineBreak(c: Char) = c == '\n' || c == '\f' // compatible with StringLike#isLineBreak
    def stripTrailingPart(s: String) = {
      val (pre, post) = s.span(c => !isLineBreak(c))
      pre + post.stripMargin
    }
    val stripped: List[String] = stringContext.parts.toList match {
      case head :: tail => head.stripMargin :: (tail map stripTrailingPart)
      case Nil => Nil
    }
    new StringContext(stripped: _*).s(args: _*)  // <= MODIFIED for s instead of raw
  }
}
like image 44
som-snytt Avatar answered Oct 13 '22 13:10

som-snytt