summaryrefslogtreecommitdiff
path: root/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/rhino/ScalaJSCoreLib.scala
blob: e937e5b33a2ec206db4cd34bf7f1bbd506bbf56e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/*                     __                                               *\
**     ________ ___   / /  ___      __ ____  Scala.js sbt plugin        **
**    / __/ __// _ | / /  / _ | __ / // __/  (c) 2013, LAMP/EPFL        **
**  __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \    http://scala-js.org/       **
** /____/\___/_/ |_/____/_/ | |__/ /____/                               **
**                          |/____/                                     **
\*                                                                      */


package scala.scalajs.sbtplugin.env.rhino

import scala.collection.mutable

import org.mozilla.javascript.{Context, Scriptable}

import scala.scalajs.ir

import scala.scalajs.tools.sem.Semantics
import scala.scalajs.tools.javascript.{Printers, ScalaJSClassEmitter}
import scala.scalajs.tools.io._
import scala.scalajs.tools.classpath._
import scala.scalajs.tools.corelib._

class ScalaJSCoreLib(semantics: Semantics, classpath: IRClasspath) {
  import ScalaJSCoreLib._

  private val (providers, exportedSymbols) = {
    val providers = mutable.Map.empty[String, VirtualScalaJSIRFile]
    val exportedSymbols = mutable.ListBuffer.empty[String]

    for (irFile <- classpath.scalaJSIR) {
      val info = irFile.roughInfo
      providers += info.encodedName -> irFile
      if (info.isExported)
        exportedSymbols += info.encodedName
    }

    (providers, exportedSymbols)
  }

  def insertInto(context: Context, scope: Scriptable) = {
    CoreJSLibs.libs(semantics).foreach(context.evaluateFile(scope, _))
    lazifyScalaJSFields(scope)

    // Make sure exported symbols are loaded
    val ScalaJS = Context.toObject(scope.get("ScalaJS", scope), scope)
    val c = Context.toObject(ScalaJS.get("c", ScalaJS), scope)
    for (encodedName <- exportedSymbols)
      c.get(encodedName, c)
  }

  /** Source maps the given stack trace (where possible) */
  def mapStackTrace(stackTrace: Scriptable,
      context: Context, scope: Scriptable): Scriptable = {
    val count = Context.toNumber(stackTrace.get("length", stackTrace)).toInt

    // Maps file -> max line (0-based)
    val neededMaps = mutable.Map.empty[String, Int]

    // Collect required line counts
    for (i <- 0 until count) {
      val elem = Context.toObject(stackTrace.get(i, stackTrace), scope)
      val fileName = Context.toString(elem.get("fileName", elem))

      if (fileName.endsWith(PseudoFileSuffix) &&
          providers.contains(fileName.stripSuffix(PseudoFileSuffix))) {

        val curMaxLine = neededMaps.getOrElse(fileName, -1)
        val reqLine = Context.toNumber(elem.get("lineNumber", elem)).toInt - 1

        if (reqLine > curMaxLine)
          neededMaps.put(fileName, reqLine)
      }
    }

    // Map required files
    val maps =
      for ((fileName, maxLine) <- neededMaps)
        yield (fileName, getSourceMapper(fileName, maxLine))

    // Create new stack trace to return
    val res = context.newArray(scope, count)

    for (i <- 0 until count) {
      val elem = Context.toObject(stackTrace.get(i, stackTrace), scope)
      val fileName = Context.toString(elem.get("fileName", elem))
      val line = Context.toNumber(elem.get("lineNumber", elem)).toInt - 1

      val pos = maps.get(fileName).fold(ir.Position.NoPosition)(_(line))

      val newElem =
        if (pos.isDefined) newPosElem(scope, context, elem, pos)
        else elem

      res.put(i, res, newElem)
    }

    res
  }

  private def getSourceMapper(fileName: String, untilLine: Int) = {
    val irFile = providers(fileName.stripSuffix(PseudoFileSuffix))
    val mapper = new Printers.ReverseSourceMapPrinter(untilLine)
    val classDef = irFile.tree
    val desugared = new ScalaJSClassEmitter(semantics).genClassDef(classDef)
    mapper.reverseSourceMap(desugared)
    mapper
  }

  private def newPosElem(scope: Scriptable, context: Context,
      origElem: Scriptable, pos: ir.Position): Scriptable = {
    assert(pos.isDefined)

    val elem = context.newObject(scope)

    elem.put("declaringClass", elem, origElem.get("declaringClass", origElem))
    elem.put("methodName", elem, origElem.get("methodName", origElem))
    elem.put("fileName", elem, pos.source.toString)
    elem.put("lineNumber", elem, pos.line + 1)
    elem.put("columnNumber", elem, pos.column + 1)

    elem
  }

  private val scalaJSLazyFields = Seq(
      Info("d"),
      Info("c"),
      Info("h"),
      Info("i", isTraitImpl = true),
      Info("n", isModule = true),
      Info("m", isModule = true),
      Info("is"),
      Info("as"),
      Info("isArrayOf"),
      Info("asArrayOf"))

  private def lazifyScalaJSFields(scope: Scriptable) = {
    val ScalaJS = Context.toObject(scope.get("ScalaJS", scope), scope)

    def makeLazyScalaJSScope(base: Scriptable, isModule: Boolean, isTraitImpl: Boolean) =
      new LazyScalaJSScope(this, scope, base, isModule, isTraitImpl)

    for (Info(name, isModule, isTraitImpl) <- scalaJSLazyFields) {
      val base = ScalaJS.get(name, ScalaJS).asInstanceOf[Scriptable]
      val lazified = makeLazyScalaJSScope(base, isModule, isTraitImpl)
      ScalaJS.put(name, ScalaJS, lazified)
    }
  }

  private[rhino] def load(scope: Scriptable, encodedName: String): Unit = {
    providers.get(encodedName) foreach { irFile =>
      val codeWriter = new java.io.StringWriter
      val printer = new Printers.JSTreePrinter(codeWriter)
      val classDef = irFile.tree
      val desugared = new ScalaJSClassEmitter(semantics).genClassDef(classDef)
      printer.printTopLevelTree(desugared)
      printer.complete()
      val ctx = Context.getCurrentContext()
      val fakeFileName = encodedName + PseudoFileSuffix
      ctx.evaluateString(scope, codeWriter.toString(),
          fakeFileName, 1, null)
    }
  }
}

object ScalaJSCoreLib {
  private case class Info(name: String,
      isModule: Boolean = false, isTraitImpl: Boolean = false)

  private val EncodedNameLine = raw""""encodedName": *"([^"]+)"""".r.unanchored

  private final val PseudoFileSuffix = ".sjsir"
}