diff options
author | Martin Odersky <odersky@gmail.com> | 2011-01-07 13:29:04 +0000 |
---|---|---|
committer | Martin Odersky <odersky@gmail.com> | 2011-01-07 13:29:04 +0000 |
commit | 8fea694f69b439e97ad6397ac8ace1173390e966 (patch) | |
tree | eb133caffe492130212f581e402c55828de846ba /src/compiler | |
parent | 6ae3072cd42691e11e9c4ff8cc507ddf2688f478 (diff) | |
download | scala-8fea694f69b439e97ad6397ac8ace1173390e966.tar.gz scala-8fea694f69b439e97ad6397ac8ace1173390e966.tar.bz2 scala-8fea694f69b439e97ad6397ac8ace1173390e966.zip |
Documented Picklers.
Diffstat (limited to 'src/compiler')
-rw-r--r-- | src/compiler/scala/tools/nsc/interactive/Global.scala | 2 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/interactive/Picklers.scala | 9 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/io/Lexer.scala | 108 | ||||
-rw-r--r-- | src/compiler/scala/tools/nsc/io/Pickler.scala | 283 |
4 files changed, 319 insertions, 83 deletions
diff --git a/src/compiler/scala/tools/nsc/interactive/Global.scala b/src/compiler/scala/tools/nsc/interactive/Global.scala index b3a56278c4..1de1f9592f 100644 --- a/src/compiler/scala/tools/nsc/interactive/Global.scala +++ b/src/compiler/scala/tools/nsc/interactive/Global.scala @@ -40,7 +40,6 @@ self => import log.logreplay - /** Print msg only when debugIDE is true. */ @inline final def debugLog(msg: => String) = if (debugIDE) println(msg) @@ -213,7 +212,6 @@ self => } if (nodesSeen == moreWorkAtNode) { - if (logreplay("cancelled", pendingResponse.isCancelled)) { throw CancelException } diff --git a/src/compiler/scala/tools/nsc/interactive/Picklers.scala b/src/compiler/scala/tools/nsc/interactive/Picklers.scala index 45134fca6d..ee816e8f8b 100644 --- a/src/compiler/scala/tools/nsc/interactive/Picklers.scala +++ b/src/compiler/scala/tools/nsc/interactive/Picklers.scala @@ -4,7 +4,7 @@ package interactive import util.{SourceFile, BatchSourceFile} import io.{AbstractFile, PlainFile} -import util.{Position, RangePosition, NoPosition, OffsetPosition, TransparentPosition} +import util.{Position, RangePosition, NoPosition, OffsetPosition, TransparentPosition, EmptyAction} import io.{Pickler, CondPickler} import io.Pickler._ import collection.mutable @@ -109,6 +109,11 @@ trait Picklers { self: Global => .wrapped { new AskToDoFirstItem(_) } { _.source } .asClass (classOf[AskToDoFirstItem]) + implicit def emptyAction: CondPickler[EmptyAction] = + pkl[Unit] + .wrapped { _ => new EmptyAction } { _ => () } + .asClass (classOf[EmptyAction]) + implicit def action: Pickler[() => Unit] = - reloadItem | askTypeAtItem | askTypeItem | askLastTypeItem | askTypeCompletionItem | askScopeCompletionItem | askToDoFirstItem + reloadItem | askTypeAtItem | askTypeItem | askLastTypeItem | askTypeCompletionItem | askScopeCompletionItem | askToDoFirstItem | emptyAction } diff --git a/src/compiler/scala/tools/nsc/io/Lexer.scala b/src/compiler/scala/tools/nsc/io/Lexer.scala index dd0e826351..262aac7809 100644 --- a/src/compiler/scala/tools/nsc/io/Lexer.scala +++ b/src/compiler/scala/tools/nsc/io/Lexer.scala @@ -4,32 +4,74 @@ import java.io.{Reader, Writer, StringReader, StringWriter} import scala.collection.mutable.{Buffer, ArrayBuffer} import scala.math.BigInt +/** Companion object of class `Lexer` which defines tokens and some utility concepts + * used for tokens and lexers + */ object Lexer { + /** An exception raised if a if input does not correspond to what's expected + * @param rdr the lexer form which the bad input is read + * @param msg the error message + */ class MalformedInput(val rdr: Lexer, val msg: String) extends Exception("Malformed JSON input at "+rdr.tokenPos+": "+msg) + /** The class of tokens, i.e. descriptions of input words (or: lexemes). + * @param str the characters making up this token + */ class Token(val str: String) { override def toString = str } + /** A subclass of `Token` representing single-character delimiters + * @param char the delimiter character making up this token + */ case class Delim(char: Char) extends Token("'"+char.toString+"'") + + /** A subclass of token representing integer literals */ case class IntLit(override val str: String) extends Token(str) + + /** A subclass of token representaing floating point literals */ case class FloatLit(override val str: String) extends Token(str) + + /** A subclass of token represenating string literals */ case class StringLit(override val str: String) extends Token(str) { override def toString = quoted(str) } + /** The `true` token */ val TrueLit = new Token("true") + + /** The `false` token */ val FalseLit = new Token("false") + + /** The `null` token */ val NullLit = new Token("null") + + /** The '`(`' token */ val LParen = new Delim('(') + + /** The '`(`' token */ val RParen = new Delim(')') + + /** The '`{`' token */ val LBrace = new Delim('{') + + /** The '`}`' token */ val RBrace = new Delim('}') + + /** The '`[`' token */ val LBracket = new Delim('[') + + /** The '`]`' token */ val RBracket = new Delim(']') + + /** The '`,`' token */ val Comma = new Delim(',') + + /** The '`:`' token */ val Colon = new Delim(':') + + /** The token representing end of input */ val EOF = new Token("<end of input>") private def toUDigit(ch: Int): Char = { @@ -52,6 +94,13 @@ object Lexer { } } + /** Returns given string enclosed in `"`-quotes with all string characters escaped + * so that they correspond to the JSON standard. + * Characters that escaped are: `"`, `\b`, `\f`, `\n`, `\r`, `\t`, `\`. + * Furthermore, every other character which is not in the ASCII range 32-127 is + * escaped as a four hex-digit unicode character of the form `\ u x x x x`. + * @param str the string to be quoted + */ def quoted(str: String): String = { val buf = new StringBuilder += '\"' str foreach (addToStr(buf, _)) @@ -64,11 +113,30 @@ object Lexer { import Lexer._ +/** A simple lexer for tokens as they are used in JSON, plus parens `(`, `)` + * Tokens understood are: + * + * `(`, `)`, `[`, `]`, `{`, `}`, `:`, `,`, `true`, `false`, `null`, + * strings (syntax as in JSON), + * integer numbers (syntax as in JSON: -?(0|\d+) + * floating point numbers (syntax as in JSON: -?(0|\d+)(\.\d+)?((e|E)(+|-)?\d+)?) + * The end of input is represented as its own token, EOF. + * Lexers can keep one token lookahead + * + * @param rd the reader from which characters are read. + */ class Lexer(rd: Reader) { + /** The last-read character */ var ch: Char = 0 + + /** The number of characters read so far */ var pos: Long = 0 + + /** The last-read token */ var token: Token = _ + + /** The number of characters read before the start of the last-read token */ var tokenPos: Long = 0 private var atEOF: Boolean = false @@ -76,6 +144,7 @@ class Lexer(rd: Reader) { private var nread: Int = 0 private var bp = 0 + /** Reads next character into `ch` */ def nextChar() { assert(!atEOF) if (bp == nread) { @@ -88,19 +157,27 @@ class Lexer(rd: Reader) { pos += 1 } + /** If last-read character equals given character, reads next character, + * otherwise raises an error + * @param c the given character to compare with last-read character + * @throws MalformedInput if character does not match + */ def acceptChar(c: Char) = if (ch == c) nextChar() else error("'"+c+"' expected") - val sb = new StringBuilder + private val sb = new StringBuilder - def putChar() { + private def putChar() { sb += ch; nextChar() } - def putAcceptString(str: String) { + private def putAcceptString(str: String) { str foreach acceptChar sb ++= str } + /** Skips whitespace and reads next lexeme into `token` + * @throws MalformedInput if lexeme not recognized as a valid token + */ def nextToken() { sb.clear() while (!atEOF && ch <= ' ') nextChar() @@ -120,11 +197,15 @@ class Lexer(rd: Reader) { case 'n' => putAcceptString("null"); token = NullLit case '"' => getString() case '-' | '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' => getNumber() - case _ => error("illegal string literal character: '"+ch+"'") + case _ => error("unrecoginezed start of token: '"+ch+"'") } - println("["+token+"]") + //println("["+token+"]") } + /** Reads a string literal, and forms a `StringLit` token from it. + * Last-read input character `ch` must be opening `"`-quote. + * @throws MalformedInput if lexeme not recognized as a string literal. + */ def getString() { def udigit() = { nextChar() @@ -160,6 +241,10 @@ class Lexer(rd: Reader) { token = StringLit(sb.toString) } + /** Reads a numeric literal, and forms an `IntLit` or `FloatLit` token from it. + * Last-read input character `ch` must be either `-` or a digit. + * @throws MalformedInput if lexeme not recognized as a numeric literal. + */ def getNumber() { def digit() = if ('0' <= ch && ch <= '9') putChar() @@ -184,11 +269,21 @@ class Lexer(rd: Reader) { token = if (isFloating) FloatLit(sb.toString) else IntLit(sb.toString) } + /** If current token equals given token, reads next token, otherwise raises an error. + * @param t the given token to compare current token with + * @throws MalformedInput if the two tokens do not match. + */ def accept(t: Token) { if (token == t) nextToken() else error(t+" expected, but "+token+" found") } + /** The current token is a delimiter consisting of given character, reads next token, + * otherwise raises an error. + * @param c the given delimiter character to compare current token with + * @throws MalformedInput if the the current token `token` is not a delimiter, or + * consists of a character different from `c`. + */ def accept(ch: Char) { token match { case Delim(`ch`) => nextToken() @@ -196,6 +291,9 @@ class Lexer(rd: Reader) { } } + /** Always throws a `MalformedInput` exception with given error message. + * @param msg the error message + */ def error(msg: String) = throw new MalformedInput(this, msg) nextChar() 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) - -} - |