From 1cbe06c2dcde9606c3d3f6fe86e89fbba3a404e3 Mon Sep 17 00:00:00 2001 From: Paul Phillips Date: Sun, 23 Jan 2011 23:06:28 +0000 Subject: Tackled source generation from a new angle rath... Tackled source generation from a new angle rather than letting genprod get any more creaky. This code generates the AnyVal source files (but those are not included here.) No review. --- src/compiler/scala/tools/cmd/gen/AnyVals.scala | 206 +++++++++++++++++++++ src/compiler/scala/tools/cmd/gen/Codegen.scala | 41 ++++ src/compiler/scala/tools/cmd/gen/CodegenSpec.scala | 30 +++ tools/codegen | 7 + 4 files changed, 284 insertions(+) create mode 100644 src/compiler/scala/tools/cmd/gen/AnyVals.scala create mode 100644 src/compiler/scala/tools/cmd/gen/Codegen.scala create mode 100644 src/compiler/scala/tools/cmd/gen/CodegenSpec.scala create mode 100755 tools/codegen diff --git a/src/compiler/scala/tools/cmd/gen/AnyVals.scala b/src/compiler/scala/tools/cmd/gen/AnyVals.scala new file mode 100644 index 0000000000..f1eb6fab5e --- /dev/null +++ b/src/compiler/scala/tools/cmd/gen/AnyVals.scala @@ -0,0 +1,206 @@ +/* NSC -- new Scala compiler + * Copyright 2007-2011 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools.cmd +package gen + +trait AnyValTemplates { + def now = "" + new java.util.Date + + def template = """ +/* __ *\ +** ________ ___ / / ___ Scala API ** +** / __/ __// _ | / / / _ | (c) 2002-2011, LAMP/EPFL ** +** __\ \/ /__/ __ |/ /__/ __ | http://scala-lang.org/ ** +** /____/\___/_/ |_/____/_/ | | ** +** |/ ** +\* */ + +// generated on %s + +package scala + +import scala.runtime.AnyValCompanion +import java.{ lang => jl } + + """.trim.format(now) + "\n\n" + + val booleanBody = """ +final class Boolean extends AnyVal { + def unary_! : Boolean = sys.error("stub") + + def ==(x: Boolean): Boolean = sys.error("stub") + def !=(x: Boolean): Boolean = sys.error("stub") + def ||(x: Boolean): Boolean = sys.error("stub") + def &&(x: Boolean): Boolean = sys.error("stub") + // Compiler won't build with these seemingly more accurate signatures + // def ||(x: => Boolean): Boolean = sys.error("stub") + // def &&(x: => Boolean): Boolean = sys.error("stub") + def |(x: Boolean): Boolean = sys.error("stub") + def &(x: Boolean): Boolean = sys.error("stub") + def ^(x: Boolean): Boolean = sys.error("stub") +} + +object Boolean extends AnyValCompanion { + override def toString = "object scala.Boolean" + def box(x: Boolean): jl.Boolean = jl.Boolean.valueOf(x) + def unbox(x: jl.Object): Boolean = x.asInstanceOf[jl.Boolean].booleanValue() +} + """.trim + + val unitBody = """ +import runtime.BoxedUnit + +final class Unit extends AnyVal { } + +object Unit extends AnyValCompanion { + override def toString = "object scala.Unit" + def box(x: Unit): BoxedUnit = BoxedUnit.UNIT + def unbox(x: jl.Object): Unit = () +} + """.trim +} + +object AnyVals extends AnyValTemplates { + val B = "Byte" + val S = "Short" + val C = "Char" + val I = "Int" + val L = "Long" + val F = "Float" + val D = "Double" + + lazy val cardinal = List(B, S, C, I, L) + lazy val floating = List(F, D) + lazy val numeric = cardinal ++ floating + + def javaType(primType: String) = "jl." + (primType match { + case C => "Character" + case I => "Integer" + case t => t + }) + + def make() = + (numeric zip (numeric map (name => new AnyValOps(name).make()))) ++ List( + ("Boolean", template + booleanBody), + ("Unit", template + unitBody) + ) + + class AnyValOps(name: String) { + val isCardinal = cardinal contains name + val restype = if ("LFD" contains name.head) name else I + val tpe = javaType(name) + val interpolations = Map( + "@restype@" -> restype, + "@name@" -> name, + "@type@" -> tpe, + "@lcname@" -> name.toLowerCase + ) + + def mkCoercions = numeric map (x => "def to%s: %s".format(x, x)) + def mkUnaryOps = unaryops map (op => "def unary_%s : @restype@".format(op)) + def mkCommon = List( + "def +(x: String): String" + ) + def mkShiftOps = ( + for (op <- shiftops ; tpe <- List(I, L)) yield + "def %s(x: %s): @restype@".format(op, tpe) + ) + + def clumps: List[List[String]] = { + val xs1 = List(mkCoercions, mkUnaryOps, mkCommon, mkShiftOps) map (xs => if (xs.isEmpty) xs else xs :+ "") + val xs2 = List( + mkBinOpsGroup(boolBinops, numeric, _ => "Boolean"), + mkBinOpsGroup(bitwiseops, cardinal, resultTypeForArg), + mkBinOpsGroup(otherBinops, numeric, resultTypeForArg) + ) + xs1 ++ xs2 + } + + def defImplementation = "sys.error(\"stub\")" + def indent(s: String) = if (s == "") "" else " " + s + def mkClass = { + val lines = clumps.foldLeft(List[String]()) { + case (res, Nil) => res + case (res, lines) => + val xs = lines map { + case "" => "" + case s => interpolate(s) + " = " + defImplementation + } + res ++ xs + } + assemble("final class", "AnyVal", lines) + } + def mkObject = assemble("object", "AnyValCompanion", companionLines map interpolate) + + def assemble(what: String, parent: String, lines: List[String]): String = ( + List(what, name, "extends", parent, "{").mkString(" ") +: + (lines map indent) :+ + "}" + ).mkString("\n", "\n", "\n") + + def make() = template + mkClass + "\n" + mkObject + + def interpolate(s: String): String = interpolations.foldLeft(s) { + case (str, (key, value)) => str.replaceAll(key, value) + } + + /** 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[String], args: List[String], resultFn: String => String): List[String] = ( + ops flatMap { op => + args.map(arg => "def %s(x: %s): ".format(op, arg) + resultFn(arg)) :+ "" + } + ) + + def resultTypeForArg(arg: String): String = arg match { + case L if isCardinal => L + case F => if (name == D) D else F + case D => D + case _ => restype + } + + def unaryops = if (isCardinal) List("+", "-", "~") else List("+", "-") + def bitwiseops = if (isCardinal) List("|", "&", "^") else Nil + def shiftops = if (isCardinal) List("<<", ">>>", ">>") else Nil + def boolBinops = List("==", "!=", "<", "<=", ">", ">=") + def otherBinops = List("+", "-" ,"*", "/", "%") + + def floatingCompanion = List( + "final val MinPositiveValue = @type@.MIN_VALUE", + "final val MinNegativeValue = -@type@.MAX_VALUE", + "final val NaN = @type@.NaN", + "final val PositiveInfinity = @type@.POSITIVE_INFINITY", + "final val NegativeInfinity = @type@.NEGATIVE_INFINITY", + "", + """@deprecated("use @name@.MinPositiveValue instead")""", + "final val Epsilon = MinPositiveValue", + """@deprecated("use @name@.MinNegativeValue instead")""", + "final val MinValue = MinNegativeValue" + ) + + def cardinalCompanion = List( + "final val MinValue = @type@.MIN_VALUE" + ) + + def commonCompanion = List( + "final val MaxValue = @type@.MAX_VALUE", + "", + "def box(x: @name@): @type@ = @type@.valueOf(x)", + "def unbox(x: jl.Object): @name@ = x.asInstanceOf[@type@].@lcname@Value()", + "override def toString = \"object scala.@name@\"" + ) + + def companionLines = ( + if (isCardinal) cardinalCompanion + else floatingCompanion + ) ++ commonCompanion + } +} \ No newline at end of file diff --git a/src/compiler/scala/tools/cmd/gen/Codegen.scala b/src/compiler/scala/tools/cmd/gen/Codegen.scala new file mode 100644 index 0000000000..0beb7c986a --- /dev/null +++ b/src/compiler/scala/tools/cmd/gen/Codegen.scala @@ -0,0 +1,41 @@ +/* NEST (New Scala Test) + * Copyright 2007-2011 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools.cmd +package gen + +class Codegen(args: List[String]) extends { + val parsed = CodegenSpec(args: _*) +} with CodegenSpec with Instance { } + +object Codegen { + def echo(msg: String) = Console println msg + + def main(args0: Array[String]): Unit = { + val runner = new Codegen(args0.toList) + import runner._ + + if (args0.isEmpty) + return println (CodegenSpec.helpMsg) + + val out = outDir getOrElse { return println("--outdir is required.") } + val all = genall || (!anyvals && !products) + + echo("Generating sources into " + out) + + if (anyvals || all) { + AnyVals.make() foreach { + case (name, code ) => + val file = out / (name + ".scala") toFile; + echo("Writing: " + file) + file writeAll code + } + } + if (products || all) { + () + } + } +} + diff --git a/src/compiler/scala/tools/cmd/gen/CodegenSpec.scala b/src/compiler/scala/tools/cmd/gen/CodegenSpec.scala new file mode 100644 index 0000000000..e655334b1b --- /dev/null +++ b/src/compiler/scala/tools/cmd/gen/CodegenSpec.scala @@ -0,0 +1,30 @@ +/* NEST (New Scala Test) + * Copyright 2007-2011 LAMP/EPFL + * @author Paul Phillips + */ + +package scala.tools.cmd +package gen + +import FromString.ExistingDir + +trait CodegenSpec extends Spec with Meta.StdOpts with Interpolation { + def referenceSpec = CodegenSpec + def programInfo = Spec.Info("codegen", "", "scala.tools.cmd.cmd") + + import FromString.ExistingDir + + help("Usage: codegen []") + + val inDir = "in" / "directory containing templates" --^ ExistingDir + val outDir = "out" / "directory for generated files" --^ ExistingDir + val install = "install" / "write source files directly to src/library/scala" + val anyvals = "anyvals" / "generate sources for AnyVal types" --? + val products = "products" / "generate sources for ProductN, FunctionN, etc." --? + val genall = "all" / "generate sources for everything" --? +} + +object CodegenSpec extends CodegenSpec with Reference { + type ThisCommandLine = CommandLine + def creator(args: List[String]): ThisCommandLine = new CommandLine(CodegenSpec, args) +} diff --git a/tools/codegen b/tools/codegen new file mode 100755 index 0000000000..eee5a3884e --- /dev/null +++ b/tools/codegen @@ -0,0 +1,7 @@ +#!/bin/sh +# + +THISDIR=`dirname $0` +SCALALIB=$THISDIR/../src/library/scala + +scala scala.tools.cmd.gen.Codegen "$@" -- cgit v1.2.3