/* __ *\
** ________ ___ / / ___ __ ____ Scala.js tools **
** / __/ __// _ | / / / _ | __ / // __/ (c) 2014, LAMP/EPFL **
** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ **
** /____/\___/_/ |_/____/_/ | |__/ /____/ **
** |/____/ **
\* */
package scala.scalajs.tools.javascript
import scala.annotation.switch
import scala.util.control.Breaks
import java.io.Writer
import java.net.URI
import scala.scalajs.ir
import ir.Position
import ir.Position.NoPosition
import ir.Printers.IndentationManager
import ir.Utils.escapeJS
import Trees._
import scala.scalajs.tools.sourcemap.SourceMapWriter
object Printers {
class JSTreePrinter(protected val out: Writer) extends IndentationManager {
def printTopLevelTree(tree: Tree) {
tree match {
case Skip() =>
// do not print anything
case Block(stats) =>
for (stat <- stats)
printTopLevelTree(stat)
case _ =>
printStat(tree)
if (shouldPrintSepAfterTree(tree))
print(";")
println()
}
}
protected def shouldPrintSepAfterTree(tree: Tree): Boolean =
!tree.isInstanceOf[DocComment]
protected def printBlock(tree: Tree): Unit = {
val trees = tree match {
case Block(trees) => trees
case _ => List(tree)
}
print("{"); indent(); println()
printSeq(trees) { x =>
printStat(x)
} { x =>
if (shouldPrintSepAfterTree(x))
print(";")
println()
}
undent(); println(); print("}")
}
protected def printSig(args: List[ParamDef]): Unit = {
printRow(args, "(", ", ", ")")
print(" ")
}
protected def printArgs(args: List[Tree]): Unit = {
printRow(args, "(", ", ", ")")
}
def printStat(tree: Tree): Unit =
printTree(tree, isStat = true)
def printTree(tree: Tree, isStat: Boolean): Unit = {
tree match {
case EmptyTree =>
print("<empty>")
// Comments
case DocComment(text) =>
val lines = text.split("\n").toList
if (lines.tail.isEmpty) {
print("/** ", lines.head, " */")
} else {
print("/** ", lines.head); println()
for (line <- lines.tail) {
print(" * ", line); println()
}
print(" */")
}
// Definitions
case VarDef(ident, mutable, rhs) =>
print("var ", ident)
if (rhs != EmptyTree)
print(" = ", rhs)
case ParamDef(ident, mutable) =>
print(ident)
// Control flow constructs
case Skip() =>
print("/*<skip>*/")
case tree @ Block(trees) =>
if (isStat)
printBlock(tree)
else
printRow(trees, "(", ", ", ")")
case Labeled(label, body) =>
print(label, ": ")
printBlock(body)
case Assign(lhs, rhs) =>
print(lhs, " = ", rhs)
case Return(expr) =>
print("return ", expr)
case If(cond, thenp, elsep) =>
if (isStat) {
print("if (", cond, ") ")
printBlock(thenp)
elsep match {
case Skip() => ()
case If(_, _, _) =>
print(" else ")
printTree(elsep, isStat)
case _ =>
print(" else ")
printBlock(elsep)
}
} else {
print("(", cond, " ? ", thenp, " : ", elsep, ")")
}
case While(cond, body, label) =>
if (label.isDefined)
print(label.get, ": ")
print("while (", cond, ") ")
printBlock(body)
case DoWhile(body, cond, label) =>
if (label.isDefined)
print(label.get, ": ")
print("do ")
printBlock(body)
print(" while (", cond, ")")
case Try(block, errVar, handler, finalizer) =>
print("try ")
printBlock(block)
if (handler != EmptyTree) {
print(" catch (", errVar, ") ")
printBlock(handler)
}
if (finalizer != EmptyTree) {
print(" finally ")
printBlock(finalizer)
}
case Throw(expr) =>
print("throw ", expr)
case Break(label) =>
if (label.isEmpty) print("break")
else print("break ", label.get)
case Continue(label) =>
if (label.isEmpty) print("continue")
else print("continue ", label.get)
case Switch(selector, cases, default) =>
print("switch (", selector, ") ")
print("{"); indent
for ((value, body) <- cases) {
println()
print("case ", value, ":"); indent; println()
printStat(body)
print(";")
undent
}
if (default != EmptyTree) {
println()
print("default:"); indent; println()
printStat(default)
print(";")
undent
}
undent; println(); print("}")
case Debugger() =>
print("debugger")
// Expressions
case New(ctor, args) =>
def containsOnlySelectsFromAtom(tree: Tree): Boolean = tree match {
case DotSelect(qual, _) => containsOnlySelectsFromAtom(qual)
case BracketSelect(qual, _) => containsOnlySelectsFromAtom(qual)
case VarRef(_, _) => true
case This() => true
case _ => false // in particular, Apply
}
if (containsOnlySelectsFromAtom(ctor))
print("new ", ctor)
else
print("new (", ctor, ")")
printArgs(args)
case DotSelect(qualifier, item) =>
print(qualifier, ".", item)
case BracketSelect(qualifier, item) =>
print(qualifier, "[", item, "]")
case Apply(fun, args) =>
print(fun)
printArgs(args)
case Delete(prop) =>
print("delete ", prop)
case UnaryOp("typeof", lhs) =>
print("typeof(", lhs, ")")
case UnaryOp(op, lhs) =>
print("(", op, lhs, ")")
case BinaryOp(op, lhs, rhs) =>
print("(", lhs, " ", op, " ", rhs, ")")
case ArrayConstr(items) =>
printRow(items, "[", ", ", "]")
case ObjectConstr(Nil) =>
print("{}")
case ObjectConstr(fields) =>
print("{"); indent; println()
printSeq(fields) {
case (name, value) => print(name, ": ", value)
} { _ =>
print(",")
println()
}
undent; println(); print("}")
// Literals
case Undefined() =>
print("(void 0)")
case Null() =>
print("null")
case BooleanLiteral(value) =>
print(if (value) "true" else "false")
case IntLiteral(value) =>
if (value >= 0)
print(value)
else
print("(", value, ")")
case DoubleLiteral(value) =>
if (value == 0 && 1 / value < 0)
print("(-0)")
else if (value >= 0)
print(value)
else
print("(", value, ")")
case StringLiteral(value) =>
print("\"", escapeJS(value), "\"")
// Atomic expressions
case VarRef(ident, _) =>
print(ident)
case This() =>
print("this")
case Function(args, body) =>
print("(function")
printSig(args)
printBlock(body)
print(")")
case _ =>
print(s"<error, elem of class ${tree.getClass()}>")
}
}
protected def printIdent(ident: Ident): Unit =
printString(escapeJS(ident.name))
def printOne(arg: Any): Unit = arg match {
case tree: Tree =>
printTree(tree, isStat = false)
case ident: Ident =>
printIdent(ident)
case arg =>
printString(if (arg == null) "null" else arg.toString)
}
protected def printString(s: String): Unit = {
out.write(s)
}
// Make it public
override def println(): Unit = super.println()
def complete(): Unit = ()
}
class JSTreePrinterWithSourceMap(_out: Writer,
sourceMap: SourceMapWriter) extends JSTreePrinter(_out) {
private var column = 0
override def printTree(tree: Tree, isStat: Boolean): Unit = {
val pos = tree.pos
if (pos.isDefined)
sourceMap.startNode(column, pos)
super.printTree(tree, isStat)
if (pos.isDefined)
sourceMap.endNode(column)
}
override protected def printIdent(ident: Ident): Unit = {
if (ident.pos.isDefined)
sourceMap.startNode(column, ident.pos, ident.originalName)
super.printIdent(ident)
if (ident.pos.isDefined)
sourceMap.endNode(column)
}
override def println(): Unit = {
super.println()
sourceMap.nextLine()
column = this.indentMargin
}
override protected def printString(s: String): Unit = {
// assume no EOL char in s, and assume s only has ASCII characters
super.printString(s)
column += s.length()
}
override def complete(): Unit = {
sourceMap.complete()
super.complete()
}
}
/** Prints a tree to find original locations based on line numbers.
* @param untilLine last 0-based line the positions should be recorded for
*/
class ReverseSourceMapPrinter(untilLine: Int)
extends JSTreePrinter(ReverseSourceMapPrinter.NullWriter) {
private val positions = Array.fill(untilLine+1)(NoPosition)
private var curLine = 0
private val doneBreak = new Breaks
def apply(x: Int): Position = positions(x)
def reverseSourceMap(tree: Tree): Unit = doneBreak.breakable {
printTopLevelTree(tree)
}
override def printTree(tree: Tree, isStat: Boolean): Unit = {
if (positions(curLine).isEmpty)
positions(curLine) = tree.pos
super.printTree(tree, isStat)
}
override protected def printIdent(ident: Ident): Unit = {
if (positions(curLine).isEmpty)
positions(curLine) = ident.pos
super.printIdent(ident)
}
override def println(): Unit = {
super.println()
curLine += 1
if (curLine > untilLine)
doneBreak.break()
}
override protected def printString(s: String): Unit = {
// assume no EOL char in s, and assume s only has ASCII characters
// therefore, we fully ignore the string
}
}
object ReverseSourceMapPrinter {
private object NullWriter extends Writer {
def close(): Unit = ()
def flush(): Unit = ()
def write(buf: Array[Char], off: Int, len: Int): Unit = ()
}
}
}