X-Git-Url: https://git.distorted.org.uk/~mdw/tripe-android/blobdiff_plain/25c3546915ef2105c0d53983939da840ddbde795..c8292b34485a2e00e676023d4164dd5841e4659f:/util.scala?ds=sidebyside diff --git a/util.scala b/util.scala index 79ac861..75b3677 100644 --- a/util.scala +++ b/util.scala @@ -28,9 +28,9 @@ package uk.org.distorted; package object tripe { /*----- 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; @@ -41,6 +41,9 @@ import java.util.concurrent.locks.{Lock, ReentrantLock}; 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 ----------------------------------*/ @@ -134,13 +137,70 @@ def closing[T, U <: Closeable](thing: U)(body: U => T): T = 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) { } } @@ -157,17 +217,18 @@ def fetchURL(url: URL, cb: URLFetchCallbacks) { /* 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 @@ -184,10 +245,6 @@ def fetchURL(url: URL, cb: URLFetchCallbacks) { } } -/*----- Running processes -------------------------------------------------*/ - -//def runProgram( - /*----- Threading things --------------------------------------------------*/ def thread[T](name: String, run: Boolean = true, daemon: Boolean = true) @@ -294,36 +351,107 @@ def splitTokens(s: String, pos: Int = 0): Seq[String] = { 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)) + } + } +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)) + } } - protected override def fetch(): Option[String] = Option(in.readLine); +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() => "" + 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 -------------------------------------------------*/