/*----- Imports -----------------------------------------------------------*/
+import scala.language.{existentials, implicitConversions};
+
+import scala.collection.mutable.{HashSet, WeakHashMap};
import scala.concurrent.duration.{Deadline, Duration};
import scala.util.control.{Breaks, ControlThrowable};
import java.nio.channels.{SelectionKey, Selector};
import java.nio.channels.spi.{AbstractSelector, AbstractSelectableChannel};
import java.nio.charset.Charset;
+import java.text.SimpleDateFormat;
import java.util.{Set => JSet};
import java.util.concurrent.locks.{Lock, ReentrantLock};
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 ----------------------------------*/
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 {
* 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 =>
* 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; }
}
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;
}
/* 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;
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] {
sb.result
}
-def formatTime(t: Int): String =
+val datefmt = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
+
+def formatDuration(t: Int): String =
if (t < -1) "???"
else {
val (s, t1) = (t%60, t/60);