I would like a review of a Scala validation library I am writing. For now we can focus on the regular expression component.
- The usage is described in RegexExample.scala
- There is an outline of the interface in RegexRuntime.scala
- But the macro and most of the craziness lives in RegexValidator.scala
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 TODO
s. 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)
1 Answer 1
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.
-
\$\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– user8339702015年06月16日 21:04:35 +00:00Commented Jun 16, 2015 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– Zim-Zam O'Pootertoot2015年06月16日 21:20:05 +00:00Commented Jun 16, 2015 at 21:20
-
\$\begingroup\$ Oh! for testing, I meant automated testing, I see how that came across wrong. \$\endgroup\$user833970– user8339702015年06月17日 02:33:09 +00:00Commented Jun 17, 2015 at 2:33
Explore related questions
See similar questions with these tags.