X-Git-Url: https://git.distorted.org.uk/~mdw/tripe-android/blobdiff_plain/9190adc66f814b8b9add2d2df2ff65b43175104b..0157de026e802e94a2d0db0421b02ffca986c616:/util.scala diff --git a/util.scala b/util.scala index 8ede691..99ec790 100644 --- a/util.scala +++ b/util.scala @@ -27,6 +27,9 @@ package uk.org.distorted; package object tripe { /*----- Imports -----------------------------------------------------------*/ +import scala.language.{existentials, implicitConversions}; + +import scala.collection.mutable.{HashSet, WeakHashMap}; import scala.concurrent.duration.{Deadline, Duration}; import scala.util.control.{Breaks, ControlThrowable}; @@ -47,7 +50,9 @@ val rng = new java.security.SecureRandom; def unreachable(msg: String): Nothing = throw new AssertionError(msg); def unreachable(): Nothing = unreachable("unreachable"); final val ok = (); -final class Brand; +class Brand(val what: String) { + override def toString(): String = s"<${getClass.getName} $what>"; +} /*----- Various pieces of implicit magic ----------------------------------*/ @@ -104,8 +109,55 @@ object Implicits { else try { body; } finally lk.unlock(); } } + + /* Implicit conversions to `Boolean'. I miss the way C integers and + * pointers convert to boolean, so let's do that here. + * + * Numeric zero, null, and empty containers are all false; other objects + * are true. + */ + implicit def truish(n: Byte): Boolean = n != 0; + implicit def truish(n: Char): Boolean = n != 0; + implicit def truish(n: Short): Boolean = n != 0; + implicit def truish(n: Int): Boolean = n != 0; + implicit def truish(n: Long): Boolean = n != 0; + implicit def truish(n: Float): Boolean = n != 0; + implicit def truish(n: Double): Boolean = n != 0; + implicit def truish(x: AnyRef): Boolean = x != null; + implicit def truish(s: String): Boolean = s != null && s != ""; + implicit def truish(o: Option[_]): Boolean = o != None; + implicit def truish(i: Iterator[_]): Boolean = i != null && i.hasNext; + implicit def truish(c: Traversable[_]): Boolean = + c != null && c.nonEmpty; + + /* Some additional bitwise operators. + * + * For now, just the `bic' operator `&~', because typing `& ~' is + * inconsistent with my current style. + */ + class BitwiseIntImplicits(x: Int) { + def &~(y: Byte): Int = x & ~y; + def &~(y: Char): Int = x & ~y; + def &~(y: Short): Int = x & ~y; + def &~(y: Int): Int = x & ~y; + def &~(y: Long): Long = x & ~y; + } + class BitwiseLongImplicits(x: Long) { + def &~(y: Byte): Long = x & ~y; + def &~(y: Char): Long = x & ~y; + def &~(y: Short): Long = x & ~y; + def &~(y: Int): Long = x & ~y; + def &~(y: Long): Long = x & ~y; + } + implicit def bitwiseImplicits(x: Byte) = new BitwiseIntImplicits(x); + implicit def bitwiseImplicits(x: Char) = new BitwiseIntImplicits(x); + implicit def bitwiseImplicits(x: Short) = new BitwiseIntImplicits(x); + implicit def bitwiseImplicits(x: Int) = new BitwiseIntImplicits(x); + implicit def bitwiseImplicits(x: Long) = new BitwiseLongImplicits(x); } +import Implicits.truish; + /*----- Cleanup assistant -------------------------------------------------*/ class Cleaner { @@ -154,7 +206,7 @@ def block[T](body: (T => Nothing) => T): T = { * you'll have to write it explicitly. */ - val mybrand = new Brand; + val mybrand = new Brand("block-exit"); try { body { result => throw new ExitBlock(mybrand, result) } } catch { case ExitBlock(brand, result) if brand eq mybrand => @@ -169,7 +221,7 @@ def blockUnit(body: (=> Nothing) => Unit) { * exit(ok); ...'. */ - val mybrand = new Brand; + val mybrand = new Brand("block-exit"); try { body { throw new ExitBlock(mybrand, null) }; } catch { case ExitBlock(brand, result) if brand eq mybrand => ok; } } @@ -427,13 +479,13 @@ def nextToken(s: String, pos: Int = 0): Option[(String, Int)] = { if (i >= n) return None; /* There is something there. Unpick the quoting and escaping. */ - while (i < n && (q != 0 || !s(i).isWhitespace)) { + while (i < n && (q || !s(i).isWhitespace)) { s(i) match { case '\\' => if (i + 1 >= n) throw new InvalidQuotingException("trailing `\\'"); b += s(i + 1); i += 2; case ch@('"' | ''') => - if (q == 0) q = ch; + if (!q) q = ch; else if (q == ch) q = 0; else b += ch; i += 1; @@ -444,7 +496,7 @@ def nextToken(s: String, pos: Int = 0): Option[(String, Int)] = { } /* Check that the quoting was valid. */ - if (q != 0) throw new InvalidQuotingException(s"unmatched `$q'"); + if (q) throw new InvalidQuotingException(s"unmatched `$q'"); /* Skip whitespace before the next token. */ while (i < n && s(i).isWhitespace) i += 1; @@ -466,6 +518,135 @@ def splitTokens(s: String, pos: Int = 0): Seq[String] = { b.result } +/*----- Hooks -------------------------------------------------------------*/ + +/* This is a really simple publisher/subscriber system. The only slight + * tweak -- and the reason I'm not just using the Scala machinery -- is that + * being attached to a hook doesn't prevent the client from being garbage + * collected. + */ + +trait BaseHookClient[E] { + /* The minimal requirements for a hook client. Honestly you should be + * using `HookClient' instead. + */ + + type H = Hook[E]; // the type of hook we attach to + def hook(hk: H, evt: E); // called with events from the hook +} + +trait HookClient[E] extends BaseHookClient[E] { + /* The properly cooked hook client. This keeps track of which hooks we're + * attached to so we can release them all easily. + */ + + private val hooks = new HashSet[H]; + protected def attachHook(hk: H) { hk.addHookClient(this); hooks += hk; } + protected def detachHook(hk: H) { hk.rmHookClient(this); hooks -= hk; } + protected def detachAllHooks() + { for (hk <- hooks) hk.rmHookClient(this); hooks.clear(); } +} + +trait Hook[E] { + type C = BaseHookClient[E]; + private val clients = new WeakHashMap[C, Unit]; + def addHookClient(c: C) { clients(c) = (); } + def rmHookClient(c: C) { clients -= c; } + protected def callHook(evt: E) + { for (c <- clients.keys) c.hook(this, evt); } +} + +/*----- Fluid variables ---------------------------------------------------*/ + +object BaseFluid { + /* The multi-fluid `let' form is defined here so that it can access the + * `capture' method of individual fluids, but users should use the + * package-level veneer. + */ + + private[tripe] def let[U](fxs: (BaseFluid[T], T) forSome { type T }*) + (body: => U): U = { + /* See the package-level `let' for details. */ + val binds = for ((f, _) <- fxs) yield f.capture; + try { for ((f, x) <- fxs) f.v = x; body } + finally { for (b <- binds) b.restore(); } + } +} +def let[U](fxs: (BaseFluid[T], T) forSome { type T }*)(body: => U): U = { + /* let(F -> X, ...) { BODY } + * + * Evaluate BODY in a dynamic context where each fluid F is bound to the + * corresponding value X. + */ + + BaseFluid.let(fxs: _*)(body); +} + +trait BaseFluid[T] { + /* The basic fluid protocol. */ + + override def toString(): String = + f"${getClass.getName}%s@${hashCode}%x($v%s)"; + + protected trait Binding { + /* A captured binding which can be restored later. Implementing this is + * a subclass responsibility. + */ + + def restore(); + /* Restore the fluid's state to the state captured here. */ + } + + /* Fetch and modify the current binding. */ + def v: T; + def v_=(x: T); + + protected def capture: Binding; + /* Capture and the current state of the fluid. */ + + def let[U](x: T)(body: => U): U = { + /* let(X) { BODY } + * + * Evaluate BODY in a dynamic context where the fluid is bound to the + * value X. + */ + + val b = capture; + try { v = x; body } finally { b.restore(); } + } +} + +class SharedFluid[T](init: T) extends BaseFluid[T] { + /* A simple global fluid. It's probably a mistake to try to access a + * `SharedFluid' from multiple threads without serious synchronization. + */ + + var v: T = init; + private class Binding(old: T) extends super.Binding + { def restore() { v = old; } } + protected def capture: super.Binding = new Binding(v); +} + +class ThreadFluid[T](init: T) extends BaseFluid[T] { + /* A thread-aware fluid. The top-level binding is truly global, shared by + * all threads, but `let'-bindings are thread-local. + */ + + private[this] var global: T = init; + private[this] var bound: ThreadLocal[Option[T]] = new ThreadLocal; + bound.set(None); + + def v: T = bound.get match { case None => global; case Some(x) => x; }; + def v_=(x: T) { bound.get match { + case None => global = x; + case _ => bound.set(Some(x)); + } } + + private class Binding(old: Option[T]) extends super.Binding + { def restore() { bound.set(old); } } + protected def capture: super.Binding = new Binding(bound.get); +} + /*----- Other random things -----------------------------------------------*/ trait LookaheadIterator[T] extends BufferedIterator[T] {