summaryrefslogtreecommitdiff
path: root/tools/jvm/src/main/scala/scala
diff options
context:
space:
mode:
Diffstat (limited to 'tools/jvm/src/main/scala/scala')
-rw-r--r--tools/jvm/src/main/scala/scala/scalajs/tools/classpath/builder/JarLibClasspathBuilder.scala13
-rw-r--r--tools/jvm/src/main/scala/scala/scalajs/tools/classpath/builder/PartialClasspathBuilder.scala38
-rw-r--r--tools/jvm/src/main/scala/scala/scalajs/tools/classpath/builder/PhysicalFileSystem.scala41
-rw-r--r--tools/jvm/src/main/scala/scala/scalajs/tools/io/FileVirtualFiles.scala157
-rw-r--r--tools/jvm/src/main/scala/scala/scalajs/tools/json/Impl.scala38
-rw-r--r--tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ClosureAstBuilder.scala47
-rw-r--r--tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ClosureAstTransformer.scala397
-rw-r--r--tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ConcurrencyUtils.scala74
-rw-r--r--tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/LoggerErrorManager.scala38
-rw-r--r--tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ParIncOptimizer.scala188
-rw-r--r--tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ScalaJSClosureOptimizer.scala216
-rw-r--r--tools/jvm/src/main/scala/scala/scalajs/tools/sourcemap/SourceMapper.scala88
12 files changed, 1335 insertions, 0 deletions
diff --git a/tools/jvm/src/main/scala/scala/scalajs/tools/classpath/builder/JarLibClasspathBuilder.scala b/tools/jvm/src/main/scala/scala/scalajs/tools/classpath/builder/JarLibClasspathBuilder.scala
new file mode 100644
index 0000000..5bc488c
--- /dev/null
+++ b/tools/jvm/src/main/scala/scala/scalajs/tools/classpath/builder/JarLibClasspathBuilder.scala
@@ -0,0 +1,13 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.classpath.builder
+
+class JarLibClasspathBuilder extends AbstractJarLibClasspathBuilder
+ with PhysicalFileSystem
diff --git a/tools/jvm/src/main/scala/scala/scalajs/tools/classpath/builder/PartialClasspathBuilder.scala b/tools/jvm/src/main/scala/scala/scalajs/tools/classpath/builder/PartialClasspathBuilder.scala
new file mode 100644
index 0000000..626c74c
--- /dev/null
+++ b/tools/jvm/src/main/scala/scala/scalajs/tools/classpath/builder/PartialClasspathBuilder.scala
@@ -0,0 +1,38 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.classpath.builder
+
+import scala.scalajs.tools.classpath._
+import scala.collection.immutable.Seq
+
+/**
+ * Allows to create a PartialClasspathBuilder from a (filesystem) classpath
+ *
+ * Rules for classpath reading:
+ * - IR goes to scalaJSIR
+ * - Descends into JARs
+ * - Entries stay in order of ‘cp‘, IR remains unordered
+ * - Earlier IR entries shadow later IR entries with the same relative path
+ * - JS goes to availableLibs (earlier libs take precedence)
+ * - JS_DEPENDENCIES are added to dependencies
+ */
+class PartialClasspathBuilder extends AbstractPartialClasspathBuilder
+ with PhysicalFileSystem
+
+object PartialClasspathBuilder {
+ /** Convenience method. The same as
+ *
+ * {{{
+ * (new PartialClasspathBuilder).build(cp)
+ * }}}
+ */
+ def build(cp: Seq[java.io.File]): PartialClasspath =
+ (new PartialClasspathBuilder).build(cp)
+}
diff --git a/tools/jvm/src/main/scala/scala/scalajs/tools/classpath/builder/PhysicalFileSystem.scala b/tools/jvm/src/main/scala/scala/scalajs/tools/classpath/builder/PhysicalFileSystem.scala
new file mode 100644
index 0000000..a0dd7a5
--- /dev/null
+++ b/tools/jvm/src/main/scala/scala/scalajs/tools/classpath/builder/PhysicalFileSystem.scala
@@ -0,0 +1,41 @@
+package scala.scalajs.tools.classpath.builder
+
+import scala.scalajs.tools.io._
+
+import scala.collection.immutable.Traversable
+
+import java.io._
+
+/** FileSystem implementation using java.io._ */
+trait PhysicalFileSystem extends FileSystem {
+
+ type File = java.io.File
+
+ val DummyVersion: String = "DUMMY_FILE"
+
+ def isDirectory(f: File): Boolean = f.isDirectory
+ def isFile(f: File): Boolean = f.isFile
+ def isJSFile(f: File): Boolean = f.isFile && f.getName.endsWith(".js")
+ def isIRFile(f: File): Boolean = f.isFile && f.getName.endsWith(".sjsir")
+ def isJARFile(f: File): Boolean = f.isFile && f.getName.endsWith(".jar")
+ def exists(f: File): Boolean = f.exists
+
+ def getName(f: File): String = f.getName
+ def getAbsolutePath(f: File): String = f.getAbsolutePath
+ def getVersion(f: File): String = f.lastModified.toString
+
+ def listFiles(d: File): Traversable[File] = {
+ require(d.isDirectory)
+ Option(d.listFiles).map(_.toList).getOrElse {
+ throw new IOException(s"Couldn't list files in $d")
+ }
+ }
+
+ def toJSFile(f: File): VirtualJSFile = FileVirtualJSFile(f)
+ def toIRFile(f: File): VirtualScalaJSIRFile = FileVirtualScalaJSIRFile(f)
+ def toReader(f: File): Reader =
+ new BufferedReader(new FileReader(f))
+ def toInputStream(f: File): InputStream =
+ new BufferedInputStream(new FileInputStream(f))
+
+}
diff --git a/tools/jvm/src/main/scala/scala/scalajs/tools/io/FileVirtualFiles.scala b/tools/jvm/src/main/scala/scala/scalajs/tools/io/FileVirtualFiles.scala
new file mode 100644
index 0000000..da29225
--- /dev/null
+++ b/tools/jvm/src/main/scala/scala/scalajs/tools/io/FileVirtualFiles.scala
@@ -0,0 +1,157 @@
+package scala.scalajs.tools.io
+
+import scala.annotation.tailrec
+
+import java.io._
+import java.net.URI
+
+/** A [[VirtualFile]] implemented by an actual file on the file system. */
+class FileVirtualFile(val file: File) extends VirtualFile {
+ import FileVirtualFile._
+
+ override def path = file.getPath
+
+ override def name = file.getName
+
+ override def version: Option[String] = {
+ if (!file.isFile) None
+ else Some(file.lastModified.toString)
+ }
+
+ override def exists: Boolean = file.exists
+
+ override def toURI: URI = file.toURI
+}
+
+object FileVirtualFile extends (File => FileVirtualFile) {
+ def apply(f: File): FileVirtualFile =
+ new FileVirtualFile(f)
+
+ /** Tests whether the given file has the specified extension.
+ * Extension contain the '.', so a typical value for `ext` would be ".js".
+ * The comparison is case-sensitive.
+ */
+ def hasExtension(file: File, ext: String): Boolean =
+ file.getName.endsWith(ext)
+
+ /** Returns a new file with the same parent as the given file but a different
+ * name.
+ */
+ def withName(file: File, newName: String): File =
+ new File(file.getParentFile(), newName)
+
+ /** Returns a new file with the same path as the given file but a different
+ * extension.
+ * Extension contain the '.', so a typical value for `ext` would be ".js".
+ * Precondition: hasExtension(file, oldExt)
+ */
+ def withExtension(file: File, oldExt: String, newExt: String): File = {
+ require(hasExtension(file, oldExt),
+ s"File $file does not have extension '$oldExt'")
+ withName(file, file.getName.stripSuffix(oldExt) + newExt)
+ }
+}
+
+/** A [[VirtualTextFile]] implemented by an actual file on the file system. */
+class FileVirtualTextFile(f: File) extends FileVirtualFile(f)
+ with VirtualTextFile {
+ import FileVirtualTextFile._
+
+ override def content: String = readFileToString(file)
+ override def reader: Reader = new BufferedReader(new FileReader(f))
+}
+
+object FileVirtualTextFile extends (File => FileVirtualTextFile) {
+ def apply(f: File): FileVirtualTextFile =
+ new FileVirtualTextFile(f)
+
+ /** Reads the entire content of a file as a UTF-8 string. */
+ def readFileToString(file: File): String = {
+ val stream = new FileInputStream(file)
+ try IO.readInputStreamToString(stream)
+ finally stream.close()
+ }
+}
+
+trait WritableFileVirtualTextFile extends FileVirtualTextFile
+ with WritableVirtualTextFile {
+ override def contentWriter: Writer = {
+ new BufferedWriter(new OutputStreamWriter(
+ new FileOutputStream(file), "UTF-8"))
+ }
+}
+
+object WritableFileVirtualTextFile {
+ def apply(f: File): WritableFileVirtualTextFile =
+ new FileVirtualTextFile(f) with WritableFileVirtualTextFile
+}
+
+/** A [[VirtualBinaryFile]] implemented by an actual file on the file system. */
+class FileVirtualBinaryFile(f: File) extends FileVirtualFile(f)
+ with VirtualBinaryFile {
+ import FileVirtualBinaryFile._
+
+ override def inputStream: InputStream =
+ new BufferedInputStream(new FileInputStream(file))
+
+ override def content: Array[Byte] =
+ readFileToByteArray(file)
+}
+
+object FileVirtualBinaryFile extends (File => FileVirtualBinaryFile) {
+ def apply(f: File): FileVirtualBinaryFile =
+ new FileVirtualBinaryFile(f)
+
+ /** Reads the entire content of a file as byte array. */
+ def readFileToByteArray(file: File): Array[Byte] = {
+ val stream = new FileInputStream(file)
+ try IO.readInputStreamToByteArray(stream)
+ finally stream.close()
+ }
+}
+
+class FileVirtualJSFile(f: File) extends FileVirtualTextFile(f)
+ with VirtualJSFile {
+ import FileVirtualFile._
+ import FileVirtualTextFile._
+
+ val sourceMapFile: File = withExtension(file, ".js", ".js.map")
+
+ override def sourceMap: Option[String] = {
+ if (sourceMapFile.exists) Some(readFileToString(sourceMapFile))
+ else None
+ }
+}
+
+object FileVirtualJSFile extends (File => FileVirtualJSFile) {
+ def apply(f: File): FileVirtualJSFile =
+ new FileVirtualJSFile(f)
+}
+
+trait WritableFileVirtualJSFile extends FileVirtualJSFile
+ with WritableFileVirtualTextFile
+ with WritableVirtualJSFile {
+
+ override def sourceMapWriter: Writer = {
+ new BufferedWriter(new OutputStreamWriter(
+ new FileOutputStream(sourceMapFile), "UTF-8"))
+ }
+}
+
+object WritableFileVirtualJSFile {
+ def apply(f: File): WritableFileVirtualJSFile =
+ new FileVirtualJSFile(f) with WritableFileVirtualJSFile
+}
+
+class FileVirtualScalaJSIRFile(f: File)
+ extends FileVirtualBinaryFile(f) with VirtualSerializedScalaJSIRFile
+
+object FileVirtualScalaJSIRFile extends (File => FileVirtualScalaJSIRFile) {
+ import FileVirtualFile._
+
+ def apply(f: File): FileVirtualScalaJSIRFile =
+ new FileVirtualScalaJSIRFile(f)
+
+ def isScalaJSIRFile(file: File): Boolean =
+ hasExtension(file, ".sjsir")
+}
diff --git a/tools/jvm/src/main/scala/scala/scalajs/tools/json/Impl.scala b/tools/jvm/src/main/scala/scala/scalajs/tools/json/Impl.scala
new file mode 100644
index 0000000..ea847e3
--- /dev/null
+++ b/tools/jvm/src/main/scala/scala/scalajs/tools/json/Impl.scala
@@ -0,0 +1,38 @@
+package scala.scalajs.tools.json
+
+import org.json.simple.JSONValue
+
+import scala.collection.JavaConverters._
+
+import java.io.{Writer, Reader}
+
+private[json] object Impl extends AbstractJSONImpl {
+
+ type Repr = Object
+
+ def fromString(x: String): Repr = x
+ def fromNumber(x: Number): Repr = x
+ def fromBoolean(x: Boolean): Repr = java.lang.Boolean.valueOf(x)
+ def fromList(x: List[Repr]): Repr = x.asJava
+ def fromMap(x: Map[String, Repr]): Repr = x.asJava
+
+ def toString(x: Repr): String = x.asInstanceOf[String]
+ def toNumber(x: Repr): Number = x.asInstanceOf[Number]
+ def toBoolean(x: Repr): Boolean =
+ x.asInstanceOf[java.lang.Boolean].booleanValue()
+ def toList(x: Repr): List[Repr] =
+ x.asInstanceOf[java.util.List[Repr]].asScala.toList
+ def toMap(x: Repr): Map[String, Repr] =
+ x.asInstanceOf[java.util.Map[String, Repr]].asScala.toMap
+
+ def serialize(x: Repr): String =
+ JSONValue.toJSONString(x)
+
+ def serialize(x: Repr, writer: Writer): Unit =
+ JSONValue.writeJSONString(x, writer)
+
+ def deserialize(str: String): Repr = JSONValue.parse(str)
+
+ def deserialize(reader: Reader): Repr = JSONValue.parse(reader)
+
+}
diff --git a/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ClosureAstBuilder.scala b/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ClosureAstBuilder.scala
new file mode 100644
index 0000000..8d2eb2b
--- /dev/null
+++ b/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ClosureAstBuilder.scala
@@ -0,0 +1,47 @@
+package scala.scalajs.tools.optimizer
+
+import scala.scalajs.ir
+import ir.Position.NoPosition
+
+import scala.scalajs.tools.javascript.Trees.Tree
+
+import com.google.javascript.rhino._
+import com.google.javascript.rhino.jstype.{StaticSourceFile, SimpleSourceFile}
+import com.google.javascript.jscomp._
+
+import scala.collection.mutable
+
+import java.net.URI
+
+class ClosureAstBuilder(
+ relativizeBaseURI: Option[URI] = None) extends JSTreeBuilder {
+
+ private val transformer = new ClosureAstTransformer(relativizeBaseURI)
+ private val treeBuf = mutable.ListBuffer.empty[Node]
+
+ def addJSTree(tree: Tree): Unit =
+ treeBuf += transformer.transformStat(tree)(NoPosition)
+
+ lazy val closureAST: SourceAst = {
+ val root = transformer.setNodePosition(IR.script(treeBuf: _*), NoPosition)
+
+ treeBuf.clear()
+
+ new ClosureAstBuilder.ScalaJSSourceAst(root)
+ }
+
+}
+
+object ClosureAstBuilder {
+ // Dummy Source AST class
+
+ class ScalaJSSourceAst(root: Node) extends SourceAst {
+ def getAstRoot(compiler: AbstractCompiler): Node = root
+ def clearAst(): Unit = () // Just for GC. Nonsensical here.
+ def getInputId(): InputId = root.getInputId()
+ def getSourceFile(): SourceFile =
+ root.getStaticSourceFile().asInstanceOf[SourceFile]
+ def setSourceFile(file: SourceFile): Unit =
+ if (getSourceFile() ne file) throw new IllegalStateException
+ }
+}
diff --git a/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ClosureAstTransformer.scala b/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ClosureAstTransformer.scala
new file mode 100644
index 0000000..af22501
--- /dev/null
+++ b/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ClosureAstTransformer.scala
@@ -0,0 +1,397 @@
+package scala.scalajs.tools.optimizer
+
+import scala.scalajs.ir
+import ir.Position
+import ir.Position.NoPosition
+
+import scala.scalajs.tools.javascript.Trees._
+
+import com.google.javascript.rhino._
+import com.google.javascript.jscomp._
+
+import scala.collection.mutable
+import scala.annotation.tailrec
+
+import java.net.URI
+
+class ClosureAstTransformer(val relativizeBaseURI: Option[URI] = None) {
+
+ private val inputId = new InputId("Scala.js IR")
+
+ private val dummySourceName = new java.net.URI("virtualfile:scala.js-ir")
+
+ def transformStat(tree: Tree)(implicit parentPos: Position): Node =
+ innerTransformStat(tree, tree.pos orElse parentPos)
+
+ private def innerTransformStat(tree: Tree, pos_in: Position): Node = {
+ implicit val pos = pos_in
+
+ wrapTransform(tree) {
+ case VarDef(ident, _, EmptyTree) =>
+ new Node(Token.VAR, transformName(ident))
+ case VarDef(ident, _, rhs) =>
+ val node = transformName(ident)
+ node.addChildToFront(transformExpr(rhs))
+ new Node(Token.VAR, node)
+ case Skip() =>
+ new Node(Token.EMPTY)
+ case Block(stats) =>
+ transformBlock(stats, pos)
+ case Labeled(label, body) =>
+ new Node(Token.LABEL, transformLabel(label), transformBlock(body))
+ case Return(EmptyTree) =>
+ new Node(Token.RETURN)
+ case Return(expr) =>
+ new Node(Token.RETURN, transformExpr(expr))
+ case If(cond, thenp, Skip()) =>
+ new Node(Token.IF, transformExpr(cond), transformBlock(thenp))
+ case If(cond, thenp, elsep) =>
+ new Node(Token.IF, transformExpr(cond),
+ transformBlock(thenp), transformBlock(elsep))
+ case While(cond, body, None) =>
+ new Node(Token.WHILE, transformExpr(cond), transformBlock(body))
+ case While(cond, body, Some(label)) =>
+ val whileNode =
+ new Node(Token.WHILE, transformExpr(cond), transformBlock(body))
+ new Node(Token.LABEL, transformLabel(label),
+ setNodePosition(whileNode, pos))
+ case DoWhile(body, cond, None) =>
+ new Node(Token.DO, transformBlock(body), transformExpr(cond))
+ case DoWhile(body, cond, Some(label)) =>
+ val doNode =
+ new Node(Token.DO, transformBlock(body), transformExpr(cond))
+ new Node(Token.LABEL, transformLabel(label),
+ setNodePosition(doNode, pos))
+ case Try(block, errVar, handler, EmptyTree) =>
+ val catchPos = handler.pos orElse pos
+ val catchNode =
+ new Node(Token.CATCH, transformName(errVar), transformBlock(handler))
+ val blockNode =
+ new Node(Token.BLOCK, setNodePosition(catchNode, catchPos))
+ new Node(Token.TRY, transformBlock(block),
+ setNodePosition(blockNode, catchPos))
+ case Try(block, _, EmptyTree, finalizer) =>
+ val blockNode = setNodePosition(new Node(Token.BLOCK), pos)
+ new Node(Token.TRY, transformBlock(block), blockNode,
+ transformBlock(finalizer))
+ case Try(block, errVar, handler, finalizer) =>
+ val catchPos = handler.pos orElse pos
+ val catchNode =
+ new Node(Token.CATCH, transformName(errVar), transformBlock(handler))
+ val blockNode =
+ new Node(Token.BLOCK, setNodePosition(catchNode, catchPos))
+ new Node(Token.TRY, transformBlock(block),
+ setNodePosition(blockNode, catchPos), transformBlock(finalizer))
+ case Throw(expr) =>
+ new Node(Token.THROW, transformExpr(expr))
+ case Break(None) =>
+ new Node(Token.BREAK)
+ case Break(Some(label)) =>
+ new Node(Token.BREAK, transformLabel(label))
+ case Continue(None) =>
+ new Node(Token.CONTINUE)
+ case Continue(Some(label)) =>
+ new Node(Token.CONTINUE, transformLabel(label))
+
+ case Switch(selector, cases, default) =>
+ val switchNode = new Node(Token.SWITCH, transformExpr(selector))
+
+ for ((expr, body) <- cases) {
+ val bodyNode = transformBlock(body)
+ bodyNode.putBooleanProp(Node.SYNTHETIC_BLOCK_PROP, true)
+ val caseNode = new Node(Token.CASE, transformExpr(expr), bodyNode)
+ switchNode.addChildToBack(
+ setNodePosition(caseNode, expr.pos orElse pos))
+ }
+
+ if (default != EmptyTree) {
+ val bodyNode = transformBlock(default)
+ bodyNode.putBooleanProp(Node.SYNTHETIC_BLOCK_PROP, true)
+ val caseNode = new Node(Token.DEFAULT_CASE, bodyNode)
+ switchNode.addChildToBack(
+ setNodePosition(caseNode, default.pos orElse pos))
+ }
+
+ switchNode
+
+ case Debugger() =>
+ new Node(Token.DEBUGGER)
+ case _ =>
+ // We just assume it is an expression
+ new Node(Token.EXPR_RESULT, transformExpr(tree))
+ }
+ }
+
+ def transformExpr(tree: Tree)(implicit parentPos: Position): Node =
+ innerTransformExpr(tree, tree.pos orElse parentPos)
+
+ private def innerTransformExpr(tree: Tree, pos_in: Position): Node = {
+ implicit val pos = pos_in
+
+ wrapTransform(tree) {
+ case Block(exprs) =>
+ exprs.map(transformExpr).reduceRight { (expr1, expr2) =>
+ setNodePosition(new Node(Token.COMMA, expr1, expr2), pos)
+ }
+ case If(cond, thenp, elsep) =>
+ new Node(Token.HOOK, transformExpr(cond),
+ transformExpr(thenp), transformExpr(elsep))
+ case Assign(lhs, rhs) =>
+ new Node(Token.ASSIGN, transformExpr(lhs), transformExpr(rhs))
+ case New(ctor, args) =>
+ val node = new Node(Token.NEW, transformExpr(ctor))
+ args.foreach(arg => node.addChildToBack(transformExpr(arg)))
+ node
+ case DotSelect(qualifier, item) =>
+ new Node(Token.GETPROP, transformExpr(qualifier), transformString(item))
+ case BracketSelect(qualifier, item) =>
+ new Node(Token.GETELEM, transformExpr(qualifier), transformExpr(item))
+
+ case Apply(fun, args) =>
+ val node = new Node(Token.CALL, transformExpr(fun))
+ args.foreach(arg => node.addChildToBack(transformExpr(arg)))
+
+ // Closure needs to know (from the parser), if the call has a bound
+ // `this` or not. Since JSDesugar inserts protects calls if necessary,
+ // it is sufficient to check if we have a select as target
+ if (!fun.isInstanceOf[DotSelect] &&
+ !fun.isInstanceOf[BracketSelect])
+ node.putBooleanProp(Node.FREE_CALL, true)
+
+ node
+
+ case Delete(prop) =>
+ new Node(Token.DELPROP, transformExpr(prop))
+ case UnaryOp(op, lhs) =>
+ mkUnaryOp(op, transformExpr(lhs))
+ case BinaryOp(op, lhs, rhs) =>
+ mkBinaryOp(op, transformExpr(lhs), transformExpr(rhs))
+ case ArrayConstr(items) =>
+ val node = new Node(Token.ARRAYLIT)
+ items.foreach(i => node.addChildToBack(transformExpr(i)))
+ node
+
+ case ObjectConstr(fields) =>
+ val node = new Node(Token.OBJECTLIT)
+
+ for ((name, expr) <- fields) {
+ val fldNode = transformStringKey(name)
+ fldNode.addChildToBack(transformExpr(expr))
+ node.addChildToBack(fldNode)
+ }
+
+ node
+
+ case Undefined() =>
+ new Node(Token.VOID, setNodePosition(Node.newNumber(0.0), pos))
+ case Null() =>
+ new Node(Token.NULL)
+ case BooleanLiteral(value) =>
+ if (value) new Node(Token.TRUE) else new Node(Token.FALSE)
+ case IntLiteral(value) =>
+ Node.newNumber(value)
+ case DoubleLiteral(value) =>
+ Node.newNumber(value)
+ case StringLiteral(value) =>
+ Node.newString(value)
+ case VarRef(ident, _) =>
+ transformName(ident)
+ case This() =>
+ new Node(Token.THIS)
+
+ case Function(args, body) =>
+ // Note that a Function may also be a statement (when it is named),
+ // but Scala.js does not have such an IR node
+ val paramList = new Node(Token.PARAM_LIST)
+ args.foreach(arg => paramList.addChildToBack(transformParam(arg)))
+
+ val emptyName = setNodePosition(Node.newString(Token.NAME, ""), pos)
+
+ new Node(Token.FUNCTION, emptyName, paramList, transformBlock(body))
+
+ case _ =>
+ throw new TransformException(s"Unknown tree of class ${tree.getClass()}")
+ }
+ }
+
+ def transformParam(param: ParamDef)(implicit parentPos: Position): Node =
+ transformName(param.name)
+
+ def transformName(ident: Ident)(implicit parentPos: Position): Node =
+ setNodePosition(Node.newString(Token.NAME, ident.name),
+ ident.pos orElse parentPos)
+
+ def transformLabel(ident: Ident)(implicit parentPos: Position): Node =
+ setNodePosition(Node.newString(Token.LABEL_NAME, ident.name),
+ ident.pos orElse parentPos)
+
+ def transformString(pName: PropertyName)(implicit parentPos: Position): Node =
+ setNodePosition(Node.newString(pName.name), pName.pos orElse parentPos)
+
+ def transformStringKey(pName: PropertyName)(
+ implicit parentPos: Position): Node = {
+ val node = Node.newString(Token.STRING_KEY, pName.name)
+
+ if (pName.isInstanceOf[StringLiteral])
+ node.setQuotedString()
+
+ setNodePosition(node, pName.pos orElse parentPos)
+ }
+
+ def transformBlock(tree: Tree)(implicit parentPos: Position): Node = {
+ val pos = if (tree.pos.isDefined) tree.pos else parentPos
+ wrapTransform(tree) {
+ case Block(stats) =>
+ transformBlock(stats, pos)
+ case tree =>
+ transformBlock(List(tree), pos)
+ } (pos)
+ }
+
+ def transformBlock(stats: List[Tree], blockPos: Position): Node = {
+ @inline def ctorDoc(node: Node) = {
+ val b = new JSDocInfoBuilder(false)
+ b.recordConstructor()
+ b.build(node)
+ }
+
+ val block = new Node(Token.BLOCK)
+
+ // The Rhino IR attaches DocComments to the following nodes (rather than
+ // having individual nodes). We preprocess these here.
+ @tailrec
+ def loop(ts: List[Tree], nextIsCtor: Boolean = false): Unit = ts match {
+ case DocComment(text) :: tss if text.startsWith("@constructor") =>
+ loop(tss, nextIsCtor = true)
+ case DocComment(text) :: tss =>
+ loop(tss)
+ case t :: tss =>
+ val node = transformStat(t)(blockPos)
+ if (nextIsCtor) {
+ // The @constructor must be propagated through an ExprResult node
+ val trg =
+ if (node.isExprResult()) node.getChildAtIndex(0)
+ else node
+
+ trg.setJSDocInfo(ctorDoc(trg))
+ }
+
+ block.addChildToBack(node)
+
+ loop(tss)
+ case Nil =>
+ }
+
+ loop(stats)
+
+ block
+ }
+
+ @inline
+ private def wrapTransform(tree: Tree)(body: Tree => Node)(
+ implicit pos: Position): Node = {
+ try {
+ setNodePosition(body(tree), pos)
+ } catch {
+ case e: TransformException =>
+ throw e // pass through
+ case e: RuntimeException =>
+ throw new TransformException(tree, e)
+ }
+ }
+
+ def setNodePosition(node: Node, pos: ir.Position): node.type = {
+ if (pos != ir.Position.NoPosition) {
+ attachSourceFile(node, pos.source)
+ node.setLineno(pos.line+1)
+ node.setCharno(pos.column)
+ } else {
+ attachSourceFile(node, dummySourceName)
+ }
+ node
+ }
+
+ private def attachSourceFile(node: Node, source: URI): node.type = {
+ val str = sourceUriToString(source)
+
+ node.setInputId(inputId)
+ node.setStaticSourceFile(new SourceFile(str))
+
+ node
+ }
+
+ private def sourceUriToString(uri: URI): String = {
+ val relURI = relativizeBaseURI.fold(uri)(ir.Utils.relativize(_, uri))
+ ir.Utils.fixFileURI(relURI).toASCIIString
+ }
+
+ // Helpers for IR
+ @inline
+ private def mkUnaryOp(op: String, lhs: Node): Node = {
+ val tok = op match {
+ case "!" => Token.NOT
+ case "~" => Token.BITNOT
+ case "+" => Token.POS
+ case "-" => Token.NEG
+ case "typeof" => Token.TYPEOF
+ case _ => throw new TransformException(s"Unhandled unary op: $op")
+ }
+
+ new Node(tok, lhs)
+ }
+
+ @inline
+ private def mkBinaryOp(op: String, lhs: Node, rhs: Node): Node = {
+ val tok = op match {
+ case "|" => Token.BITOR
+ case "&" => Token.BITAND
+ case "^" => Token.BITXOR
+ case "==" => Token.EQ
+ case "!=" => Token.NE
+ case "<" => Token.LT
+ case "<=" => Token.LE
+ case ">" => Token.GT
+ case ">=" => Token.GE
+ case "<<" => Token.LSH
+ case ">>" => Token.RSH
+ case ">>>" => Token.URSH
+ case "+" => Token.ADD
+ case "-" => Token.SUB
+ case "*" => Token.MUL
+ case "/" => Token.DIV
+ case "%" => Token.MOD
+ case "===" => Token.SHEQ
+ case "!==" => Token.SHNE
+ case "in" => Token.IN
+ case "instanceof" => Token.INSTANCEOF
+ case "||" => Token.OR
+ case "&&" => Token.AND
+ case _ =>
+ throw new TransformException(s"Unhandled binary op: $op")
+ }
+
+ new Node(tok, lhs, rhs)
+ }
+
+ // Exception wrapper in transforms
+
+ class TransformException private (msg: String, e: Throwable)
+ extends RuntimeException(msg, e) {
+
+ def this(tree: Tree, e: Throwable) =
+ this(TransformException.mkMsg(tree), e)
+
+ def this(msg: String) = this(msg, null)
+ }
+
+ object TransformException {
+ import ir.Printers._
+ import java.io._
+
+ private def mkMsg(tree: Tree): String = {
+ "Exception while translating Scala.js JS tree to GCC IR at tree:\n" +
+ tree.show
+ }
+ }
+
+}
diff --git a/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ConcurrencyUtils.scala b/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ConcurrencyUtils.scala
new file mode 100644
index 0000000..471ed65
--- /dev/null
+++ b/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ConcurrencyUtils.scala
@@ -0,0 +1,74 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.optimizer
+
+import scala.annotation.tailrec
+
+import scala.collection.concurrent.TrieMap
+
+import java.util.concurrent.atomic._
+
+private[optimizer] object ConcurrencyUtils {
+
+ /** An atomic accumulator supports adding single elements and retrieving and
+ * deleting all contained elements */
+ type AtomicAcc[T] = AtomicReference[List[T]]
+
+ object AtomicAcc {
+ @inline final def empty[T]: AtomicAcc[T] =
+ new AtomicReference[List[T]](Nil)
+ @inline final def apply[T](l: List[T]): AtomicAcc[T] =
+ new AtomicReference(l)
+ }
+
+ implicit class AtomicAccOps[T](val acc: AtomicAcc[T]) extends AnyVal {
+ @inline final def size: Int = acc.get.size
+
+ @inline
+ final def +=(x: T): Unit = AtomicAccOps.append(acc, x)
+
+ @inline
+ final def removeAll(): List[T] = AtomicAccOps.removeAll(acc)
+ }
+
+ object AtomicAccOps {
+ @inline
+ @tailrec
+ private final def append[T](acc: AtomicAcc[T], x: T): Boolean = {
+ val oldV = acc.get
+ val newV = x :: oldV
+ acc.compareAndSet(oldV, newV) || append(acc, x)
+ }
+
+ @inline
+ private final def removeAll[T](acc: AtomicAcc[T]): List[T] =
+ acc.getAndSet(Nil)
+ }
+
+ type TrieSet[T] = TrieMap[T, Null]
+
+ implicit class TrieSetOps[T](val set: TrieSet[T]) extends AnyVal {
+ @inline final def +=(x: T): Unit = set.put(x, null)
+ }
+
+ object TrieSet {
+ @inline final def empty[T]: TrieSet[T] = TrieMap.empty
+ }
+
+ implicit class TrieMapOps[K,V](val map: TrieMap[K,V]) extends AnyVal {
+ @inline final def getOrPut(k: K, default: => V): V = {
+ map.get(k).getOrElse {
+ val v = default
+ map.putIfAbsent(k, v).getOrElse(v)
+ }
+ }
+ }
+
+}
diff --git a/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/LoggerErrorManager.scala b/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/LoggerErrorManager.scala
new file mode 100644
index 0000000..d51dd7b
--- /dev/null
+++ b/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/LoggerErrorManager.scala
@@ -0,0 +1,38 @@
+package scala.scalajs.tools.optimizer
+
+import com.google.javascript.jscomp.{ BasicErrorManager, CheckLevel, JSError }
+
+import scala.scalajs.tools.logging.Logger
+
+/** A Google Closure Error Manager that forwards to a tools.logging.Logger */
+class LoggerErrorManager(private val log: Logger) extends BasicErrorManager {
+
+ /** Ugly hack to disable FRACTIONAL_BITWISE_OPERAND warnings. Since its
+ * DiagnosticType (PeepholeFoldConstants.FRACTIONAL_BITWISE_OPERAND) is
+ * package private.
+ */
+ override def report(level: CheckLevel, error: JSError): Unit = {
+ if (error.getType.key == "JSC_FRACTIONAL_BITWISE_OPERAND")
+ super.report(CheckLevel.OFF, error)
+ else
+ super.report(level, error)
+ }
+
+ def println(level: CheckLevel, error: JSError): Unit = level match {
+ case CheckLevel.WARNING => log.warn (s"Closure: ${error}")
+ case CheckLevel.ERROR => log.error(s"Closure: ${error}")
+ case CheckLevel.OFF =>
+ }
+
+ protected def printSummary(): Unit = {
+ val msg = s"Closure: ${getErrorCount} error(s), ${getWarningCount} warning(s)"
+
+ if (getErrorCount > 0)
+ log.error(msg)
+ else if (getWarningCount > 0)
+ log.warn(msg)
+ else
+ log.info(msg)
+ }
+
+}
diff --git a/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ParIncOptimizer.scala b/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ParIncOptimizer.scala
new file mode 100644
index 0000000..422238e
--- /dev/null
+++ b/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ParIncOptimizer.scala
@@ -0,0 +1,188 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.optimizer
+
+import scala.collection.{GenTraversableOnce, GenIterable}
+import scala.collection.concurrent.TrieMap
+import scala.collection.parallel.mutable.{ParTrieMap, ParArray}
+
+import java.util.concurrent.atomic._
+
+import scala.scalajs.tools.sem.Semantics
+
+import ConcurrencyUtils._
+
+class ParIncOptimizer(semantics: Semantics) extends GenIncOptimizer(semantics) {
+
+ protected object CollOps extends GenIncOptimizer.AbsCollOps {
+ type Map[K, V] = TrieMap[K, V]
+ type ParMap[K, V] = ParTrieMap[K, V]
+ type AccMap[K, V] = TrieMap[K, AtomicAcc[V]]
+ type ParIterable[V] = ParArray[V]
+ type Addable[V] = AtomicAcc[V]
+
+ def emptyAccMap[K, V]: AccMap[K, V] = TrieMap.empty
+ def emptyMap[K, V]: Map[K, V] = TrieMap.empty
+ def emptyParMap[K, V]: ParMap[K, V] = ParTrieMap.empty
+ def emptyParIterable[V]: ParIterable[V] = ParArray.empty
+
+ // Operations on ParMap
+ def put[K, V](map: ParMap[K, V], k: K, v: V): Unit = map.put(k, v)
+ def remove[K, V](map: ParMap[K, V], k: K): Option[V] = map.remove(k)
+
+ def retain[K, V](map: ParMap[K, V])(p: (K, V) => Boolean): Unit = {
+ map.foreach { case (k, v) =>
+ if (!p(k, v))
+ map.remove(k)
+ }
+ }
+
+ // Operations on AccMap
+ def acc[K, V](map: AccMap[K, V], k: K, v: V): Unit =
+ map.getOrPut(k, AtomicAcc.empty) += v
+
+ def getAcc[K, V](map: AccMap[K, V], k: K): GenIterable[V] =
+ map.get(k).fold[Iterable[V]](Nil)(_.removeAll()).toParArray
+
+ def parFlatMapKeys[A, B](map: AccMap[A, _])(
+ f: A => GenTraversableOnce[B]): GenIterable[B] =
+ map.keys.flatMap(f).toParArray
+
+ // Operations on ParIterable
+ def prepAdd[V](it: ParIterable[V]): Addable[V] =
+ AtomicAcc(it.toList)
+
+ def add[V](addable: Addable[V], v: V): Unit =
+ addable += v
+
+ def finishAdd[V](addable: Addable[V]): ParIterable[V] =
+ addable.removeAll().toParArray
+ }
+
+ private val _interfaces = TrieMap.empty[String, InterfaceType]
+ protected def getInterface(encodedName: String): InterfaceType =
+ _interfaces.getOrPut(encodedName, new ParInterfaceType(encodedName))
+
+ private val methodsToProcess: AtomicAcc[MethodImpl] = AtomicAcc.empty
+ protected def scheduleMethod(method: MethodImpl): Unit =
+ methodsToProcess += method
+
+ protected def newMethodImpl(owner: MethodContainer,
+ encodedName: String): MethodImpl = new ParMethodImpl(owner, encodedName)
+
+ protected def processAllTaggedMethods(): Unit = {
+ val methods = methodsToProcess.removeAll().toParArray
+ logProcessingMethods(methods.count(!_.deleted))
+ for (method <- methods)
+ method.process()
+ }
+
+ private class ParInterfaceType(encName: String) extends InterfaceType(encName) {
+ private val ancestorsAskers = TrieSet.empty[MethodImpl]
+ private val dynamicCallers = TrieMap.empty[String, TrieSet[MethodImpl]]
+ private val staticCallers = TrieMap.empty[String, TrieSet[MethodImpl]]
+
+ private var _ancestors: List[String] = encodedName :: Nil
+
+ private val _instantiatedSubclasses: TrieSet[Class] = TrieSet.empty
+
+ /** PROCESS PASS ONLY. Concurrency safe except with
+ * [[addInstantiatedSubclass]] and [[removeInstantiatedSubclass]]
+ */
+ def instantiatedSubclasses: Iterable[Class] =
+ _instantiatedSubclasses.keys
+
+ /** UPDATE PASS ONLY. Concurrency safe except with
+ * [[instantiatedSubclasses]]
+ */
+ def addInstantiatedSubclass(x: Class): Unit =
+ _instantiatedSubclasses += x
+
+ /** UPDATE PASS ONLY. Concurrency safe except with
+ * [[instantiatedSubclasses]]
+ */
+ def removeInstantiatedSubclass(x: Class): Unit =
+ _instantiatedSubclasses -= x
+
+ /** PROCESS PASS ONLY. Concurrency safe except with [[ancestors_=]] */
+ def ancestors: List[String] = _ancestors
+
+ /** UPDATE PASS ONLY. Not concurrency safe. */
+ def ancestors_=(v: List[String]): Unit = {
+ if (v != _ancestors) {
+ _ancestors = v
+ ancestorsAskers.keysIterator.foreach(_.tag())
+ ancestorsAskers.clear()
+ }
+ }
+
+ /** PROCESS PASS ONLY. Concurrency safe except with [[ancestors_=]]. */
+ def registerAskAncestors(asker: MethodImpl): Unit =
+ ancestorsAskers += asker
+
+ /** PROCESS PASS ONLY. */
+ def registerDynamicCaller(methodName: String, caller: MethodImpl): Unit =
+ dynamicCallers.getOrPut(methodName, TrieSet.empty) += caller
+
+ /** PROCESS PASS ONLY. */
+ def registerStaticCaller(methodName: String, caller: MethodImpl): Unit =
+ staticCallers.getOrPut(methodName, TrieSet.empty) += caller
+
+ /** UPDATE PASS ONLY. */
+ def unregisterDependee(dependee: MethodImpl): Unit = {
+ ancestorsAskers -= dependee
+ dynamicCallers.valuesIterator.foreach(_ -= dependee)
+ staticCallers.valuesIterator.foreach(_ -= dependee)
+ }
+
+ /** UPDATE PASS ONLY. */
+ def tagDynamicCallersOf(methodName: String): Unit =
+ dynamicCallers.remove(methodName).foreach(_.keysIterator.foreach(_.tag()))
+
+ /** UPDATE PASS ONLY. */
+ def tagStaticCallersOf(methodName: String): Unit =
+ staticCallers.remove(methodName).foreach(_.keysIterator.foreach(_.tag()))
+ }
+
+ private class ParMethodImpl(owner: MethodContainer,
+ encodedName: String) extends MethodImpl(owner, encodedName) {
+
+ private val bodyAskers = TrieSet.empty[MethodImpl]
+
+ /** PROCESS PASS ONLY. */
+ def registerBodyAsker(asker: MethodImpl): Unit =
+ bodyAskers += asker
+
+ /** UPDATE PASS ONLY. */
+ def unregisterDependee(dependee: MethodImpl): Unit =
+ bodyAskers -= dependee
+
+ /** UPDATE PASS ONLY. */
+ def tagBodyAskers(): Unit = {
+ bodyAskers.keysIterator.foreach(_.tag())
+ bodyAskers.clear()
+ }
+
+ private val _registeredTo = AtomicAcc.empty[Unregisterable]
+ private val tagged = new AtomicBoolean(false)
+
+ protected def registeredTo(intf: Unregisterable): Unit =
+ _registeredTo += intf
+
+ protected def unregisterFromEverywhere(): Unit = {
+ _registeredTo.removeAll().foreach(_.unregisterDependee(this))
+ }
+
+ protected def protectTag(): Boolean = !tagged.getAndSet(true)
+ protected def resetTag(): Unit = tagged.set(false)
+
+ }
+
+}
diff --git a/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ScalaJSClosureOptimizer.scala b/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ScalaJSClosureOptimizer.scala
new file mode 100644
index 0000000..146b2b8
--- /dev/null
+++ b/tools/jvm/src/main/scala/scala/scalajs/tools/optimizer/ScalaJSClosureOptimizer.scala
@@ -0,0 +1,216 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013-2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.optimizer
+
+import scala.scalajs.tools.sem.Semantics
+import scala.scalajs.tools.classpath._
+import scala.scalajs.tools.logging._
+import scala.scalajs.tools.io._
+import scala.scalajs.tools.corelib.CoreJSLibs
+
+import com.google.javascript.jscomp.{
+ SourceFile => ClosureSource,
+ Compiler => ClosureCompiler,
+ CompilerOptions => ClosureOptions,
+ _
+}
+import scala.collection.JavaConverters._
+import scala.collection.immutable.{Seq, Traversable}
+
+import java.net.URI
+
+/** Scala.js Closure optimizer: does advanced optimizations with Closure. */
+class ScalaJSClosureOptimizer(semantics: Semantics) {
+ import ScalaJSClosureOptimizer._
+
+ private def toClosureSource(file: VirtualJSFile) =
+ ClosureSource.fromReader(file.toURI.toString(), file.reader)
+
+ private def toClosureInput(file: VirtualJSFile) =
+ new CompilerInput(toClosureSource(file))
+
+ /** Fully optimizes an [[IRClasspath]] by asking the ScalaJSOptimizer to
+ * emit a closure AST and then compiling this AST directly
+ */
+ def optimizeCP(optimizer: ScalaJSOptimizer,
+ inputs: Inputs[ScalaJSOptimizer.Inputs[IRClasspath]],
+ outCfg: OutputConfig, logger: Logger): LinkedClasspath = {
+
+ val cp = inputs.input.input
+
+ CacheUtils.cached(cp.version, outCfg.output, outCfg.cache) {
+ logger.info(s"Full Optimizing ${outCfg.output.path}")
+
+ val irFastOptInput =
+ inputs.input.copy(input = inputs.input.input.scalaJSIR)
+ val irFullOptInput = inputs.copy(input = irFastOptInput)
+
+ optimizeIR(optimizer, irFullOptInput, outCfg, logger)
+ }
+
+ new LinkedClasspath(cp.jsLibs, outCfg.output, cp.requiresDOM, cp.version)
+ }
+
+ def optimizeIR(optimizer: ScalaJSOptimizer,
+ inputs: Inputs[ScalaJSOptimizer.Inputs[Traversable[VirtualScalaJSIRFile]]],
+ outCfg: OutputConfig, logger: Logger): Unit = {
+
+ // Build Closure IR via ScalaJSOptimizer
+ val builder = new ClosureAstBuilder(outCfg.relativizeSourceMapBase)
+
+ optimizer.optimizeIR(inputs.input, outCfg, builder, logger)
+
+ // Build a Closure JSModule which includes the core libs
+ val module = new JSModule("Scala.js")
+
+ for (lib <- CoreJSLibs.libs(semantics))
+ module.add(toClosureInput(lib))
+
+ val ast = builder.closureAST
+ module.add(new CompilerInput(ast, ast.getInputId(), false))
+
+ for (export <- inputs.additionalExports)
+ module.add(toClosureInput(export))
+
+ // Compile the module
+ val closureExterns =
+ (ScalaJSExternsFile +: inputs.additionalExterns).map(toClosureSource)
+
+ val options = closureOptions(outCfg)
+ val compiler = closureCompiler(logger)
+
+ val result = GenIncOptimizer.logTime(logger, "Closure Compiler pass") {
+ compiler.compileModules(
+ closureExterns.asJava, List(module).asJava, options)
+ }
+
+ GenIncOptimizer.logTime(logger, "Write Closure result") {
+ writeResult(result, compiler, outCfg.output)
+ }
+ }
+
+ private def writeResult(result: Result, compiler: ClosureCompiler,
+ output: WritableVirtualJSFile): Unit = {
+
+ val outputContent = if (result.errors.nonEmpty) ""
+ else "(function(){'use strict';" + compiler.toSource + "}).call(this);\n"
+
+ val sourceMap = Option(compiler.getSourceMap())
+
+ // Write optimized code
+ val w = output.contentWriter
+ try {
+ w.write(outputContent)
+ if (sourceMap.isDefined)
+ w.write("//# sourceMappingURL=" + output.name + ".map\n")
+ } finally w.close()
+
+ // Write source map (if available)
+ sourceMap.foreach { sm =>
+ val w = output.sourceMapWriter
+ try sm.appendTo(w, output.name)
+ finally w.close()
+ }
+ }
+
+ private def closureOptions(optConfig: OptimizerConfig,
+ noSourceMap: Boolean = false) = {
+
+ val options = new ClosureOptions
+ options.prettyPrint = optConfig.prettyPrint
+ CompilationLevel.ADVANCED_OPTIMIZATIONS.setOptionsForCompilationLevel(options)
+ options.setLanguageIn(ClosureOptions.LanguageMode.ECMASCRIPT5)
+ options.setCheckGlobalThisLevel(CheckLevel.OFF)
+
+ if (!noSourceMap && optConfig.wantSourceMap) {
+ options.setSourceMapOutputPath(optConfig.output.name + ".map")
+ options.setSourceMapDetailLevel(SourceMap.DetailLevel.ALL)
+ }
+
+ options
+ }
+
+ private def closureCompiler(logger: Logger) = {
+ val compiler = new ClosureCompiler
+ compiler.setErrorManager(new LoggerErrorManager(logger))
+ compiler
+ }
+}
+
+object ScalaJSClosureOptimizer {
+ /** Inputs of the Scala.js Closure optimizer. */
+ final case class Inputs[T](
+ /** Input to optimize (classpath or file-list) */
+ input: T,
+ /** Additional externs files to be given to Closure. */
+ additionalExterns: Seq[VirtualJSFile] = Nil,
+ /** Additional exports to be given to Closure.
+ * These files are just appended to the classpath, given to Closure,
+ * but not used in the Scala.js optimizer pass when direct optimizing
+ */
+ additionalExports: Seq[VirtualJSFile] = Nil
+ )
+
+ /** Configuration the closure part of the optimizer needs.
+ * See [[OutputConfig]] for a description of the fields.
+ */
+ trait OptimizerConfig {
+ val output: WritableVirtualJSFile
+ val cache: Option[WritableVirtualTextFile]
+ val wantSourceMap: Boolean
+ val prettyPrint: Boolean
+ val relativizeSourceMapBase: Option[URI]
+ }
+
+ /** Configuration for the output of the Scala.js Closure optimizer */
+ final case class OutputConfig(
+ /** Writer for the output */
+ output: WritableVirtualJSFile,
+ /** Cache file */
+ cache: Option[WritableVirtualTextFile] = None,
+ /** If true, performs expensive checks of the IR for the used parts. */
+ checkIR: Boolean = false,
+ /** If true, the optimizer removes trees that have not been used in the
+ * last run from the cache. Otherwise, all trees that has been used once,
+ * are kept in memory. */
+ unCache: Boolean = true,
+ /** If true, no optimizations are performed */
+ disableOptimizer: Boolean = false,
+ /** If true, nothing is performed incrementally */
+ batchMode: Boolean = false,
+ /** Ask to produce source map for the output */
+ wantSourceMap: Boolean = false,
+ /** Pretty-print the output. */
+ prettyPrint: Boolean = false,
+ /** Base path to relativize paths in the source map */
+ relativizeSourceMapBase: Option[URI] = None
+ ) extends OptimizerConfig with ScalaJSOptimizer.OptimizerConfig
+
+ /** Minimal set of externs to compile Scala.js-emitted code with Closure. */
+ val ScalaJSExterns = """
+ /** @constructor */
+ function Object() {}
+ Object.protoype.toString = function() {};
+ /** @constructor */
+ function Array() {}
+ Array.prototype.length = 0;
+ /** @constructor */
+ function Function() {}
+ Function.prototype.constructor = function() {};
+ Function.prototype.call = function() {};
+ Function.prototype.apply = function() {};
+ var global = {};
+ var __ScalaJSEnv = {};
+ """
+
+ val ScalaJSExternsFile = new MemVirtualJSFile("ScalaJSExterns.js").
+ withContent(ScalaJSExterns)
+
+}
diff --git a/tools/jvm/src/main/scala/scala/scalajs/tools/sourcemap/SourceMapper.scala b/tools/jvm/src/main/scala/scala/scalajs/tools/sourcemap/SourceMapper.scala
new file mode 100644
index 0000000..6f856a9
--- /dev/null
+++ b/tools/jvm/src/main/scala/scala/scalajs/tools/sourcemap/SourceMapper.scala
@@ -0,0 +1,88 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js sbt plugin **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.sourcemap
+
+import scala.scalajs.tools.classpath._
+
+import com.google.debugging.sourcemap._
+
+class SourceMapper(classpath: CompleteClasspath) {
+
+ def map(ste: StackTraceElement, columnNumber: Int): StackTraceElement = {
+ val mapped = for {
+ sourceMap <- findSourceMap(ste.getFileName)
+ } yield map(ste, columnNumber, sourceMap)
+
+ mapped.getOrElse(ste)
+ }
+
+ def map(ste: StackTraceElement, columnNumber: Int,
+ sourceMap: String): StackTraceElement = {
+
+ val sourceMapConsumer =
+ SourceMapConsumerFactory
+ .parse(sourceMap)
+ .asInstanceOf[SourceMapConsumerV3]
+
+ /* **Attention**
+ * - StackTrace positions are 1-based
+ * - SourceMapConsumer.getMappingForLine() is 1-based
+ */
+
+ val lineNumber = ste.getLineNumber
+ val column =
+ if (columnNumber == -1) getFirstColumn(sourceMapConsumer, lineNumber)
+ else columnNumber
+
+ val originalMapping =
+ sourceMapConsumer.getMappingForLine(lineNumber, column)
+
+ if (originalMapping != null) {
+ new StackTraceElement(
+ ste.getClassName,
+ ste.getMethodName,
+ originalMapping.getOriginalFile,
+ originalMapping.getLineNumber)
+ } else ste
+ }
+
+ /** both `lineNumber` and the resulting column are 1-based */
+ private def getFirstColumn(sourceMapConsumer: SourceMapConsumerV3,
+ lineNumber1Based: Int) = {
+
+ /* **Attention**
+ * - SourceMapConsumerV3.EntryVisitor is 0-based!!!
+ */
+
+ val lineNumber = lineNumber1Based - 1
+
+ var column: Option[Int] = None
+
+ sourceMapConsumer.visitMappings(
+ new SourceMapConsumerV3.EntryVisitor {
+ def visit(sourceName: String,
+ symbolName: String,
+ sourceStartPosition: FilePosition,
+ startPosition: FilePosition,
+ endPosition: FilePosition): Unit =
+ if (!column.isDefined && startPosition.getLine == lineNumber)
+ column = Some(startPosition.getColumn)
+ })
+
+ val column0Based = column.getOrElse(0)
+ column0Based + 1
+ }
+
+ private def findSourceMap(path: String) = {
+ val candidates = classpath.allCode.filter(_.path == path)
+ if (candidates.size != 1) None // better no sourcemap than a wrong one
+ else candidates.head.sourceMap
+ }
+}