package java.util
import scala.annotation.switch
import scala.scalajs.js
import java.io._
import java.lang._
final class Formatter(private val dest: Appendable) extends Closeable with Flushable {
import Formatter._
var closed = false
def this() = this(new StringBuilder())
def close(): Unit = {
if (!closed) {
dest match {
case cl: Closeable => cl.close()
case _ =>
}
}
closed = true
}
def flush(): Unit = ifNotClosed {
dest match {
case fl: Flushable => fl.flush()
case _ =>
}
}
// Begin implem of format()
def format(format_in: String, args: Array[AnyRef]): Formatter = ifNotClosed {
import js.Any.fromDouble // to have .toFixed and .toExponential on Doubles
var fmt: String = format_in
var lastImplicitIndex: Int = 0
var lastIndex: Int = 0 // required for < flag
while (!fmt.isEmpty) {
fmt match {
case RegularChunk(matchResult) =>
fmt = fmt.substring(matchResult(0).get.length)
dest.append(matchResult(0).get)
case DoublePercent(_) =>
fmt = fmt.substring(2)
dest.append('%')
case EOLChunk(_) =>
fmt = fmt.substring(2)
dest.append('\n')
case FormattedChunk(matchResult) =>
fmt = fmt.substring(matchResult(0).get.length)
val flags = matchResult(2).get
def hasFlag(flag: String) = flags.indexOf(flag) >= 0
val indexStr = matchResult(1).getOrElse("")
val index = if (!indexStr.isEmpty) {
Integer.parseInt(indexStr)
} else if (hasFlag("<")) {
lastIndex
} else {
lastImplicitIndex += 1
lastImplicitIndex
}
lastIndex = index
if (index <= 0 || index > args.length)
throw new MissingFormatArgumentException(matchResult(5).get)
val arg = args(index-1)
val widthStr = matchResult(3).getOrElse("")
val hasWidth = !widthStr.isEmpty
val width =
if (hasWidth) Integer.parseInt(widthStr)
else 0
val precisionStr = matchResult(4).getOrElse("")
val hasPrecision = !precisionStr.isEmpty
val precision =
if (hasPrecision) Integer.parseInt(precisionStr)
else 0
val conversion = matchResult(5).get.charAt(0)
def intArg: Int = (arg: Any) match {
case arg: Int => arg
case arg: Char => arg.toInt
}
def numberArg: scala.Double = (arg: Any) match {
case arg: Number => arg.doubleValue()
case arg: Char => arg.toDouble
}
def padCaptureSign(argStr: String, prefix: String) = {
val firstChar = argStr.charAt(0)
if (firstChar == '+' || firstChar == '-')
pad(argStr.substring(1), firstChar+prefix)
else
pad(argStr, prefix)
}
def strRepeat(s: String, times: Int) = {
var result: String = ""
var i = times
while (i > 0) {
result += s
i -= 1
}
result
}
def with_+(s: String, preventZero: scala.Boolean = false) = {
if (s.charAt(0) != '-') {
if (hasFlag("+"))
pad(s, "+", preventZero)
else if (hasFlag(" "))
pad(s, " ", preventZero)
else
pad(s, "", preventZero)
} else {
if (hasFlag("("))
pad(s.substring(1) + ")", "(", preventZero)
else
pad(s.substring(1), "-", preventZero)
}
}
def pad(argStr: String, prefix: String = "",
preventZero: Boolean = false) = {
val prePadLen = argStr.length + prefix.length
val padStr = {
if (width <= prePadLen) {
prefix + argStr
} else {
val padRight = hasFlag("-")
val padZero = hasFlag("0") && !preventZero
val padLength = width - prePadLen
val padChar: String = if (padZero) "0" else " "
val padding = strRepeat(padChar, padLength)
if (padZero && padRight)
throw new java.util.IllegalFormatFlagsException(flags)
else if (padRight) prefix + argStr + padding
else if (padZero) prefix + padding + argStr
else padding + prefix + argStr
}
}
val casedStr =
if (conversion.isUpper) padStr.toUpperCase()
else padStr
dest.append(casedStr)
}
(conversion: @switch) match {
case 'b' | 'B' => pad { arg match {
case null => "false"
case b: Boolean => String.valueOf(b)
case _ => "true"
} }
case 'h' | 'H' => pad {
if (arg eq null) "null"
else Integer.toHexString(arg.hashCode)
}
case 's' | 'S' => arg match {
case null if !hasFlag("#") => pad("null")
case formattable: Formattable =>
val flags = (
(if (hasFlag("-")) FormattableFlags.LEFT_JUSTIFY else 0) |
(if (hasFlag("#")) FormattableFlags.ALTERNATE else 0) |
(if (conversion.isUpper) FormattableFlags.UPPERCASE else 0)
)
formattable.formatTo(this, flags,
if (hasWidth) width.toInt else -1,
if (hasPrecision) precision.toInt else -1)
None // no further processing
case t: AnyRef if !hasFlag("#") => pad(t.toString)
case _ =>
throw new FormatFlagsConversionMismatchException("#", 's')
}
case 'c' | 'C' =>
pad(js.String.fromCharCode(intArg))
case 'd' =>
with_+(numberArg.toString())
case 'o' =>
val str = (arg: Any) match {
case arg: scala.Int => Integer.toOctalString(arg)
case arg: scala.Long => Long.toOctalString(arg)
}
padCaptureSign(str, if (hasFlag("#")) "0" else "")
case 'x' | 'X' =>
val str = (arg: Any) match {
case arg: scala.Int => Integer.toHexString(arg)
case arg: scala.Long => Long.toHexString(arg)
}
padCaptureSign(str, if (hasFlag("#")) "0x" else "")
case 'e' | 'E' =>
sciNotation(if (hasPrecision) precision else 6)
case 'g' | 'G' =>
val m = Math.abs(numberArg)
// precision handling according to JavaDoc
// precision here means number of significant digits
// not digits after decimal point
val p =
if (!hasPrecision) 6
else if (precision == 0) 1
else precision
// between 1e-4 and 10e(p): display as fixed
if (m >= 1e-4 && m < Math.pow(10, p)) {
val sig = Math.ceil(Math.log10(m))
with_+(numberArg.toFixed(Math.max(p - sig, 0)))
} else sciNotation(p - 1)
case 'f' =>
with_+({
// JavaDoc: 6 is default precision
numberArg.toFixed(if (hasPrecision) precision else 6)
}, numberArg.isNaN || numberArg.isInfinite)
}
def sciNotation(precision: Int) = {
val exp = numberArg.toExponential(precision)
with_+({
// check if we need additional 0 padding in exponent
// JavaDoc: at least 2 digits
if ("e" == exp.charAt(exp.length - 3)) {
exp.substring(0, exp.length - 1) + "0" +
exp.charAt(exp.length - 1)
} else exp
}, numberArg.isNaN || numberArg.isInfinite)
}
}
}
this
}
def ioException(): IOException = null
def locale(): Locale = ifNotClosed { null }
def out(): Appendable = ifNotClosed { dest }
override def toString(): String = out().toString()
@inline private def ifNotClosed[T](body: => T): T =
if (closed) throwClosedException()
else body
private def throwClosedException(): Nothing =
throw new FormatterClosedException()
}
object Formatter {
private class RegExpExtractor(val regexp: js.RegExp) {
def unapply(str: String): Option[js.RegExp.ExecResult] = {
Option(regexp.exec(str))
}
}
private val RegularChunk = new RegExpExtractor(new js.RegExp("""^[^\x25]+"""))
private val DoublePercent = new RegExpExtractor(new js.RegExp("""^\x25{2}"""))
private val EOLChunk = new RegExpExtractor(new js.RegExp("""^\x25n"""))
private val FormattedChunk = new RegExpExtractor(new js.RegExp(
"""^\x25(?:([1-9]\d*)\$)?([-#+ 0,\(<]*)(\d*)(?:\.(\d+))?([A-Za-z])"""))
}