Some more infrastructure. Maybe other things.
[tripe-android] / util.scala
index 8ede691..99ec790 100644 (file)
@@ -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] {