diff options
author | Haoyi Li <haoyi@haoyi-mbp.corp.dropbox.com> | 2014-11-26 00:45:31 -0800 |
---|---|---|
committer | Haoyi Li <haoyi@haoyi-mbp.corp.dropbox.com> | 2014-11-26 00:45:31 -0800 |
commit | 2c4b142503bd2d871e6818b5cab8c38627d9e4a0 (patch) | |
tree | 6ba33d2980a1a7a1286100202a695c6631bd240e /compiler/src/main/scala/scala/scalajs/compiler/JSEncoding.scala | |
download | hands-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.scala | 261 |
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 +} |