Skip to content

Commit 36975c1

Browse files
committed
Add remaining collection classes to stdlib
1 parent 766b020 commit 36975c1

File tree

2 files changed

+401
-0
lines changed

2 files changed

+401
-0
lines changed
Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
/*
2+
* Scala (https://www.scala-lang.org)
3+
*
4+
* Copyright EPFL and Lightbend, Inc.
5+
*
6+
* Licensed under Apache License 2.0
7+
* (http://www.apache.org/licenses/LICENSE-2.0).
8+
*
9+
* See the NOTICE file distributed with this work for
10+
* additional information regarding copyright ownership.
11+
*/
12+
13+
package scala
14+
package collection
15+
16+
import scala.annotation.tailrec
17+
import language.experimental.captureChecking
18+
19+
/** A module containing the implementations of parsers from strings to numeric types, and boolean
20+
*/
21+
private[scala] object StringParsers {
22+
23+
//compile-time constant helpers
24+
25+
//Int.MinValue == -2147483648
26+
private final val intOverflowBoundary = -214748364
27+
private final val intOverflowDigit = 9
28+
//Long.MinValue == -9223372036854775808L
29+
private final val longOverflowBoundary = -922337203685477580L
30+
private final val longOverflowDigit = 9
31+
32+
@inline
33+
private[this] final def decValue(ch: Char): Int = java.lang.Character.digit(ch, 10)
34+
35+
@inline
36+
private[this] final def stepToOverflow(from: String, len: Int, agg: Int, isPositive: Boolean, min: Int): Option[Int] = {
37+
@tailrec
38+
def rec(i: Int, agg: Int): Option[Int] =
39+
if (agg < min) None
40+
else if (i == len) {
41+
if (!isPositive) Some(agg)
42+
else if (agg == min) None
43+
else Some(-agg)
44+
}
45+
else {
46+
val digit = decValue(from.charAt(i))
47+
if (digit == -1) None
48+
else rec(i + 1, agg * 10 - digit)
49+
}
50+
rec(1, agg)
51+
}
52+
53+
@inline
54+
private[this] final def isDigit(c: Char): Boolean = c >= '0' && c <= '9'
55+
56+
//bool
57+
@inline
58+
final def parseBool(from: String): Option[Boolean] =
59+
if (from.equalsIgnoreCase("true")) Some(true)
60+
else if (from.equalsIgnoreCase("false")) Some(false)
61+
else None
62+
63+
//integral types
64+
final def parseByte(from: String): Option[Byte] = {
65+
val len = from.length()
66+
//empty strings parse to None
67+
if (len == 0) None
68+
else {
69+
val first = from.charAt(0)
70+
val v = decValue(first)
71+
if (len == 1) {
72+
//"+" and "-" parse to None
73+
if (v > -1) Some(v.toByte)
74+
else None
75+
}
76+
else if (v > -1) stepToOverflow(from, len, -v, true, Byte.MinValue).map(_.toByte)
77+
else if (first == '+') stepToOverflow(from, len, 0, true, Byte.MinValue).map(_.toByte)
78+
else if (first == '-') stepToOverflow(from, len, 0, false, Byte.MinValue).map(_.toByte)
79+
else None
80+
}
81+
}
82+
83+
final def parseShort(from: String): Option[Short] = {
84+
val len = from.length()
85+
//empty strings parse to None
86+
if (len == 0) None
87+
else {
88+
val first = from.charAt(0)
89+
val v = decValue(first)
90+
if (len == 1) {
91+
//"+" and "-" parse to None
92+
if (v > -1) Some(v.toShort)
93+
else None
94+
}
95+
else if (v > -1) stepToOverflow(from, len, -v, true, Short.MinValue).map(_.toShort)
96+
else if (first == '+') stepToOverflow(from, len, 0, true, Short.MinValue).map(_.toShort)
97+
else if (first == '-') stepToOverflow(from, len, 0, false, Short.MinValue).map(_.toShort)
98+
else None
99+
}
100+
}
101+
102+
final def parseInt(from: String): Option[Int] = {
103+
val len = from.length()
104+
105+
@tailrec
106+
def step(i: Int, agg: Int, isPositive: Boolean): Option[Int] = {
107+
if (i == len) {
108+
if (!isPositive) Some(agg)
109+
else if (agg == Int.MinValue) None
110+
else Some(-agg)
111+
}
112+
else if (agg < intOverflowBoundary) None
113+
else {
114+
val digit = decValue(from.charAt(i))
115+
if (digit == -1 || (agg == intOverflowBoundary && digit == intOverflowDigit)) None
116+
else step(i + 1, (agg * 10) - digit, isPositive)
117+
}
118+
}
119+
//empty strings parse to None
120+
if (len == 0) None
121+
else {
122+
val first = from.charAt(0)
123+
val v = decValue(first)
124+
if (len == 1) {
125+
//"+" and "-" parse to None
126+
if (v > -1) Some(v)
127+
else None
128+
}
129+
else if (v > -1) step(1, -v, true)
130+
else if (first == '+') step(1, 0, true)
131+
else if (first == '-') step(1, 0, false)
132+
else None
133+
}
134+
}
135+
136+
final def parseLong(from: String): Option[Long] = {
137+
//like parseInt, but Longer
138+
val len = from.length()
139+
140+
@tailrec
141+
def step(i: Int, agg: Long, isPositive: Boolean): Option[Long] = {
142+
if (i == len) {
143+
if (isPositive && agg == Long.MinValue) None
144+
else if (isPositive) Some(-agg)
145+
else Some(agg)
146+
}
147+
else if (agg < longOverflowBoundary) None
148+
else {
149+
val digit = decValue(from.charAt(i))
150+
if (digit == -1 || (agg == longOverflowBoundary && digit == longOverflowDigit)) None
151+
else step(i + 1, agg * 10 - digit, isPositive)
152+
}
153+
}
154+
//empty strings parse to None
155+
if (len == 0) None
156+
else {
157+
val first = from.charAt(0)
158+
val v = decValue(first).toLong
159+
if (len == 1) {
160+
//"+" and "-" parse to None
161+
if (v > -1) Some(v)
162+
else None
163+
}
164+
else if (v > -1) step(1, -v, true)
165+
else if (first == '+') step(1, 0, true)
166+
else if (first == '-') step(1, 0, false)
167+
else None
168+
}
169+
}
170+
171+
//floating point
172+
final def checkFloatFormat(format: String): Boolean = {
173+
//indices are tracked with a start index which points *at* the first index
174+
//and an end index which points *after* the last index
175+
//so that slice length === end - start
176+
//thus start == end <=> empty slice
177+
//and format.substring(start, end) is equivalent to the slice
178+
179+
//some utilities for working with index bounds into the original string
180+
@inline
181+
def forAllBetween(start: Int, end: Int, pred: Char => Boolean): Boolean = {
182+
@tailrec
183+
def rec(i: Int): Boolean = i >= end || pred(format.charAt(i)) && rec(i + 1)
184+
rec(start)
185+
}
186+
187+
//one after last index for the predicate to hold, or `from` if none hold
188+
//may point after the end of the string
189+
@inline
190+
def skipIndexWhile(predicate: Char => Boolean, from: Int, until: Int): Int = {
191+
@tailrec @inline
192+
def rec(i: Int): Int = if ((i < until) && predicate(format.charAt(i))) rec(i + 1)
193+
else i
194+
rec(from)
195+
}
196+
197+
198+
def isHexFloatLiteral(startIndex: Int, endIndex: Int): Boolean = {
199+
def isHexDigit(ch: Char) = ((ch >= '0' && ch <= '9') ||
200+
(ch >= 'a' && ch <= 'f') ||
201+
(ch >= 'A' && ch <= 'F'))
202+
203+
def prefixOK(startIndex: Int, endIndex: Int): Boolean = {
204+
val len = endIndex - startIndex
205+
(len > 0) && {
206+
//the prefix part is
207+
//hexDigits
208+
//hexDigits.
209+
//hexDigits.hexDigits
210+
//.hexDigits
211+
//but not .
212+
if (format.charAt(startIndex) == '.') {
213+
(len > 1) && forAllBetween(startIndex + 1, endIndex, isHexDigit)
214+
} else {
215+
val noLeading = skipIndexWhile(isHexDigit, startIndex, endIndex)
216+
(noLeading >= endIndex) ||
217+
((format.charAt(noLeading) == '.') && forAllBetween(noLeading + 1, endIndex, isHexDigit))
218+
}
219+
}
220+
}
221+
222+
def postfixOK(startIndex: Int, endIndex: Int): Boolean =
223+
(startIndex < endIndex) && {
224+
(forAllBetween(startIndex, endIndex, isDigit)) || {
225+
val startchar = format.charAt(startIndex)
226+
(startchar == '+' || startchar == '-') &&
227+
(endIndex - startIndex > 1) &&
228+
forAllBetween(startIndex + 1, endIndex, isDigit)
229+
}
230+
}
231+
// prefix [pP] postfix
232+
val pIndex = format.indexWhere(ch => ch == 'p' || ch == 'P', startIndex)
233+
(pIndex <= endIndex) && prefixOK(startIndex, pIndex) && postfixOK(pIndex + 1, endIndex)
234+
}
235+
236+
def isDecFloatLiteral(startIndex: Int, endIndex: Int): Boolean = {
237+
//invariant: endIndex > startIndex
238+
239+
def isExp(c: Char): Boolean = c == 'e' || c == 'E'
240+
241+
def expOK(startIndex: Int, endIndex: Int): Boolean =
242+
(startIndex < endIndex) && {
243+
val startChar = format.charAt(startIndex)
244+
if (startChar == '+' || startChar == '-')
245+
(endIndex > (startIndex + 1)) &&
246+
skipIndexWhile(isDigit, startIndex + 1, endIndex) == endIndex
247+
else skipIndexWhile(isDigit, startIndex, endIndex) == endIndex
248+
}
249+
250+
//significant can be one of
251+
//* digits.digits
252+
//* .digits
253+
//* digits.
254+
//but not just .
255+
val startChar = format.charAt(startIndex)
256+
if (startChar == '.') {
257+
val noSignificant = skipIndexWhile(isDigit, startIndex + 1, endIndex)
258+
// a digit is required followed by optional exp
259+
(noSignificant > startIndex + 1) && (noSignificant >= endIndex ||
260+
isExp(format.charAt(noSignificant)) && expOK(noSignificant + 1, endIndex)
261+
)
262+
}
263+
else if (isDigit(startChar)) {
264+
// one set of digits, then optionally a period, then optionally another set of digits, then optionally an exponent
265+
val noInt = skipIndexWhile(isDigit, startIndex, endIndex)
266+
// just the digits
267+
(noInt == endIndex) || {
268+
if (format.charAt(noInt) == '.') {
269+
val noSignificant = skipIndexWhile(isDigit, noInt + 1, endIndex)
270+
(noSignificant >= endIndex) || //no exponent
271+
isExp(format.charAt(noSignificant)) && expOK(noSignificant + 1, endIndex)
272+
} else
273+
isExp(format.charAt(noInt)) && expOK(noInt + 1, endIndex)
274+
}
275+
}
276+
else false
277+
}
278+
279+
//count 0x00 to 0x20 as "whitespace", and nothing else
280+
val unspacedStart = format.indexWhere(ch => ch.toInt > 0x20)
281+
val unspacedEnd = format.lastIndexWhere(ch => ch.toInt > 0x20) + 1
282+
283+
if (unspacedStart == -1 || unspacedStart >= unspacedEnd || unspacedEnd <= 0) false
284+
else {
285+
//all formats can have a sign
286+
val unsigned = {
287+
val startchar = format.charAt(unspacedStart)
288+
if (startchar == '-' || startchar == '+') unspacedStart + 1 else unspacedStart
289+
}
290+
if (unsigned >= unspacedEnd) false
291+
//that's it for NaN and Infinity
292+
else if (format.charAt(unsigned) == 'N') format.substring(unsigned, unspacedEnd) == "NaN"
293+
else if (format.charAt(unsigned) == 'I') format.substring(unsigned, unspacedEnd) == "Infinity"
294+
else {
295+
//all other formats can have a format suffix
296+
val desuffixed = {
297+
val endchar = format.charAt(unspacedEnd - 1)
298+
if (endchar == 'f' || endchar == 'F' || endchar == 'd' || endchar == 'D') unspacedEnd - 1
299+
else unspacedEnd
300+
}
301+
val len = desuffixed - unsigned
302+
if (len <= 0) false
303+
else if (len >= 2 && (format.charAt(unsigned + 1) == 'x' || format.charAt(unsigned + 1) == 'X'))
304+
format.charAt(unsigned) == '0' && isHexFloatLiteral(unsigned + 2, desuffixed)
305+
else isDecFloatLiteral(unsigned, desuffixed)
306+
}
307+
}
308+
}
309+
310+
@inline
311+
def parseFloat(from: String): Option[Float] =
312+
if (checkFloatFormat(from)) Some(java.lang.Float.parseFloat(from))
313+
else None
314+
315+
@inline
316+
def parseDouble(from: String): Option[Double] =
317+
if (checkFloatFormat(from)) Some(java.lang.Double.parseDouble(from))
318+
else None
319+
320+
}

0 commit comments

Comments
 (0)