7
\$\begingroup\$

I would like a review of a Scala validation library I am writing. For now we can focus on the regular expression component.

The idea being, if compilation breaks spectacularly the user can change imports from the validated RegexValidator, to the unvalidated RegexRuntime. Like how you can switch between the mutable and immutable collections library. I'd be really interested better names if someone can come up with any.

Here is an abridged version of the macro code:

object RegexValidator {

  implicit class RegexHelper(val sc: StringContext) extends AnyVal {
    def r(args: Any*): Pattern = macro RegexHelperimpl
  }


  def RegexHelperimpl(c: Context)(args: c.Expr[Any]*): c.Expr[Pattern] = {
    import c.universe._

    c.prefix.tree match {
      // access data of string interpolation
      case Apply(_, List(Apply(_, rawParts))) =>

        // `parts` contain the strings a string interpolation is built from
        val parts = rawParts map { case t @ Literal(Constant(const: String)) => (const, t.pos) }

        parts match {
          // if there is only one string literal
          case List((raw, pos)) => {

            //compiletime validation here
            try {
              val p = Pattern.compile(raw)
            } catch {
              case ex: PatternSyntaxException => {

                //fancyness with underlineing

                //TODO: this seems a little iffy...
                val rpos = pos.asInstanceOf[scala.reflect.internal.util.OffsetPosition]

                //TODO: better class?
                val outpos = new RangePosition(rpos.source, rpos.start + ex.getIndex, rpos.start + ex.getIndex, rpos.start + ex.getIndex)

                c.error(outpos.asInstanceOf[c.universe.Position], ex.getDescription())
              }

              //... catch other errors and handle sensibly ...
            }

            //then parse at compile time
            c.Expr[Pattern]( q" riteofwhey.ocd.regex.RegexRuntime.parse($raw) ")
          }

          // if there is more then 1 string chunck i.e.   r"regex_${2 + 2}ex" 
          // fall back to runtime interpolation
          case _ => {
            c.Expr[Pattern]( q" riteofwhey.ocd.regex.RegexRuntime.parse(StringContext(..$rawParts), Seq[Any](..$args) ) ")
          }
        }
  }
}

I have marked the places I find particularly worrying with TODOs. But I would also be interested in feedback on

  • documentation
  • general macro stuff
  • variable names
  • macro automated testing strategies

(Made corrections based on Zim-Zam O'Pootertoot's comment to use quasiqoates)

\$\endgroup\$
2
+100
\$\begingroup\$

I highly recommend using quasiquotes wherever possible - they make the macro much more readable/maintainable.

For debugging I found that the REPL was much more useful than my IDE, especially when I made heavy use of Context.abort commands to pinpoint errors. This also helps make the code somewhat self-documenting.

\$\endgroup\$
  • \$\begingroup\$ I tried to rewrite those awful ast blocks with quasiquotes for that exact reason... But I couldn't figure it out. The thpes didn't seem to line up and the syntax I was trying to emulate is a little crazy. \$\endgroup\$ – user833970 Jun 16 '15 at 21:04
  • \$\begingroup\$ @user833970 There's some sample quasiquote macro code that I wrote here that might help demystify it a bit \$\endgroup\$ – Zim-Zam O'Pootertoot Jun 16 '15 at 21:20
  • \$\begingroup\$ Oh! for testing, I meant automated testing, I see how that came across wrong. \$\endgroup\$ – user833970 Jun 17 '15 at 2:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.