summaryrefslogtreecommitdiff
path: root/tools/shared/src/main/scala/scala/scalajs/tools/sourcemap/SourceMapWriter.scala
diff options
context:
space:
mode:
Diffstat (limited to 'tools/shared/src/main/scala/scala/scalajs/tools/sourcemap/SourceMapWriter.scala')
-rw-r--r--tools/shared/src/main/scala/scala/scalajs/tools/sourcemap/SourceMapWriter.scala213
1 files changed, 213 insertions, 0 deletions
diff --git a/tools/shared/src/main/scala/scala/scalajs/tools/sourcemap/SourceMapWriter.scala b/tools/shared/src/main/scala/scala/scalajs/tools/sourcemap/SourceMapWriter.scala
new file mode 100644
index 0000000..5d8bdb1
--- /dev/null
+++ b/tools/shared/src/main/scala/scala/scalajs/tools/sourcemap/SourceMapWriter.scala
@@ -0,0 +1,213 @@
+/* __ *\
+** ________ ___ / / ___ __ ____ Scala.js tools **
+** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL **
+** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
+** /____/\___/_/ |_/____/_/ | |__/ /____/ **
+** |/____/ **
+\* */
+
+
+package scala.scalajs.tools.sourcemap
+
+import java.io.Writer
+import java.net.URI
+
+import scala.collection.mutable.{ ListBuffer, HashMap, Stack, StringBuilder }
+
+import scala.scalajs.ir
+import ir.Position
+import ir.Position._
+import ir.Utils
+
+object SourceMapWriter {
+ private val Base64Map =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
+ "abcdefghijklmnopqrstuvwxyz" +
+ "0123456789+/"
+
+ // Some constants for writeBase64VLQ
+ // Each base-64 digit covers 6 bits, but 1 is used for the continuation
+ private final val VLQBaseShift = 5
+ private final val VLQBase = 1 << VLQBaseShift
+ private final val VLQBaseMask = VLQBase - 1
+ private final val VLQContinuationBit = VLQBase
+
+ private def jsonString(s: String) =
+ "\"" + Utils.escapeJS(s) + "\""
+}
+
+class SourceMapWriter(
+ val out: Writer,
+ val generatedFile: String,
+ val relativizeBaseURI: Option[URI] = None) {
+
+ import SourceMapWriter._
+
+ private val sources = new ListBuffer[String]
+ private val _srcToIndex = new HashMap[SourceFile, Int]
+
+ private val names = new ListBuffer[String]
+ private val _nameToIndex = new HashMap[String, Int]
+
+ private val nodePosStack = new Stack[(Position, Option[String])]
+ nodePosStack.push((NoPosition, None))
+
+ private var lineCountInGenerated = 0
+ private var lastColumnInGenerated = 0
+ private var firstSegmentOfLine = true
+ private var lastSource: SourceFile = null
+ private var lastSourceIndex = 0
+ private var lastLine: Int = 0
+ private var lastColumn: Int = 0
+ private var lastNameIndex: Int = 0
+
+ private var pendingColumnInGenerated: Int = -1
+ private var pendingPos: Position = NoPosition
+ private var pendingName: Option[String] = None
+
+ writeHeader()
+
+ private def sourceToIndex(source: SourceFile) =
+ _srcToIndex.getOrElseUpdate(source,
+ (sources += sourceToURI(source)).size-1)
+
+ /** Relatively hacky way to get a Web-friendly URI to the source file */
+ private def sourceToURI(source: SourceFile): String = {
+ val uri = source
+ val relURI = relativizeBaseURI.fold(uri)(Utils.relativize(_, uri))
+
+ Utils.fixFileURI(relURI).toASCIIString
+ }
+
+ private def nameToIndex(name: String) =
+ _nameToIndex.getOrElseUpdate(name, (names += name).size-1)
+
+ private def writeHeader(): Unit = {
+ out.write("{\n")
+ out.write("\"version\": 3,\n")
+ out.write("\"file\": " + jsonString(generatedFile) + ",\n")
+ out.write("\"mappings\": \"")
+ }
+
+ def nextLine(): Unit = {
+ writePendingSegment()
+ out.write(';')
+ lineCountInGenerated += 1
+ lastColumnInGenerated = 0
+ firstSegmentOfLine = true
+ pendingColumnInGenerated = -1
+ pendingPos = nodePosStack.top._1
+ pendingName = nodePosStack.top._2
+ }
+
+ def startNode(column: Int, originalPos: Position,
+ originalName: Option[String] = None): Unit = {
+ nodePosStack.push((originalPos, originalName))
+ startSegment(column, originalPos, originalName)
+ }
+
+ def endNode(column: Int): Unit = {
+ nodePosStack.pop()
+ startSegment(column, nodePosStack.top._1, nodePosStack.top._2)
+ }
+
+ private def startSegment(startColumn: Int, originalPos: Position,
+ originalName: Option[String]): Unit = {
+ // There is no point in outputting a segment with the same information
+ if ((originalPos == pendingPos) && (originalName == pendingName))
+ return
+
+ // Write pending segment if it covers a non-empty range
+ if (startColumn != pendingColumnInGenerated)
+ writePendingSegment()
+
+ // New pending
+ pendingColumnInGenerated = startColumn
+ pendingPos = originalPos
+ pendingName =
+ if (startColumn != pendingColumnInGenerated) originalName
+ else pendingName orElse originalName
+ }
+
+ private def writePendingSegment() {
+ if (pendingColumnInGenerated < 0)
+ return
+
+ // Segments of a line are separated by ','
+ if (firstSegmentOfLine) firstSegmentOfLine = false
+ else out.write(',')
+
+ // Generated column field
+ writeBase64VLQ(pendingColumnInGenerated-lastColumnInGenerated)
+ lastColumnInGenerated = pendingColumnInGenerated
+
+ // If the position is NoPosition, stop here
+ if (!pendingPos.isDefined)
+ return
+
+ // Extract relevant properties of pendingPos
+ val source = pendingPos.source
+ val line = pendingPos.line
+ val column = pendingPos.column
+
+ // Source index field
+ if (source == lastSource) { // highly likely
+ writeBase64VLQ0()
+ } else {
+ val sourceIndex = sourceToIndex(source)
+ writeBase64VLQ(sourceIndex-lastSourceIndex)
+ lastSource = source
+ lastSourceIndex = sourceIndex
+ }
+
+ // Line field
+ writeBase64VLQ(line - lastLine)
+ lastLine = line
+
+ // Column field
+ writeBase64VLQ(column - lastColumn)
+ lastColumn = column
+
+ // Name field
+ if (pendingName.isDefined) {
+ val nameIndex = nameToIndex(pendingName.get)
+ writeBase64VLQ(nameIndex-lastNameIndex)
+ lastNameIndex = nameIndex
+ }
+ }
+
+ def complete(): Unit = {
+ writePendingSegment()
+
+ out.write("\",\n")
+ out.write(
+ sources.map(jsonString(_)).mkString("\"sources\": [", ", ", "],\n"))
+ out.write(
+ names.map(jsonString(_)).mkString("\"names\": [", ", ", "],\n"))
+ out.write("\"lineCount\": "+lineCountInGenerated+"\n")
+ out.write("}\n")
+ }
+
+ /** Write the Base 64 VLQ of an integer to the mappings
+ * Inspired by the implementation in Closure Compiler:
+ * http://code.google.com/p/closure-compiler/source/browse/src/com/google/debugging/sourcemap/Base64VLQ.java
+ */
+ private def writeBase64VLQ(value0: Int): Unit = {
+ // Sign is encoded in the least significant bit
+ var value =
+ if (value0 < 0) ((-value0) << 1) + 1
+ else value0 << 1
+
+ // Write as many base-64 digits as necessary to encode value
+ do {
+ var digit = value & VLQBaseMask
+ value = value >>> VLQBaseShift
+ if (value != 0)
+ digit |= VLQContinuationBit
+ out.write(Base64Map.charAt(digit))
+ } while (value != 0)
+ }
+
+ private def writeBase64VLQ0(): Unit =
+ out.write('A')
+}