Last active
December 29, 2015 05:29
-
-
Save jedws/7622747 to your computer and use it in GitHub Desktop.
the two macro implementations are almost identical, but all attempts to share implementation are frustrated by obscure runtime problems
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import reflect.macros.Context | |
import scalaz.{ -\/ , \/-, syntax } | |
import syntax.id._ | |
import util.control.NonFatal | |
object Encoders { | |
trait Base { | |
def instance: String => Result[BigInt] | |
} | |
object Base16Macro extends Base { | |
val instance: String => Result[BigInt] = Encoding.B16.toBigInt | |
def encode(c: Context)(): c.Expr[BigInt] = MacroCommon.encodeImpl(c)(Base16Macro) | |
} | |
object Base32Macro extends Base { | |
val instance: String => Result[BigInt] = Encoding.B32.toBigInt | |
def encode(c: Context)(): c.Expr[BigInt] = MacroCommon.encodeImpl(c)(Base32Macro) | |
} | |
private[Encoders] object MacroCommon { | |
def encodeImpl(c: Context)(instance: Base with Singleton)(implicit itag: c.TypeTag[instance.type]): c.Expr[BigInt] = { | |
import c.universe._ | |
val iSym = itag.tpe match { | |
case SingleType(_, sym) if !sym.isFreeTerm && sym.isStatic => sym | |
case x => sys.error("Instance must be static (was " + x + ").") | |
} | |
val i = c.Expr[Base](Ident(iSym)) | |
c.Expr[BigInt] { | |
c.prefix.tree match { | |
case Apply(_, Apply(_, Literal(Constant(repr: String)) :: Nil) :: Nil) => | |
instance.instance(repr) match { | |
case \/-(_) => | |
val toTree = implicitly[Liftable[String]] | |
val s = c.Expr[String](toTree(c.universe, repr)) | |
reify { i.splice.instance(s.splice).fold(_ => throw new RuntimeException, identity) }.tree | |
case -\/(e) => | |
c.abort(c.enclosingPosition, "Invalid Base16 literal, parsing failed: " + e) | |
} | |
case _ => c.abort(c.enclosingPosition, "Invalid Base16 literal.") | |
} | |
} | |
} | |
} | |
} | |
object Encoding { | |
object B16 extends Base(('0' to '9') ++ ('A' to 'F'), 16) | |
object B32 extends Base(('A' to 'Z') ++ ('2' to '7'), 32) | |
class Base(chars: IndexedSeq[Char], base: Int) extends (Int => Char) { | |
def apply(i: Int) = | |
chars(i) | |
val contains: Char => Boolean = | |
c => chars.contains(c.toUpper) | |
val indexOf: Char => Int = | |
chars.indexOf(_) | |
def toBigIntUnsafe(s: String): BigInt = | |
if (s.forall { contains }) | |
s.map { c => indexOf(c.toUpper) }.foldLeft(0: BigInt) { (a, b) => a * base + b } | |
else | |
throw new NumberFormatException(s"'$s' contains characters not in $chars") | |
def toBigInt(s: String): Exception \/ BigInt = | |
try toBigIntUnsafe(s).right | |
catch { case NonFatal(e) => e.left } | |
} | |
implicit class Base16EncodingMacro(sc: StringContext) { | |
def b16(): BigInt = macro Base16Macro.encode | |
} | |
implicit class Base32EncodingMacro(sc: StringContext) { | |
def b32(): BigInt = macro Base32Macro.encode | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment