/*----- Imports -----------------------------------------------------------*/
import scala.concurrent.duration.{Deadline, Duration};
-import scala.util.control.Breaks;
+import scala.util.control.{Breaks, ControlThrowable};
-import java.io.{BufferedReader, Closeable, File, Reader};
+import java.io.{BufferedReader, Closeable, File, InputStream, Reader};
import java.net.{URL, URLConnection};
import java.nio.{ByteBuffer, CharBuffer};
import java.nio.charset.Charset;
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;
/*----- Various pieces of implicit magic ----------------------------------*/
class InvalidCStringException(msg: String) extends Exception(msg);
-type CString = Array[Byte];
-object Magic {
+object Implicits {
/* --- Syntactic sugar for locks --- */
implicit class LockOps(lk: Lock) {
/* LK withLock { BODY }
* LK.withLock(INTERRUPT) { BODY }
- * LK.withLock(DUR, [INTERRUPT]) { BODY } orelse { ALT }
- * LK.withLock(DL, [INTERRUPT]) { BODY } orelse { ALT }
+ * LK.withLock(DUR, [INTERRUPT]) { BODY } orElse { ALT }
+ * LK.withLock(DL, [INTERRUPT]) { BODY } orElse { ALT }
*
* Acquire a lock while executing a BODY. If a duration or deadline is
* given then wait so long for the lock, and then give up and run ALT
def withLock[T](body: => T): T = withLock(true)(body);
}
- class PendingLock[T] private[Magic]
+ class PendingLock[T] private[Implicits]
(val lk: Lock, val dur: Duration,
val interrupt: Boolean, body: => T) {
- /* An auxiliary class for LockOps; provides the `orelse' qualifier. */
+ /* An auxiliary class for LockOps; provides the `orElse' qualifier. */
- def orelse(alt: => T): T = {
+ def orElse(alt: => T): T = {
val locked = (dur, interrupt) match {
case (Duration.Inf, true) => lk.lockInterruptibly(); true
case (Duration.Inf, false) => lk.lock(); true
else try { body; } finally lk.unlock();
}
}
-
- /* --- Conversion to/from C strings --- */
-
- implicit class ConvertJStringToCString(s: String) {
- /* Magic to convert a string into a C string (null-terminated bytes). */
-
- def toCString: CString = {
- /* Convert the receiver to a C string.
- *
- * We do this by hand, rather than relying on the JNI's built-in
- * conversions, because we use the default encoding taken from the
- * locale settings, rather than the ridiculous `modified UTF-8' which
- * is (a) insensitive to the user's chosen locale and (b) not actually
- * UTF-8 either.
- */
-
- val enc = Charset.defaultCharset.newEncoder;
- val in = CharBuffer.wrap(s);
- var sz: Int = (s.length*enc.averageBytesPerChar + 1).toInt;
- var out = ByteBuffer.allocate(sz);
-
- while (true) {
- /* If there's still stuff to encode, then encode it. Otherwise,
- * there must be some dregs left in the encoder, so flush them out.
- */
- val r = if (in.hasRemaining) enc.encode(in, out, true)
- else enc.flush(out);
-
- /* Sift through the wreckage to figure out what to do. */
- if (r.isError) r.throwException();
- else if (r.isOverflow) {
- /* No space in the buffer. Make it bigger. */
-
- sz *= 2;
- val newout = ByteBuffer.allocate(sz);
- out.flip(); newout.put(out);
- out = newout;
- } else if (r.isUnderflow) {
- /* All done. Check that there are no unexpected zero bytes -- so
- * this will indeed be a valid C string -- and convert into a byte
- * array that the C code will be able to pick apart.
- */
-
- out.flip(); val n = out.limit; val u = out.array;
- if ({val z = u.indexOf(0); 0 <= z && z < n})
- throw new InvalidCStringException("null byte in encoding");
- val v = new Array[Byte](n + 1);
- out.array.copyToArray(v, 0, n);
- v(n) = 0;
- return v;
- }
- }
-
- /* Placate the type checker. */
- unreachable("unreachable");
- }
- }
-
- implicit class ConvertCStringToJString(v: CString) {
- /* Magic to convert a C string into a `proper' string. */
-
- def toJString: String = {
- /* Convert the receiver to a C string.
- *
- * We do this by hand, rather than relying on the JNI's built-in
- * conversions, because we use the default encoding taken from the
- * locale settings, rather than the ridiculous `modified UTF-8' which
- * is (a) insensitive to the user's chosen locale and (b) not actually
- * UTF-8 either.
- */
-
- val inlen = v.indexOf(0) match {
- case -1 => v.length
- case n => n
- }
- val dec = Charset.defaultCharset.newDecoder;
- val in = ByteBuffer.wrap(v, 0, inlen);
- dec.decode(in).toString
- }
- }
}
/*----- Cleanup assistant -------------------------------------------------*/
try { body(thing) }
finally { thing.close(); }
+/*----- Control structures ------------------------------------------------*/
+
+private case class ExitBlock[T](brand: Brand, result: T)
+ extends ControlThrowable;
+
+def block[T](body: (T => Nothing) => T): T = {
+ /* block { exit[T] => ...; exit(x); ... }
+ *
+ * Execute the body until it calls the `exit' function or finishes.
+ * Annoyingly, Scala isn't clever enough to infer the return type, so
+ * you'll have to write it explicitly.
+ */
+
+ val mybrand = new Brand;
+ try { body { result => throw new ExitBlock(mybrand, result) } }
+ catch {
+ case ExitBlock(brand, result) if brand eq mybrand =>
+ result.asInstanceOf[T]
+ }
+}
+
+def blockUnit(body: (=> Nothing) => Unit) {
+ /* blockUnit { exit => ...; exit; ... }
+ *
+ * Like `block'; it just saves you having to write `exit[Unit] => ...;
+ * exit(ok); ...'.
+ */
+
+ val mybrand = new Brand;
+ try { body { throw new ExitBlock(mybrand, null) }; }
+ catch { case ExitBlock(brand, result) if brand eq mybrand => ok; }
+}
+
+def loop[T](body: (T => Nothing) => Unit): T = {
+ /* loop { exit[T] => ...; exit(x); ... }
+ *
+ * Repeatedly execute the body until it calls the `exit' function.
+ * Annoyingly, Scala isn't clever enough to infer the return type, so
+ * you'll have to write it explicitly.
+ */
+
+ block { exit => while (true) body(exit); unreachable }
+}
+
+def loopUnit(body: (=> Nothing) => Unit): Unit = {
+ /* loopUnit { exit => ...; exit; ... }
+ *
+ * Like `loop'; it just saves you having to write `exit[Unit] => ...;
+ * exit(()); ...'.
+ */
+
+ blockUnit { exit => while (true) body(exit); }
+}
+
+val BREAKS = new Breaks;
+import BREAKS.{breakable, break};
+
/*----- A gadget for fetching URLs ----------------------------------------*/
class URLFetchException(msg: String) extends Exception(msg);
trait URLFetchCallbacks {
def preflight(conn: URLConnection) { }
- def write(buf: Array[Byte], n: Int, len: Int): Unit;
+ def write(buf: Array[Byte], n: Int, len: Long): Unit;
def done(win: Boolean) { }
}
/* Start fetching data. */
val in = c.getInputStream; clean { in.close(); }
- val explen = c.getContentLength();
+ val explen = c.getContentLength;
/* Read a buffer at a time, and give it to the callback. Maintain a
* running total.
*/
- val buf = new Array[Byte](4096);
- var n = 0;
- var len = 0;
- while ({n = in.read(buf); n >= 0 && (explen == -1 || len <= explen)}) {
- cb.write(buf, n, len);
- len += n;
+ var len: Long = 0;
+ blockUnit { exit =>
+ for ((buf, n) <- blocks(in)) {
+ cb.write(buf, n, len);
+ len += n;
+ if (explen != -1 && len > explen) exit;
+ }
}
/* I can't find it documented anywhere that the existing machinery
}
}
-/*----- Running processes -------------------------------------------------*/
-
-//def runProgram(
-
/*----- Threading things --------------------------------------------------*/
def thread[T](name: String, run: Boolean = true, daemon: Boolean = true)
val b = List.newBuilder[String];
var i = pos;
- while (nextToken(s, i) match {
- case Some((w, j)) => b += w; i = j; true
- case None => false
- }) ();
+ loopUnit { exit => nextToken(s, i) match {
+ case Some((w, j)) => b += w; i = j;
+ case None => exit;
+ } }
b.result
}
+/*----- Other random things -----------------------------------------------*/
+
trait LookaheadIterator[T] extends BufferedIterator[T] {
- private[this] var st: Option[T] = None;
+ /* An iterator in terms of a single `maybe there's another item' function.
+ *
+ * It seems like every time I write an iterator in Scala, the only way to
+ * find out whether there's a next item, for `hasNext', is to actually try
+ * to fetch it. So here's an iterator in terms of a function which goes
+ * off and maybe returns a next thing. It turns out to be easy to satisfy
+ * the additional requirements for `BufferedIterator', so why not?
+ */
+
+ /* Subclass responsibility. */
protected def fetch(): Option[T];
+
+ /* The machinery. `st' is `None' if there's no current item, null if we've
+ * actually hit the end, or `Some(x)' if the current item is x.
+ */
+ private[this] var st: Option[T] = None;
private[this] def peek() {
+ /* Arrange to have a current item. */
if (st == None) fetch() match {
case None => st = null;
case x@Some(_) => st = x;
}
}
+
+ /* The `BufferedIterator' protocol. */
override def hasNext: Boolean = { peek(); st != null }
- override def head(): T =
+ override def head: T =
{ peek(); if (st == null) throw new NoSuchElementException; st.get }
- override def next(): T = { val it = head(); st = None; it }
+ override def next(): T = { val it = head; st = None; it }
}
-def lines(r: Reader) = new LookaheadIterator[String] {
- /* Iterates over the lines of text in a `Reader' object. */
+def bufferedReader(r: Reader): BufferedReader = r match {
+ case br: BufferedReader => br
+ case _ => new BufferedReader(r)
+}
- private[this] val in = r match {
- case br: BufferedReader => br;
- case _ => new BufferedReader(r);
+def lines(r: BufferedReader): BufferedIterator[String] =
+ new LookaheadIterator[String] {
+ /* Iterates over the lines of text in a `Reader' object. */
+ override protected def fetch() = Option(r.readLine());
+ }
+def lines(r: Reader): BufferedIterator[String] = lines(bufferedReader(r));
+
+def blocks(in: InputStream, blksz: Int):
+ BufferedIterator[(Array[Byte], Int)] =
+ /* Iterates over (possibly irregularly sized) blocks in a stream. */
+ new LookaheadIterator[(Array[Byte], Int)] {
+ val buf = new Array[Byte](blksz)
+ override protected def fetch() = {
+ val n = in.read(buf);
+ if (n < 0) None
+ else Some((buf, n))
+ }
}
- protected override def fetch(): Option[String] = Option(in.readLine);
+def blocks(in: InputStream):
+ BufferedIterator[(Array[Byte], Int)] = blocks(in, 4096);
+
+def blocks(in: BufferedReader, blksz: Int):
+ BufferedIterator[(Array[Char], Int)] =
+ /* Iterates over (possibly irregularly sized) blocks in a reader. */
+ new LookaheadIterator[(Array[Char], Int)] {
+ val buf = new Array[Char](blksz)
+ override protected def fetch() = {
+ val n = in.read(buf);
+ if (n < 0) None
+ else Some((buf, n))
+ }
+ }
+def blocks(in: BufferedReader):
+ BufferedIterator[(Array[Char], Int)] = blocks(in, 4096);
+def blocks(r: Reader, blksz: Int): BufferedIterator[(Array[Char], Int)] =
+ blocks(bufferedReader(r), blksz);
+def blocks(r: Reader): BufferedIterator[(Array[Char], Int)] =
+ blocks(bufferedReader(r));
+
+def oxford(conj: String, things: Seq[String]): String = things match {
+ case Seq() => "<nothing>"
+ case Seq(a) => a
+ case Seq(a, b) => s"$a $conj $b"
+ case Seq(a, tail@_*) =>
+ val sb = new StringBuilder;
+ sb ++= a; sb ++= ", ";
+ def iter(rest: Seq[String]) {
+ rest match {
+ case Seq() => unreachable;
+ case Seq(a) => sb ++= conj; sb += ' '; sb ++= a;
+ case Seq(a, tail@_*) => sb ++= a; sb ++= ", "; iter(tail);
+ }
+ }
+ iter(tail);
+ sb.result
}
/*----- That's all, folks -------------------------------------------------*/