summaryrefslogtreecommitdiff
path: root/project/GenerateAnyVals.scala
diff options
context:
space:
mode:
Diffstat (limited to 'project/GenerateAnyVals.scala')
-rw-r--r--project/GenerateAnyVals.scala492
1 files changed, 492 insertions, 0 deletions
diff --git a/project/GenerateAnyVals.scala b/project/GenerateAnyVals.scala
new file mode 100644
index 0000000000..f349bfd16b
--- /dev/null
+++ b/project/GenerateAnyVals.scala
@@ -0,0 +1,492 @@
+package scala.build
+
+/** Code generation of the AnyVal types and their companions. */
+trait GenerateAnyValReps {
+ self: GenerateAnyVals =>
+
+ sealed abstract class AnyValNum(name: String, repr: Option[String], javaEquiv: String)
+ extends AnyValRep(name,repr,javaEquiv) {
+
+ case class Op(op : String, doc : String)
+
+ private def companionCoercions(tos: AnyValRep*) = {
+ tos.toList map (to =>
+ s"implicit def @javaequiv@2${to.javaEquiv}(x: @name@): ${to.name} = x.to${to.name}"
+ )
+ }
+ def coercionComment =
+"""/** Language mandated coercions from @name@ to "wider" types. */
+import scala.language.implicitConversions"""
+
+ def implicitCoercions: List[String] = {
+ val coercions = this match {
+ case B => companionCoercions(S, I, L, F, D)
+ case S | C => companionCoercions(I, L, F, D)
+ case I => companionCoercions(L, F, D)
+ case L => companionCoercions(F, D)
+ case F => companionCoercions(D)
+ case _ => Nil
+ }
+ if (coercions.isEmpty) Nil
+ else coercionComment.lines.toList ++ coercions
+ }
+
+ def isCardinal: Boolean = isIntegerType(this)
+ def unaryOps = {
+ val ops = List(
+ Op("+", "/** Returns this value, unmodified. */"),
+ Op("-", "/** Returns the negation of this value. */"))
+
+ if(isCardinal)
+ Op("~", "/**\n" +
+ " * Returns the bitwise negation of this value.\n" +
+ " * @example {{{\n" +
+ " * ~5 == -6\n" +
+ " * // in binary: ~00000101 ==\n" +
+ " * // 11111010\n" +
+ " * }}}\n" +
+ " */") :: ops
+ else ops
+ }
+
+ def bitwiseOps =
+ if (isCardinal)
+ List(
+ Op("|", "/**\n" +
+ " * Returns the bitwise OR of this value and `x`.\n" +
+ " * @example {{{\n" +
+ " * (0xf0 | 0xaa) == 0xfa\n" +
+ " * // in binary: 11110000\n" +
+ " * // | 10101010\n" +
+ " * // --------\n" +
+ " * // 11111010\n" +
+ " * }}}\n" +
+ " */"),
+ Op("&", "/**\n" +
+ " * Returns the bitwise AND of this value and `x`.\n" +
+ " * @example {{{\n" +
+ " * (0xf0 & 0xaa) == 0xa0\n" +
+ " * // in binary: 11110000\n" +
+ " * // & 10101010\n" +
+ " * // --------\n" +
+ " * // 10100000\n" +
+ " * }}}\n" +
+ " */"),
+ Op("^", "/**\n" +
+ " * Returns the bitwise XOR of this value and `x`.\n" +
+ " * @example {{{\n" +
+ " * (0xf0 ^ 0xaa) == 0x5a\n" +
+ " * // in binary: 11110000\n" +
+ " * // ^ 10101010\n" +
+ " * // --------\n" +
+ " * // 01011010\n" +
+ " * }}}\n" +
+ " */"))
+ else Nil
+
+ def shiftOps =
+ if (isCardinal)
+ List(
+ Op("<<", "/**\n" +
+ " * Returns this value bit-shifted left by the specified number of bits,\n" +
+ " * filling in the new right bits with zeroes.\n" +
+ " * @example {{{ 6 << 3 == 48 // in binary: 0110 << 3 == 0110000 }}}\n" +
+ " */"),
+
+ Op(">>>", "/**\n" +
+ " * Returns this value bit-shifted right by the specified number of bits,\n" +
+ " * filling the new left bits with zeroes.\n" +
+ " * @example {{{ 21 >>> 3 == 2 // in binary: 010101 >>> 3 == 010 }}}\n" +
+ " * @example {{{\n" +
+ " * -21 >>> 3 == 536870909\n" +
+ " * // in binary: 11111111 11111111 11111111 11101011 >>> 3 ==\n" +
+ " * // 00011111 11111111 11111111 11111101\n" +
+ " * }}}\n" +
+ " */"),
+
+ Op(">>", "/**\n" +
+ " * Returns this value bit-shifted right by the specified number of bits,\n" +
+ " * filling in the left bits with the same value as the left-most bit of this.\n" +
+ " * The effect of this is to retain the sign of the value.\n" +
+ " * @example {{{\n" +
+ " * -21 >> 3 == -3\n" +
+ " * // in binary: 11111111 11111111 11111111 11101011 >> 3 ==\n" +
+ " * // 11111111 11111111 11111111 11111101\n" +
+ " * }}}\n" +
+ " */"))
+ else Nil
+
+ def comparisonOps = List(
+ Op("==", "/** Returns `true` if this value is equal to x, `false` otherwise. */"),
+ Op("!=", "/** Returns `true` if this value is not equal to x, `false` otherwise. */"),
+ Op("<", "/** Returns `true` if this value is less than x, `false` otherwise. */"),
+ Op("<=", "/** Returns `true` if this value is less than or equal to x, `false` otherwise. */"),
+ Op(">", "/** Returns `true` if this value is greater than x, `false` otherwise. */"),
+ Op(">=", "/** Returns `true` if this value is greater than or equal to x, `false` otherwise. */"))
+
+ def otherOps = List(
+ Op("+", "/** Returns the sum of this value and `x`. */"),
+ Op("-", "/** Returns the difference of this value and `x`. */"),
+ Op("*", "/** Returns the product of this value and `x`. */"),
+ Op("/", "/** Returns the quotient of this value and `x`. */"),
+ Op("%", "/** Returns the remainder of the division of this value by `x`. */"))
+
+ // Given two numeric value types S and T , the operation type of S and T is defined as follows:
+ // If both S and T are subrange types then the operation type of S and T is Int.
+ // Otherwise the operation type of S and T is the larger of the two types wrt ranking.
+ // Given two numeric values v and w the operation type of v and w is the operation type
+ // of their run-time types.
+ def opType(that: AnyValNum): AnyValNum = {
+ val rank = IndexedSeq(I, L, F, D)
+ (rank indexOf this, rank indexOf that) match {
+ case (-1, -1) => I
+ case (r1, r2) => rank apply (r1 max r2)
+ }
+ }
+
+ def mkCoercions = numeric map (x => "def to%s: %s".format(x, x))
+ def mkUnaryOps = unaryOps map (x => "%s\n def unary_%s : %s".format(x.doc, x.op, this opType I))
+ def mkStringOps = List("def +(x: String): String")
+ def mkShiftOps = (
+ for (op <- shiftOps ; arg <- List(I, L)) yield
+ "%s\n def %s(x: %s): %s".format(op.doc, op.op, arg, this opType I)
+ )
+
+ def clumps: List[List[String]] = {
+ val xs1 = List(mkCoercions, mkUnaryOps, mkStringOps, mkShiftOps) map (xs => if (xs.isEmpty) xs else xs :+ "")
+ val xs2 = List(
+ mkBinOpsGroup(comparisonOps, numeric, _ => Z),
+ mkBinOpsGroup(bitwiseOps, cardinal, this opType _),
+ mkBinOpsGroup(otherOps, numeric, this opType _)
+ )
+ xs1 ++ xs2
+ }
+ def classLines = (clumps :+ commonClassLines).foldLeft(List[String]()) {
+ case (res, Nil) => res
+ case (res, lines) =>
+ val xs = lines map {
+ case "" => ""
+ case s => interpolate(s)
+ }
+ res ++ xs
+ }
+ def objectLines = {
+ val comp = if (isCardinal) cardinalCompanion else floatingCompanion
+ interpolate(comp + allCompanions + "\n" + nonUnitCompanions).trim.lines.toList ++ (implicitCoercions map interpolate)
+ }
+
+ /** Makes a set of binary operations based on the given set of ops, args, and resultFn.
+ *
+ * @param ops list of function names e.g. List(">>", "%")
+ * @param args list of types which should appear as arguments
+ * @param resultFn function which calculates return type based on arg type
+ * @return list of function definitions
+ */
+ def mkBinOpsGroup(ops: List[Op], args: List[AnyValNum], resultFn: AnyValNum => AnyValRep): List[String] = (
+ ops flatMap (op =>
+ args.map(arg =>
+ "%s\n def %s(x: %s): %s".format(op.doc, op.op, arg, resultFn(arg))) :+ ""
+ )
+ ).toList
+ }
+
+ sealed abstract class AnyValRep(val name: String, val repr: Option[String], val javaEquiv: String) {
+ def classLines: List[String]
+ def objectLines: List[String]
+ def commonClassLines = List(
+ "// Provide a more specific return type for Scaladoc",
+ "override def getClass(): Class[@name@] = ???"
+ )
+
+ def lcname = name.toLowerCase
+ def boxedSimpleName = this match {
+ case C => "Character"
+ case I => "Integer"
+ case _ => name
+ }
+ def boxedName = this match {
+ case U => "scala.runtime.BoxedUnit"
+ case _ => "java.lang." + boxedSimpleName
+ }
+ def zeroRep = this match {
+ case L => "0L"
+ case F => "0.0f"
+ case D => "0.0d"
+ case _ => "0"
+ }
+
+ def representation = repr.map(", a " + _).getOrElse("")
+
+ def indent(s: String) = if (s == "") "" else " " + s
+ def indentN(s: String) = s.lines map indent mkString "\n"
+
+ def boxUnboxInterpolations = Map(
+ "@boxRunTimeDoc@" -> """
+ * Runtime implementation determined by `scala.runtime.BoxesRunTime.boxTo%s`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]].
+ *""".format(boxedSimpleName),
+ "@unboxRunTimeDoc@" -> """
+ * Runtime implementation determined by `scala.runtime.BoxesRunTime.unboxTo%s`. See [[https://github.com/scala/scala src/library/scala/runtime/BoxesRunTime.java]].
+ *""".format(name),
+ "@unboxDoc@" -> "the %s resulting from calling %sValue() on `x`".format(name, lcname),
+ "@boxImpl@" -> "???",
+ "@unboxImpl@" -> "???"
+ )
+ def interpolations = Map(
+ "@name@" -> name,
+ "@representation@" -> representation,
+ "@javaequiv@" -> javaEquiv,
+ "@boxed@" -> boxedName,
+ "@lcname@" -> lcname,
+ "@zero@" -> zeroRep
+ ) ++ boxUnboxInterpolations
+
+ def interpolate(s: String): String = interpolations.foldLeft(s) {
+ case (str, (key, value)) => str.replaceAll(key, value)
+ }
+ def classDoc = interpolate(classDocTemplate)
+ def objectDoc = ""
+ def mkImports = ""
+
+ def mkClass = assemble("final abstract class " + name + " private extends AnyVal", classLines)
+ def mkObject = assemble("object " + name + " extends AnyValCompanion", objectLines)
+ def make() = List[String](
+ headerTemplate,
+ mkImports,
+ classDoc,
+ mkClass,
+ objectDoc,
+ mkObject
+ ) mkString ""
+
+ def assemble(decl: String, lines: List[String]): String = {
+ val body = if (lines.isEmpty) " { }\n\n" else lines map indent mkString (" {\n", "\n", "\n}\n")
+
+ decl + body + "\n"
+ }
+ override def toString = name
+ }
+}
+
+trait GenerateAnyValTemplates {
+ def headerTemplate = """/* __ *\
+** ________ ___ / / ___ Scala API **
+** / __/ __// _ | / / / _ | (c) 2002-2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ **
+** /____/\___/_/ |_/____/_/ | | **
+** |/ **
+\* */
+
+// DO NOT EDIT, CHANGES WILL BE LOST
+// This auto-generated code can be modified in "project/GenerateAnyVals.scala".
+// Afterwards, running "sbt generateSources" regenerates this source file.
+
+package scala
+
+"""
+
+ def classDocTemplate = ("""
+/** `@name@`@representation@ (equivalent to Java's `@javaequiv@` primitive type) is a
+ * subtype of [[scala.AnyVal]]. Instances of `@name@` are not
+ * represented by an object in the underlying runtime system.
+ *
+ * There is an implicit conversion from [[scala.@name@]] => [[scala.runtime.Rich@name@]]
+ * which provides useful non-primitive operations.
+ */
+""".trim + "\n")
+
+ def allCompanions = """
+/** Transform a value type into a boxed reference type.
+ *@boxRunTimeDoc@
+ * @param x the @name@ to be boxed
+ * @return a @boxed@ offering `x` as its underlying value.
+ */
+def box(x: @name@): @boxed@ = @boxImpl@
+
+/** Transform a boxed type into a value type. Note that this
+ * method is not typesafe: it accepts any Object, but will throw
+ * an exception if the argument is not a @boxed@.
+ *@unboxRunTimeDoc@
+ * @param x the @boxed@ to be unboxed.
+ * @throws ClassCastException if the argument is not a @boxed@
+ * @return @unboxDoc@
+ */
+def unbox(x: java.lang.Object): @name@ = @unboxImpl@
+
+/** The String representation of the scala.@name@ companion object. */
+override def toString = "object scala.@name@"
+"""
+
+ def nonUnitCompanions = "" // todo
+
+ def cardinalCompanion = """
+/** The smallest value representable as a @name@. */
+final val MinValue = @boxed@.MIN_VALUE
+
+/** The largest value representable as a @name@. */
+final val MaxValue = @boxed@.MAX_VALUE
+"""
+
+ def floatingCompanion = """
+/** The smallest positive value greater than @zero@ which is
+ * representable as a @name@.
+ */
+final val MinPositiveValue = @boxed@.MIN_VALUE
+final val NaN = @boxed@.NaN
+final val PositiveInfinity = @boxed@.POSITIVE_INFINITY
+final val NegativeInfinity = @boxed@.NEGATIVE_INFINITY
+
+/** The negative number with the greatest (finite) absolute value which is representable
+ * by a @name@. Note that it differs from [[java.lang.@name@.MIN_VALUE]], which
+ * is the smallest positive value representable by a @name@. In Scala that number
+ * is called @name@.MinPositiveValue.
+ */
+final val MinValue = -@boxed@.MAX_VALUE
+
+/** The largest finite positive number representable as a @name@. */
+final val MaxValue = @boxed@.MAX_VALUE
+"""
+}
+
+class GenerateAnyVals extends GenerateAnyValReps with GenerateAnyValTemplates {
+ object B extends AnyValNum("Byte", Some("8-bit signed integer"), "byte")
+ object S extends AnyValNum("Short", Some("16-bit signed integer"), "short")
+ object C extends AnyValNum("Char", Some("16-bit unsigned integer"), "char")
+ object I extends AnyValNum("Int", Some("32-bit signed integer"), "int")
+ object L extends AnyValNum("Long", Some("64-bit signed integer"), "long")
+ object F extends AnyValNum("Float", Some("32-bit IEEE-754 floating point number"), "float")
+ object D extends AnyValNum("Double", Some("64-bit IEEE-754 floating point number"), "double")
+ object Z extends AnyValRep("Boolean", None, "boolean") {
+ def classLines = """
+/** Negates a Boolean expression.
+ *
+ * - `!a` results in `false` if and only if `a` evaluates to `true` and
+ * - `!a` results in `true` if and only if `a` evaluates to `false`.
+ *
+ * @return the negated expression
+ */
+def unary_! : Boolean
+
+/** Compares two Boolean expressions and returns `true` if they evaluate to the same value.
+ *
+ * `a == b` returns `true` if and only if
+ * - `a` and `b` are `true` or
+ * - `a` and `b` are `false`.
+ */
+def ==(x: Boolean): Boolean
+
+/**
+ * Compares two Boolean expressions and returns `true` if they evaluate to a different value.
+ *
+ * `a != b` returns `true` if and only if
+ * - `a` is `true` and `b` is `false` or
+ * - `a` is `false` and `b` is `true`.
+ */
+def !=(x: Boolean): Boolean
+
+/** Compares two Boolean expressions and returns `true` if one or both of them evaluate to true.
+ *
+ * `a || b` returns `true` if and only if
+ * - `a` is `true` or
+ * - `b` is `true` or
+ * - `a` and `b` are `true`.
+ *
+ * @note This method uses 'short-circuit' evaluation and
+ * behaves as if it was declared as `def ||(x: => Boolean): Boolean`.
+ * If `a` evaluates to `true`, `true` is returned without evaluating `b`.
+ */
+def ||(x: Boolean): Boolean
+
+/** Compares two Boolean expressions and returns `true` if both of them evaluate to true.
+ *
+ * `a && b` returns `true` if and only if
+ * - `a` and `b` are `true`.
+ *
+ * @note This method uses 'short-circuit' evaluation and
+ * behaves as if it was declared as `def &&(x: => Boolean): Boolean`.
+ * If `a` evaluates to `false`, `false` is returned without evaluating `b`.
+ */
+def &&(x: Boolean): Boolean
+
+// Compiler won't build with these seemingly more accurate signatures
+// def ||(x: => Boolean): Boolean
+// def &&(x: => Boolean): Boolean
+
+/** Compares two Boolean expressions and returns `true` if one or both of them evaluate to true.
+ *
+ * `a | b` returns `true` if and only if
+ * - `a` is `true` or
+ * - `b` is `true` or
+ * - `a` and `b` are `true`.
+ *
+ * @note This method evaluates both `a` and `b`, even if the result is already determined after evaluating `a`.
+ */
+def |(x: Boolean): Boolean
+
+/** Compares two Boolean expressions and returns `true` if both of them evaluate to true.
+ *
+ * `a & b` returns `true` if and only if
+ * - `a` and `b` are `true`.
+ *
+ * @note This method evaluates both `a` and `b`, even if the result is already determined after evaluating `a`.
+ */
+def &(x: Boolean): Boolean
+
+/** Compares two Boolean expressions and returns `true` if they evaluate to a different value.
+ *
+ * `a ^ b` returns `true` if and only if
+ * - `a` is `true` and `b` is `false` or
+ * - `a` is `false` and `b` is `true`.
+ */
+def ^(x: Boolean): Boolean
+
+// Provide a more specific return type for Scaladoc
+override def getClass(): Class[Boolean] = ???
+ """.trim.lines.toList
+
+ def objectLines = interpolate(allCompanions + "\n" + nonUnitCompanions).lines.toList
+ }
+ object U extends AnyValRep("Unit", None, "void") {
+ override def classDoc = """
+/** `Unit` is a subtype of [[scala.AnyVal]]. There is only one value of type
+ * `Unit`, `()`, and it is not represented by any object in the underlying
+ * runtime system. A method with return type `Unit` is analogous to a Java
+ * method which is declared `void`.
+ */
+"""
+ def classLines = List(
+ "// Provide a more specific return type for Scaladoc",
+ "override def getClass(): Class[Unit] = ???"
+ )
+ def objectLines = interpolate(allCompanions).lines.toList
+
+ override def boxUnboxInterpolations = Map(
+ "@boxRunTimeDoc@" -> "",
+ "@unboxRunTimeDoc@" -> "",
+ "@unboxDoc@" -> "the Unit value ()",
+ "@boxImpl@" -> "scala.runtime.BoxedUnit.UNIT",
+ "@unboxImpl@" -> "x.asInstanceOf[scala.runtime.BoxedUnit]"
+ )
+ }
+
+ def isSubrangeType = Set(B, S, C)
+ def isIntegerType = Set(B, S, C, I, L)
+ def isFloatingType = Set(F, D)
+ def isWideType = Set(L, D)
+
+ def cardinal = numeric filter isIntegerType
+ def numeric = List(B, S, C, I, L, F, D)
+ def values = List(U, Z) ++ numeric
+
+ def make() = values map (x => (x.name, x.make()))
+}
+
+object GenerateAnyVals {
+ def run(outDir: java.io.File) {
+ val av = new GenerateAnyVals
+
+ av.make() foreach { case (name, code ) =>
+ val file = new java.io.File(outDir, name + ".scala")
+ sbt.IO.write(file, code, java.nio.charset.Charset.forName("UTF-8"), false)
+ }
+ }
+}