Merge pull request #8001 from kaushalmodi/fix-isupper-islower-try2

Make isUpper (and variants) work for strings with non-alpha chars
This commit is contained in:
Andreas Rumpf 2018-06-09 08:58:08 +02:00 • committed by GitHub
commit d323ee81e4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 151 additions and 48 deletions

View file

@ -42,6 +42,12 @@
- ``math.`mod` `` for floats now behaves the same as ``mod`` for integers - ``math.`mod` `` for floats now behaves the same as ``mod`` for integers
(previously it used floor division like Python). Use ``math.floorMod`` for the old behavior. (previously it used floor division like Python). Use ``math.floorMod`` for the old behavior.
- For string inputs, ``unicode.isUpper`` and ``unicode.isLower`` now require a
second mandatory parameter ``skipNonAlpha``.
- For string inputs, ``strutils.isUpperAscii`` and ``strutils.isLowerAscii`` now
require a second mandatory parameter ``skipNonAlpha``.
#### Breaking changes in the compiler #### Breaking changes in the compiler
- The undocumented ``#? braces`` parsing mode was removed. - The undocumented ``#? braces`` parsing mode was removed.

View file

@ -150,23 +150,52 @@ proc isSpaceAscii*(s: string): bool {.noSideEffect, procvar,
## characters and there is at least one character in `s`. ## characters and there is at least one character in `s`.
isImpl isSpaceAscii isImpl isSpaceAscii
proc isLowerAscii*(s: string): bool {.noSideEffect, procvar, template isCaseImpl(s, charProc, skipNonAlpha) =
rtl, extern: "nsuIsLowerAsciiStr".} = var hasAtleastOneAlphaChar = false
## Checks whether or not `s` contains all lower case characters. if s.len == 0: return false
## for c in s:
## This checks ASCII characters only. if skipNonAlpha:
## Returns true if all characters in `s` are lower case var charIsAlpha = c.isAlphaAscii()
## and there is at least one character in `s`. if not hasAtleastOneAlphaChar:
isImpl isLowerAscii hasAtleastOneAlphaChar = charIsAlpha
if charIsAlpha and (not charProc(c)):
return false
else:
if not charProc(c):
return false
return if skipNonAlpha: hasAtleastOneAlphaChar else: true
proc isUpperAscii*(s: string): bool {.noSideEffect, procvar, proc isLowerAscii*(s: string, skipNonAlpha: bool): bool =
rtl, extern: "nsuIsUpperAsciiStr".} = ## Checks whether ``s`` is lower case.
## Checks whether or not `s` contains all upper case characters.
## ##
## This checks ASCII characters only. ## This checks ASCII characters only.
## Returns true if all characters in `s` are upper case ##
## and there is at least one character in `s`. ## If ``skipNonAlpha`` is true, returns true if all alphabetical
isImpl isUpperAscii ## characters in ``s`` are lower case. Returns false if none of the
## characters in ``s`` are alphabetical.
##
## If ``skipNonAlpha`` is false, returns true only if all characters
## in ``s`` are alphabetical and lower case.
##
## For either value of ``skipNonAlpha``, returns false if ``s`` is
## an empty string.
isCaseImpl(s, isLowerAscii, skipNonAlpha)
proc isUpperAscii*(s: string, skipNonAlpha: bool): bool =
## Checks whether ``s`` is upper case.
##
## This checks ASCII characters only.
##
## If ``skipNonAlpha`` is true, returns true if all alphabetical
## characters in ``s`` are upper case. Returns false if none of the
## characters in ``s`` are alphabetical.
##
## If ``skipNonAlpha`` is false, returns true only if all characters
## in ``s`` are alphabetical and upper case.
##
## For either value of ``skipNonAlpha``, returns false if ``s`` is
## an empty string.
isCaseImpl(s, isUpperAscii, skipNonAlpha)
proc toLowerAscii*(c: char): char {.noSideEffect, procvar, proc toLowerAscii*(c: char): char {.noSideEffect, procvar,
rtl, extern: "nsuToLowerAsciiChar".} = rtl, extern: "nsuToLowerAsciiChar".} =
@ -2516,19 +2545,34 @@ when isMainModule:
doAssert(not isLowerAscii('A')) doAssert(not isLowerAscii('A'))
doAssert(not isLowerAscii('5')) doAssert(not isLowerAscii('5'))
doAssert(not isLowerAscii('&')) doAssert(not isLowerAscii('&'))
doAssert(not isLowerAscii(' '))
doAssert isLowerAscii("abcd") doAssert isLowerAscii("abcd", false)
doAssert(not isLowerAscii("abCD")) doAssert(not isLowerAscii("33aa", false))
doAssert(not isLowerAscii("33aa")) doAssert(not isLowerAscii("a b", false))
doAssert(not isLowerAscii("abCD", true))
doAssert isLowerAscii("33aa", true)
doAssert isLowerAscii("a b", true)
doAssert isLowerAscii("1, 2, 3 go!", true)
doAssert(not isLowerAscii(" ", true))
doAssert(not isLowerAscii("(*&#@(^#$ ", true)) # None of the string chars are alphabets
doAssert isUpperAscii('A') doAssert isUpperAscii('A')
doAssert(not isUpperAscii('b')) doAssert(not isUpperAscii('b'))
doAssert(not isUpperAscii('5')) doAssert(not isUpperAscii('5'))
doAssert(not isUpperAscii('%')) doAssert(not isUpperAscii('%'))
doAssert isUpperAscii("ABC") doAssert isUpperAscii("ABC", false)
doAssert(not isUpperAscii("AAcc")) doAssert(not isUpperAscii("A#$", false))
doAssert(not isUpperAscii("A#$")) doAssert(not isUpperAscii("A B", false))
doAssert(not isUpperAscii("AAcc", true))
doAssert isUpperAscii("A#$", true)
doAssert isUpperAscii("A B", true)
doAssert isUpperAscii("1, 2, 3 GO!", true)
doAssert(not isUpperAscii(" ", true))
doAssert(not isUpperAscii("(*&#@(^#$ ", true)) # None of the string chars are alphabets
doAssert rsplit("foo bar", seps=Whitespace) == @["foo", "bar"] doAssert rsplit("foo bar", seps=Whitespace) == @["foo", "bar"]
doAssert rsplit(" foo bar", seps=Whitespace, maxsplit=1) == @[" foo", "bar"] doAssert rsplit(" foo bar", seps=Whitespace, maxsplit=1) == @[" foo", "bar"]
@ -2601,4 +2645,3 @@ bar
nonStaticTests() nonStaticTests()
staticTests() staticTests()
static: staticTests() static: staticTests()

View file

@ -1392,7 +1392,7 @@ proc isCombining*(c: Rune): bool {.rtl, extern: "nuc$1", procvar.} =
(c >= 0xfe20 and c <= 0xfe2f)) (c >= 0xfe20 and c <= 0xfe2f))
template runeCheck(s, runeProc) = template runeCheck(s, runeProc) =
## Common code for rune.isLower, rune.isUpper, etc ## Common code for isAlpha and isSpace.
result = if len(s) == 0: false else: true result = if len(s) == 0: false else: true
var var
@ -1403,16 +1403,6 @@ template runeCheck(s, runeProc) =
fastRuneAt(s, i, rune, doInc=true) fastRuneAt(s, i, rune, doInc=true)
result = runeProc(rune) and result result = runeProc(rune) and result
proc isUpper*(s: string): bool {.noSideEffect, procvar,
rtl, extern: "nuc$1Str".} =
## Returns true iff `s` contains all upper case unicode characters.
runeCheck(s, isUpper)
proc isLower*(s: string): bool {.noSideEffect, procvar,
rtl, extern: "nuc$1Str".} =
## Returns true iff `s` contains all lower case unicode characters.
runeCheck(s, isLower)
proc isAlpha*(s: string): bool {.noSideEffect, procvar, proc isAlpha*(s: string): bool {.noSideEffect, procvar,
rtl, extern: "nuc$1Str".} = rtl, extern: "nuc$1Str".} =
## Returns true iff `s` contains all alphabetic unicode characters. ## Returns true iff `s` contains all alphabetic unicode characters.
@ -1423,6 +1413,56 @@ proc isSpace*(s: string): bool {.noSideEffect, procvar,
## Returns true iff `s` contains all whitespace unicode characters. ## Returns true iff `s` contains all whitespace unicode characters.
runeCheck(s, isWhiteSpace) runeCheck(s, isWhiteSpace)
template runeCaseCheck(s, runeProc, skipNonAlpha) =
## Common code for rune.isLower and rune.isUpper.
if len(s) == 0: return false
var
i = 0
rune: Rune
hasAtleastOneAlphaRune = false
while i < len(s):
fastRuneAt(s, i, rune, doInc=true)
if skipNonAlpha:
var runeIsAlpha = isAlpha(rune)
if not hasAtleastOneAlphaRune:
hasAtleastOneAlphaRune = runeIsAlpha
if runeIsAlpha and (not runeProc(rune)):
return false
else:
if not runeProc(rune):
return false
return if skipNonAlpha: hasAtleastOneAlphaRune else: true
proc isLower*(s: string, skipNonAlpha: bool): bool =
## Checks whether ``s`` is lower case.
##
## If ``skipNonAlpha`` is true, returns true if all alphabetical
## runes in ``s`` are lower case. Returns false if none of the
## runes in ``s`` are alphabetical.
##
## If ``skipNonAlpha`` is false, returns true only if all runes in
## ``s`` are alphabetical and lower case.
##
## For either value of ``skipNonAlpha``, returns false if ``s`` is
## an empty string.
runeCaseCheck(s, isLower, skipNonAlpha)
proc isUpper*(s: string, skipNonAlpha: bool): bool =
## Checks whether ``s`` is upper case.
##
## If ``skipNonAlpha`` is true, returns true if all alphabetical
## runes in ``s`` are upper case. Returns false if none of the
## runes in ``s`` are alphabetical.
##
## If ``skipNonAlpha`` is false, returns true only if all runes in
## ``s`` are alphabetical and upper case.
##
## For either value of ``skipNonAlpha``, returns false if ``s`` is
## an empty string.
runeCaseCheck(s, isUpper, skipNonAlpha)
template convertRune(s, runeProc) = template convertRune(s, runeProc) =
## Convert runes in `s` using `runeProc` as the converter. ## Convert runes in `s` using `runeProc` as the converter.
result = newString(len(s)) result = newString(len(s))
@ -1755,25 +1795,39 @@ when isMainModule:
doAssert(not isSpace("")) doAssert(not isSpace(""))
doAssert(not isSpace("ΑΓc \td")) doAssert(not isSpace("ΑΓc \td"))
doAssert isLower("a") doAssert(not isLower(' '.Rune))
doAssert isLower("γ")
doAssert(not isLower("Γ"))
doAssert(not isLower("4"))
doAssert(not isLower(""))
doAssert isLower("abcdγ") doAssert isLower("a", false)
doAssert(not isLower("abCDΓ")) doAssert isLower("γ", true)
doAssert(not isLower("33aaΓ")) doAssert(not isLower("Γ", false))
doAssert(not isLower("4", true))
doAssert(not isLower("", false))
doAssert isLower("abcdγ", false)
doAssert(not isLower("33aaΓ", false))
doAssert(not isLower("a b", false))
doAssert isUpper("Γ") doAssert(not isLower("abCDΓ", true))
doAssert(not isUpper("b")) doAssert isLower("a b", true)
doAssert(not isUpper("α")) doAssert isLower("1, 2, 3 go!", true)
doAssert(not isUpper("✓")) doAssert(not isLower(" ", true))
doAssert(not isUpper("")) doAssert(not isLower("(*&#@(^#$✓ ", true)) # None of the string runes are alphabets
doAssert isUpper("ΑΒΓ") doAssert(not isUpper(' '.Rune))
doAssert(not isUpper("AAccβ"))
doAssert(not isUpper("A#$β")) doAssert isUpper("Γ", false)
doAssert(not isUpper("α", false))
doAssert(not isUpper("", false))
doAssert isUpper("ΑΒΓ", false)
doAssert(not isUpper("A#$β", false))
doAssert(not isUpper("A B", false))
doAssert(not isUpper("b", true))
doAssert(not isUpper("✓", true))
doAssert(not isUpper("AAccβ", true))
doAssert isUpper("A B", true)
doAssert isUpper("1, 2, 3 GO!", true)
doAssert(not isUpper(" ", true))
doAssert(not isUpper("(*&#@(^#$✓ ", true)) # None of the string runes are alphabets
doAssert toUpper("Γ") == "Γ" doAssert toUpper("Γ") == "Γ"
doAssert toUpper("b") == "B" doAssert toUpper("b") == "B"