Skip to content

Commit 0f652ac

Browse files
authored
Merge pull request #653 from ashawley/char-refactor
Refactor Char and String generators
2 parents 8510f33 + 03ee05e commit 0f652ac

File tree

2 files changed

+79
-34
lines changed

2 files changed

+79
-34
lines changed

src/main/scala/org/scalacheck/Arbitrary.scala

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -121,16 +121,13 @@ private[scalacheck] sealed trait ArbitraryLowPriority {
121121

122122
/** Arbitrary instance of Char */
123123
implicit lazy val arbChar: Arbitrary[Char] = Arbitrary {
124-
// exclude 0xFFFE due to this bug: https://bit.ly/2pTpAu8
125-
// also exclude 0xFFFF as it is not unicode: http://bit.ly/2cVBrzK
126-
val validRangesInclusive = List[(Char, Char)](
127-
(0x0000, 0xD7FF),
128-
(0xE000, 0xFFFD)
129-
)
130-
131-
Gen.frequency((validRangesInclusive.map {
132-
case (first, last) => (last + 1 - first, Gen.choose[Char](first, last))
133-
}: List[(Int, Gen[Char])]): _*)
124+
// valid ranges are [0x0000, 0xD7FF] and [0xE000, 0xFFFD].
125+
//
126+
// ((0xFFFD + 1) - 0xE000) + ((0xD7FF + 1) - 0x0000)
127+
choose(0, 63486).map { i =>
128+
if (i <= 0xD7FF) i.toChar
129+
else (i + 2048).toChar
130+
}
134131
}
135132

136133
/** Arbitrary instance of Byte */
@@ -149,7 +146,7 @@ private[scalacheck] sealed trait ArbitraryLowPriority {
149146

150147
/** Arbitrary instance of String */
151148
implicit lazy val arbString: Arbitrary[String] =
152-
Arbitrary(arbitrary[List[Char]] map (_.mkString))
149+
Arbitrary(Gen.stringOf(arbitrary[Char]))
153150

154151
/** Arbitrary instance of Date */
155152
implicit lazy val arbDate: Arbitrary[java.util.Date] =

src/main/scala/org/scalacheck/Gen.scala

Lines changed: 71 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -823,86 +823,127 @@ object Gen extends GenArities with GenVersionSpecific {
823823

824824
//// Character Generators ////
825825

826+
private def charSample(cs: Array[Char]): Gen[Char] =
827+
new Gen[Char] {
828+
def doApply(p: P, seed0: Seed): Gen.R[Char] = {
829+
val seed1 = p.initialSeed.getOrElse(seed0)
830+
val (x, seed2) = seed1.long
831+
val i = ((x & Long.MaxValue) % cs.length).toInt
832+
r(Some(cs(i)), seed2)
833+
}
834+
}
835+
826836
/** Generates a numerical character */
827837
val numChar: Gen[Char] =
828-
choose(48.toChar, 57.toChar)
838+
charSample(('0' to '9').toArray)
829839

830840
/** Generates an upper-case alpha character */
831841
val alphaUpperChar: Gen[Char] =
832-
choose(65.toChar, 90.toChar)
842+
charSample(('A' to 'Z').toArray)
833843

834844
/** Generates a lower-case alpha character */
835845
val alphaLowerChar: Gen[Char] =
836-
choose(97.toChar, 122.toChar)
846+
charSample(('a' to 'z').toArray)
837847

838848
/** Generates an alpha character */
839849
val alphaChar: Gen[Char] =
840-
frequency((1,alphaUpperChar), (9,alphaLowerChar))
850+
charSample((('A' to 'Z') ++ ('a' to 'z')).toArray)
841851

842852
/** Generates an alphanumerical character */
843853
val alphaNumChar: Gen[Char] =
844-
frequency((1,numChar), (9,alphaChar))
854+
charSample((('0' to '9') ++ ('A' to 'Z') ++ ('a' to 'z')).toArray)
845855

846856
/** Generates a ASCII character, with extra weighting for printable characters */
847857
val asciiChar: Gen[Char] =
848-
chooseNum(0, 127, 32 to 126:_*).map(_.toChar)
858+
charSample((0.toChar to 127.toChar).toArray)
849859

850860
/** Generates a ASCII printable character */
851861
val asciiPrintableChar: Gen[Char] =
852-
choose(32.toChar, 126.toChar)
862+
charSample((32.toChar to 126.toChar).toArray)
853863

854864
/** Generates a character that can represent a valid hexadecimal digit. This
855865
* includes both upper and lower case values.
856866
*/
857867
val hexChar: Gen[Char] =
858-
Gen.oneOf(
859-
Gen.oneOf("0123456789abcdef".toSeq),
860-
Gen.oneOf("0123456789ABCDEF".toSeq)
861-
)
868+
charSample("0123456789abcdef0123456789ABCDEF".toArray)
862869

863870
//// String Generators ////
864871

872+
private def mkString(n: Int, sb: StringBuilder, gc: Gen[Char], p: P, seed0: Seed): R[String] = {
873+
var seed: Seed = seed0
874+
val allowedFailures = Gen.collectionRetries(n)
875+
var failures = 0
876+
var count = 0
877+
while (count < n) {
878+
val res = gc.doApply(p, seed)
879+
res.retrieve match {
880+
case Some(c) =>
881+
sb += c
882+
count += 1
883+
case None =>
884+
failures += 1
885+
if (failures >= allowedFailures) return r(None, res.seed)
886+
}
887+
seed = res.seed
888+
}
889+
r(Some(sb.toString), seed)
890+
}
891+
892+
def stringOfN(n: Int, gc: Gen[Char]): Gen[String] =
893+
gen { (p, seed) =>
894+
mkString(n, new StringBuilder, gc, p, seed)
895+
}
896+
897+
def stringOf(gc: Gen[Char]): Gen[String] =
898+
gen { (p, seed0) =>
899+
val (n, seed1) = Gen.mkSize(p, seed0)
900+
mkString(n, new StringBuilder, gc, p, seed1)
901+
}
902+
865903
/** Generates a string that starts with a lower-case alpha character,
866904
* and only contains alphanumerical characters */
867905
val identifier: Gen[String] =
868-
for {
869-
c <- alphaLowerChar
870-
cs <- listOf(alphaNumChar)
871-
} yield (c::cs).mkString
906+
gen { (p, seed0) =>
907+
val (n, seed1) = Gen.mkSize(p, seed0)
908+
val sb = new StringBuilder
909+
val res1 = alphaLowerChar.doApply(p, seed1)
910+
sb += res1.retrieve.get
911+
mkString(n - 1, sb, alphaNumChar, p, res1.seed)
912+
}
872913

873914
/** Generates a string of digits */
874915
val numStr: Gen[String] =
875-
listOf(numChar).map(_.mkString)
916+
stringOf(numChar)
876917

877918
/** Generates a string of upper-case alpha characters */
878919
val alphaUpperStr: Gen[String] =
879-
listOf(alphaUpperChar).map(_.mkString)
920+
stringOf(alphaUpperChar)
880921

881922
/** Generates a string of lower-case alpha characters */
882923
val alphaLowerStr: Gen[String] =
883-
listOf(alphaLowerChar).map(_.mkString)
924+
stringOf(alphaLowerChar)
884925

885926
/** Generates a string of alpha characters */
886927
val alphaStr: Gen[String] =
887-
listOf(alphaChar).map(_.mkString)
928+
stringOf(alphaChar)
888929

889930
/** Generates a string of alphanumerical characters */
890931
val alphaNumStr: Gen[String] =
891-
listOf(alphaNumChar).map(_.mkString)
932+
stringOf(alphaNumChar)
892933

893934
/** Generates a string of ASCII characters, with extra weighting for printable characters */
894935
val asciiStr: Gen[String] =
895-
listOf(asciiChar).map(_.mkString)
936+
stringOf(asciiChar)
896937

897938
/** Generates a string of ASCII printable characters */
898939
val asciiPrintableStr: Gen[String] =
899-
listOf(asciiPrintableChar).map(_.mkString)
940+
stringOf(asciiPrintableChar)
900941

901942
/** Generates a string that can represent a valid hexadecimal digit. This
902943
* includes both upper and lower case values.
903944
*/
904945
val hexStr: Gen[String] =
905-
listOf(hexChar).map(_.mkString)
946+
stringOf(hexChar)
906947

907948
//// Number Generators ////
908949

@@ -1041,6 +1082,13 @@ object Gen extends GenArities with GenVersionSpecific {
10411082
1 -> const(Duration.Zero),
10421083
6 -> finiteDuration)
10431084

1085+
// used to compute a uniformly-distributed size
1086+
private def mkSize(p: Gen.Parameters, seed0: Seed): (Int, Seed) = {
1087+
val maxSize = Integer.max(p.size + 1, 1)
1088+
val (x, seed1) = seed0.long
1089+
(((x & Long.MaxValue) % maxSize).toInt, seed1)
1090+
}
1091+
10441092
// used to calculate how many per-item retries we should allow.
10451093
private def collectionRetries(n: Int): Int =
10461094
Integer.max(10, n / 10)

0 commit comments

Comments
 (0)