bugfix: strutils.find was broken for strings with uneven number of chars
For some reason, the problem was manifesting only inside the VM, it was detecting an attempt to read past the string end (i.e. the formerly accessible null byte). To catch such errors, strutils now performs static tests too. I've solved the problem by re-implementing the Boyer-Moore algotihm in a cleaner way and I took the opportunity to make some other optimisations to strutils.
This commit is contained in:
parent
cf13c5fba4
commit
70ec344bbf
1 changed files with 245 additions and 209 deletions
|
|
@ -1262,21 +1262,20 @@ proc initSkipTable*(a: var SkipTable, sub: string)
|
||||||
{.noSideEffect, rtl, extern: "nsuInitSkipTable".} =
|
{.noSideEffect, rtl, extern: "nsuInitSkipTable".} =
|
||||||
## Preprocess table `a` for `sub`.
|
## Preprocess table `a` for `sub`.
|
||||||
let m = len(sub)
|
let m = len(sub)
|
||||||
let m1 = m + 1
|
|
||||||
var i = 0
|
var i = 0
|
||||||
while i <= 0xff-7:
|
while i <= 0xff-7:
|
||||||
a[chr(i + 0)] = m1
|
a[chr(i + 0)] = m
|
||||||
a[chr(i + 1)] = m1
|
a[chr(i + 1)] = m
|
||||||
a[chr(i + 2)] = m1
|
a[chr(i + 2)] = m
|
||||||
a[chr(i + 3)] = m1
|
a[chr(i + 3)] = m
|
||||||
a[chr(i + 4)] = m1
|
a[chr(i + 4)] = m
|
||||||
a[chr(i + 5)] = m1
|
a[chr(i + 5)] = m
|
||||||
a[chr(i + 6)] = m1
|
a[chr(i + 6)] = m
|
||||||
a[chr(i + 7)] = m1
|
a[chr(i + 7)] = m
|
||||||
i += 8
|
i += 8
|
||||||
|
|
||||||
for i in 0..m-1:
|
for i in 0 ..< m - 1:
|
||||||
a[sub[i]] = m-i
|
a[sub[i]] = m - 1 - i
|
||||||
|
|
||||||
proc find*(a: SkipTable, s, sub: string, start: Natural = 0, last: Natural = 0): int
|
proc find*(a: SkipTable, s, sub: string, start: Natural = 0, last: Natural = 0): int
|
||||||
{.noSideEffect, rtl, extern: "nsuFindStrA".} =
|
{.noSideEffect, rtl, extern: "nsuFindStrA".} =
|
||||||
|
|
@ -1284,18 +1283,29 @@ proc find*(a: SkipTable, s, sub: string, start: Natural = 0, last: Natural = 0):
|
||||||
## If `last` is unspecified, it defaults to `s.high`.
|
## If `last` is unspecified, it defaults to `s.high`.
|
||||||
##
|
##
|
||||||
## Searching is case-sensitive. If `sub` is not in `s`, -1 is returned.
|
## Searching is case-sensitive. If `sub` is not in `s`, -1 is returned.
|
||||||
|
|
||||||
let
|
let
|
||||||
last = if last==0: s.high else: last
|
last = if last==0: s.high else: last
|
||||||
m = len(sub)
|
sLen = last - start + 1
|
||||||
n = last + 1
|
subLast = sub.len - 1
|
||||||
# search:
|
|
||||||
var j = start
|
if subLast == -1:
|
||||||
while j <= n - m:
|
# this was an empty needle string,
|
||||||
block match:
|
# we count this as match in the first possible position:
|
||||||
for k in 0..m-1:
|
return start
|
||||||
if sub[k] != s[k+j]: break match
|
|
||||||
return j
|
# This is an implementation of the Boyer-Moore Horspool algorithms
|
||||||
inc(j, a[s[j+m]])
|
# https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore%E2%80%93Horspool_algorithm
|
||||||
|
var skip = start
|
||||||
|
|
||||||
|
while last - skip >= subLast:
|
||||||
|
var i = subLast
|
||||||
|
while s[skip + i] == sub[i]:
|
||||||
|
if i == 0:
|
||||||
|
return skip
|
||||||
|
dec i
|
||||||
|
inc skip, a[s[skip + subLast]]
|
||||||
|
|
||||||
return -1
|
return -1
|
||||||
|
|
||||||
when not (defined(js) or defined(nimdoc) or defined(nimscript)):
|
when not (defined(js) or defined(nimdoc) or defined(nimscript)):
|
||||||
|
|
@ -1455,23 +1465,41 @@ proc contains*(s: string, chars: set[char]): bool {.noSideEffect.} =
|
||||||
proc replace*(s, sub: string, by = ""): string {.noSideEffect,
|
proc replace*(s, sub: string, by = ""): string {.noSideEffect,
|
||||||
rtl, extern: "nsuReplaceStr".} =
|
rtl, extern: "nsuReplaceStr".} =
|
||||||
## Replaces `sub` in `s` by the string `by`.
|
## Replaces `sub` in `s` by the string `by`.
|
||||||
var a {.noinit.}: SkipTable
|
|
||||||
result = ""
|
result = ""
|
||||||
initSkipTable(a, sub)
|
let subLen = sub.len
|
||||||
let last = s.high
|
if subLen == 0:
|
||||||
var i = 0
|
for c in s:
|
||||||
while true:
|
add result, by
|
||||||
let j = find(a, s, sub, i, last)
|
add result, c
|
||||||
if j < 0: break
|
|
||||||
add result, substr(s, i, j - 1)
|
|
||||||
add result, by
|
add result, by
|
||||||
if sub.len == 0:
|
return
|
||||||
if i < s.len: add result, s[i]
|
elif subLen == 1:
|
||||||
i = j + 1
|
# when the pattern is a single char, we use a faster
|
||||||
else:
|
# char-based search that doesn't need a skip table:
|
||||||
i = j + sub.len
|
var c = sub[0]
|
||||||
# copy the rest:
|
let last = s.high
|
||||||
add result, substr(s, i)
|
var i = 0
|
||||||
|
while true:
|
||||||
|
let j = find(s, c, i, last)
|
||||||
|
if j < 0: break
|
||||||
|
add result, substr(s, i, j - 1)
|
||||||
|
add result, by
|
||||||
|
i = j + subLen
|
||||||
|
# copy the rest:
|
||||||
|
add result, substr(s, i)
|
||||||
|
else:
|
||||||
|
var a {.noinit.}: SkipTable
|
||||||
|
initSkipTable(a, sub)
|
||||||
|
let last = s.high
|
||||||
|
var i = 0
|
||||||
|
while true:
|
||||||
|
let j = find(a, s, sub, i, last)
|
||||||
|
if j < 0: break
|
||||||
|
add result, substr(s, i, j - 1)
|
||||||
|
add result, by
|
||||||
|
i = j + subLen
|
||||||
|
# copy the rest:
|
||||||
|
add result, substr(s, i)
|
||||||
|
|
||||||
proc replace*(s: string, sub, by: char): string {.noSideEffect,
|
proc replace*(s: string, sub, by: char): string {.noSideEffect,
|
||||||
rtl, extern: "nsuReplaceChar".} =
|
rtl, extern: "nsuReplaceChar".} =
|
||||||
|
|
@ -1492,6 +1520,7 @@ proc replaceWord*(s, sub: string, by = ""): string {.noSideEffect,
|
||||||
## Each occurrence of `sub` has to be surrounded by word boundaries
|
## Each occurrence of `sub` has to be surrounded by word boundaries
|
||||||
## (comparable to ``\\w`` in regular expressions), otherwise it is not
|
## (comparable to ``\\w`` in regular expressions), otherwise it is not
|
||||||
## replaced.
|
## replaced.
|
||||||
|
if sub.len == 0: return s
|
||||||
const wordChars = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\128'..'\255'}
|
const wordChars = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\128'..'\255'}
|
||||||
var a {.noinit.}: SkipTable
|
var a {.noinit.}: SkipTable
|
||||||
result = ""
|
result = ""
|
||||||
|
|
@ -2326,236 +2355,243 @@ proc removePrefix*(s: var string, prefix: string) {.
|
||||||
s.delete(0, prefix.len - 1)
|
s.delete(0, prefix.len - 1)
|
||||||
|
|
||||||
when isMainModule:
|
when isMainModule:
|
||||||
doAssert align("abc", 4) == " abc"
|
proc nonStaticTests =
|
||||||
doAssert align("a", 0) == "a"
|
doAssert formatBiggestFloat(1234.567, ffDecimal, -1) == "1234.567000"
|
||||||
doAssert align("1232", 6) == " 1232"
|
doAssert formatBiggestFloat(1234.567, ffDecimal, 0) == "1235."
|
||||||
doAssert align("1232", 6, '#') == "##1232"
|
doAssert formatBiggestFloat(1234.567, ffDecimal, 1) == "1234.6"
|
||||||
|
doAssert formatBiggestFloat(0.00000000001, ffDecimal, 11) == "0.00000000001"
|
||||||
|
doAssert formatBiggestFloat(0.00000000001, ffScientific, 1, ',') in
|
||||||
|
["1,0e-11", "1,0e-011"]
|
||||||
|
# bug #6589
|
||||||
|
doAssert formatFloat(123.456, ffScientific, precision = -1) == "1.234560e+02"
|
||||||
|
|
||||||
doAssert alignLeft("abc", 4) == "abc "
|
doAssert "$# $3 $# $#" % ["a", "b", "c"] == "a c b c"
|
||||||
doAssert alignLeft("a", 0) == "a"
|
doAssert "${1}12 ${-1}$2" % ["a", "b"] == "a12 bb"
|
||||||
doAssert alignLeft("1232", 6) == "1232 "
|
|
||||||
doAssert alignLeft("1232", 6, '#') == "1232##"
|
|
||||||
|
|
||||||
let
|
block: # formatSize tests
|
||||||
inp = """ this is a long text -- muchlongerthan10chars and here
|
doAssert formatSize((1'i64 shl 31) + (300'i64 shl 20)) == "2.293GiB"
|
||||||
it goes"""
|
doAssert formatSize((2.234*1024*1024).int) == "2.234MiB"
|
||||||
outp = " this is a\nlong text\n--\nmuchlongerthan10chars\nand here\nit goes"
|
doAssert formatSize(4096) == "4KiB"
|
||||||
doAssert wordWrap(inp, 10, false) == outp
|
doAssert formatSize(4096, prefix=bpColloquial, includeSpace=true) == "4 kB"
|
||||||
|
doAssert formatSize(4096, includeSpace=true) == "4 KiB"
|
||||||
|
doAssert formatSize(5_378_934, prefix=bpColloquial, decimalSep=',') == "5,13MB"
|
||||||
|
|
||||||
let
|
block: # formatEng tests
|
||||||
longInp = """ThisIsOneVeryLongStringWhichWeWillSplitIntoEightSeparatePartsNow"""
|
doAssert formatEng(0, 2, trim=false) == "0.00"
|
||||||
longOutp = "ThisIsOn\neVeryLon\ngStringW\nhichWeWi\nllSplitI\nntoEight\nSeparate\nPartsNow"
|
doAssert formatEng(0, 2) == "0"
|
||||||
doAssert wordWrap(longInp, 8, true) == longOutp
|
doAssert formatEng(53, 2, trim=false) == "53.00"
|
||||||
|
doAssert formatEng(0.053, 2, trim=false) == "53.00e-3"
|
||||||
|
doAssert formatEng(0.053, 4, trim=false) == "53.0000e-3"
|
||||||
|
doAssert formatEng(0.053, 4, trim=true) == "53e-3"
|
||||||
|
doAssert formatEng(0.053, 0) == "53e-3"
|
||||||
|
doAssert formatEng(52731234) == "52.731234e6"
|
||||||
|
doAssert formatEng(-52731234) == "-52.731234e6"
|
||||||
|
doAssert formatEng(52731234, 1) == "52.7e6"
|
||||||
|
doAssert formatEng(-52731234, 1) == "-52.7e6"
|
||||||
|
doAssert formatEng(52731234, 1, decimalSep=',') == "52,7e6"
|
||||||
|
doAssert formatEng(-52731234, 1, decimalSep=',') == "-52,7e6"
|
||||||
|
|
||||||
doAssert formatBiggestFloat(1234.567, ffDecimal, -1) == "1234.567000"
|
doAssert formatEng(4100, siPrefix=true, unit="V") == "4.1 kV"
|
||||||
doAssert formatBiggestFloat(1234.567, ffDecimal, 0) == "1235."
|
doAssert formatEng(4.1, siPrefix=true, unit="V", useUnitSpace=true) == "4.1 V"
|
||||||
doAssert formatBiggestFloat(1234.567, ffDecimal, 1) == "1234.6"
|
doAssert formatEng(4.1, siPrefix=true) == "4.1" # Note lack of space
|
||||||
doAssert formatBiggestFloat(0.00000000001, ffDecimal, 11) == "0.00000000001"
|
doAssert formatEng(4100, siPrefix=true) == "4.1 k"
|
||||||
doAssert formatBiggestFloat(0.00000000001, ffScientific, 1, ',') in
|
doAssert formatEng(4.1, siPrefix=true, unit="", useUnitSpace=true) == "4.1 " # Includes space
|
||||||
["1,0e-11", "1,0e-011"]
|
doAssert formatEng(4100, siPrefix=true, unit="") == "4.1 k"
|
||||||
# bug #6589
|
doAssert formatEng(4100) == "4.1e3"
|
||||||
doAssert formatFloat(123.456, ffScientific, precision = -1) == "1.234560e+02"
|
doAssert formatEng(4100, unit="V", useUnitSpace=true) == "4.1e3 V"
|
||||||
|
doAssert formatEng(4100, unit="", useUnitSpace=true) == "4.1e3 "
|
||||||
|
# Don't use SI prefix as number is too big
|
||||||
|
doAssert formatEng(3.1e22, siPrefix=true, unit="a", useUnitSpace=true) == "31e21 a"
|
||||||
|
# Don't use SI prefix as number is too small
|
||||||
|
doAssert formatEng(3.1e-25, siPrefix=true, unit="A", useUnitSpace=true) == "310e-27 A"
|
||||||
|
|
||||||
doAssert "$# $3 $# $#" % ["a", "b", "c"] == "a c b c"
|
proc staticTests =
|
||||||
doAssert "${1}12 ${-1}$2" % ["a", "b"] == "a12 bb"
|
doAssert align("abc", 4) == " abc"
|
||||||
|
doAssert align("a", 0) == "a"
|
||||||
|
doAssert align("1232", 6) == " 1232"
|
||||||
|
doAssert align("1232", 6, '#') == "##1232"
|
||||||
|
|
||||||
block: # formatSize tests
|
doAssert alignLeft("abc", 4) == "abc "
|
||||||
doAssert formatSize((1'i64 shl 31) + (300'i64 shl 20)) == "2.293GiB"
|
doAssert alignLeft("a", 0) == "a"
|
||||||
doAssert formatSize((2.234*1024*1024).int) == "2.234MiB"
|
doAssert alignLeft("1232", 6) == "1232 "
|
||||||
doAssert formatSize(4096) == "4KiB"
|
doAssert alignLeft("1232", 6, '#') == "1232##"
|
||||||
doAssert formatSize(4096, prefix=bpColloquial, includeSpace=true) == "4 kB"
|
|
||||||
doAssert formatSize(4096, includeSpace=true) == "4 KiB"
|
|
||||||
doAssert formatSize(5_378_934, prefix=bpColloquial, decimalSep=',') == "5,13MB"
|
|
||||||
|
|
||||||
doAssert "$animal eats $food." % ["animal", "The cat", "food", "fish"] ==
|
let
|
||||||
"The cat eats fish."
|
inp = """ this is a long text -- muchlongerthan10chars and here
|
||||||
|
it goes"""
|
||||||
|
outp = " this is a\nlong text\n--\nmuchlongerthan10chars\nand here\nit goes"
|
||||||
|
doAssert wordWrap(inp, 10, false) == outp
|
||||||
|
|
||||||
doAssert "-ld a-ldz -ld".replaceWord("-ld") == " a-ldz "
|
let
|
||||||
doAssert "-lda-ldz -ld abc".replaceWord("-ld") == "-lda-ldz abc"
|
longInp = """ThisIsOneVeryLongStringWhichWeWillSplitIntoEightSeparatePartsNow"""
|
||||||
|
longOutp = "ThisIsOn\neVeryLon\ngStringW\nhichWeWi\nllSplitI\nntoEight\nSeparate\nPartsNow"
|
||||||
|
doAssert wordWrap(longInp, 8, true) == longOutp
|
||||||
|
|
||||||
doAssert "-lda-ldz -ld abc".replaceWord("") == "lda-ldz ld abc"
|
doAssert "$animal eats $food." % ["animal", "The cat", "food", "fish"] ==
|
||||||
doAssert "oo".replace("", "abc") == "abcoabcoabc"
|
"The cat eats fish."
|
||||||
|
|
||||||
type MyEnum = enum enA, enB, enC, enuD, enE
|
doAssert "-ld a-ldz -ld".replaceWord("-ld") == " a-ldz "
|
||||||
doAssert parseEnum[MyEnum]("enu_D") == enuD
|
doAssert "-lda-ldz -ld abc".replaceWord("-ld") == "-lda-ldz abc"
|
||||||
|
|
||||||
doAssert parseEnum("invalid enum value", enC) == enC
|
doAssert "-lda-ldz -ld abc".replaceWord("") == "-lda-ldz -ld abc"
|
||||||
|
doAssert "oo".replace("", "abc") == "abcoabcoabc"
|
||||||
|
|
||||||
doAssert center("foo", 13) == " foo "
|
type MyEnum = enum enA, enB, enC, enuD, enE
|
||||||
doAssert center("foo", 0) == "foo"
|
doAssert parseEnum[MyEnum]("enu_D") == enuD
|
||||||
doAssert center("foo", 3, fillChar = 'a') == "foo"
|
|
||||||
doAssert center("foo", 10, fillChar = '\t') == "\t\t\tfoo\t\t\t\t"
|
|
||||||
|
|
||||||
doAssert count("foofoofoo", "foofoo") == 1
|
doAssert parseEnum("invalid enum value", enC) == enC
|
||||||
doAssert count("foofoofoo", "foofoo", overlapping = true) == 2
|
|
||||||
doAssert count("foofoofoo", 'f') == 3
|
|
||||||
doAssert count("foofoofoobar", {'f','b'}) == 4
|
|
||||||
|
|
||||||
doAssert strip(" foofoofoo ") == "foofoofoo"
|
doAssert center("foo", 13) == " foo "
|
||||||
doAssert strip("sfoofoofoos", chars = {'s'}) == "foofoofoo"
|
doAssert center("foo", 0) == "foo"
|
||||||
doAssert strip("barfoofoofoobar", chars = {'b', 'a', 'r'}) == "foofoofoo"
|
doAssert center("foo", 3, fillChar = 'a') == "foo"
|
||||||
doAssert strip("stripme but don't strip this stripme",
|
doAssert center("foo", 10, fillChar = '\t') == "\t\t\tfoo\t\t\t\t"
|
||||||
chars = {'s', 't', 'r', 'i', 'p', 'm', 'e'}) ==
|
|
||||||
" but don't strip this "
|
|
||||||
doAssert strip("sfoofoofoos", leading = false, chars = {'s'}) == "sfoofoofoo"
|
|
||||||
doAssert strip("sfoofoofoos", trailing = false, chars = {'s'}) == "foofoofoos"
|
|
||||||
|
|
||||||
doAssert " foo\n bar".indent(4, "Q") == "QQQQ foo\nQQQQ bar"
|
doAssert count("foofoofoo", "foofoo") == 1
|
||||||
|
doAssert count("foofoofoo", "foofoo", overlapping = true) == 2
|
||||||
|
doAssert count("foofoofoo", 'f') == 3
|
||||||
|
doAssert count("foofoofoobar", {'f','b'}) == 4
|
||||||
|
|
||||||
doAssert "abba".multiReplace(("a", "b"), ("b", "a")) == "baab"
|
doAssert strip(" foofoofoo ") == "foofoofoo"
|
||||||
doAssert "Hello World.".multiReplace(("ello", "ELLO"), ("World.", "PEOPLE!")) == "HELLO PEOPLE!"
|
doAssert strip("sfoofoofoos", chars = {'s'}) == "foofoofoo"
|
||||||
doAssert "aaaa".multiReplace(("a", "aa"), ("aa", "bb")) == "aaaaaaaa"
|
doAssert strip("barfoofoofoobar", chars = {'b', 'a', 'r'}) == "foofoofoo"
|
||||||
|
doAssert strip("stripme but don't strip this stripme",
|
||||||
|
chars = {'s', 't', 'r', 'i', 'p', 'm', 'e'}) ==
|
||||||
|
" but don't strip this "
|
||||||
|
doAssert strip("sfoofoofoos", leading = false, chars = {'s'}) == "sfoofoofoo"
|
||||||
|
doAssert strip("sfoofoofoos", trailing = false, chars = {'s'}) == "foofoofoos"
|
||||||
|
|
||||||
doAssert isAlphaAscii('r')
|
doAssert " foo\n bar".indent(4, "Q") == "QQQQ foo\nQQQQ bar"
|
||||||
doAssert isAlphaAscii('A')
|
|
||||||
doAssert(not isAlphaAscii('$'))
|
|
||||||
|
|
||||||
doAssert isAlphaAscii("Rasp")
|
doAssert "abba".multiReplace(("a", "b"), ("b", "a")) == "baab"
|
||||||
doAssert isAlphaAscii("Args")
|
doAssert "Hello World.".multiReplace(("ello", "ELLO"), ("World.", "PEOPLE!")) == "HELLO PEOPLE!"
|
||||||
doAssert(not isAlphaAscii("$Tomato"))
|
doAssert "aaaa".multiReplace(("a", "aa"), ("aa", "bb")) == "aaaaaaaa"
|
||||||
|
|
||||||
doAssert isAlphaNumeric('3')
|
doAssert isAlphaAscii('r')
|
||||||
doAssert isAlphaNumeric('R')
|
doAssert isAlphaAscii('A')
|
||||||
doAssert(not isAlphaNumeric('!'))
|
doAssert(not isAlphaAscii('$'))
|
||||||
|
|
||||||
doAssert isAlphaNumeric("34ABc")
|
doAssert isAlphaAscii("Rasp")
|
||||||
doAssert isAlphaNumeric("Rad")
|
doAssert isAlphaAscii("Args")
|
||||||
doAssert isAlphaNumeric("1234")
|
doAssert(not isAlphaAscii("$Tomato"))
|
||||||
doAssert(not isAlphaNumeric("@nose"))
|
|
||||||
|
|
||||||
doAssert isDigit('3')
|
doAssert isAlphaNumeric('3')
|
||||||
doAssert(not isDigit('a'))
|
doAssert isAlphaNumeric('R')
|
||||||
doAssert(not isDigit('%'))
|
doAssert(not isAlphaNumeric('!'))
|
||||||
|
|
||||||
doAssert isDigit("12533")
|
doAssert isAlphaNumeric("34ABc")
|
||||||
doAssert(not isDigit("12.33"))
|
doAssert isAlphaNumeric("Rad")
|
||||||
doAssert(not isDigit("A45b"))
|
doAssert isAlphaNumeric("1234")
|
||||||
|
doAssert(not isAlphaNumeric("@nose"))
|
||||||
|
|
||||||
doAssert isSpaceAscii('\t')
|
doAssert isDigit('3')
|
||||||
doAssert isSpaceAscii('\l')
|
doAssert(not isDigit('a'))
|
||||||
doAssert(not isSpaceAscii('A'))
|
doAssert(not isDigit('%'))
|
||||||
|
|
||||||
doAssert isSpaceAscii("\t\l \v\r\f")
|
doAssert isDigit("12533")
|
||||||
doAssert isSpaceAscii(" ")
|
doAssert(not isDigit("12.33"))
|
||||||
doAssert(not isSpaceAscii("ABc \td"))
|
doAssert(not isDigit("A45b"))
|
||||||
|
|
||||||
doAssert(isNilOrWhitespace(""))
|
doAssert isSpaceAscii('\t')
|
||||||
doAssert(isNilOrWhitespace(" "))
|
doAssert isSpaceAscii('\l')
|
||||||
doAssert(isNilOrWhitespace("\t\l \v\r\f"))
|
doAssert(not isSpaceAscii('A'))
|
||||||
doAssert(not isNilOrWhitespace("ABc \td"))
|
|
||||||
|
|
||||||
doAssert isLowerAscii('a')
|
doAssert isSpaceAscii("\t\l \v\r\f")
|
||||||
doAssert isLowerAscii('z')
|
doAssert isSpaceAscii(" ")
|
||||||
doAssert(not isLowerAscii('A'))
|
doAssert(not isSpaceAscii("ABc \td"))
|
||||||
doAssert(not isLowerAscii('5'))
|
|
||||||
doAssert(not isLowerAscii('&'))
|
|
||||||
|
|
||||||
doAssert isLowerAscii("abcd")
|
doAssert(isNilOrWhitespace(""))
|
||||||
doAssert(not isLowerAscii("abCD"))
|
doAssert(isNilOrWhitespace(" "))
|
||||||
doAssert(not isLowerAscii("33aa"))
|
doAssert(isNilOrWhitespace("\t\l \v\r\f"))
|
||||||
|
doAssert(not isNilOrWhitespace("ABc \td"))
|
||||||
|
|
||||||
doAssert isUpperAscii('A')
|
doAssert isLowerAscii('a')
|
||||||
doAssert(not isUpperAscii('b'))
|
doAssert isLowerAscii('z')
|
||||||
doAssert(not isUpperAscii('5'))
|
doAssert(not isLowerAscii('A'))
|
||||||
doAssert(not isUpperAscii('%'))
|
doAssert(not isLowerAscii('5'))
|
||||||
|
doAssert(not isLowerAscii('&'))
|
||||||
|
|
||||||
doAssert isUpperAscii("ABC")
|
doAssert isLowerAscii("abcd")
|
||||||
doAssert(not isUpperAscii("AAcc"))
|
doAssert(not isLowerAscii("abCD"))
|
||||||
doAssert(not isUpperAscii("A#$"))
|
doAssert(not isLowerAscii("33aa"))
|
||||||
|
|
||||||
doAssert rsplit("foo bar", seps=Whitespace) == @["foo", "bar"]
|
doAssert isUpperAscii('A')
|
||||||
doAssert rsplit(" foo bar", seps=Whitespace, maxsplit=1) == @[" foo", "bar"]
|
doAssert(not isUpperAscii('b'))
|
||||||
doAssert rsplit(" foo bar ", seps=Whitespace, maxsplit=1) == @[" foo bar", ""]
|
doAssert(not isUpperAscii('5'))
|
||||||
doAssert rsplit(":foo:bar", sep=':') == @["", "foo", "bar"]
|
doAssert(not isUpperAscii('%'))
|
||||||
doAssert rsplit(":foo:bar", sep=':', maxsplit=2) == @["", "foo", "bar"]
|
|
||||||
doAssert rsplit(":foo:bar", sep=':', maxsplit=3) == @["", "foo", "bar"]
|
|
||||||
doAssert rsplit("foothebar", sep="the") == @["foo", "bar"]
|
|
||||||
|
|
||||||
doAssert(unescape(r"\x013", "", "") == "\x013")
|
doAssert isUpperAscii("ABC")
|
||||||
|
doAssert(not isUpperAscii("AAcc"))
|
||||||
|
doAssert(not isUpperAscii("A#$"))
|
||||||
|
|
||||||
doAssert join(["foo", "bar", "baz"]) == "foobarbaz"
|
doAssert rsplit("foo bar", seps=Whitespace) == @["foo", "bar"]
|
||||||
doAssert join(@["foo", "bar", "baz"], ", ") == "foo, bar, baz"
|
doAssert rsplit(" foo bar", seps=Whitespace, maxsplit=1) == @[" foo", "bar"]
|
||||||
doAssert join([1, 2, 3]) == "123"
|
doAssert rsplit(" foo bar ", seps=Whitespace, maxsplit=1) == @[" foo bar", ""]
|
||||||
doAssert join(@[1, 2, 3], ", ") == "1, 2, 3"
|
doAssert rsplit(":foo:bar", sep=':') == @["", "foo", "bar"]
|
||||||
|
doAssert rsplit(":foo:bar", sep=':', maxsplit=2) == @["", "foo", "bar"]
|
||||||
|
doAssert rsplit(":foo:bar", sep=':', maxsplit=3) == @["", "foo", "bar"]
|
||||||
|
doAssert rsplit("foothebar", sep="the") == @["foo", "bar"]
|
||||||
|
|
||||||
doAssert """~~!!foo
|
doAssert(unescape(r"\x013", "", "") == "\x013")
|
||||||
|
|
||||||
|
doAssert join(["foo", "bar", "baz"]) == "foobarbaz"
|
||||||
|
doAssert join(@["foo", "bar", "baz"], ", ") == "foo, bar, baz"
|
||||||
|
doAssert join([1, 2, 3]) == "123"
|
||||||
|
doAssert join(@[1, 2, 3], ", ") == "1, 2, 3"
|
||||||
|
|
||||||
|
doAssert """~~!!foo
|
||||||
~~!!bar
|
~~!!bar
|
||||||
~~!!baz""".unindent(2, "~~!!") == "foo\nbar\nbaz"
|
~~!!baz""".unindent(2, "~~!!") == "foo\nbar\nbaz"
|
||||||
|
|
||||||
doAssert """~~!!foo
|
doAssert """~~!!foo
|
||||||
~~!!bar
|
~~!!bar
|
||||||
~~!!baz""".unindent(2, "~~!!aa") == "~~!!foo\n~~!!bar\n~~!!baz"
|
~~!!baz""".unindent(2, "~~!!aa") == "~~!!foo\n~~!!bar\n~~!!baz"
|
||||||
doAssert """~~foo
|
doAssert """~~foo
|
||||||
~~ bar
|
~~ bar
|
||||||
~~ baz""".unindent(4, "~") == "foo\n bar\n baz"
|
~~ baz""".unindent(4, "~") == "foo\n bar\n baz"
|
||||||
doAssert """foo
|
doAssert """foo
|
||||||
bar
|
bar
|
||||||
baz
|
baz
|
||||||
""".unindent(4) == "foo\nbar\nbaz\n"
|
""".unindent(4) == "foo\nbar\nbaz\n"
|
||||||
doAssert """foo
|
doAssert """foo
|
||||||
bar
|
bar
|
||||||
baz
|
baz
|
||||||
""".unindent(2) == "foo\n bar\n baz\n"
|
""".unindent(2) == "foo\n bar\n baz\n"
|
||||||
doAssert """foo
|
doAssert """foo
|
||||||
bar
|
bar
|
||||||
baz
|
baz
|
||||||
""".unindent(100) == "foo\nbar\nbaz\n"
|
""".unindent(100) == "foo\nbar\nbaz\n"
|
||||||
|
|
||||||
doAssert """foo
|
doAssert """foo
|
||||||
foo
|
foo
|
||||||
bar
|
bar
|
||||||
""".unindent() == "foo\nfoo\nbar\n"
|
""".unindent() == "foo\nfoo\nbar\n"
|
||||||
|
|
||||||
let s = " this is an example "
|
let s = " this is an example "
|
||||||
let s2 = ":this;is;an:example;;"
|
let s2 = ":this;is;an:example;;"
|
||||||
|
|
||||||
doAssert s.split() == @["", "this", "is", "an", "example", "", ""]
|
doAssert s.split() == @["", "this", "is", "an", "example", "", ""]
|
||||||
doAssert s2.split(seps={':', ';'}) == @["", "this", "is", "an", "example", "", ""]
|
doAssert s2.split(seps={':', ';'}) == @["", "this", "is", "an", "example", "", ""]
|
||||||
doAssert s.split(maxsplit=4) == @["", "this", "is", "an", "example "]
|
doAssert s.split(maxsplit=4) == @["", "this", "is", "an", "example "]
|
||||||
doAssert s.split(' ', maxsplit=1) == @["", "this is an example "]
|
doAssert s.split(' ', maxsplit=1) == @["", "this is an example "]
|
||||||
doAssert s.split(" ", maxsplit=4) == @["", "this", "is", "an", "example "]
|
doAssert s.split(" ", maxsplit=4) == @["", "this", "is", "an", "example "]
|
||||||
|
|
||||||
doAssert s.splitWhitespace() == @["this", "is", "an", "example"]
|
doAssert s.splitWhitespace() == @["this", "is", "an", "example"]
|
||||||
doAssert s.splitWhitespace(maxsplit=1) == @["this", "is an example "]
|
doAssert s.splitWhitespace(maxsplit=1) == @["this", "is an example "]
|
||||||
doAssert s.splitWhitespace(maxsplit=2) == @["this", "is", "an example "]
|
doAssert s.splitWhitespace(maxsplit=2) == @["this", "is", "an example "]
|
||||||
doAssert s.splitWhitespace(maxsplit=3) == @["this", "is", "an", "example "]
|
doAssert s.splitWhitespace(maxsplit=3) == @["this", "is", "an", "example "]
|
||||||
doAssert s.splitWhitespace(maxsplit=4) == @["this", "is", "an", "example"]
|
doAssert s.splitWhitespace(maxsplit=4) == @["this", "is", "an", "example"]
|
||||||
|
|
||||||
block: # formatEng tests
|
block: # startsWith / endsWith char tests
|
||||||
doAssert formatEng(0, 2, trim=false) == "0.00"
|
var s = "abcdef"
|
||||||
doAssert formatEng(0, 2) == "0"
|
doAssert s.startsWith('a')
|
||||||
doAssert formatEng(53, 2, trim=false) == "53.00"
|
doAssert s.startsWith('b') == false
|
||||||
doAssert formatEng(0.053, 2, trim=false) == "53.00e-3"
|
doAssert s.endsWith('f')
|
||||||
doAssert formatEng(0.053, 4, trim=false) == "53.0000e-3"
|
doAssert s.endsWith('a') == false
|
||||||
doAssert formatEng(0.053, 4, trim=true) == "53e-3"
|
doAssert s.endsWith('\0') == false
|
||||||
doAssert formatEng(0.053, 0) == "53e-3"
|
|
||||||
doAssert formatEng(52731234) == "52.731234e6"
|
|
||||||
doAssert formatEng(-52731234) == "-52.731234e6"
|
|
||||||
doAssert formatEng(52731234, 1) == "52.7e6"
|
|
||||||
doAssert formatEng(-52731234, 1) == "-52.7e6"
|
|
||||||
doAssert formatEng(52731234, 1, decimalSep=',') == "52,7e6"
|
|
||||||
doAssert formatEng(-52731234, 1, decimalSep=',') == "-52,7e6"
|
|
||||||
|
|
||||||
doAssert formatEng(4100, siPrefix=true, unit="V") == "4.1 kV"
|
#echo("strutils tests passed")
|
||||||
doAssert formatEng(4.1, siPrefix=true, unit="V", useUnitSpace=true) == "4.1 V"
|
|
||||||
doAssert formatEng(4.1, siPrefix=true) == "4.1" # Note lack of space
|
|
||||||
doAssert formatEng(4100, siPrefix=true) == "4.1 k"
|
|
||||||
doAssert formatEng(4.1, siPrefix=true, unit="", useUnitSpace=true) == "4.1 " # Includes space
|
|
||||||
doAssert formatEng(4100, siPrefix=true, unit="") == "4.1 k"
|
|
||||||
doAssert formatEng(4100) == "4.1e3"
|
|
||||||
doAssert formatEng(4100, unit="V", useUnitSpace=true) == "4.1e3 V"
|
|
||||||
doAssert formatEng(4100, unit="", useUnitSpace=true) == "4.1e3 "
|
|
||||||
# Don't use SI prefix as number is too big
|
|
||||||
doAssert formatEng(3.1e22, siPrefix=true, unit="a", useUnitSpace=true) == "31e21 a"
|
|
||||||
# Don't use SI prefix as number is too small
|
|
||||||
doAssert formatEng(3.1e-25, siPrefix=true, unit="A", useUnitSpace=true) == "310e-27 A"
|
|
||||||
|
|
||||||
block: # startsWith / endsWith char tests
|
nonStaticTests()
|
||||||
var s = "abcdef"
|
staticTests()
|
||||||
doAssert s.startsWith('a')
|
static: staticTests()
|
||||||
doAssert s.startsWith('b') == false
|
|
||||||
doAssert s.endsWith('f')
|
|
||||||
doAssert s.endsWith('a') == false
|
|
||||||
doAssert s.endsWith('\0') == false
|
|
||||||
|
|
||||||
#echo("strutils tests passed")
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue