Last active
June 19, 2018 19:48
-
-
Save mrange/8fc9a63f7125c9d2026e30b00a4a52d9 to your computer and use it in GitHub Desktop.
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 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