summaryrefslogtreecommitdiff
path: root/src/compiler/scala/tools/nsc/io/Pickler.scala
diff options
context:
space:
mode:
authorMartin Odersky <odersky@gmail.com>2011-01-07 13:29:04 +0000
committerMartin Odersky <odersky@gmail.com>2011-01-07 13:29:04 +0000
commit8fea694f69b439e97ad6397ac8ace1173390e966 (patch)
treeeb133caffe492130212f581e402c55828de846ba /src/compiler/scala/tools/nsc/io/Pickler.scala
parent6ae3072cd42691e11e9c4ff8cc507ddf2688f478 (diff)
downloadscala-8fea694f69b439e97ad6397ac8ace1173390e966.tar.gz
scala-8fea694f69b439e97ad6397ac8ace1173390e966.tar.bz2
scala-8fea694f69b439e97ad6397ac8ace1173390e966.zip
Documented Picklers.
Diffstat (limited to 'src/compiler/scala/tools/nsc/io/Pickler.scala')
-rw-r--r--src/compiler/scala/tools/nsc/io/Pickler.scala283
1 files changed, 209 insertions, 74 deletions
diff --git a/src/compiler/scala/tools/nsc/io/Pickler.scala b/src/compiler/scala/tools/nsc/io/Pickler.scala
index e57e513132..78c1369b64 100644
--- a/src/compiler/scala/tools/nsc/io/Pickler.scala
+++ b/src/compiler/scala/tools/nsc/io/Pickler.scala
@@ -4,19 +4,89 @@ import annotation.unchecked
import Lexer._
import java.io.Writer
+/** An abstract class for writing and reading Scala objects to and
+ * from a legible representation. The presesentation follows the folloing grammar:
+ * {{{
+ * Pickled = `true' | `false' | `null' | NumericLit | StringLit |
+ * Labelled | Pickled `,' Pickled
+ * Labelled = StringLit `(' Pickled? `)'
+ * }}}
+ *
+ * All ...Lit classes are as in JSON. @see scala.tools.nsc.io.Lexer
+ *
+ * Subclasses of `Pickler` each can write and read individual classes
+ * of values.
+ *
+ * @param T the type of values handled by this pickler.
+ *
+ * These Picklers build on the work of Andrew Kennedy. They are most closely inspired by
+ * Iulian Dragos' picklers for Scala to XML. See:
+ *
+ * <a href="http://code.google.com/p/gdata-scala-client/wiki/DevelopersGuide">
+ * http://code.google.com/p/gdata-scala-client/wiki/DevelopersGuide
+ * </a>
+ */
abstract class Pickler[T] {
import Pickler._
+ /** Writes value in pickled form
+ * @param wr the writer to which pickled form is written
+ * @param x the value to write
+ */
def pickle(wr: Writer, x: T)
+
+ /** Reads value from pickled form.
+ *
+ * @param rd the lexer from which lexemes are read
+ * @return An `UnpickleSuccess value if the current input corresponds to the
+ * kind of value that is unpickled by the current subclass of `Pickler`,
+ * an `UnpickleFailure` value otherwise.
+ * @throws `Lexer.MalformedInput` if input is invalid, or if
+ * an `Unpickle
+ */
def unpickle(rd: Lexer): Unpickled[T]
+ /** A pickler representing a `~`-pair of values as two consecutive pickled
+ * strings, separated by a comma.
+ * @param that the second pickler which together with the current pickler makes
+ * up the pair `this ~ that` to be pickled.
+ */
def ~ [U] (that: => Pickler[U]): Pickler[T ~ U] = seqPickler(this, that)
+ /** A pickler that adds a label to the current pickler, using the representation
+ * `label ( <current pickler> )`
+ *
+ * @label the string to be added as a label.
+ */
def labelled(label: String): Pickler[T] = labelledPickler(label, this)
+
+ /** A pickler obtained from the current pickler by a pair of transformer functions
+ * @param in the function that maps values handled by the current pickler to
+ * values handled by the wrapped pickler.
+ * @param out the function that maps values handled by the wrapped pickler to
+ * values handled by the current pickler.
+ */
def wrapped [U] (in: T => U)(out: U => T): Pickler[U] = wrappedPickler(this)(in)(out)
+
+ /** A pickler obtained from the current pickler by also admitting `null` as
+ * a handled value, represented as the token `null`.
+ *
+ * @param fromNull an implicit evidence parameter ensuring that the type of values
+ * handled by this pickler contains `null`.
+ */
def orNull(implicit fromNull: Null <:< T): Pickler[T] = nullablePickler(this)
+
+ /** A conditional pickler obtained from the current pickler.
+ * @param cond the condition to test to find out whether pickler can handle
+ * some Scala value.
+ */
def cond(p: Any => Boolean): CondPickler[T] = conditionalPickler(this, p)
+
+ /** A conditional pickler handling values of some Scala class. It adds the
+ * class name as a label to the representation of the current pickler and
+ * @param c the class of values handled by this pickler.
+ */
def asClass[U <: T](c: Class[U]): CondPickler[T] = this.labelled(c.getName).cond(c isInstance _)
}
@@ -24,28 +94,59 @@ object Pickler {
var picklerDebugMode = false
+ /** A base class representing unpickler result. It has two subclasses:
+ * `UnpickleSucess` for successful unpicklings and `UnpickleFailure` for failures,
+ * where a value of the given type `T` could not be unpickled from input.
+ * @param T the type of unpickled values in case of success.
+ */
abstract class Unpickled[+T] {
+ /** Transforms success values to success values using given function,
+ * leaves failures alone
+ * @param f the function to apply.
+ */
def map[U](f: T => U): Unpickled[U] = this match {
case UnpickleSuccess(x) => UnpickleSuccess(f(x))
case f: UnpickleFailure => f
}
+ /** Transforms success values to successes or failures using given function,
+ * leaves failures alone.
+ * @param f the function to apply.
+ */
def flatMap[U](f: T => Unpickled[U]): Unpickled[U] = this match {
case UnpickleSuccess(x) => f(x)
case f: UnpickleFailure => f
}
+ /** Tries alternate expression if current result is a failure
+ * @param alt the alternate expression to be tried in case of failure
+ */
def orElse[U >: T](alt: => Unpickled[U]): Unpickled[U] = this match {
case UnpickleSuccess(x) => this
case f: UnpickleFailure => alt
}
+
+ /** Transforms failures into thrown `MalformedInput` exceptions.
+ * @throws MalformedInput if current result is a failure
+ */
def requireSuccess: UnpickleSuccess[T] = this match {
case s @ UnpickleSuccess(x) => s
- case f: UnpickleFailure => throw new Error("Unrecoverable unpickle failure:\n"+f)
+ case f: UnpickleFailure =>
+ throw new MalformedInput(f.rd, "Unrecoverable unpickle failure:\n"+f.errMsg)
}
}
+ /** A class representing successful unpicklings
+ * @param T the type of the unpickled value
+ * @param result the unpickled value
+ */
case class UnpickleSuccess[+T](result: T) extends Unpickled[T]
- class UnpickleFailure(msg: => String, rd: Lexer) extends Unpickled[Nothing] {
+ /** A class representing unpickle failures
+ * @param msg an error message describing what failed.
+ * @param rd the lexer unpickled values were read from (can be used to get
+ * error position, for instance).
+ */
+ class UnpickleFailure(msg: => String, val rd: Lexer) extends Unpickled[Nothing] {
+ def errMsg = msg
override def toString = "Failure at "+rd.tokenPos+":\n"+msg
}
@@ -59,20 +160,35 @@ object Pickler {
UnpickleSuccess(result)
}
+ /** The implicit `Pickler` value for type `T`. Equivalent to `implicitly[Pickler[T]]`.
+ */
def pkl[T: Pickler] = implicitly[Pickler[T]]
+ /** A class represenenting `~`-pairs */
case class ~[S, T](fst: S, snd: T)
+ /** A wrapper class to be able to use `~` s an infix method */
class TildeDecorator[S](x: S) {
+ /** Infix method that forms a `~`-pair. */
def ~ [T](y: T): S ~ T = new ~ (x, y)
}
+ /** An implicit wrapper that adds `~` as a method to any value. */
implicit def tildeDecorator[S](x: S): TildeDecorator[S] = new TildeDecorator(x)
+ /** A converter from binary functions to functions over `~`-pairs
+ */
implicit def fromTilde[T1, T2, R](f: (T1, T2) => R): T1 ~ T2 => R = { case x1 ~ x2 => f(x1, x2) }
+ /** An converter from unctions returning Options over pair to functions returning `~`-pairs
+ * The converted function will raise a `MatchError` where the original function returned
+ * a `None`. This converter is useful for turning `unapply` methods of case classes
+ * into wrapper methods that can be passed as second argument to `wrap`.
+ */
implicit def toTilde[T1, T2, S](f: S => Option[(T1, T2)]): S => T1 ~ T2 = { x => (f(x): @unchecked) match { case Some((x1, x2)) => x1 ~ x2 } }
+ /** Same as `p.labelled(label)`.
+ */
def labelledPickler[T](label: String, p: Pickler[T]): Pickler[T] = new Pickler[T] {
def pickle(wr: Writer, x: T) = {
wr.write(quoted(label));
@@ -93,16 +209,22 @@ object Pickler {
}
}
+ /** Same as `p.wrap(in)(out)`
+ */
def wrappedPickler[S, T](p: Pickler[S])(in: S => T)(out: T => S) = new Pickler[T] {
def pickle(wr: Writer, x: T) = p.pickle(wr, out(x))
def unpickle(rd: Lexer) = p.unpickle(rd) map in
}
- def conditionalPickler[T](p: Pickler[T], cond: Any => Boolean) = new CondPickler[T](cond) {
+ /** Same as `p.cond(condition)`
+ */
+ def conditionalPickler[T](p: Pickler[T], condition: Any => Boolean) = new CondPickler[T](condition) {
def pickle(wr: Writer, x: T) = p.pickle(wr, x)
def unpickle(rd: Lexer) = p.unpickle(rd)
}
+ /** Same as `p ~ q`
+ */
def seqPickler[T, U](p: Pickler[T], q: => Pickler[U]) = new Pickler[T ~ U] {
lazy val qq = q
def pickle(wr: Writer, x: T ~ U) = {
@@ -115,6 +237,8 @@ object Pickler {
yield x ~ y
}
+ /** Same as `p | q`
+ */
def eitherPickler[T, U <: T, V <: T](p: CondPickler[U], q: => CondPickler[V]) =
new CondPickler[T](x => p.canPickle(x) || q.canPickle(x)) {
lazy val qq = q
@@ -126,12 +250,8 @@ object Pickler {
def unpickle(rd: Lexer) = p.unpickle(rd) orElse qq.unpickle(rd)
}
- def singletonPickler[T <: AnyRef](x: T): CondPickler[T] =
- unitPickler
- .wrapped { _ => x } { x => () }
- .labelled (x.getClass.getName)
- .cond (x eq _.asInstanceOf[AnyRef])
-
+ /** Same as `p.orNull`
+ */
def nullablePickler[T](p: Pickler[T])(implicit fromNull: Null <:< T): Pickler[T] = new Pickler[T] {
def pickle(wr: Writer, x: T) =
if (x == null) wr.write("null") else p.pickle(wr, x)
@@ -140,10 +260,38 @@ object Pickler {
else p.unpickle(rd)
}
+ /** A conditional pickler for singleton objects. It represents these
+ * with the object's underlying class as a label.
+ * Example: Object scala.None would be represented as `scala.None$()`.
+ */
+ def singletonPickler[T <: AnyRef](x: T): CondPickler[T] =
+ unitPickler
+ .wrapped { _ => x } { x => () }
+ .labelled (x.getClass.getName)
+ .cond (x eq _.asInstanceOf[AnyRef])
+
+ /** A pickler the handles instances of classes that have an empty constructor.
+ * It represents than as `$new ( <name of class> )`.
+ * When unpickling, a new instance of the class is created using the empty
+ * constructor of the class via `Class.forName(<name of class>).newInstance()`.
+ */
def javaInstancePickler[T <: AnyRef]: Pickler[T] =
(stringPickler labelled "$new")
.wrapped { name => Class.forName(name).newInstance().asInstanceOf[T] } { _.getClass.getName }
+ /** A picklers that handles iterators. It pickles all values
+ * returned by an iterator separated by commas.
+ * When unpickling, it always returns an `UnpickleSuccess` containing an iterator.
+ * This iterator returns 0 or more values that are obtained by unpickling
+ * until a closing parenthesis, bracket or brace or the end of input is encountered.
+ *
+ * This means that iterator picklers should not be directly followed by `~`
+ * because the pickler would also read any values belonging to the second
+ * part of the `~`-pair.
+ *
+ * What's usually done instead is that the iterator pickler is wrapped and labelled
+ * to handle other kinds of sequences.
+ */
implicit def iterPickler[T: Pickler]: Pickler[Iterator[T]] = new Pickler[Iterator[T]] {
lazy val p = pkl[T]
def pickle(wr: Writer, xs: Iterator[T]) {
@@ -166,6 +314,12 @@ object Pickler {
})
}
+ /** A pickler that handles values that can be represented as a single token.
+ * @param kind the kind of token representing the value, used in error messages
+ * for unpickling.
+ * @param matcher A partial function from tokens to handled values. Unpickling
+ * succeeds if the matcher function is defined on the current token.
+ */
private def tokenPickler[T](kind: String)(matcher: PartialFunction[Token, T]) = new Pickler[T] {
def pickle(wr: Writer, x: T) = wr.write(x.toString)
def unpickle(rd: Lexer) =
@@ -173,28 +327,44 @@ object Pickler {
else errorExpected(rd, kind)
}
+ /** A pickler for values of type `Long`, represented as integer literals */
implicit val longPickler: Pickler[Long] =
tokenPickler("integer literal") { case IntLit(s) => s.toLong }
+
+ /** A pickler for values of type `Double`, represented as floating point literals */
implicit val doublePickler: Pickler[Double] =
tokenPickler("floating point literal") { case FloatLit(s) => s.toDouble }
+ /** A pickler for values of type `Byte`, represented as integer literals */
implicit val bytePickler: Pickler[Byte] = longPickler.wrapped { _.toByte } { _.toLong }
+
+ /** A pickler for values of type `Short`, represented as integer literals */
implicit val shortPickler: Pickler[Short] = longPickler.wrapped { _.toShort } { _.toLong }
+
+ /** A pickler for values of type `Int`, represented as integer literals */
implicit val intPickler: Pickler[Int] = longPickler.wrapped { _.toInt } { _.toLong }
+
+ /** A pickler for values of type `Float`, represented as floating point literals */
implicit val floatPickler: Pickler[Float] = doublePickler.wrapped { _.toFloat } { _.toLong }
+ /** A conditional pickler for the boolean value `true` */
private val truePickler =
tokenPickler("boolean literal") { case TrueLit => true } cond { _ == true }
+
+ /** A conditional pickler for the boolean value `false` */
private val falsePickler =
tokenPickler("boolean literal") { case FalseLit => false } cond { _ == false }
+ /** A pickler for values of type `Boolean`, represented as the literals `true` or `false`. */
implicit def booleanPickler: Pickler[Boolean] = truePickler | falsePickler
+ /** A pickler for values of type `Unit`, represented by the empty character string */
implicit val unitPickler: Pickler[Unit] = new Pickler[Unit] {
def pickle(wr: Writer, x: Unit) {}
def unpickle(rd: Lexer): Unpickled[Unit] = UnpickleSuccess(())
}
+ /** A pickler for values of type `String`, represented as string literals */
implicit val stringPickler: Pickler[String] = new Pickler[String] {
def pickle(wr: Writer, x: String) = wr.write(if (x == null) "null" else quoted(x))
def unpickle(rd: Lexer) = rd.token match {
@@ -204,117 +374,82 @@ object Pickler {
}
}
+ /** A pickler for values of type `Char`, represented as string literals of length 1 */
implicit val charPickler: Pickler[Char] =
stringPickler
.wrapped { s => require(s.length == 1, "single character string literal expected, but "+quoted(s)+" found"); s(0) } { _.toString }
+ /** A pickler for pairs, represented as `~`-pairs */
implicit def tuple2Pickler[T1: Pickler, T2: Pickler]: Pickler[(T1, T2)] =
(pkl[T1] ~ pkl[T2])
.wrapped { case x1 ~ x2 => (x1, x2) } { case (x1, x2) => x1 ~ x2 }
.labelled ("tuple2")
+ /** A pickler for 3-tuples, represented as `~`-tuples */
implicit def tuple3Pickler[T1, T2, T3](implicit p1: Pickler[T1], p2: Pickler[T2], p3: Pickler[T3]): Pickler[(T1, T2, T3)] =
(p1 ~ p2 ~ p3)
.wrapped { case x1 ~ x2 ~ x3 => (x1, x2, x3) } { case (x1, x2, x3) => x1 ~ x2 ~ x3 }
.labelled ("tuple3")
+ /** A pickler for 4-tuples, represented as `~`-tuples */
implicit def tuple4Pickler[T1, T2, T3, T4](implicit p1: Pickler[T1], p2: Pickler[T2], p3: Pickler[T3], p4: Pickler[T4]): Pickler[(T1, T2, T3, T4)] =
(p1 ~ p2 ~ p3 ~ p4)
.wrapped { case x1 ~ x2 ~ x3 ~ x4 => (x1, x2, x3, x4) } { case (x1, x2, x3, x4) => x1 ~ x2 ~ x3 ~ x4 }
.labelled ("tuple4")
+ /** A conditional pickler for the `scala.None` object */
implicit val nonePickler = singletonPickler(None)
+ /** A conditional pickler for instances of class `scala.Some` */
implicit def somePickler[T: Pickler]: CondPickler[Some[T]] =
pkl[T]
.wrapped { Some(_) } { _.get }
.asClass (classOf[Some[T]])
+ /** A pickler for optional values */
implicit def optionPickler[T: Pickler]: Pickler[Option[T]] = nonePickler | somePickler[T]
+ /** A pickler for list values */
implicit def listPickler[T: Pickler]: Pickler[List[T]] =
iterPickler[T] .wrapped { _.toList } { _.iterator } .labelled ("scala.List")
+ /** A pickler for vector values */
implicit def vectorPickler[T: Pickler]: Pickler[Vector[T]] =
iterPickler[T] .wrapped { Vector() ++ _ } { _.iterator } .labelled ("scala.Vector")
+ /** A pickler for array values */
implicit def array[T : ClassManifest : Pickler]: Pickler[Array[T]] =
iterPickler[T] .wrapped { _.toArray} { _.iterator } .labelled ("scala.Array")
}
+/** A subclass of Pickler can indicate whether a particular value can be pickled by instances
+ * of this class.
+ * @param canPickle The predicate that indicates whether a given value
+ * can be pickled by instances of this class.
+ */
abstract class CondPickler[T](val canPickle: Any => Boolean) extends Pickler[T] {
import Pickler._
+
+ /** Pickles given value `x` if possible, as indicated by `canPickle(x)`.
+ */
def tryPickle(wr: Writer, x: Any): Boolean = {
val result = canPickle(x)
if (result) pickle(wr, x.asInstanceOf[T])
result
}
+
+ /** A pickler obtained from this pickler and an alternative pickler.
+ * To pickle a value, this pickler is tried first. If it cannot handle
+ * the object (as indicated by its `canPickle` test), then the
+ * alternative pickler is tried.
+ * To unpickle a value, this unpickler is tried first. If it cannot read
+ * the input (as indicated by a `UnpickleFailure` result), then the
+ * alternative pickler is tried.
+ * @param V The handled type of the returned pickler.
+ * @param U The handled type of the alternative pickler.
+ * @param that The alternative pickler.
+ */
def | [V >: T, U <: V] (that: => CondPickler[U]): CondPickler[V] =
eitherPickler[V, T, U](this, that)
}
-object Test extends Application {
- import Pickler._
- import java.io.{StringReader, StringWriter}
-
- case class Foo(x: Int, y: String)
-
- implicit val fooPickler: Pickler[Foo] =
- (pkl[Int] ~ pkl[String])
- .wrapped { Foo.apply } { toTilde(Foo.unapply) }
- .asClass (classOf[Foo])
-
- case class Rec(x: Int, r: Rec)
-
- implicit val recPickler: Pickler[Rec] =
- (pkl[Int] ~ pkl[Rec])
- .wrapped { Rec.apply } { toTilde(Rec.unapply) }
- .asClass (classOf[Rec])
- .orNull
-
- abstract class L[+T]
-
- case class Cons[T](head: T, tail: L[T]) extends L[T]
- case object NIL extends L[Nothing]
-
- implicit def consPickler[T: Pickler] =
- (pkl[T] ~ pkl[L[T]])
- .wrapped { case x ~ y => Cons(x, y) } { toTilde(Cons.unapply) }
- .asClass (classOf[Cons[T]])
-
- implicit lazy val nilPickler = singletonPickler(NIL)
-
- implicit def lPickler[T: Pickler]: Pickler[L[T]] = consPickler[T] | nilPickler
-
- implicit lazy val testPickler = singletonPickler(Test)
-
- implicit def anyThrowableInstance[T <: Throwable]: CondPickler[T] = javaInstancePickler[T] cond { _ => true }
-
- def testRelaxed[T: Pickler](x: T): T = {
- val sw = new PrettyWriter(new StringWriter())
- val pickler = pkl[T]
- val pickled = pickler.pickle(sw, x)
- sw.close()
- val s = sw.toString
- println("pickled: "+s)
- val sr = new Lexer(new StringReader(s))
- val r = pickler.unpickle(sr)
- println("unpickled: "+r)
- val UnpickleSuccess(y) = r
- y
- }
-
- def test[T: Pickler](x: T) {
- assert(testRelaxed(x) == x)
- }
-
- test(Foo(123, "abc"))
- test(List(1, 2, 3))
- test(Test)
- test(Foo(1, null))
- test(Rec(1, Rec(2, Rec(3, null))))
- test(Cons(1, Cons(2, Cons(3, NIL))))
- testRelaxed(new java.io.IOException)
-
-}
-