diff options
Diffstat (limited to 'examples/scala-js/javalib/src/main/scala/java/util/Formatter.scala')
-rw-r--r-- | examples/scala-js/javalib/src/main/scala/java/util/Formatter.scala | 273 |
1 files changed, 273 insertions, 0 deletions
diff --git a/examples/scala-js/javalib/src/main/scala/java/util/Formatter.scala b/examples/scala-js/javalib/src/main/scala/java/util/Formatter.scala new file mode 100644 index 0000000..5e0ab22 --- /dev/null +++ b/examples/scala-js/javalib/src/main/scala/java/util/Formatter.scala @@ -0,0 +1,273 @@ +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])""")) + +} |