Skip to content

Instantly share code, notes, and snippets.

@mrange
Last active June 19, 2018 19:48
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mrange/8fc9a63f7125c9d2026e30b00a4a52d9 to your computer and use it in GitHub Desktop.
Save mrange/8fc9a63f7125c9d2026e30b00a4a52d9 to your computer and use it in GitHub Desktop.
import java.util.concurrent.atomic.AtomicInteger
import scala.reflect._
import scala.util.control._
trait Default[+T] {
val zero: T
}
object Default {
def create[T](v: T): Default[T] = new Default[T] {
val zero = v
}
}
sealed trait TransformPathElement
object TransformPathElement {
case class Member(name: String) extends TransformPathElement
case class Index(index: Int) extends TransformPathElement
case class Named(name: String) extends TransformPathElement
}
sealed trait TransformFailure
object TransformFailure {
case class Message(message: String) extends TransformFailure
case class Exception(thrown: Throwable) extends TransformFailure
}
sealed trait TransformFailureTree {
import TransformFailureTree._
def isGood = {
this match {
case Empty() => true
case Suppress(_) => true
case _ => false
}
}
def fork(right: TransformFailureTree) = {
(this, right) match {
case (Empty() , _ ) => right
case (_ , Empty() ) => this
case (Suppress(l) , Suppress(r) ) => Suppress(Fork(this, right))
case (_ , _ ) => Fork(this, right)
}
}
def suppress = {
this match {
case Empty() => TransformFailureTree.empty
case Suppress(_) => this
case _ => TransformFailureTree.Suppress(this)
}
}
def collapse: Seq[(Boolean, TransformContext, TransformFailure)] = {
val result = Seq.newBuilder[(Boolean, TransformContext, TransformFailure)]
def traverse(suppress: Boolean, t: TransformFailureTree): Unit = {
t match {
case Empty() => Unit
case Leaf(ctx, f) =>
val r = (suppress, ctx, f)
result += r
case Suppress(tt) => traverse(true, tt)
case Fork(l, r) =>
traverse(suppress, l)
traverse(suppress, r)
}
}
traverse(false, this)
result.result
}
}
object TransformFailureTree {
case class Empty() extends TransformFailureTree
case class Leaf(context: TransformContext, failure: TransformFailure) extends TransformFailureTree
case class Suppress(tree: TransformFailureTree) extends TransformFailureTree
case class Fork(left: TransformFailureTree, right: TransformFailureTree) extends TransformFailureTree
val empty = Empty()
}
case class TransformResult[+T](value: T, failureTree: TransformFailureTree)
case class TransformContext(path: List[TransformPathElement]) {
def append(p: TransformPathElement) = TransformContext(p::path)
override def toString: String = {
val sb = new StringBuilder("this")
for (e <- path.reverse) {
e match {
case TransformPathElement.Member(name) => sb.append(s".$name")
case TransformPathElement.Index(index) => sb.append(s"[$index]")
case TransformPathElement.Named(name) => sb.append(s"($name)")
}
}
sb.result
}
def failWithMessage(msg: String): TransformFailureTree =
TransformFailureTree.Leaf(this, TransformFailure.Message(msg))
def failWithException(exc: Throwable): TransformFailureTree =
TransformFailureTree.Leaf(this, TransformFailure.Exception(exc))
def success[T](v: T): TransformResult[T] =
TransformResult(v, TransformFailureTree.empty)
def failWithMessage[T](fv: T, msg: String): TransformResult[T] =
TransformResult(fv, failWithMessage(msg))
def failWithException[T](fv: T, exc: Throwable): TransformResult[T] =
TransformResult(fv, failWithException(exc))
}
object TransformContext {
val empty = TransformContext(List())
}
trait Transform[+T] {
def run(context: TransformContext, current: Any): TransformResult[T]
def flatMap[U](uf: T => Transform[U]): Transform[U] = Transform.flatMap(this, uf)
def map[U](m: T => U): Transform[U] = Transform.map(this, m)
def and[U](u: Transform[U]): Transform[(T, U)] = Transform.and(this, u)
def debug(name: String): Transform[T] = Transform.debug(name, this)
def optional: Transform[Option[T]] = Transform.optional(this)
def transform(v: Any): (T, Seq[(Boolean, TransformContext, TransformFailure)]) =
Transform.transform(v, this)
}
//noinspection ConvertExpressionToSAM
object Transform {
object Implicits {
implicit class ImplicitFlattenTransform[T](val t: Transform[Transform[T]]) extends AnyVal {
def flatten: Transform[T] = Transform.flatten(t)
}
implicit class ImplicitNoCovarianceTransform[T](val t: Transform[T]) extends AnyVal {
def tryCatch(fv: T): Transform[T] = Transform.tryCatch(fv, t)
def or(o: Transform[T]): Transform[T] = Transform.or(t, o)
def at(name: String, fv: T): Transform[T] = Transform.at(name, fv, t)
def at(index: Int, fv: T): Transform[T] = Transform.at(index, fv, t)
def at(name: String)(implicit default: Default[T]): Transform[T] = Transform.at(name, t)(default)
def at(index: Int)(implicit default: Default[T]): Transform[T] = Transform.at(index, t)(default)
def tryCatch(implicit default: Default[T]): Transform[T] = Transform.tryCatch(t)(default)
}
implicit class ImplicitApplyTransform[T, U](val f: Transform[T => U]) extends AnyVal {
def apply(t: Transform[T]) = Transform.apply(f, t)
}
}
def create[T](m: (TransformContext, Any) => TransformResult[T]): Transform[T] = new Transform[T] {
def run(context: TransformContext, current: Any): TransformResult[T] = {
m(context, current)
}
}
def value[T](v: T): Transform[T] = new Transform[T] {
def run(context: TransformContext, current: Any): TransformResult[T] = {
context.success(v)
}
}
def failWith[T](fv: T, message: String): Transform[T] = new Transform[T] {
def run(context: TransformContext, current: Any): TransformResult[T] = {
context.failWithMessage(fv, message)
}
}
def failWith[T](message: String)(implicit default: Default[T]): Transform[T] =
failWith(default.zero, message)
def flatMap[T, U](t: Transform[T], uf: T => Transform[U]): Transform[U] = new Transform[U] {
def run(context: TransformContext, current: Any): TransformResult[U] = {
val tr = t.run(context, current)
val u = uf(tr.value)
val ur = u.run(context, current)
ur.copy(failureTree = tr.failureTree.fork(ur.failureTree))
}
}
def tryCatch[T](fv: T, t: Transform[T]): Transform[T] = new Transform[T] {
def run(context: TransformContext, current: Any): TransformResult[T] = {
try {
t.run(context, current)
} catch {
case NonFatal(te) => context.failWithException(fv, te)
}
}
}
def tryCatch[T](t: Transform[T])(implicit default: Default[T]): Transform[T] =
tryCatch(default.zero, t)
def flatten[T](t: Transform[Transform[T]]): Transform[T] = t.flatMap(tv => tv)
def apply[T, U](f: Transform[T => U], t: Transform[T]): Transform[U] = f.flatMap(fv => t.flatMap(tv => value(fv(tv))))
def map[T, U](t: Transform[T], m: T => U): Transform[U] = t.flatMap(tv => value(m(tv)))
def and[T, U](t: Transform[T], u: Transform[U]): Transform[(T, U)] = t.flatMap(tv => u.flatMap(uv => value((tv, uv))))
def or[T](l: Transform[T], r: Transform[T]): Transform[T] = new Transform[T] {
def run(context: TransformContext, current: Any): TransformResult[T] = {
val lr = l.run(context, current)
val rr = r.run(context, current)
if (lr.failureTree.isGood) {
lr.copy(failureTree = lr.failureTree.fork(rr.failureTree.suppress))
} else if (rr.failureTree.isGood) {
rr.copy(failureTree = lr.failureTree.suppress.fork(rr.failureTree))
} else {
rr.copy(failureTree = lr.failureTree.fork(rr.failureTree))
}
}
}
private val counter = new AtomicInteger()
def debug[T](name: String, t: Transform[T]): Transform[T] = new Transform[T] {
def run(context: TransformContext, current: Any): TransformResult[T] = {
val id = counter.incrementAndGet()
println(s"TRANSFORMER_DEBUG - BEFORE - [$id] - $name, $context, $current")
val tr = t.run(context, current)
println(s"TRANSFORMER_DEBUG - AFTER - [$id] - $name, $context, $current, $tr")
tr
}
}
def optional[T](t: Transform[T]): Transform[Option[T]] = new Transform[Option[T]] {
def run(context: TransformContext, current: Any): TransformResult[Option[T]] = {
val tr = t.run(context, current)
if (tr.failureTree.isGood) {
tr.copy(value = Some(tr.value))
} else {
tr.copy(value = None, failureTree = tr.failureTree.suppress)
}
}
}
def cast[T : ClassTag](fv: T): Transform[T] = new Transform[T] {
def run(context: TransformContext, current: Any): TransformResult[T] = {
current match {
case tv : T => context.success(tv)
case _ =>
val rc = classTag[T].runtimeClass
context.failWithMessage(fv, s"Cannot cast current to ${rc.getName}")
}
}
}
def cast[T : ClassTag](implicit default: Default[T]): Transform[T] =
cast(default.zero)
def named[T](name: String, t: Transform[T]): Transform[T] = new Transform[T] {
def run(context: TransformContext, current: Any): TransformResult[T] = {
val nctx = context.append(TransformPathElement.Named(name))
t.run(nctx, current)
}
}
def at[T](name: String, fv: T, t: Transform[T]): Transform[T] = new Transform[T] {
def run(context: TransformContext, current: Any): TransformResult[T] = {
current match {
case m: Map[String, _] =>
m.get(name) match {
case Some(v) =>
val nctx = context.append(TransformPathElement.Member(name))
t.run(nctx, v)
case None =>
context.failWithMessage(fv, s"No member named: $name")
}
case _ =>
context.failWithMessage(fv, "Cannot cast current to Map[String, _]")
}
}
}
def at[T](name: String, t: Transform[T])(implicit default: Default[T]): Transform[T] =
at(name, default.zero, t)
def at[T](index: Int, fv: T, t: Transform[T]): Transform[T] = new Transform[T] {
def run(context: TransformContext, current: Any): TransformResult[T] = {
current match {
case s: Seq[_] =>
if (index >= 0 && index < s.length) {
val nctx = context.append(TransformPathElement.Index(index))
t.run(nctx, s(index))
} else {
context.failWithMessage(fv, s"Index out of range, index:$index, length:${s.length}")
}
case _ =>
context.failWithMessage(fv, "Cannot cast current to collection")
}
}
}
def at[T](index: Int, t: Transform[T])(implicit default: Default[T]): Transform[T] =
at(index, default.zero, t)
def transform[T](v: Any, t: Transform[T]): (T, Seq[(Boolean, TransformContext, TransformFailure)]) = {
val tr = t.run(TransformContext.empty, v)
val errors = tr.failureTree.collapse
(tr.value, errors.distinct)
}
}
object Program {
import Transform._
import Transform.Implicits._
def map(kvs: (String, Any)*) = Map(kvs:_*)
def seq(vs: Any*) = Seq(vs:_*)
def main(args: Array[String]): Unit = {
val m = map(
"one" -> 1,
"two" -> "Hello",
"three" -> map("one" -> 2),
"four" -> seq("four")
)
def check[T](t: Transform[T]) = {
val tr = t.transform(m)
println(tr)
}
val int = cast(-1)
val string = cast("")
implicit val defaultInt = Default.create(-1)
implicit val defaultString = Default.create("")
implicit val defaultOption = Default.create(None)
check(value(1))
check(int.at("one"))
check(
int.at("one")
.and(string.at("two")))
check(
int.at("one")
.and(string.at("two"))
.and(int.at("one").at("three"))
.and(string.at(0).at("four")))
check(int.at(0).optional)
check(int.at(0))
check(
int.at("one'")
.and(string.at("two'"))
.and(int.at("one'").at("three"))
.and(string.at(-1).at("four")))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment