summaryrefslogtreecommitdiff
path: root/compiler/src/main/scala/scala/scalajs/compiler/JSEncoding.scala
diff options
context:
space:
mode:
authorHaoyi Li <haoyi@haoyi-mbp.corp.dropbox.com>2014-11-26 00:45:31 -0800
committerHaoyi Li <haoyi@haoyi-mbp.corp.dropbox.com>2014-11-26 00:45:31 -0800
commit2c4b142503bd2d871e6818b5cab8c38627d9e4a0 (patch)
tree6ba33d2980a1a7a1286100202a695c6631bd240e /compiler/src/main/scala/scala/scalajs/compiler/JSEncoding.scala
downloadhands-on-scala-js-2c4b142503bd2d871e6818b5cab8c38627d9e4a0.tar.gz
hands-on-scala-js-2c4b142503bd2d871e6818b5cab8c38627d9e4a0.tar.bz2
hands-on-scala-js-2c4b142503bd2d871e6818b5cab8c38627d9e4a0.zip
Squashed 'examples/scala-js/' content from commit 47311ba
git-subtree-dir: examples/scala-js git-subtree-split: 47311ba693f949f204f27ea9475bb63425fbd4f3
Diffstat (limited to 'compiler/src/main/scala/scala/scalajs/compiler/JSEncoding.scala')
-rw-r--r--compiler/src/main/scala/scala/scalajs/compiler/JSEncoding.scala261
1 files changed, 261 insertions, 0 deletions
diff --git a/compiler/src/main/scala/scala/scalajs/compiler/JSEncoding.scala b/compiler/src/main/scala/scala/scalajs/compiler/JSEncoding.scala
new file mode 100644
index 0000000..bc7f8be
--- /dev/null
+++ b/compiler/src/main/scala/scala/scalajs/compiler/JSEncoding.scala
@@ -0,0 +1,261 @@
+/* Scala.js compiler
+ * Copyright 2013 LAMP/EPFL
+ * @author Sébastien Doeraene
+ */
+
+package scala.scalajs.compiler
+
+import scala.collection.mutable
+
+import scala.tools.nsc._
+
+import scala.scalajs.ir
+import ir.{Trees => js, Types => jstpe}
+
+import util.ScopedVar
+import ScopedVar.withScopedVars
+
+/** Encoding of symbol names for JavaScript
+ *
+ * Some issues that this encoding solves:
+ * * Overloading: encode the full signature in the JS name
+ * * Same scope for fields and methods of a class
+ * * Global access to classes and modules (by their full name)
+ *
+ * @author Sébastien Doeraene
+ */
+trait JSEncoding extends SubComponent { self: GenJSCode =>
+ import global._
+ import jsAddons._
+
+ /** Outer separator string (between parameter types) */
+ final val OuterSep = "__"
+
+ /** Inner separator character (replace dots in full names) */
+ final val InnerSep = "_"
+
+ /** Name given to the local Scala.js environment variable */
+ final val ScalaJSEnvironmentName = "ScalaJS"
+
+ /** Name given to all exported stuff of a class for DCE */
+ final val dceExportName = "<exported>"
+
+ // Fresh local name generator ----------------------------------------------
+
+ private val usedLocalNames = new ScopedVar[mutable.Set[String]]
+ private val localSymbolNames = new ScopedVar[mutable.Map[Symbol, String]]
+ private val isKeywordOrReserved =
+ js.isKeyword ++ Seq("arguments", "eval", ScalaJSEnvironmentName)
+
+ def withNewLocalNameScope[A](body: => A): A =
+ withScopedVars(
+ usedLocalNames := mutable.Set.empty,
+ localSymbolNames := mutable.Map.empty
+ )(body)
+
+ private def freshName(base: String = "x"): String = {
+ var suffix = 1
+ var longName = base
+ while (usedLocalNames(longName) || isKeywordOrReserved(longName)) {
+ suffix += 1
+ longName = base+"$"+suffix
+ }
+ usedLocalNames += longName
+ longName
+ }
+
+ def freshLocalIdent()(implicit pos: ir.Position): js.Ident =
+ js.Ident(freshName(), None)
+
+ def freshLocalIdent(base: String)(implicit pos: ir.Position): js.Ident =
+ js.Ident(freshName(base), Some(base))
+
+ private def localSymbolName(sym: Symbol): String =
+ localSymbolNames.getOrElseUpdate(sym, freshName(sym.name.toString))
+
+ // Encoding methods ----------------------------------------------------------
+
+ def encodeLabelSym(sym: Symbol)(implicit pos: Position): js.Ident = {
+ require(sym.isLabel, "encodeLabelSym called with non-label symbol: " + sym)
+ js.Ident(localSymbolName(sym), Some(sym.unexpandedName.decoded))
+ }
+
+ private lazy val allRefClasses: Set[Symbol] = {
+ import definitions._
+ (Set(ObjectRefClass, VolatileObjectRefClass) ++
+ refClass.values ++ volatileRefClass.values)
+ }
+
+ def encodeFieldSym(sym: Symbol)(implicit pos: Position): js.Ident = {
+ require(sym.owner.isClass && sym.isTerm && !sym.isMethod && !sym.isModule,
+ "encodeFieldSym called with non-field symbol: " + sym)
+
+ val name0 = encodeMemberNameInternal(sym)
+ val name =
+ if (name0.charAt(name0.length()-1) != ' ') name0
+ else name0.substring(0, name0.length()-1)
+
+ /* We have to special-case fields of Ref types (IntRef, ObjectRef, etc.)
+ * because they are emitted as private by our .scala source files, but
+ * they are considered public at use site since their symbols come from
+ * Java-emitted .class files.
+ */
+ val idSuffix =
+ if (sym.isPrivate || allRefClasses.contains(sym.owner))
+ sym.owner.ancestors.count(!_.isInterface).toString
+ else
+ "f"
+
+ val encodedName = name + "$" + idSuffix
+ js.Ident(mangleJSName(encodedName), Some(sym.unexpandedName.decoded))
+ }
+
+ def encodeMethodSym(sym: Symbol, reflProxy: Boolean = false)
+ (implicit pos: Position): js.Ident = {
+ val (encodedName, paramsString) = encodeMethodNameInternal(sym, reflProxy)
+ js.Ident(encodedName + paramsString,
+ Some(sym.unexpandedName.decoded + paramsString))
+ }
+
+ def encodeMethodName(sym: Symbol, reflProxy: Boolean = false): String = {
+ val (encodedName, paramsString) = encodeMethodNameInternal(sym, reflProxy)
+ encodedName + paramsString
+ }
+
+ /** Encodes a method symbol of java.lang.String for use in RuntimeString.
+ *
+ * This basically means adding an initial parameter of type
+ * java.lang.String, which is the `this` parameter.
+ */
+ def encodeRTStringMethodSym(sym: Symbol)(
+ implicit pos: Position): (Symbol, js.Ident) = {
+ require(sym.isMethod, "encodeMethodSym called with non-method symbol: " + sym)
+ require(sym.owner == definitions.StringClass)
+ require(!sym.isClassConstructor && !sym.isPrivate)
+
+ val (encodedName, paramsString) =
+ encodeMethodNameInternal(sym, inRTClass = true)
+ val methodIdent = js.Ident(encodedName + paramsString,
+ Some(sym.unexpandedName.decoded + paramsString))
+
+ (jsDefinitions.RuntimeStringModuleClass, methodIdent)
+ }
+
+ private def encodeMethodNameInternal(sym: Symbol,
+ reflProxy: Boolean = false,
+ inRTClass: Boolean = false): (String, String) = {
+ require(sym.isMethod, "encodeMethodSym called with non-method symbol: " + sym)
+
+ def name = encodeMemberNameInternal(sym)
+
+ val encodedName = {
+ if (sym.isClassConstructor)
+ "init" + InnerSep
+ else if (foreignIsImplClass(sym.owner))
+ encodeClassFullName(sym.owner) + OuterSep + name
+ else if (sym.isPrivate)
+ mangleJSName(name) + OuterSep + "p" +
+ sym.owner.ancestors.count(!_.isInterface).toString
+ else
+ mangleJSName(name)
+ }
+
+ val paramsString = makeParamsString(sym, reflProxy, inRTClass)
+
+ (encodedName, paramsString)
+ }
+
+ def encodeStaticMemberSym(sym: Symbol)(implicit pos: Position): js.Ident = {
+ require(sym.isStaticMember,
+ "encodeStaticMemberSym called with non-static symbol: " + sym)
+ js.Ident(
+ mangleJSName(encodeMemberNameInternal(sym)) +
+ makeParamsString(List(internalName(sym.tpe))),
+ Some(sym.unexpandedName.decoded))
+ }
+
+ def encodeLocalSym(sym: Symbol)(implicit pos: Position): js.Ident = {
+ require(!sym.owner.isClass && sym.isTerm && !sym.isMethod && !sym.isModule,
+ "encodeLocalSym called with non-local symbol: " + sym)
+ js.Ident(mangleJSName(localSymbolName(sym)), Some(sym.unexpandedName.decoded))
+ }
+
+ def foreignIsImplClass(sym: Symbol): Boolean =
+ sym.isModuleClass && nme.isImplClassName(sym.name)
+
+ def encodeClassType(sym: Symbol): jstpe.Type = {
+ if (sym == definitions.ObjectClass) jstpe.AnyType
+ else if (isRawJSType(sym.toTypeConstructor)) jstpe.AnyType
+ else {
+ assert(sym != definitions.ArrayClass,
+ "encodeClassType() cannot be called with ArrayClass")
+ jstpe.ClassType(encodeClassFullName(sym))
+ }
+ }
+
+ def encodeClassFullNameIdent(sym: Symbol)(implicit pos: Position): js.Ident = {
+ js.Ident(encodeClassFullName(sym), Some(sym.fullName))
+ }
+
+ def encodeModuleFullNameIdent(sym: Symbol)(implicit pos: Position): js.Ident = {
+ js.Ident(encodeModuleFullName(sym), Some(sym.fullName))
+ }
+
+ def encodeClassFullName(sym: Symbol): String = {
+ ir.Definitions.encodeClassName(
+ sym.fullName + (if (needsModuleClassSuffix(sym)) "$" else ""))
+ }
+
+ def needsModuleClassSuffix(sym: Symbol): Boolean =
+ sym.isModuleClass && !foreignIsImplClass(sym)
+
+ def encodeModuleFullName(sym: Symbol): String =
+ ir.Definitions.encodeClassName(sym.fullName + "$").dropRight(1)
+
+ private def encodeMemberNameInternal(sym: Symbol): String =
+ sym.name.toString.replace("_", "$und")
+
+ // Encoding of method signatures
+
+ private def makeParamsString(sym: Symbol, reflProxy: Boolean,
+ inRTClass: Boolean): String = {
+ val tpe = sym.tpe
+ val paramTypeNames = tpe.params map (p => internalName(p.tpe))
+ val paramAndResultTypeNames = {
+ if (sym.isClassConstructor)
+ paramTypeNames
+ else if (reflProxy)
+ paramTypeNames :+ ""
+ else {
+ val paramAndResultTypeNames0 =
+ paramTypeNames :+ internalName(tpe.resultType)
+ if (!inRTClass) paramAndResultTypeNames0
+ else internalName(sym.owner.toTypeConstructor) +: paramAndResultTypeNames0
+ }
+ }
+ makeParamsString(paramAndResultTypeNames)
+ }
+
+ private def makeParamsString(paramAndResultTypeNames: List[String]) =
+ paramAndResultTypeNames.mkString(OuterSep, OuterSep, "")
+
+ /** Computes the internal name for a type. */
+ private def internalName(tpe: Type): String = internalName(toTypeKind(tpe))
+
+ private def internalName(kind: TypeKind): String = kind match {
+ case VOID => "V"
+ case kind: ValueTypeKind => kind.primitiveCharCode.toString()
+ case NOTHING => ir.Definitions.RuntimeNothingClass
+ case NULL => ir.Definitions.RuntimeNullClass
+ case REFERENCE(cls) => encodeClassFullName(cls)
+ case ARRAY(elem) => "A"+internalName(elem)
+ }
+
+ /** mangles names that are illegal in JavaScript by prepending a $
+ * also mangles names that would collide with these mangled names
+ */
+ private def mangleJSName(name: String) =
+ if (js.isKeyword(name) || name(0).isDigit || name(0) == '$')
+ "$" + name
+ else name
+}