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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
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])"""))
}
|