Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Char and String generators #653

Merged
merged 3 commits into from
May 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 8 additions & 11 deletions src/main/scala/org/scalacheck/Arbitrary.scala
Original file line number Diff line number Diff line change
Expand Up @@ -121,16 +121,13 @@ private[scalacheck] sealed trait ArbitraryLowPriority {

/** Arbitrary instance of Char */
implicit lazy val arbChar: Arbitrary[Char] = Arbitrary {
// exclude 0xFFFE due to this bug: https://bit.ly/2pTpAu8
// also exclude 0xFFFF as it is not unicode: http://bit.ly/2cVBrzK
val validRangesInclusive = List[(Char, Char)](
(0x0000, 0xD7FF),
(0xE000, 0xFFFD)
)

Gen.frequency((validRangesInclusive.map {
case (first, last) => (last + 1 - first, Gen.choose[Char](first, last))
}: List[(Int, Gen[Char])]): _*)
// valid ranges are [0x0000, 0xD7FF] and [0xE000, 0xFFFD].
//
// ((0xFFFD + 1) - 0xE000) + ((0xD7FF + 1) - 0x0000)
choose(0, 63486).map { i =>
if (i <= 0xD7FF) i.toChar
else (i + 2048).toChar
}
}

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

/** Arbitrary instance of String */
implicit lazy val arbString: Arbitrary[String] =
Arbitrary(arbitrary[List[Char]] map (_.mkString))
Arbitrary(Gen.stringOf(arbitrary[Char]))

/** Arbitrary instance of Date */
implicit lazy val arbDate: Arbitrary[java.util.Date] =
Expand Down
94 changes: 71 additions & 23 deletions src/main/scala/org/scalacheck/Gen.scala
Original file line number Diff line number Diff line change
Expand Up @@ -823,86 +823,127 @@ object Gen extends GenArities with GenVersionSpecific {

//// Character Generators ////

private def charSample(cs: Array[Char]): Gen[Char] =
new Gen[Char] {
def doApply(p: P, seed0: Seed): Gen.R[Char] = {
val seed1 = p.initialSeed.getOrElse(seed0)
val (x, seed2) = seed1.long
val i = ((x & Long.MaxValue) % cs.length).toInt
r(Some(cs(i)), seed2)
}
}

/** Generates a numerical character */
val numChar: Gen[Char] =
choose(48.toChar, 57.toChar)
charSample(('0' to '9').toArray)

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

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

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

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

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

/** Generates a ASCII printable character */
val asciiPrintableChar: Gen[Char] =
choose(32.toChar, 126.toChar)
charSample((32.toChar to 126.toChar).toArray)

/** Generates a character that can represent a valid hexadecimal digit. This
* includes both upper and lower case values.
*/
val hexChar: Gen[Char] =
Gen.oneOf(
Gen.oneOf("0123456789abcdef".toSeq),
Gen.oneOf("0123456789ABCDEF".toSeq)
)
charSample("0123456789abcdef0123456789ABCDEF".toArray)

//// String Generators ////

private def mkString(n: Int, sb: StringBuilder, gc: Gen[Char], p: P, seed0: Seed): R[String] = {
var seed: Seed = seed0
val allowedFailures = Gen.collectionRetries(n)
var failures = 0
var count = 0
while (count < n) {
val res = gc.doApply(p, seed)
res.retrieve match {
case Some(c) =>
sb += c
count += 1
case None =>
failures += 1
if (failures >= allowedFailures) return r(None, res.seed)
}
seed = res.seed
}
r(Some(sb.toString), seed)
}

def stringOfN(n: Int, gc: Gen[Char]): Gen[String] =
gen { (p, seed) =>
mkString(n, new StringBuilder, gc, p, seed)
}

def stringOf(gc: Gen[Char]): Gen[String] =
gen { (p, seed0) =>
val (n, seed1) = Gen.mkSize(p, seed0)
mkString(n, new StringBuilder, gc, p, seed1)
}

/** Generates a string that starts with a lower-case alpha character,
* and only contains alphanumerical characters */
val identifier: Gen[String] =
for {
c <- alphaLowerChar
cs <- listOf(alphaNumChar)
} yield (c::cs).mkString
gen { (p, seed0) =>
val (n, seed1) = Gen.mkSize(p, seed0)
val sb = new StringBuilder
val res1 = alphaLowerChar.doApply(p, seed1)
sb += res1.retrieve.get
mkString(n - 1, sb, alphaNumChar, p, res1.seed)
}

/** Generates a string of digits */
val numStr: Gen[String] =
listOf(numChar).map(_.mkString)
stringOf(numChar)

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

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

/** Generates a string of alpha characters */
val alphaStr: Gen[String] =
listOf(alphaChar).map(_.mkString)
stringOf(alphaChar)

/** Generates a string of alphanumerical characters */
val alphaNumStr: Gen[String] =
listOf(alphaNumChar).map(_.mkString)
stringOf(alphaNumChar)

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

/** Generates a string of ASCII printable characters */
val asciiPrintableStr: Gen[String] =
listOf(asciiPrintableChar).map(_.mkString)
stringOf(asciiPrintableChar)

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

//// Number Generators ////

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

// used to compute a uniformly-distributed size
private def mkSize(p: Gen.Parameters, seed0: Seed): (Int, Seed) = {
val maxSize = Integer.max(p.size + 1, 1)
val (x, seed1) = seed0.long
(((x & Long.MaxValue) % maxSize).toInt, seed1)
}

// used to calculate how many per-item retries we should allow.
private def collectionRetries(n: Int): Int =
Integer.max(10, n / 10)
Expand Down