From 1ecf6ed85d1eb0345e7d79df3571f52f8f01ae78 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Mon, 28 Sep 2020 08:30:46 -0300 Subject: [PATCH 01/64] Add parseFloatThousandSep --- changelog.md | 4 +++- lib/pure/strmisc.nim | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index bd5674610786..65dd31902e20 100644 --- a/changelog.md +++ b/changelog.md @@ -195,9 +195,11 @@ is cool! """ - ``` + ``` - Add `initUri(isIpv6: bool)` to `uri` module, now `uri` supports parsing ipv6 hostname. +- Add `strmisc.parseFloatThousandSep` designed to parse floats as found in the wild formatted for humans. + ## Language changes diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index 061c2063b675..70f69c1e4216 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -11,6 +11,7 @@ ## used in comparison to `strutils `_. import strutils +import std/private/since proc expandTabs*(s: string, tabSize: int = 8): string {.noSideEffect.} = ## Expand tab characters in `s` replacing them by spaces. @@ -85,6 +86,41 @@ proc rpartition*(s: string, sep: string): (string, string, string) return partition(s, sep, right = true) +func parseFloatThousandSep*(s: string; thousandSep = ','; floatSep = '.'): float {.since: (1, 3).} = + ## Convenience func for `parseFloat` but it can take 2 `char` separators, + ## one is **likely** the thousands separator, other is **likely** the floating point, + ## but if both separators are swapped it can still parse correctly, see the examples, + ## this is designed to parse floats as found in the wild formatted for humans. + ## + ## See also: + ## * `strutils `_ + runnableExamples: + doAssert parseFloatThousandSep("0") == 0.0 + doAssert parseFloatThousandSep("0.0") == 0.0 + doAssert parseFloatThousandSep("1.0") == 1.0 + doAssert parseFloatThousandSep("-1.0") == -1.0 + doAssert parseFloatThousandSep("1.000") == 1.0 + doAssert parseFloatThousandSep("1,000") == 1.0 + doAssert parseFloatThousandSep("10,000.000") == 10000.0 + doAssert parseFloatThousandSep("10.000,000") == 10.0 + doAssert parseFloatThousandSep("1,000,000.000") == 1000000.0 + doAssert parseFloatThousandSep("1.000.000,000") == 1000000.0 + doAssert parseFloatThousandSep("0000000000000001.00,0,000,00000,,00") == 1.0 + doAssert parseFloatThousandSep("0000000000000001,00.0.000.00000..00") == 1.0 + doAssert parseFloatThousandSep("000,1.000,,,,,,,,,,,,,,000,,,,,0000") == 1.0 + doAssert parseFloatThousandSep("000.1,000..............000.....0000") == 1.0 + doAssert parseFloatThousandSep("1'000'000.000", thousandSep = '\'') == 1000000.0 + doAssert parseFloatThousandSep("1_000_000.000", thousandSep = '_') == 1000000.0 + result = + if thousandSep in s and s.count(floatSep) == 1: # 1,000,000.0 1.000,000 + parseFloat(s.replace($thousandSep, "")) + elif floatSep in s and s.count(thousandSep) == 1: # 1.000.000,000 + parseFloat(s.replace($floatSep, "").replace(thousandSep, floatSep)) + elif floatSep notin s and s.count(thousandSep) == 1: # 100,000 + parseFloat(s.replace(thousandSep, floatSep)) + else: parseFloat(s) # 1.0 + + when isMainModule: doAssert expandTabs("\t", 4) == " " doAssert expandTabs("\tfoo\t", 4) == " foo " From 0b79ec4421bebe4d7cd43011cf5ccb729f00211e Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Tue, 29 Sep 2020 19:15:17 -0300 Subject: [PATCH 02/64] https://github.com/nim-lang/Nim/pull/15421#issuecomment-700791178 --- lib/pure/strmisc.nim | 138 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 117 insertions(+), 21 deletions(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index 70f69c1e4216..90a1a966744d 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -86,39 +86,135 @@ proc rpartition*(s: string, sep: string): (string, string, string) return partition(s, sep, right = true) -func parseFloatThousandSep*(s: string; thousandSep = ','; floatSep = '.'): float {.since: (1, 3).} = - ## Convenience func for `parseFloat` but it can take 2 `char` separators, - ## one is **likely** the thousands separator, other is **likely** the floating point, - ## but if both separators are swapped it can still parse correctly, see the examples, + +template parseFloatThousandSepImpl(s: var string; sep: static char; decimalDot: static char): float = + assert sep notin {'-', ' '} and decimalDot notin {'-', ' '} and sep != decimalDot + + template bail(m: string) = + raise newException(ValueError, "Invalid float containing thousand separators, " & m) + + if likely(s.len > 1): # Allow "0" thats valid, is 0.0 + var idx, successive: int + var afterDot, lastWasDot, lastWasSep, hasAnySep, isNegative: bool + while idx < s.len: + case s[idx] + of '0' .. '9': # Digits + if hasAnySep and successive > 2: + bail("more than 3 digits between thousand separators.") + else: + lastWasSep = false + lastWasDot = false + inc successive + inc idx + of sep: # Thousands separator + if idx == 0: + bail("string starts with thousand separator.") + elif lastWasSep: + bail("two separators in a row.") + elif afterDot: + bail("separator found after decimal dot.") + else: + s.delete(idx, idx) + lastWasSep = true + hasAnySep = true + successive = 0 + of decimalDot: + if idx == 0: # Wont allow .1 + bail("string starts with decimal dot.") + elif hasAnySep and successive != 3: + bail("not 3 successive digits before decimal point, despite larger 1000.") + else: + when decimalDot != '.': + s[idx] = '.' # Replace decimalDot to '.' so parseFloat can take it. + successive = 0 + lastWasDot = true + afterDot = true + inc idx + of '-': # Allow negative float + if unlikely(isNegative): # Wont allow ---1.0 + bail("string must not contain more than 1 '-' character.") + else: + isNegative = true + inc idx + else: + bail("invalid character in float: " & $s[idx]) + parseFloat(s) + + +func parseFloatThousandSep*(s: string; sep: static char = ','; decimalDot: static char = '.'): float {.since: (1, 3).} = + ## Convenience func for `parseFloat` which allows for thousand separators, ## this is designed to parse floats as found in the wild formatted for humans. ## + ## The following assumptions and requirements must be met: + ## - String must not be empty. + ## - String must be stripped of trailing and leading whitespaces. + ## - `sep` must not be `'-'` nor `' '`. + ## - `decimalDot` must not be `'-'` nor `' '`. + ## - `sep` and `decimalDot` must be different. + ## - No separator before a digit. + ## - First separator can be anywhere after first digit, but no more than 3 characters. + ## - There has to be 3 digits between successive separators. + ## - There has to be 3 digits between the last separator and the decimal dot. + ## - No separator after decimal dot. + ## - No duplicate separators. + ## - Floats without separator allowed. + ## ## See also: ## * `strutils `_ runnableExamples: doAssert parseFloatThousandSep("0") == 0.0 + doAssert parseFloatThousandSep("-0") == -0.0 doAssert parseFloatThousandSep("0.0") == 0.0 doAssert parseFloatThousandSep("1.0") == 1.0 + doAssert parseFloatThousandSep("-0.0") == -0.0 doAssert parseFloatThousandSep("-1.0") == -1.0 doAssert parseFloatThousandSep("1.000") == 1.0 - doAssert parseFloatThousandSep("1,000") == 1.0 + doAssert parseFloatThousandSep("-1.000") == -1.0 + doAssert parseFloatThousandSep("1,000") == 1000.0 + doAssert parseFloatThousandSep("-1,000") == -1000.0 doAssert parseFloatThousandSep("10,000.000") == 10000.0 - doAssert parseFloatThousandSep("10.000,000") == 10.0 doAssert parseFloatThousandSep("1,000,000.000") == 1000000.0 - doAssert parseFloatThousandSep("1.000.000,000") == 1000000.0 - doAssert parseFloatThousandSep("0000000000000001.00,0,000,00000,,00") == 1.0 - doAssert parseFloatThousandSep("0000000000000001,00.0.000.00000..00") == 1.0 - doAssert parseFloatThousandSep("000,1.000,,,,,,,,,,,,,,000,,,,,0000") == 1.0 - doAssert parseFloatThousandSep("000.1,000..............000.....0000") == 1.0 - doAssert parseFloatThousandSep("1'000'000.000", thousandSep = '\'') == 1000000.0 - doAssert parseFloatThousandSep("1_000_000.000", thousandSep = '_') == 1000000.0 - result = - if thousandSep in s and s.count(floatSep) == 1: # 1,000,000.0 1.000,000 - parseFloat(s.replace($thousandSep, "")) - elif floatSep in s and s.count(thousandSep) == 1: # 1.000.000,000 - parseFloat(s.replace($floatSep, "").replace(thousandSep, floatSep)) - elif floatSep notin s and s.count(thousandSep) == 1: # 100,000 - parseFloat(s.replace(thousandSep, floatSep)) - else: parseFloat(s) # 1.0 + doAssert parseFloatThousandSep("10,000,000.000") == 10000000.0 + doAssert parseFloatThousandSep("10.000,0", '.', ',') == 10000.0 + doAssert parseFloatThousandSep("1'000'000,000", '\'', ',') == 1000000.0 + var copiedString = s + parseFloatThousandSepImpl(copiedString, sep, decimalDot) + + +func parseFloatThousandSep*(s: var string; sep: static char = ','; decimalDot: static char = '.'): float {.since: (1, 3).} = + ## In-place version of `parseFloatThousandSep`, does not copy the string internally. + runnableExamples: + var a = "0" + doAssert parseFloatThousandSep(a) == 0.0 + var b = "-0" + doAssert parseFloatThousandSep(b) == -0.0 + var c = "0.0" + doAssert parseFloatThousandSep(c) == 0.0 + var d = "1.0" + doAssert parseFloatThousandSep(d) == 1.0 + var e = "-0.0" + doAssert parseFloatThousandSep(e) == -0.0 + var f = "-1.0" + doAssert parseFloatThousandSep(f) == -1.0 + var g = "1.000" + doAssert parseFloatThousandSep(g) == 1.0 + var h = "-1.000" + doAssert parseFloatThousandSep(h) == -1.0 + var i = "1,000" + doAssert parseFloatThousandSep(i) == 1000.0 + var j = "-1,000" + doAssert parseFloatThousandSep(j) == -1000.0 + var k = "10,000.000" + doAssert parseFloatThousandSep(k) == 10000.0 + var l = "1,000,000.000" + doAssert parseFloatThousandSep(l) == 1000000.0 + var m = "10,000,000.000" + doAssert parseFloatThousandSep(m) == 10000000.0 + var n = "10.000,0" + doAssert parseFloatThousandSep(n, '.', ',') == 10000.0 + var o = "1'000'000,000" + doAssert parseFloatThousandSep(o, '\'', ',') == 1000000.0 + parseFloatThousandSepImpl(s, sep, decimalDot) when isMainModule: From 53c3e182800e16b907d5ae6769a1cc9aa705fca8 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Tue, 29 Sep 2020 20:13:48 -0300 Subject: [PATCH 03/64] https://github.com/nim-lang/Nim/pull/15421#issuecomment-700791178 --- lib/pure/strmisc.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index 90a1a966744d..4f1720d06bca 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -107,7 +107,7 @@ template parseFloatThousandSepImpl(s: var string; sep: static char; decimalDot: inc successive inc idx of sep: # Thousands separator - if idx == 0: + if unlikely(isNegative and idx == 1 or idx == 0): bail("string starts with thousand separator.") elif lastWasSep: bail("two separators in a row.") @@ -119,7 +119,7 @@ template parseFloatThousandSepImpl(s: var string; sep: static char; decimalDot: hasAnySep = true successive = 0 of decimalDot: - if idx == 0: # Wont allow .1 + if unlikely(isNegative and idx == 1 or idx == 0): # Wont allow .1 bail("string starts with decimal dot.") elif hasAnySep and successive != 3: bail("not 3 successive digits before decimal point, despite larger 1000.") From b3ada57d203b35ed584b17b5aac55a8840d73459 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Tue, 29 Sep 2020 20:23:24 -0300 Subject: [PATCH 04/64] https://github.com/nim-lang/Nim/pull/15421#issuecomment-700791178 --- lib/pure/strmisc.nim | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index 4f1720d06bca..dbb654156443 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -133,6 +133,8 @@ template parseFloatThousandSepImpl(s: var string; sep: static char; decimalDot: of '-': # Allow negative float if unlikely(isNegative): # Wont allow ---1.0 bail("string must not contain more than 1 '-' character.") + elif unlikely(idx != 0): + bail("the '-' character can only be at the start of the string.") else: isNegative = true inc idx From d63112c0d9068a378f44e4f46568d7de538371dc Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Mon, 5 Oct 2020 14:10:58 -0300 Subject: [PATCH 05/64] feedback --- lib/pure/strmisc.nim | 147 +++++++++++++++++-------------------------- 1 file changed, 56 insertions(+), 91 deletions(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index dbb654156443..6a87b4ba2746 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -87,20 +87,53 @@ proc rpartition*(s: string, sep: string): (string, string, string) return partition(s, sep, right = true) -template parseFloatThousandSepImpl(s: var string; sep: static char; decimalDot: static char): float = +func parseFloatThousandSep*(str: string; sep: static char = ','; decimalDot: static char = '.'): float {.since: (1, 3).} = + ## Convenience func for `parseFloat` which allows for thousand separators, + ## this is designed to parse floats as found in the wild formatted for humans. + ## + ## The following assumptions and requirements must be met: + ## - String must not be empty. + ## - String must be stripped of trailing and leading whitespaces. + ## - `sep` must not be `'-'` nor `' '`. + ## - `decimalDot` must not be `'-'` nor `' '`. + ## - `sep` and `decimalDot` must be different. + ## - No separator before a digit. + ## - First separator can be anywhere after first digit, but no more than 3 characters. + ## - There has to be 3 digits between successive separators. + ## - There has to be 3 digits between the last separator and the decimal dot. + ## - No separator after decimal dot. + ## - No duplicate separators. + ## - Floats without separator allowed. + ## + ## See also: + ## * `strutils `_ + runnableExamples: + doAssert parseFloatThousandSep("0") == 0.0 + doAssert parseFloatThousandSep("-0") == -0.0 + doAssert parseFloatThousandSep("0.0") == 0.0 + doAssert parseFloatThousandSep("1.0") == 1.0 + doAssert parseFloatThousandSep("-0.0") == -0.0 + doAssert parseFloatThousandSep("-1.0") == -1.0 + doAssert parseFloatThousandSep("1.000") == 1.0 + doAssert parseFloatThousandSep("-1.000") == -1.0 + doAssert parseFloatThousandSep("1,000") == 1000.0 + doAssert parseFloatThousandSep("-1,000") == -1000.0 + doAssert parseFloatThousandSep("10,000.000") == 10000.0 + doAssert parseFloatThousandSep("1,000,000.000") == 1000000.0 + doAssert parseFloatThousandSep("10,000,000.000") == 10000000.0 + doAssert parseFloatThousandSep("10.000,0", '.', ',') == 10000.0 + doAssert parseFloatThousandSep("1'000'000,000", '\'', ',') == 1000000.0 assert sep notin {'-', ' '} and decimalDot notin {'-', ' '} and sep != decimalDot - - template bail(m: string) = - raise newException(ValueError, "Invalid float containing thousand separators, " & m) - - if likely(s.len > 1): # Allow "0" thats valid, is 0.0 + var s = str + if s.len > 1: # Allow "0" thats valid, is 0.0 var idx, successive: int var afterDot, lastWasDot, lastWasSep, hasAnySep, isNegative: bool while idx < s.len: case s[idx] of '0' .. '9': # Digits if hasAnySep and successive > 2: - bail("more than 3 digits between thousand separators.") + raise newException(ValueError, + "Invalid float containing thousand separators, more than 3 digits between thousand separators.") else: lastWasSep = false lastWasDot = false @@ -108,11 +141,14 @@ template parseFloatThousandSepImpl(s: var string; sep: static char; decimalDot: inc idx of sep: # Thousands separator if unlikely(isNegative and idx == 1 or idx == 0): - bail("string starts with thousand separator.") + raise newException(ValueError, + "Invalid float containing thousand separators, string starts with thousand separator.") elif lastWasSep: - bail("two separators in a row.") + raise newException(ValueError, + "Invalid float containing thousand separators, two separators in a row.") elif afterDot: - bail("separator found after decimal dot.") + raise newException(ValueError, + "Invalid float containing thousand separators, separator found after decimal dot.") else: s.delete(idx, idx) lastWasSep = true @@ -120,9 +156,11 @@ template parseFloatThousandSepImpl(s: var string; sep: static char; decimalDot: successive = 0 of decimalDot: if unlikely(isNegative and idx == 1 or idx == 0): # Wont allow .1 - bail("string starts with decimal dot.") + raise newException(ValueError, + "Invalid float containing thousand separators, string starts with decimal dot.") elif hasAnySep and successive != 3: - bail("not 3 successive digits before decimal point, despite larger 1000.") + raise newException(ValueError, + "Invalid float containing thousand separators, not 3 successive digits before decimal point, despite larger 1000.") else: when decimalDot != '.': s[idx] = '.' # Replace decimalDot to '.' so parseFloat can take it. @@ -132,93 +170,20 @@ template parseFloatThousandSepImpl(s: var string; sep: static char; decimalDot: inc idx of '-': # Allow negative float if unlikely(isNegative): # Wont allow ---1.0 - bail("string must not contain more than 1 '-' character.") + raise newException(ValueError, + "Invalid float containing thousand separators, string must not contain more than 1 '-' character.") elif unlikely(idx != 0): - bail("the '-' character can only be at the start of the string.") + raise newException(ValueError, + "Invalid float containing thousand separators, the '-' character can only be at the start of the string.") else: isNegative = true inc idx else: - bail("invalid character in float: " & $s[idx]) + raise newException(ValueError, + "Invalid float containing thousand separators, invalid character in float: " & $s[idx]) parseFloat(s) -func parseFloatThousandSep*(s: string; sep: static char = ','; decimalDot: static char = '.'): float {.since: (1, 3).} = - ## Convenience func for `parseFloat` which allows for thousand separators, - ## this is designed to parse floats as found in the wild formatted for humans. - ## - ## The following assumptions and requirements must be met: - ## - String must not be empty. - ## - String must be stripped of trailing and leading whitespaces. - ## - `sep` must not be `'-'` nor `' '`. - ## - `decimalDot` must not be `'-'` nor `' '`. - ## - `sep` and `decimalDot` must be different. - ## - No separator before a digit. - ## - First separator can be anywhere after first digit, but no more than 3 characters. - ## - There has to be 3 digits between successive separators. - ## - There has to be 3 digits between the last separator and the decimal dot. - ## - No separator after decimal dot. - ## - No duplicate separators. - ## - Floats without separator allowed. - ## - ## See also: - ## * `strutils `_ - runnableExamples: - doAssert parseFloatThousandSep("0") == 0.0 - doAssert parseFloatThousandSep("-0") == -0.0 - doAssert parseFloatThousandSep("0.0") == 0.0 - doAssert parseFloatThousandSep("1.0") == 1.0 - doAssert parseFloatThousandSep("-0.0") == -0.0 - doAssert parseFloatThousandSep("-1.0") == -1.0 - doAssert parseFloatThousandSep("1.000") == 1.0 - doAssert parseFloatThousandSep("-1.000") == -1.0 - doAssert parseFloatThousandSep("1,000") == 1000.0 - doAssert parseFloatThousandSep("-1,000") == -1000.0 - doAssert parseFloatThousandSep("10,000.000") == 10000.0 - doAssert parseFloatThousandSep("1,000,000.000") == 1000000.0 - doAssert parseFloatThousandSep("10,000,000.000") == 10000000.0 - doAssert parseFloatThousandSep("10.000,0", '.', ',') == 10000.0 - doAssert parseFloatThousandSep("1'000'000,000", '\'', ',') == 1000000.0 - var copiedString = s - parseFloatThousandSepImpl(copiedString, sep, decimalDot) - - -func parseFloatThousandSep*(s: var string; sep: static char = ','; decimalDot: static char = '.'): float {.since: (1, 3).} = - ## In-place version of `parseFloatThousandSep`, does not copy the string internally. - runnableExamples: - var a = "0" - doAssert parseFloatThousandSep(a) == 0.0 - var b = "-0" - doAssert parseFloatThousandSep(b) == -0.0 - var c = "0.0" - doAssert parseFloatThousandSep(c) == 0.0 - var d = "1.0" - doAssert parseFloatThousandSep(d) == 1.0 - var e = "-0.0" - doAssert parseFloatThousandSep(e) == -0.0 - var f = "-1.0" - doAssert parseFloatThousandSep(f) == -1.0 - var g = "1.000" - doAssert parseFloatThousandSep(g) == 1.0 - var h = "-1.000" - doAssert parseFloatThousandSep(h) == -1.0 - var i = "1,000" - doAssert parseFloatThousandSep(i) == 1000.0 - var j = "-1,000" - doAssert parseFloatThousandSep(j) == -1000.0 - var k = "10,000.000" - doAssert parseFloatThousandSep(k) == 10000.0 - var l = "1,000,000.000" - doAssert parseFloatThousandSep(l) == 1000000.0 - var m = "10,000,000.000" - doAssert parseFloatThousandSep(m) == 10000000.0 - var n = "10.000,0" - doAssert parseFloatThousandSep(n, '.', ',') == 10000.0 - var o = "1'000'000,000" - doAssert parseFloatThousandSep(o, '\'', ',') == 1000000.0 - parseFloatThousandSepImpl(s, sep, decimalDot) - - when isMainModule: doAssert expandTabs("\t", 4) == " " doAssert expandTabs("\tfoo\t", 4) == " foo " From 590702a9e936f9aac65d666f1c47de5918f92db7 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Mon, 5 Oct 2020 19:44:31 -0300 Subject: [PATCH 06/64] feedbacks --- lib/pure/strmisc.nim | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index 6a87b4ba2746..8aafa38c48f4 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -87,7 +87,7 @@ proc rpartition*(s: string, sep: string): (string, string, string) return partition(s, sep, right = true) -func parseFloatThousandSep*(str: string; sep: static char = ','; decimalDot: static char = '.'): float {.since: (1, 3).} = +func parseFloatThousandSep*(str: string; sep = ','; decimalDot = '.'): float {.since: (1, 3).} = ## Convenience func for `parseFloat` which allows for thousand separators, ## this is designed to parse floats as found in the wild formatted for humans. ## @@ -124,22 +124,22 @@ func parseFloatThousandSep*(str: string; sep: static char = ','; decimalDot: sta doAssert parseFloatThousandSep("10.000,0", '.', ',') == 10000.0 doAssert parseFloatThousandSep("1'000'000,000", '\'', ',') == 1000000.0 assert sep notin {'-', ' '} and decimalDot notin {'-', ' '} and sep != decimalDot - var s = str - if s.len > 1: # Allow "0" thats valid, is 0.0 + if str.len > 1: # Allow "0" thats valid, is 0.0 + var s = newStringOfCap(str.len) var idx, successive: int var afterDot, lastWasDot, lastWasSep, hasAnySep, isNegative: bool - while idx < s.len: - case s[idx] - of '0' .. '9': # Digits + for c in str: + if c in '0' .. '9': # Digits if hasAnySep and successive > 2: raise newException(ValueError, "Invalid float containing thousand separators, more than 3 digits between thousand separators.") else: + s.add c lastWasSep = false lastWasDot = false inc successive inc idx - of sep: # Thousands separator + if c == sep: # Thousands separator, this is NOT the dot if unlikely(isNegative and idx == 1 or idx == 0): raise newException(ValueError, "Invalid float containing thousand separators, string starts with thousand separator.") @@ -150,11 +150,10 @@ func parseFloatThousandSep*(str: string; sep: static char = ','; decimalDot: sta raise newException(ValueError, "Invalid float containing thousand separators, separator found after decimal dot.") else: - s.delete(idx, idx) - lastWasSep = true + lastWasSep = true # Do NOT add the Thousands separator here. hasAnySep = true successive = 0 - of decimalDot: + if c == decimalDot: # This is the dot if unlikely(isNegative and idx == 1 or idx == 0): # Wont allow .1 raise newException(ValueError, "Invalid float containing thousand separators, string starts with decimal dot.") @@ -162,13 +161,12 @@ func parseFloatThousandSep*(str: string; sep: static char = ','; decimalDot: sta raise newException(ValueError, "Invalid float containing thousand separators, not 3 successive digits before decimal point, despite larger 1000.") else: - when decimalDot != '.': - s[idx] = '.' # Replace decimalDot to '.' so parseFloat can take it. + s.add '.' # Replace decimalDot to '.' so parseFloat can take it. successive = 0 lastWasDot = true afterDot = true inc idx - of '-': # Allow negative float + if c == '-': # Allow negative float if unlikely(isNegative): # Wont allow ---1.0 raise newException(ValueError, "Invalid float containing thousand separators, string must not contain more than 1 '-' character.") @@ -176,12 +174,12 @@ func parseFloatThousandSep*(str: string; sep: static char = ','; decimalDot: sta raise newException(ValueError, "Invalid float containing thousand separators, the '-' character can only be at the start of the string.") else: + s.add '-' isNegative = true inc idx - else: - raise newException(ValueError, - "Invalid float containing thousand separators, invalid character in float: " & $s[idx]) - parseFloat(s) + result = parseFloat(s) + else: + result = parseFloat(str) when isMainModule: From af6821a9928316bc3aecad8699d0bea02f60e335 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Thu, 15 Oct 2020 09:56:30 -0300 Subject: [PATCH 07/64] https://github.com/nim-lang/Nim/pull/15421#discussion_r505507037 --- lib/pure/strmisc.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index 8aafa38c48f4..246185b24a97 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -94,7 +94,7 @@ func parseFloatThousandSep*(str: string; sep = ','; decimalDot = '.'): float {.s ## The following assumptions and requirements must be met: ## - String must not be empty. ## - String must be stripped of trailing and leading whitespaces. - ## - `sep` must not be `'-'` nor `' '`. + ## - `sep` must not be `'-'`. ## - `decimalDot` must not be `'-'` nor `' '`. ## - `sep` and `decimalDot` must be different. ## - No separator before a digit. @@ -123,7 +123,7 @@ func parseFloatThousandSep*(str: string; sep = ','; decimalDot = '.'): float {.s doAssert parseFloatThousandSep("10,000,000.000") == 10000000.0 doAssert parseFloatThousandSep("10.000,0", '.', ',') == 10000.0 doAssert parseFloatThousandSep("1'000'000,000", '\'', ',') == 1000000.0 - assert sep notin {'-', ' '} and decimalDot notin {'-', ' '} and sep != decimalDot + assert sep != '-' and decimalDot notin {'-', ' '} and sep != decimalDot if str.len > 1: # Allow "0" thats valid, is 0.0 var s = newStringOfCap(str.len) var idx, successive: int From af078eafed4c592f5a98b7bf9fb53fc10c6378f5 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Thu, 12 Nov 2020 19:52:49 -0300 Subject: [PATCH 08/64] https://github.com/nim-lang/Nim/pull/15421#discussion_r522453389 --- lib/pure/strmisc.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index 246185b24a97..0c6a73be1836 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -87,7 +87,7 @@ proc rpartition*(s: string, sep: string): (string, string, string) return partition(s, sep, right = true) -func parseFloatThousandSep*(str: string; sep = ','; decimalDot = '.'): float {.since: (1, 3).} = +func parseFloatThousandSep*(str: string; sep = ','; decimalDot = '.'): float {.since: (1, 5).} = ## Convenience func for `parseFloat` which allows for thousand separators, ## this is designed to parse floats as found in the wild formatted for humans. ## From 923663de2a88a9fc83bc14fb249938cf55ae0652 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Thu, 12 Nov 2020 19:55:01 -0300 Subject: [PATCH 09/64] https://github.com/nim-lang/Nim/pull/15421#discussion_r522453304 --- lib/pure/strutils.nim | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 0eff80863ac4..afe42c8f69a2 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -1150,6 +1150,9 @@ proc parseFloat*(s: string): float {.noSideEffect, ## ## If `s` is not a valid floating point number, `ValueError` is raised. ##``NAN``, ``INF``, ``-INF`` are also supported (case insensitive comparison). + ## + ## See also: + ## * `strmisc `_ runnableExamples: doAssert parseFloat("3.14") == 3.14 doAssert parseFloat("inf") == 1.0/0 From 32d68e95fc23794a192d93a0a4ceb097ed1e5061 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Thu, 12 Nov 2020 19:57:51 -0300 Subject: [PATCH 10/64] Remove unlikely --- lib/pure/strmisc.nim | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index 0c6a73be1836..bbeee31c1728 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -140,7 +140,7 @@ func parseFloatThousandSep*(str: string; sep = ','; decimalDot = '.'): float {.s inc successive inc idx if c == sep: # Thousands separator, this is NOT the dot - if unlikely(isNegative and idx == 1 or idx == 0): + if isNegative and idx == 1 or idx == 0: raise newException(ValueError, "Invalid float containing thousand separators, string starts with thousand separator.") elif lastWasSep: @@ -154,7 +154,7 @@ func parseFloatThousandSep*(str: string; sep = ','; decimalDot = '.'): float {.s hasAnySep = true successive = 0 if c == decimalDot: # This is the dot - if unlikely(isNegative and idx == 1 or idx == 0): # Wont allow .1 + if isNegative and idx == 1 or idx == 0: # Wont allow .1 raise newException(ValueError, "Invalid float containing thousand separators, string starts with decimal dot.") elif hasAnySep and successive != 3: @@ -167,10 +167,10 @@ func parseFloatThousandSep*(str: string; sep = ','; decimalDot = '.'): float {.s afterDot = true inc idx if c == '-': # Allow negative float - if unlikely(isNegative): # Wont allow ---1.0 + if isNegative: # Wont allow ---1.0 raise newException(ValueError, "Invalid float containing thousand separators, string must not contain more than 1 '-' character.") - elif unlikely(idx != 0): + elif idx != 0: raise newException(ValueError, "Invalid float containing thousand separators, the '-' character can only be at the start of the string.") else: From d2bb63f7bdda0f1767e02b551ebd4b331ebbea90 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Thu, 12 Nov 2020 20:02:05 -0300 Subject: [PATCH 11/64] Fix Changelog conflicts --- changelog.md | 149 ++------------------------------------------------- 1 file changed, 3 insertions(+), 146 deletions(-) diff --git a/changelog.md b/changelog.md index 7e94b1b3ba00..c029d482e4e9 100644 --- a/changelog.md +++ b/changelog.md @@ -31,156 +31,13 @@ - Removed deprecated `iup` module from stdlib, it has already moved to [nimble](https://github.com/nim-lang/iup). - The following modules now compile on both JS and NimScript: `parsecsv`, - `parsecfg`, `parsesql`, `xmlparser`, `htmlparser` and `ropes`. Additionally - supported for JS is `cstrutils.startsWith` and `cstrutils.endsWith`, for - NimScript: `json`, `parsejson`, `strtabs` and `unidecode`. - -- Added `streams.readStr` and `streams.peekStr` overloads to - accept an existing string to modify, which avoids memory - allocations, similar to `streams.readLine` (#13857). - -- Added high-level `asyncnet.sendTo` and `asyncnet.recvFrom` UDP functionality. - -- `dollars.$` now works for unsigned ints with `nim js` - -- Improvements to the `bitops` module, including bitslices, non-mutating versions - of the original masking functions, `mask`/`masked`, and varargs support for - `bitand`, `bitor`, and `bitxor`. - -- `sugar.=>` and `sugar.->` changes: Previously `(x, y: int)` was transformed - into `(x: auto, y: int)`, it now becomes `(x: int, y: int)` in consistency - with regular proc definitions (although you cannot use semicolons). - - Pragmas and using a name are now allowed on the lefthand side of `=>`. Here - is an aggregate example of these changes: - ```nim - import sugar - - foo(x, y: int) {.noSideEffect.} => x + y - - # is transformed into - - proc foo(x: int, y: int): auto {.noSideEffect.} = x + y - ``` - -- The fields of `times.DateTime` are now private, and are accessed with getters and deprecated setters. - -- The `times` module now handles the default value for `DateTime` more consistently. - Most procs raise an assertion error when given - an uninitialized `DateTime`, the exceptions are `==` and `$` (which returns `"Uninitialized DateTime"`). - The proc `times.isInitialized` has been added which can be used to check if - a `DateTime` has been initialized. - -- Fix a bug where calling `close` on io streams in osproc.startProcess was a noop and led to - hangs if a process had both reads from stdin and writes (eg to stdout). - -- The callback that is passed to `system.onThreadDestruction` must now be `.raises: []`. -- The callback that is assigned to `system.onUnhandledException` must now be `.gcsafe`. - -- `osproc.execCmdEx` now takes an optional `input` for stdin, `workingDir` and `env` - parameters. - -- Added a `ssl_config` module containing lists of secure ciphers as recommended by - [Mozilla OpSec](https://wiki.mozilla.org/Security/Server_Side_TLS) - -- `net.newContext` now defaults to the list of ciphers targeting - ["Intermediate compatibility"](https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28recommended.29) - per Mozilla's recommendation instead of `ALL`. This change should protect - users from the use of weak and insecure ciphers while still provides - adequate compatibility with the majority of the Internet. - -- A new module `std/jsonutils` with hookable `jsonTo,toJson,fromJson` operations for json - serialization/deserialization of custom types was added. - -- A new proc `heapqueue.find[T](heap: HeapQueue[T], x: T): int` to get index of element ``x`` - was added. -- Added `rstgen.rstToLatex` convenience proc for `renderRstToOut` and `initRstGenerator` - with `outLatex` output. -- Added `os.normalizeExe`, e.g.: `koch` => `./koch`. -- `macros.newLit` now preserves named vs unnamed tuples; use `-d:nimHasWorkaround14720` - to keep old behavior. -- Added `random.gauss`, that uses the ratio of uniforms method of sampling from a Gaussian distribution. -- Added `typetraits.elementType` to get element type of an iterable. -- `typetraits.$` changes: `$(int,)` is now `"(int,)"` instead of `"(int)"`; - `$tuple[]` is now `"tuple[]"` instead of `"tuple"`; - `$((int, float), int)` is now `"((int, float), int)"` instead of `"(tuple of (int, float), int)"` -- Added `macros.extractDocCommentsAndRunnables` helper - -- `strformat.fmt` and `strformat.&` support `= specifier`. `fmt"{expr=}"` now - expands to `fmt"expr={expr}"`. -- deprecations: `os.existsDir` => `dirExists`, `os.existsFile` => `fileExists` - -- Added `jsre` module, [Regular Expressions for the JavaScript target.](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions) -- Made `maxLines` argument `Positive` in `logging.newRollingFileLogger`, - because negative values will result in a new file being created for each logged - line which doesn't make sense. -- Changed `log` in `logging` to use proper log level on JavaScript target, - e.g. `debug` uses `console.debug`, `info` uses `console.info`, `warn` uses `console.warn`, etc. -- Tables, HashSets, SharedTables and deques don't require anymore that the passed - initial size must be a power of two - this is done internally. - Proc `rightSize` for Tables and HashSets is deprecated, as it is not needed anymore. - `CountTable.inc` takes `val: int` again not `val: Positive`; I.e. it can "count down" again. -- Removed deprecated symbols from `macros` module, deprecated as far back as `0.15`. -- Removed `sugar.distinctBase`, deprecated since `0.19`. -- Export `asyncdispatch.PDispatcher.handles` so that an external library can register them. - -- `std/with`, `sugar.dup` now support object field assignment expression: - ```nim - import std/with - - type Foo = object - x, y: int - - var foo = Foo() - with foo: - x = 10 - y = 20 - - echo foo - ``` - -- Proc `math.round` is no longer deprecated. The advice to use `strformat` instead - cannot be applied to every use case. The limitations and the (lack of) reliability - of `round` are well documented. - -- Added `getprotobyname` to `winlean`. Added `getProtoByname` to `nativesockets` which returns a protocol code - from the database that matches the protocol `name`. - -- Added missing attributes and methods to `dom.Navigator` like `deviceMemory`, `onLine`, `vibrate()`, etc. - -- Added `strutils.indentation` and `strutils.dedent` which enable indented string literals: - ```nim - import strutils - echo dedent """ - This - is - cool! - """ - ``` - -- Add `initUri(isIpv6: bool)` to `uri` module, now `uri` supports parsing ipv6 hostname. -- Add `strmisc.parseFloatThousandSep` designed to parse floats as found in the wild formatted for humans. - -- Added `initUri(isIpv6: bool)` to `uri` module, now `uri` supports parsing ipv6 hostname. - -- Added `readLines(p: Process)` to `osproc` module for `startProcess` convenience. - -- Added the below `to` procs for collections. The usage is similar to procs such as - `sets.toHashSet` and `tables.toTable`. Previously, it was necessary to create the - respective empty collection and add items manually. - * `critbits.toCritBitTree`, which creates a `CritBitTree` from an `openArray` of - items or an `openArray` of pairs. - * `deques.toDeque`, which creates a `Deque` from an `openArray`. - * `heapqueue.toHeapQueue`, which creates a `HeapQueue` from an `openArray`. - * `intsets.toIntSet`, which creates an `IntSet` from an `openArray`. - -- Added `progressInterval` argument to `asyncftpclient.newAsyncFtpClient` to control the interval - at which progress callbacks are called. - nodejs now supports osenv: `getEnv`, `putEnv`, `envPairs`, `delEnv`, `existsEnv` - `doAssertRaises` now correctly handles foreign exceptions. +- Add `strmisc.parseFloatThousandSep` designed to parse floats as found in the wild formatted for humans. + + ## Language changes - `nimscript` now handles `except Exception as e` From 6547469d921798c11e921c60d47fbc2181ea1773 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Thu, 12 Nov 2020 20:11:16 -0300 Subject: [PATCH 12/64] https://github.com/nim-lang/Nim/pull/15421#discussion_r522454607 --- lib/pure/strmisc.nim | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index bbeee31c1728..a6f49a9c3a4a 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -132,7 +132,8 @@ func parseFloatThousandSep*(str: string; sep = ','; decimalDot = '.'): float {.s if c in '0' .. '9': # Digits if hasAnySep and successive > 2: raise newException(ValueError, - "Invalid float containing thousand separators, more than 3 digits between thousand separators.") + "Invalid float containing thousand separators, more than 3 digits between thousand separators '" & + c & "' at index " & $idx) else: s.add c lastWasSep = false @@ -142,13 +143,16 @@ func parseFloatThousandSep*(str: string; sep = ','; decimalDot = '.'): float {.s if c == sep: # Thousands separator, this is NOT the dot if isNegative and idx == 1 or idx == 0: raise newException(ValueError, - "Invalid float containing thousand separators, string starts with thousand separator.") + "Invalid float containing thousand separators, string starts with thousand separator '" & + c & "' at index " & $idx) elif lastWasSep: raise newException(ValueError, - "Invalid float containing thousand separators, two separators in a row.") + "Invalid float containing thousand separators, two separators in a row '" & + c & "' at index " & $idx) elif afterDot: raise newException(ValueError, - "Invalid float containing thousand separators, separator found after decimal dot.") + "Invalid float containing thousand separators, separator found after decimal dot '" & + c & "' at index " & $idx) else: lastWasSep = true # Do NOT add the Thousands separator here. hasAnySep = true @@ -156,10 +160,12 @@ func parseFloatThousandSep*(str: string; sep = ','; decimalDot = '.'): float {.s if c == decimalDot: # This is the dot if isNegative and idx == 1 or idx == 0: # Wont allow .1 raise newException(ValueError, - "Invalid float containing thousand separators, string starts with decimal dot.") + "Invalid float containing thousand separators, string starts with decimal dot '" & + c & "' at index " & $idx) elif hasAnySep and successive != 3: raise newException(ValueError, - "Invalid float containing thousand separators, not 3 successive digits before decimal point, despite larger 1000.") + "Invalid float containing thousand separators, not 3 successive digits before decimal point, despite larger 1000 '" & + c & "' at index " & $idx) else: s.add '.' # Replace decimalDot to '.' so parseFloat can take it. successive = 0 @@ -169,10 +175,12 @@ func parseFloatThousandSep*(str: string; sep = ','; decimalDot = '.'): float {.s if c == '-': # Allow negative float if isNegative: # Wont allow ---1.0 raise newException(ValueError, - "Invalid float containing thousand separators, string must not contain more than 1 '-' character.") + "Invalid float containing thousand separators, string must not contain more than 1 '-' character '" & + c & "' at index " & $idx) elif idx != 0: raise newException(ValueError, - "Invalid float containing thousand separators, the '-' character can only be at the start of the string.") + "Invalid float containing thousand separators, the '-' character can only be at the start of the string '" & + c & "' at index " & $idx) else: s.add '-' isNegative = true From bf88002ba924a0abd9141b13c8d744e093de63ce Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Thu, 12 Nov 2020 21:09:33 -0300 Subject: [PATCH 13/64] Delete half runnableExamples --- lib/pure/strmisc.nim | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index a6f49a9c3a4a..d527524e2e4c 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -108,14 +108,6 @@ func parseFloatThousandSep*(str: string; sep = ','; decimalDot = '.'): float {.s ## See also: ## * `strutils `_ runnableExamples: - doAssert parseFloatThousandSep("0") == 0.0 - doAssert parseFloatThousandSep("-0") == -0.0 - doAssert parseFloatThousandSep("0.0") == 0.0 - doAssert parseFloatThousandSep("1.0") == 1.0 - doAssert parseFloatThousandSep("-0.0") == -0.0 - doAssert parseFloatThousandSep("-1.0") == -1.0 - doAssert parseFloatThousandSep("1.000") == 1.0 - doAssert parseFloatThousandSep("-1.000") == -1.0 doAssert parseFloatThousandSep("1,000") == 1000.0 doAssert parseFloatThousandSep("-1,000") == -1000.0 doAssert parseFloatThousandSep("10,000.000") == 10000.0 From 2d2f558171440754db3d4b636b31aa6830af505a Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Thu, 12 Nov 2020 21:12:35 -0300 Subject: [PATCH 14/64] Move half runnableExamples to testament --- tests/stdlib/tstrmisc.nim | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 tests/stdlib/tstrmisc.nim diff --git a/tests/stdlib/tstrmisc.nim b/tests/stdlib/tstrmisc.nim new file mode 100644 index 000000000000..43e373128cc9 --- /dev/null +++ b/tests/stdlib/tstrmisc.nim @@ -0,0 +1,20 @@ +discard """ + action: "run" +""" + +import strmisc + + +func main() = + doAssert parseFloatThousandSep("0") == 0.0 + doAssert parseFloatThousandSep("-0") == -0.0 + doAssert parseFloatThousandSep("0.0") == 0.0 + doAssert parseFloatThousandSep("1.0") == 1.0 + doAssert parseFloatThousandSep("-0.0") == -0.0 + doAssert parseFloatThousandSep("-1.0") == -1.0 + doAssert parseFloatThousandSep("1.000") == 1.0 + doAssert parseFloatThousandSep("-1.000") == -1.0 + + +main() +static: main() From a02f7e17c4b3250ec80fb8edf6a5f741bc53daf7 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Thu, 12 Nov 2020 21:33:17 -0300 Subject: [PATCH 15/64] clean out --- tests/stdlib/tstrmisc.nim | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/stdlib/tstrmisc.nim b/tests/stdlib/tstrmisc.nim index 43e373128cc9..1307fea7b88d 100644 --- a/tests/stdlib/tstrmisc.nim +++ b/tests/stdlib/tstrmisc.nim @@ -1,7 +1,3 @@ -discard """ - action: "run" -""" - import strmisc From 3901c56ff38b0c397693dbe7c8916dd801d99dd6 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Thu, 12 Nov 2020 21:51:26 -0300 Subject: [PATCH 16/64] https://github.com/nim-lang/Nim/pull/15421#discussion_r522517532 --- lib/pure/strmisc.nim | 43 ++++++++++++------------------------------- 1 file changed, 12 insertions(+), 31 deletions(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index d527524e2e4c..cc80b402dd93 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -116,6 +116,11 @@ func parseFloatThousandSep*(str: string; sep = ','; decimalDot = '.'): float {.s doAssert parseFloatThousandSep("10.000,0", '.', ',') == 10000.0 doAssert parseFloatThousandSep("1'000'000,000", '\'', ',') == 1000000.0 assert sep != '-' and decimalDot notin {'-', ' '} and sep != decimalDot + + proc raiseError(i: int, c: char) {.noinline, noreturn.} = + raise newException(ValueError, + "Invalid float containing thousand separators, invalid char at position " & $i & " for input " & c) + if str.len > 1: # Allow "0" thats valid, is 0.0 var s = newStringOfCap(str.len) var idx, successive: int @@ -123,9 +128,7 @@ func parseFloatThousandSep*(str: string; sep = ','; decimalDot = '.'): float {.s for c in str: if c in '0' .. '9': # Digits if hasAnySep and successive > 2: - raise newException(ValueError, - "Invalid float containing thousand separators, more than 3 digits between thousand separators '" & - c & "' at index " & $idx) + raiseError(idx, c) else: s.add c lastWasSep = false @@ -133,31 +136,15 @@ func parseFloatThousandSep*(str: string; sep = ','; decimalDot = '.'): float {.s inc successive inc idx if c == sep: # Thousands separator, this is NOT the dot - if isNegative and idx == 1 or idx == 0: - raise newException(ValueError, - "Invalid float containing thousand separators, string starts with thousand separator '" & - c & "' at index " & $idx) - elif lastWasSep: - raise newException(ValueError, - "Invalid float containing thousand separators, two separators in a row '" & - c & "' at index " & $idx) - elif afterDot: - raise newException(ValueError, - "Invalid float containing thousand separators, separator found after decimal dot '" & - c & "' at index " & $idx) + if lastWasSep or afterDot or (isNegative and idx == 1 or idx == 0): + raiseError(idx, c) else: lastWasSep = true # Do NOT add the Thousands separator here. hasAnySep = true successive = 0 if c == decimalDot: # This is the dot - if isNegative and idx == 1 or idx == 0: # Wont allow .1 - raise newException(ValueError, - "Invalid float containing thousand separators, string starts with decimal dot '" & - c & "' at index " & $idx) - elif hasAnySep and successive != 3: - raise newException(ValueError, - "Invalid float containing thousand separators, not 3 successive digits before decimal point, despite larger 1000 '" & - c & "' at index " & $idx) + if (isNegative and idx == 1 or idx == 0) or (hasAnySep and successive != 3): # Wont allow .1 + raiseError(idx, c) else: s.add '.' # Replace decimalDot to '.' so parseFloat can take it. successive = 0 @@ -165,14 +152,8 @@ func parseFloatThousandSep*(str: string; sep = ','; decimalDot = '.'): float {.s afterDot = true inc idx if c == '-': # Allow negative float - if isNegative: # Wont allow ---1.0 - raise newException(ValueError, - "Invalid float containing thousand separators, string must not contain more than 1 '-' character '" & - c & "' at index " & $idx) - elif idx != 0: - raise newException(ValueError, - "Invalid float containing thousand separators, the '-' character can only be at the start of the string '" & - c & "' at index " & $idx) + if isNegative or idx != 0: # Wont allow ---1.0 + raiseError(idx, c) else: s.add '-' isNegative = true From 4c58bdd67970a08a5c23f0ed91536bce32eed624 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Thu, 12 Nov 2020 22:03:36 -0300 Subject: [PATCH 17/64] Add raise test --- tests/stdlib/tstrmisc.nim | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/stdlib/tstrmisc.nim b/tests/stdlib/tstrmisc.nim index 1307fea7b88d..8ba0d14155d2 100644 --- a/tests/stdlib/tstrmisc.nim +++ b/tests/stdlib/tstrmisc.nim @@ -10,6 +10,12 @@ func main() = doAssert parseFloatThousandSep("-1.0") == -1.0 doAssert parseFloatThousandSep("1.000") == 1.0 doAssert parseFloatThousandSep("-1.000") == -1.0 + doAssertRaises(ValueError): + discard parseFloatThousandSep("1,0000.000") + discard parseFloatThousandSep("--") + discard parseFloatThousandSep("..") + discard parseFloatThousandSep("000,1.000,,,,,,,,,,,,,,000,,,,,0000") + discard parseFloatThousandSep("000.1,000..............000.....0000") main() From e19a15c61545edef730a61ff29249acccfa0b85b Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Thu, 12 Nov 2020 22:12:11 -0300 Subject: [PATCH 18/64] See also to func not to module --- lib/pure/strutils.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index afe42c8f69a2..289a3d69078b 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -1152,7 +1152,7 @@ proc parseFloat*(s: string): float {.noSideEffect, ##``NAN``, ``INF``, ``-INF`` are also supported (case insensitive comparison). ## ## See also: - ## * `strmisc `_ + ## * `parseFloatThousandSep `_ runnableExamples: doAssert parseFloat("3.14") == 3.14 doAssert parseFloat("inf") == 1.0/0 From b8310c3e32c26c48198c64a49d8da2efae324c28 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Thu, 12 Nov 2020 22:14:56 -0300 Subject: [PATCH 19/64] See also to func not to module --- lib/pure/strmisc.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index cc80b402dd93..935be29538a8 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -106,7 +106,7 @@ func parseFloatThousandSep*(str: string; sep = ','; decimalDot = '.'): float {.s ## - Floats without separator allowed. ## ## See also: - ## * `strutils `_ + ## * `parseFloat `_ runnableExamples: doAssert parseFloatThousandSep("1,000") == 1000.0 doAssert parseFloatThousandSep("-1,000") == -1000.0 From 48828f5008f909495cc9b96ade6b3e9df7d1d9c1 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Thu, 12 Nov 2020 22:50:56 -0300 Subject: [PATCH 20/64] Improve a test --- tests/stdlib/tstrmisc.nim | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/stdlib/tstrmisc.nim b/tests/stdlib/tstrmisc.nim index 8ba0d14155d2..eaac8b42ec9b 100644 --- a/tests/stdlib/tstrmisc.nim +++ b/tests/stdlib/tstrmisc.nim @@ -10,12 +10,11 @@ func main() = doAssert parseFloatThousandSep("-1.0") == -1.0 doAssert parseFloatThousandSep("1.000") == 1.0 doAssert parseFloatThousandSep("-1.000") == -1.0 - doAssertRaises(ValueError): - discard parseFloatThousandSep("1,0000.000") - discard parseFloatThousandSep("--") - discard parseFloatThousandSep("..") - discard parseFloatThousandSep("000,1.000,,,,,,,,,,,,,,000,,,,,0000") - discard parseFloatThousandSep("000.1,000..............000.....0000") + doAssertRaises(ValueError): discard parseFloatThousandSep("1,0000.000") + doAssertRaises(ValueError): discard parseFloatThousandSep("--") + doAssertRaises(ValueError): discard parseFloatThousandSep("..") + doAssertRaises(ValueError): discard parseFloatThousandSep("000,1.000,,,,,,,,,,,,,,000,,,,,0000") + doAssertRaises(ValueError): discard parseFloatThousandSep("000.1,000..............000.....0000") main() From a98907bd17d1fc2c73480f189fe1901f54b88680 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Thu, 12 Nov 2020 23:06:58 -0300 Subject: [PATCH 21/64] raise proc --- lib/pure/strmisc.nim | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index 935be29538a8..6c457cc525bd 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -117,9 +117,10 @@ func parseFloatThousandSep*(str: string; sep = ','; decimalDot = '.'): float {.s doAssert parseFloatThousandSep("1'000'000,000", '\'', ',') == 1000000.0 assert sep != '-' and decimalDot notin {'-', ' '} and sep != decimalDot - proc raiseError(i: int, c: char) {.noinline, noreturn.} = + proc raiseError(i: int; c: char) {.noinline, noreturn.} = raise newException(ValueError, - "Invalid float containing thousand separators, invalid char at position " & $i & " for input " & c) + "Invalid float containing thousand separators, invalid char '" & + c & "' at index " & $i & " for input " & str) if str.len > 1: # Allow "0" thats valid, is 0.0 var s = newStringOfCap(str.len) From e1e43fc1cda99b3222011a7d1b5075f78a5929ef Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Thu, 12 Nov 2020 23:38:28 -0300 Subject: [PATCH 22/64] improve the improvements --- tests/stdlib/tstrmisc.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/stdlib/tstrmisc.nim b/tests/stdlib/tstrmisc.nim index eaac8b42ec9b..327f74b077f0 100644 --- a/tests/stdlib/tstrmisc.nim +++ b/tests/stdlib/tstrmisc.nim @@ -13,8 +13,8 @@ func main() = doAssertRaises(ValueError): discard parseFloatThousandSep("1,0000.000") doAssertRaises(ValueError): discard parseFloatThousandSep("--") doAssertRaises(ValueError): discard parseFloatThousandSep("..") - doAssertRaises(ValueError): discard parseFloatThousandSep("000,1.000,,,,,,,,,,,,,,000,,,,,0000") - doAssertRaises(ValueError): discard parseFloatThousandSep("000.1,000..............000.....0000") + doAssertRaises(ValueError): discard parseFloatThousandSep("1,,000") + doAssertRaises(ValueError): discard parseFloatThousandSep("1..000") main() From ce8c59103be852621f7b55c550e39310f051caca Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Thu, 12 Nov 2020 23:38:34 -0300 Subject: [PATCH 23/64] improve the improvements --- lib/pure/strmisc.nim | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index 6c457cc525bd..e796f69875ca 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -97,13 +97,7 @@ func parseFloatThousandSep*(str: string; sep = ','; decimalDot = '.'): float {.s ## - `sep` must not be `'-'`. ## - `decimalDot` must not be `'-'` nor `' '`. ## - `sep` and `decimalDot` must be different. - ## - No separator before a digit. - ## - First separator can be anywhere after first digit, but no more than 3 characters. - ## - There has to be 3 digits between successive separators. - ## - There has to be 3 digits between the last separator and the decimal dot. - ## - No separator after decimal dot. - ## - No duplicate separators. - ## - Floats without separator allowed. + ## - Exactly 1 separator must appear after each 3 consecutive inner digits to the left of the dot and nowhere else. ## ## See also: ## * `parseFloat `_ @@ -122,7 +116,7 @@ func parseFloatThousandSep*(str: string; sep = ','; decimalDot = '.'): float {.s "Invalid float containing thousand separators, invalid char '" & c & "' at index " & $i & " for input " & str) - if str.len > 1: # Allow "0" thats valid, is 0.0 + if str.len > 1: # Allow "0" which is valid, equal to 0.0 var s = newStringOfCap(str.len) var idx, successive: int var afterDot, lastWasDot, lastWasSep, hasAnySep, isNegative: bool @@ -144,7 +138,7 @@ func parseFloatThousandSep*(str: string; sep = ','; decimalDot = '.'): float {.s hasAnySep = true successive = 0 if c == decimalDot: # This is the dot - if (isNegative and idx == 1 or idx == 0) or (hasAnySep and successive != 3): # Wont allow .1 + if (isNegative and idx == 1 or idx == 0) or (hasAnySep and successive != 3): # Disallow .1 raiseError(idx, c) else: s.add '.' # Replace decimalDot to '.' so parseFloat can take it. From 842532d1f2a5ef2f2618bc32e01bb84a651843c8 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Thu, 12 Nov 2020 23:47:40 -0300 Subject: [PATCH 24/64] improve the improvements --- lib/pure/strmisc.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index e796f69875ca..55c41f10f475 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -113,8 +113,8 @@ func parseFloatThousandSep*(str: string; sep = ','; decimalDot = '.'): float {.s proc raiseError(i: int; c: char) {.noinline, noreturn.} = raise newException(ValueError, - "Invalid float containing thousand separators, invalid char '" & - c & "' at index " & $i & " for input " & str) + "Invalid float containing thousand separators, invalid char '$1' at index $2 for input $3" % + [$c, $i, str]) if str.len > 1: # Allow "0" which is valid, equal to 0.0 var s = newStringOfCap(str.len) From 0d2ac4db31b5d50dcd1bf0070052f40e54d64076 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Thu, 12 Nov 2020 23:58:03 -0300 Subject: [PATCH 25/64] the noinline thingy --- lib/pure/strmisc.nim | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index 55c41f10f475..c2908258c03c 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -111,10 +111,10 @@ func parseFloatThousandSep*(str: string; sep = ','; decimalDot = '.'): float {.s doAssert parseFloatThousandSep("1'000'000,000", '\'', ',') == 1000000.0 assert sep != '-' and decimalDot notin {'-', ' '} and sep != decimalDot - proc raiseError(i: int; c: char) {.noinline, noreturn.} = + proc raiseError(i: int; c: char; s: string) {.noinline, noreturn.} = raise newException(ValueError, "Invalid float containing thousand separators, invalid char '$1' at index $2 for input $3" % - [$c, $i, str]) + [$c, $i, s]) if str.len > 1: # Allow "0" which is valid, equal to 0.0 var s = newStringOfCap(str.len) @@ -123,7 +123,7 @@ func parseFloatThousandSep*(str: string; sep = ','; decimalDot = '.'): float {.s for c in str: if c in '0' .. '9': # Digits if hasAnySep and successive > 2: - raiseError(idx, c) + raiseError(idx, c, str) else: s.add c lastWasSep = false @@ -132,14 +132,14 @@ func parseFloatThousandSep*(str: string; sep = ','; decimalDot = '.'): float {.s inc idx if c == sep: # Thousands separator, this is NOT the dot if lastWasSep or afterDot or (isNegative and idx == 1 or idx == 0): - raiseError(idx, c) + raiseError(idx, c, str) else: lastWasSep = true # Do NOT add the Thousands separator here. hasAnySep = true successive = 0 if c == decimalDot: # This is the dot if (isNegative and idx == 1 or idx == 0) or (hasAnySep and successive != 3): # Disallow .1 - raiseError(idx, c) + raiseError(idx, c, str) else: s.add '.' # Replace decimalDot to '.' so parseFloat can take it. successive = 0 @@ -148,7 +148,7 @@ func parseFloatThousandSep*(str: string; sep = ','; decimalDot = '.'): float {.s inc idx if c == '-': # Allow negative float if isNegative or idx != 0: # Wont allow ---1.0 - raiseError(idx, c) + raiseError(idx, c, str) else: s.add '-' isNegative = true From c754556ea990824e74b25e005a08dc64a632712f Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Fri, 13 Nov 2020 00:10:40 -0300 Subject: [PATCH 26/64] char repr --- lib/pure/strmisc.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index c2908258c03c..319bd5440372 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -113,8 +113,8 @@ func parseFloatThousandSep*(str: string; sep = ','; decimalDot = '.'): float {.s proc raiseError(i: int; c: char; s: string) {.noinline, noreturn.} = raise newException(ValueError, - "Invalid float containing thousand separators, invalid char '$1' at index $2 for input $3" % - [$c, $i, s]) + "Invalid float containing thousand separators, invalid char $1 at index $2 for input $3" % + [c.repr, $i, s]) if str.len > 1: # Allow "0" which is valid, equal to 0.0 var s = newStringOfCap(str.len) From c151058628ac0246e86032244638550a64a3f113 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Fri, 13 Nov 2020 00:16:13 -0300 Subject: [PATCH 27/64] https://github.com/nim-lang/Nim/pull/15421#discussion_r522586680 --- lib/pure/strmisc.nim | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index 319bd5440372..14025479a4fb 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -118,9 +118,9 @@ func parseFloatThousandSep*(str: string; sep = ','; decimalDot = '.'): float {.s if str.len > 1: # Allow "0" which is valid, equal to 0.0 var s = newStringOfCap(str.len) - var idx, successive: int + var successive: int var afterDot, lastWasDot, lastWasSep, hasAnySep, isNegative: bool - for c in str: + for idx, c in str: if c in '0' .. '9': # Digits if hasAnySep and successive > 2: raiseError(idx, c, str) @@ -129,7 +129,6 @@ func parseFloatThousandSep*(str: string; sep = ','; decimalDot = '.'): float {.s lastWasSep = false lastWasDot = false inc successive - inc idx if c == sep: # Thousands separator, this is NOT the dot if lastWasSep or afterDot or (isNegative and idx == 1 or idx == 0): raiseError(idx, c, str) @@ -145,14 +144,12 @@ func parseFloatThousandSep*(str: string; sep = ','; decimalDot = '.'): float {.s successive = 0 lastWasDot = true afterDot = true - inc idx if c == '-': # Allow negative float if isNegative or idx != 0: # Wont allow ---1.0 raiseError(idx, c, str) else: s.add '-' isNegative = true - inc idx result = parseFloat(s) else: result = parseFloat(str) From a18df8ac7b9150858d23e432b9782ffdc9897aff Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Fri, 13 Nov 2020 01:31:03 -0300 Subject: [PATCH 28/64] https://github.com/nim-lang/Nim/pull/15421#discussion_r522587122 --- lib/pure/strmisc.nim | 96 +++++++++++++++++++++------------------ tests/stdlib/tstrmisc.nim | 41 +++++++++++------ 2 files changed, 81 insertions(+), 56 deletions(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index 14025479a4fb..60d0c1fe51bf 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -87,7 +87,51 @@ proc rpartition*(s: string, sep: string): (string, string, string) return partition(s, sep, right = true) -func parseFloatThousandSep*(str: string; sep = ','; decimalDot = '.'): float {.since: (1, 5).} = +template parseFloatThousandSepImpl(str; sep: char; decimalDot: char): float = + assert sep != '-' and decimalDot notin {'-', ' '} and sep != decimalDot + + proc raiseError(i: int; c: char; s: string) {.noinline, noreturn.} = + raise newException(ValueError, + "Invalid float containing thousand separators, invalid char $1 at index $2 for input $3" % + [c.repr, $i, s]) + + var s = newStringOfCap(str.len) + var successive: int + var afterDot, lastWasDot, lastWasSep, hasAnySep, isNegative: bool + for idx, c in str: + if c in '0' .. '9': # Digits + if hasAnySep and successive > 2: + raiseError(idx, c, $str) + else: + s.add c + lastWasSep = false + lastWasDot = false + inc successive + if c == sep: # Thousands separator, this is NOT the dot + if lastWasSep or afterDot or (isNegative and idx == 1 or idx == 0): + raiseError(idx, c, $str) + else: + lastWasSep = true # Do NOT add the Thousands separator here. + hasAnySep = true + successive = 0 + if c == decimalDot: # This is the dot + if (isNegative and idx == 1 or idx == 0) or (hasAnySep and successive != 3): # Disallow .1 + raiseError(idx, c, $str) + else: + s.add '.' # Replace decimalDot to '.' so parseFloat can take it. + successive = 0 + lastWasDot = true + afterDot = true + if c == '-': # Allow negative float + if isNegative or idx != 0: # Wont allow ---1.0 + raiseError(idx, c, $str) + else: + s.add '-' + isNegative = true + parseFloat(s) + + +func parseFloatThousandSep*(s: string; sep = ','; decimalDot = '.'): float {.since: (1, 5).} = ## Convenience func for `parseFloat` which allows for thousand separators, ## this is designed to parse floats as found in the wild formatted for humans. ## @@ -109,50 +153,16 @@ func parseFloatThousandSep*(str: string; sep = ','; decimalDot = '.'): float {.s doAssert parseFloatThousandSep("10,000,000.000") == 10000000.0 doAssert parseFloatThousandSep("10.000,0", '.', ',') == 10000.0 doAssert parseFloatThousandSep("1'000'000,000", '\'', ',') == 1000000.0 - assert sep != '-' and decimalDot notin {'-', ' '} and sep != decimalDot + if s.len > 1: parseFloatThousandSepImpl(s, sep, decimalDot) else: parseFloat(s) - proc raiseError(i: int; c: char; s: string) {.noinline, noreturn.} = - raise newException(ValueError, - "Invalid float containing thousand separators, invalid char $1 at index $2 for input $3" % - [c.repr, $i, s]) - if str.len > 1: # Allow "0" which is valid, equal to 0.0 - var s = newStringOfCap(str.len) - var successive: int - var afterDot, lastWasDot, lastWasSep, hasAnySep, isNegative: bool - for idx, c in str: - if c in '0' .. '9': # Digits - if hasAnySep and successive > 2: - raiseError(idx, c, str) - else: - s.add c - lastWasSep = false - lastWasDot = false - inc successive - if c == sep: # Thousands separator, this is NOT the dot - if lastWasSep or afterDot or (isNegative and idx == 1 or idx == 0): - raiseError(idx, c, str) - else: - lastWasSep = true # Do NOT add the Thousands separator here. - hasAnySep = true - successive = 0 - if c == decimalDot: # This is the dot - if (isNegative and idx == 1 or idx == 0) or (hasAnySep and successive != 3): # Disallow .1 - raiseError(idx, c, str) - else: - s.add '.' # Replace decimalDot to '.' so parseFloat can take it. - successive = 0 - lastWasDot = true - afterDot = true - if c == '-': # Allow negative float - if isNegative or idx != 0: # Wont allow ---1.0 - raiseError(idx, c, str) - else: - s.add '-' - isNegative = true - result = parseFloat(s) - else: - result = parseFloat(str) +func parseFloatThousandSep*(s: openArray[char]; sep = ','; decimalDot = '.'): float {.since: (1, 5).} = + runnableExamples: + doAssert parseFloatThousandSep(['1', ',', '0', '0', '0']) == 1000.0 + doAssert parseFloatThousandSep(['-', '1', ',', '0', '0', '0']) == -1000.0 + doAssert parseFloatThousandSep(['1', '0', ',', '0', '0', '0', '.', '0', '0', '0']) == 10000.0 + doAssert parseFloatThousandSep(['1', '0', '.', '0', '0', '0', ',', '0'], '.', ',') == 10000.0 + if s.len > 1: parseFloatThousandSepImpl(s, sep, decimalDot) else: parseFloat($s[0]) when isMainModule: diff --git a/tests/stdlib/tstrmisc.nim b/tests/stdlib/tstrmisc.nim index 327f74b077f0..669ffd719d8a 100644 --- a/tests/stdlib/tstrmisc.nim +++ b/tests/stdlib/tstrmisc.nim @@ -2,19 +2,34 @@ import strmisc func main() = - doAssert parseFloatThousandSep("0") == 0.0 - doAssert parseFloatThousandSep("-0") == -0.0 - doAssert parseFloatThousandSep("0.0") == 0.0 - doAssert parseFloatThousandSep("1.0") == 1.0 - doAssert parseFloatThousandSep("-0.0") == -0.0 - doAssert parseFloatThousandSep("-1.0") == -1.0 - doAssert parseFloatThousandSep("1.000") == 1.0 - doAssert parseFloatThousandSep("-1.000") == -1.0 - doAssertRaises(ValueError): discard parseFloatThousandSep("1,0000.000") - doAssertRaises(ValueError): discard parseFloatThousandSep("--") - doAssertRaises(ValueError): discard parseFloatThousandSep("..") - doAssertRaises(ValueError): discard parseFloatThousandSep("1,,000") - doAssertRaises(ValueError): discard parseFloatThousandSep("1..000") + block: + doAssert parseFloatThousandSep("0") == 0.0 + doAssert parseFloatThousandSep("-0") == -0.0 + doAssert parseFloatThousandSep("0.0") == 0.0 + doAssert parseFloatThousandSep("1.0") == 1.0 + doAssert parseFloatThousandSep("-0.0") == -0.0 + doAssert parseFloatThousandSep("-1.0") == -1.0 + doAssert parseFloatThousandSep("1.000") == 1.0 + doAssert parseFloatThousandSep("-1.000") == -1.0 + doAssertRaises(ValueError): discard parseFloatThousandSep("1,0000.000") + doAssertRaises(ValueError): discard parseFloatThousandSep("--") + doAssertRaises(ValueError): discard parseFloatThousandSep("..") + doAssertRaises(ValueError): discard parseFloatThousandSep("1,,000") + doAssertRaises(ValueError): discard parseFloatThousandSep("1..000") + block: + doAssert parseFloatThousandSep(['0']) == 0.0 + doAssert parseFloatThousandSep(['-', '0']) == -0.0 + doAssert parseFloatThousandSep(['0', '.', '0']) == 0.0 + doAssert parseFloatThousandSep(['1', '.', '0']) == 1.0 + doAssert parseFloatThousandSep(['-', '0', '.', '0']) == -0.0 + doAssert parseFloatThousandSep(['-', '1', '.', '0']) == -1.0 + doAssert parseFloatThousandSep(['1', '.', '0', '0', '0']) == 1.0 + doAssert parseFloatThousandSep(['-', '1', '.', '0', '0', '0']) == -1.0 + doAssertRaises(ValueError): discard parseFloatThousandSep("1,0000.000") + doAssertRaises(ValueError): discard parseFloatThousandSep(['-', '-']) + doAssertRaises(ValueError): discard parseFloatThousandSep(['.', '.']) + doAssertRaises(ValueError): discard parseFloatThousandSep(['1', ',', ',', '0', '0', '0']) + doAssertRaises(ValueError): discard parseFloatThousandSep(['1', '.', '.', '0', '0', '0']) main() From 64a306fae7d4b03e8480b5b1ab011254046e0f4f Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Fri, 13 Nov 2020 01:38:51 -0300 Subject: [PATCH 29/64] https://github.com/nim-lang/Nim/pull/15421#discussion_r522587122 --- lib/pure/strmisc.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index 60d0c1fe51bf..a84e73fde6c8 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -123,7 +123,7 @@ template parseFloatThousandSepImpl(str; sep: char; decimalDot: char): float = lastWasDot = true afterDot = true if c == '-': # Allow negative float - if isNegative or idx != 0: # Wont allow ---1.0 + if isNegative or idx != 0: # Disallow ---1.0 raiseError(idx, c, $str) else: s.add '-' From f5314a19d6bd4961a6d6f42831bf93f667901d22 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Fri, 13 Nov 2020 01:39:35 -0300 Subject: [PATCH 30/64] https://github.com/nim-lang/Nim/pull/15421#discussion_r522587122 --- tests/stdlib/tstrmisc.nim | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/stdlib/tstrmisc.nim b/tests/stdlib/tstrmisc.nim index 669ffd719d8a..683dfdb9add3 100644 --- a/tests/stdlib/tstrmisc.nim +++ b/tests/stdlib/tstrmisc.nim @@ -25,7 +25,6 @@ func main() = doAssert parseFloatThousandSep(['-', '1', '.', '0']) == -1.0 doAssert parseFloatThousandSep(['1', '.', '0', '0', '0']) == 1.0 doAssert parseFloatThousandSep(['-', '1', '.', '0', '0', '0']) == -1.0 - doAssertRaises(ValueError): discard parseFloatThousandSep("1,0000.000") doAssertRaises(ValueError): discard parseFloatThousandSep(['-', '-']) doAssertRaises(ValueError): discard parseFloatThousandSep(['.', '.']) doAssertRaises(ValueError): discard parseFloatThousandSep(['1', ',', ',', '0', '0', '0']) From fce794c9bb55e098df2912abee373e4f6b8e4c6a Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Fri, 13 Nov 2020 02:12:46 -0300 Subject: [PATCH 31/64] https://github.com/nim-lang/Nim/pull/15421#discussion_r522636227 --- lib/pure/strmisc.nim | 99 +++++++++++++++++---------------------- tests/stdlib/tstrmisc.nim | 13 ----- 2 files changed, 44 insertions(+), 68 deletions(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index a84e73fde6c8..d29957e4a919 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -87,51 +87,7 @@ proc rpartition*(s: string, sep: string): (string, string, string) return partition(s, sep, right = true) -template parseFloatThousandSepImpl(str; sep: char; decimalDot: char): float = - assert sep != '-' and decimalDot notin {'-', ' '} and sep != decimalDot - - proc raiseError(i: int; c: char; s: string) {.noinline, noreturn.} = - raise newException(ValueError, - "Invalid float containing thousand separators, invalid char $1 at index $2 for input $3" % - [c.repr, $i, s]) - - var s = newStringOfCap(str.len) - var successive: int - var afterDot, lastWasDot, lastWasSep, hasAnySep, isNegative: bool - for idx, c in str: - if c in '0' .. '9': # Digits - if hasAnySep and successive > 2: - raiseError(idx, c, $str) - else: - s.add c - lastWasSep = false - lastWasDot = false - inc successive - if c == sep: # Thousands separator, this is NOT the dot - if lastWasSep or afterDot or (isNegative and idx == 1 or idx == 0): - raiseError(idx, c, $str) - else: - lastWasSep = true # Do NOT add the Thousands separator here. - hasAnySep = true - successive = 0 - if c == decimalDot: # This is the dot - if (isNegative and idx == 1 or idx == 0) or (hasAnySep and successive != 3): # Disallow .1 - raiseError(idx, c, $str) - else: - s.add '.' # Replace decimalDot to '.' so parseFloat can take it. - successive = 0 - lastWasDot = true - afterDot = true - if c == '-': # Allow negative float - if isNegative or idx != 0: # Disallow ---1.0 - raiseError(idx, c, $str) - else: - s.add '-' - isNegative = true - parseFloat(s) - - -func parseFloatThousandSep*(s: string; sep = ','; decimalDot = '.'): float {.since: (1, 5).} = +func parseFloatThousandSep*(str: openArray[char]; sep = ','; decimalDot = '.'): float {.since: (1, 5).} = ## Convenience func for `parseFloat` which allows for thousand separators, ## this is designed to parse floats as found in the wild formatted for humans. ## @@ -146,23 +102,56 @@ func parseFloatThousandSep*(s: string; sep = ','; decimalDot = '.'): float {.sin ## See also: ## * `parseFloat `_ runnableExamples: - doAssert parseFloatThousandSep("1,000") == 1000.0 doAssert parseFloatThousandSep("-1,000") == -1000.0 doAssert parseFloatThousandSep("10,000.000") == 10000.0 doAssert parseFloatThousandSep("1,000,000.000") == 1000000.0 doAssert parseFloatThousandSep("10,000,000.000") == 10000000.0 doAssert parseFloatThousandSep("10.000,0", '.', ',') == 10000.0 doAssert parseFloatThousandSep("1'000'000,000", '\'', ',') == 1000000.0 - if s.len > 1: parseFloatThousandSepImpl(s, sep, decimalDot) else: parseFloat(s) - + assert sep != '-' and decimalDot notin {'-', ' '} and sep != decimalDot -func parseFloatThousandSep*(s: openArray[char]; sep = ','; decimalDot = '.'): float {.since: (1, 5).} = - runnableExamples: - doAssert parseFloatThousandSep(['1', ',', '0', '0', '0']) == 1000.0 - doAssert parseFloatThousandSep(['-', '1', ',', '0', '0', '0']) == -1000.0 - doAssert parseFloatThousandSep(['1', '0', ',', '0', '0', '0', '.', '0', '0', '0']) == 10000.0 - doAssert parseFloatThousandSep(['1', '0', '.', '0', '0', '0', ',', '0'], '.', ',') == 10000.0 - if s.len > 1: parseFloatThousandSepImpl(s, sep, decimalDot) else: parseFloat($s[0]) + proc raiseError(i: int; c: char; s: openArray[char]) {.noinline, noreturn.} = + raise newException(ValueError, + "Invalid float containing thousand separators, invalid char $1 at index $2 for input $3" % + [c.repr, $i, s.repr]) + + if str.len > 1: + var s = newStringOfCap(str.len) + var successive: int + var afterDot, lastWasDot, lastWasSep, hasAnySep, isNegative: bool + for idx, c in str: + if c in '0' .. '9': # Digits + if hasAnySep and successive > 2: + raiseError(idx, c, str) + else: + s.add c + lastWasSep = false + lastWasDot = false + inc successive + if c == sep: # Thousands separator, this is NOT the dot + if lastWasSep or afterDot or (isNegative and idx == 1 or idx == 0): + raiseError(idx, c, str) + else: + lastWasSep = true # Do NOT add the Thousands separator here. + hasAnySep = true + successive = 0 + if c == decimalDot: # This is the dot + if (isNegative and idx == 1 or idx == 0) or (hasAnySep and successive != 3): # Disallow .1 + raiseError(idx, c, str) + else: + s.add '.' # Replace decimalDot to '.' so parseFloat can take it. + successive = 0 + lastWasDot = true + afterDot = true + if c == '-': # Allow negative float + if isNegative or idx != 0: # Disallow ---1.0 + raiseError(idx, c, str) + else: + s.add '-' + isNegative = true + result = parseFloat(s) + else: + result = parseFloat($str[0]) when isMainModule: diff --git a/tests/stdlib/tstrmisc.nim b/tests/stdlib/tstrmisc.nim index 683dfdb9add3..2c225dac7a83 100644 --- a/tests/stdlib/tstrmisc.nim +++ b/tests/stdlib/tstrmisc.nim @@ -16,19 +16,6 @@ func main() = doAssertRaises(ValueError): discard parseFloatThousandSep("..") doAssertRaises(ValueError): discard parseFloatThousandSep("1,,000") doAssertRaises(ValueError): discard parseFloatThousandSep("1..000") - block: - doAssert parseFloatThousandSep(['0']) == 0.0 - doAssert parseFloatThousandSep(['-', '0']) == -0.0 - doAssert parseFloatThousandSep(['0', '.', '0']) == 0.0 - doAssert parseFloatThousandSep(['1', '.', '0']) == 1.0 - doAssert parseFloatThousandSep(['-', '0', '.', '0']) == -0.0 - doAssert parseFloatThousandSep(['-', '1', '.', '0']) == -1.0 - doAssert parseFloatThousandSep(['1', '.', '0', '0', '0']) == 1.0 - doAssert parseFloatThousandSep(['-', '1', '.', '0', '0', '0']) == -1.0 - doAssertRaises(ValueError): discard parseFloatThousandSep(['-', '-']) - doAssertRaises(ValueError): discard parseFloatThousandSep(['.', '.']) - doAssertRaises(ValueError): discard parseFloatThousandSep(['1', ',', ',', '0', '0', '0']) - doAssertRaises(ValueError): discard parseFloatThousandSep(['1', '.', '.', '0', '0', '0']) main() From 765b6daf5073427d80e6973359911c4d8a6b5af3 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Fri, 13 Nov 2020 02:16:13 -0300 Subject: [PATCH 32/64] https://github.com/nim-lang/Nim/pull/15421#discussion_r522636227 --- lib/pure/strutils.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 289a3d69078b..372ac1bf62f6 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -1152,7 +1152,7 @@ proc parseFloat*(s: string): float {.noSideEffect, ##``NAN``, ``INF``, ``-INF`` are also supported (case insensitive comparison). ## ## See also: - ## * `parseFloatThousandSep `_ + ## * `parseFloatThousandSep `_ runnableExamples: doAssert parseFloat("3.14") == 3.14 doAssert parseFloat("inf") == 1.0/0 From 7c6c941c32e6deb957eeb6a7da0b38b362fd770e Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Fri, 13 Nov 2020 02:19:41 -0300 Subject: [PATCH 33/64] https://github.com/nim-lang/Nim/pull/15421#discussion_r522636227 --- lib/pure/strmisc.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index d29957e4a919..ab28c4b102c6 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -92,8 +92,8 @@ func parseFloatThousandSep*(str: openArray[char]; sep = ','; decimalDot = '.'): ## this is designed to parse floats as found in the wild formatted for humans. ## ## The following assumptions and requirements must be met: - ## - String must not be empty. - ## - String must be stripped of trailing and leading whitespaces. + ## - `str` must not be empty. + ## - `str` must be stripped of trailing and leading whitespaces. ## - `sep` must not be `'-'`. ## - `decimalDot` must not be `'-'` nor `' '`. ## - `sep` and `decimalDot` must be different. From d37375e3705246d6d20c48e213d8ad38666d188b Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Fri, 13 Nov 2020 02:25:18 -0300 Subject: [PATCH 34/64] https://github.com/nim-lang/Nim/pull/15421#discussion_r522636227 --- tests/stdlib/tstrmisc.nim | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/tests/stdlib/tstrmisc.nim b/tests/stdlib/tstrmisc.nim index 2c225dac7a83..327f74b077f0 100644 --- a/tests/stdlib/tstrmisc.nim +++ b/tests/stdlib/tstrmisc.nim @@ -2,20 +2,19 @@ import strmisc func main() = - block: - doAssert parseFloatThousandSep("0") == 0.0 - doAssert parseFloatThousandSep("-0") == -0.0 - doAssert parseFloatThousandSep("0.0") == 0.0 - doAssert parseFloatThousandSep("1.0") == 1.0 - doAssert parseFloatThousandSep("-0.0") == -0.0 - doAssert parseFloatThousandSep("-1.0") == -1.0 - doAssert parseFloatThousandSep("1.000") == 1.0 - doAssert parseFloatThousandSep("-1.000") == -1.0 - doAssertRaises(ValueError): discard parseFloatThousandSep("1,0000.000") - doAssertRaises(ValueError): discard parseFloatThousandSep("--") - doAssertRaises(ValueError): discard parseFloatThousandSep("..") - doAssertRaises(ValueError): discard parseFloatThousandSep("1,,000") - doAssertRaises(ValueError): discard parseFloatThousandSep("1..000") + doAssert parseFloatThousandSep("0") == 0.0 + doAssert parseFloatThousandSep("-0") == -0.0 + doAssert parseFloatThousandSep("0.0") == 0.0 + doAssert parseFloatThousandSep("1.0") == 1.0 + doAssert parseFloatThousandSep("-0.0") == -0.0 + doAssert parseFloatThousandSep("-1.0") == -1.0 + doAssert parseFloatThousandSep("1.000") == 1.0 + doAssert parseFloatThousandSep("-1.000") == -1.0 + doAssertRaises(ValueError): discard parseFloatThousandSep("1,0000.000") + doAssertRaises(ValueError): discard parseFloatThousandSep("--") + doAssertRaises(ValueError): discard parseFloatThousandSep("..") + doAssertRaises(ValueError): discard parseFloatThousandSep("1,,000") + doAssertRaises(ValueError): discard parseFloatThousandSep("1..000") main() From f8886baf35defa8a59ec43f4778db32b4a345ad7 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Fri, 13 Nov 2020 02:32:55 -0300 Subject: [PATCH 35/64] Add 1 assert to test --- tests/stdlib/tstrmisc.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/stdlib/tstrmisc.nim b/tests/stdlib/tstrmisc.nim index 327f74b077f0..e59d288901df 100644 --- a/tests/stdlib/tstrmisc.nim +++ b/tests/stdlib/tstrmisc.nim @@ -15,6 +15,7 @@ func main() = doAssertRaises(ValueError): discard parseFloatThousandSep("..") doAssertRaises(ValueError): discard parseFloatThousandSep("1,,000") doAssertRaises(ValueError): discard parseFloatThousandSep("1..000") + doAssertRaises(ValueError): discard parseFloatThousandSep("1,000000") main() From 744355f937c25bf6c665ec85cd4ad634bee48205 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Fri, 13 Nov 2020 02:33:40 -0300 Subject: [PATCH 36/64] Add 1 assert to test --- lib/pure/strmisc.nim | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index ab28c4b102c6..3aa1991d1083 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -108,6 +108,8 @@ func parseFloatThousandSep*(str: openArray[char]; sep = ','; decimalDot = '.'): doAssert parseFloatThousandSep("10,000,000.000") == 10000000.0 doAssert parseFloatThousandSep("10.000,0", '.', ',') == 10000.0 doAssert parseFloatThousandSep("1'000'000,000", '\'', ',') == 1000000.0 + doAssert parseFloatThousandSep("1000,000") == 1000000.0 + doAssert parseFloatThousandSep("1000000") == 1000000.0 assert sep != '-' and decimalDot notin {'-', ' '} and sep != decimalDot proc raiseError(i: int; c: char; s: openArray[char]) {.noinline, noreturn.} = From a14b96d3778280cca24ac26262f8acb76b461bce Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Fri, 13 Nov 2020 03:05:10 -0300 Subject: [PATCH 37/64] https://github.com/nim-lang/Nim/pull/15421#discussion_r522671941 --- lib/pure/strmisc.nim | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index 3aa1991d1083..96d97cb8e568 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -97,7 +97,13 @@ func parseFloatThousandSep*(str: openArray[char]; sep = ','; decimalDot = '.'): ## - `sep` must not be `'-'`. ## - `decimalDot` must not be `'-'` nor `' '`. ## - `sep` and `decimalDot` must be different. - ## - Exactly 1 separator must appear after each 3 consecutive inner digits to the left of the dot and nowhere else. + ## - No separator before a digit. + ## - First separator can be anywhere after first digit, but no more than 3 characters. + ## - There has to be 3 digits between successive separators. + ## - There has to be 3 digits between the last separator and the decimal dot. + ## - No separator after decimal dot. + ## - No duplicate separators. + ## - Floats without separator allowed. ## ## See also: ## * `parseFloat `_ From 4ec5d07f5957d8e14c50dbd7ef3278a4d3519e28 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Fri, 13 Nov 2020 03:12:21 -0300 Subject: [PATCH 38/64] Add a comment --- lib/pure/strmisc.nim | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index 96d97cb8e568..2167dcfaad04 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -114,8 +114,10 @@ func parseFloatThousandSep*(str: openArray[char]; sep = ','; decimalDot = '.'): doAssert parseFloatThousandSep("10,000,000.000") == 10000000.0 doAssert parseFloatThousandSep("10.000,0", '.', ',') == 10000.0 doAssert parseFloatThousandSep("1'000'000,000", '\'', ',') == 1000000.0 - doAssert parseFloatThousandSep("1000,000") == 1000000.0 doAssert parseFloatThousandSep("1000000") == 1000000.0 + ## You can omit `sep`, but then all subsequent `sep` to the left must also be omitted: + doAssert parseFloatThousandSep("1000,000") == 1000000.0 + assert sep != '-' and decimalDot notin {'-', ' '} and sep != decimalDot proc raiseError(i: int; c: char; s: openArray[char]) {.noinline, noreturn.} = From 74115869b248d6c957cbaf26388388f56737fd70 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Fri, 13 Nov 2020 03:20:45 -0300 Subject: [PATCH 39/64] Remove confusing doc line --- lib/pure/strmisc.nim | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index 2167dcfaad04..ba3e4dd96905 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -98,7 +98,6 @@ func parseFloatThousandSep*(str: openArray[char]; sep = ','; decimalDot = '.'): ## - `decimalDot` must not be `'-'` nor `' '`. ## - `sep` and `decimalDot` must be different. ## - No separator before a digit. - ## - First separator can be anywhere after first digit, but no more than 3 characters. ## - There has to be 3 digits between successive separators. ## - There has to be 3 digits between the last separator and the decimal dot. ## - No separator after decimal dot. From c8f210d21e8598925427f3a03ae35013a0849c6c Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Fri, 13 Nov 2020 03:39:22 -0300 Subject: [PATCH 40/64] https://github.com/nim-lang/Nim/pull/15421#discussion_r522678760 --- lib/pure/strmisc.nim | 71 +++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 37 deletions(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index ba3e4dd96905..add4144dbb49 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -124,43 +124,40 @@ func parseFloatThousandSep*(str: openArray[char]; sep = ','; decimalDot = '.'): "Invalid float containing thousand separators, invalid char $1 at index $2 for input $3" % [c.repr, $i, s.repr]) - if str.len > 1: - var s = newStringOfCap(str.len) - var successive: int - var afterDot, lastWasDot, lastWasSep, hasAnySep, isNegative: bool - for idx, c in str: - if c in '0' .. '9': # Digits - if hasAnySep and successive > 2: - raiseError(idx, c, str) - else: - s.add c - lastWasSep = false - lastWasDot = false - inc successive - if c == sep: # Thousands separator, this is NOT the dot - if lastWasSep or afterDot or (isNegative and idx == 1 or idx == 0): - raiseError(idx, c, str) - else: - lastWasSep = true # Do NOT add the Thousands separator here. - hasAnySep = true - successive = 0 - if c == decimalDot: # This is the dot - if (isNegative and idx == 1 or idx == 0) or (hasAnySep and successive != 3): # Disallow .1 - raiseError(idx, c, str) - else: - s.add '.' # Replace decimalDot to '.' so parseFloat can take it. - successive = 0 - lastWasDot = true - afterDot = true - if c == '-': # Allow negative float - if isNegative or idx != 0: # Disallow ---1.0 - raiseError(idx, c, str) - else: - s.add '-' - isNegative = true - result = parseFloat(s) - else: - result = parseFloat($str[0]) + var s = newStringOfCap(str.len) + var successive: int + var afterDot, lastWasDot, lastWasSep, hasAnySep, isNegative: bool + for idx, c in str: + if c in '0' .. '9': # Digits + if hasAnySep and successive > 2: + raiseError(idx, c, str) + else: + s.add c + lastWasSep = false + lastWasDot = false + inc successive + if c == sep: # Thousands separator, this is NOT the dot + if lastWasSep or afterDot or (isNegative and idx == 1 or idx == 0): + raiseError(idx, c, str) + else: + lastWasSep = true # Do NOT add the Thousands separator here. + hasAnySep = true + successive = 0 + if c == decimalDot: # This is the dot + if (isNegative and idx == 1 or idx == 0) or (hasAnySep and successive != 3): # Disallow .1 + raiseError(idx, c, str) + else: + s.add '.' # Replace decimalDot to '.' so parseFloat can take it. + successive = 0 + lastWasDot = true + afterDot = true + if c == '-': # Allow negative float + if isNegative or idx != 0: # Disallow ---1.0 + raiseError(idx, c, str) + else: + s.add '-' + isNegative = true + result = parseFloat(s) when isMainModule: From 38b26f1ad9c38e0a084420fa24bd1985aaed8b8a Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Tue, 17 Nov 2020 09:40:53 -0300 Subject: [PATCH 41/64] https://github.com/nim-lang/Nim/pull/15421#discussion_r522841456 --- lib/pure/strmisc.nim | 6 ++++++ tests/stdlib/tstrmisc.nim | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index add4144dbb49..dab6aabbfcef 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -103,6 +103,7 @@ func parseFloatThousandSep*(str: openArray[char]; sep = ','; decimalDot = '.'): ## - No separator after decimal dot. ## - No duplicate separators. ## - Floats without separator allowed. + ## - No trailing or leading thousand separators. ## ## See also: ## * `parseFloat `_ @@ -124,6 +125,11 @@ func parseFloatThousandSep*(str: openArray[char]; sep = ','; decimalDot = '.'): "Invalid float containing thousand separators, invalid char $1 at index $2 for input $3" % [c.repr, $i, s.repr]) + if str[0] in {sep, decimalDot}: raiseError(0, sep, str) # Fail fast for ",9" + if str[^1] in {sep, decimalDot}: raiseError(str.len, sep, str) # Fail for "9," + # Must be len > 4 to have thousands sep. Fail fast for "9,9", "9,99", etc. + if not(str.len > 4) and sep in str: raiseError(0, sep, str) # "1,1" "1,11" + var s = newStringOfCap(str.len) var successive: int var afterDot, lastWasDot, lastWasSep, hasAnySep, isNegative: bool diff --git a/tests/stdlib/tstrmisc.nim b/tests/stdlib/tstrmisc.nim index e59d288901df..b4631f12a219 100644 --- a/tests/stdlib/tstrmisc.nim +++ b/tests/stdlib/tstrmisc.nim @@ -10,6 +10,11 @@ func main() = doAssert parseFloatThousandSep("-1.0") == -1.0 doAssert parseFloatThousandSep("1.000") == 1.0 doAssert parseFloatThousandSep("-1.000") == -1.0 + + doAssert parseFloatThousandSep("1,111") == 1111.0 + doAssertRaises(ValueError): discard parseFloatThousandSep("1,11") + doAssertRaises(ValueError): discard parseFloatThousandSep("1,1") + doAssertRaises(ValueError): discard parseFloatThousandSep("1,0000.000") doAssertRaises(ValueError): discard parseFloatThousandSep("--") doAssertRaises(ValueError): discard parseFloatThousandSep("..") @@ -17,6 +22,11 @@ func main() = doAssertRaises(ValueError): discard parseFloatThousandSep("1..000") doAssertRaises(ValueError): discard parseFloatThousandSep("1,000000") + doAssertRaises(ValueError): discard parseFloatThousandSep(",1") + doAssertRaises(ValueError): discard parseFloatThousandSep("1,") + doAssertRaises(ValueError): discard parseFloatThousandSep("1.") + doAssertRaises(ValueError): discard parseFloatThousandSep(".1") + main() static: main() From 5e55e92a1bcf8e08e233eaa6371f1045bdba3c59 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Wed, 18 Nov 2020 08:33:19 -0300 Subject: [PATCH 42/64] refactor and more tests --- lib/pure/strmisc.nim | 196 +++++++++++++++++++++++--------------- tests/stdlib/tstrmisc.nim | 63 +++++++----- 2 files changed, 161 insertions(+), 98 deletions(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index dab6aabbfcef..38474ab33f9f 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -87,83 +87,127 @@ proc rpartition*(s: string, sep: string): (string, string, string) return partition(s, sep, right = true) -func parseFloatThousandSep*(str: openArray[char]; sep = ','; decimalDot = '.'): float {.since: (1, 5).} = - ## Convenience func for `parseFloat` which allows for thousand separators, - ## this is designed to parse floats as found in the wild formatted for humans. - ## - ## The following assumptions and requirements must be met: - ## - `str` must not be empty. - ## - `str` must be stripped of trailing and leading whitespaces. - ## - `sep` must not be `'-'`. - ## - `decimalDot` must not be `'-'` nor `' '`. - ## - `sep` and `decimalDot` must be different. - ## - No separator before a digit. - ## - There has to be 3 digits between successive separators. - ## - There has to be 3 digits between the last separator and the decimal dot. - ## - No separator after decimal dot. - ## - No duplicate separators. - ## - Floats without separator allowed. - ## - No trailing or leading thousand separators. - ## - ## See also: - ## * `parseFloat `_ - runnableExamples: - doAssert parseFloatThousandSep("-1,000") == -1000.0 - doAssert parseFloatThousandSep("10,000.000") == 10000.0 - doAssert parseFloatThousandSep("1,000,000.000") == 1000000.0 - doAssert parseFloatThousandSep("10,000,000.000") == 10000000.0 - doAssert parseFloatThousandSep("10.000,0", '.', ',') == 10000.0 - doAssert parseFloatThousandSep("1'000'000,000", '\'', ',') == 1000000.0 - doAssert parseFloatThousandSep("1000000") == 1000000.0 - ## You can omit `sep`, but then all subsequent `sep` to the left must also be omitted: - doAssert parseFloatThousandSep("1000,000") == 1000000.0 - - assert sep != '-' and decimalDot notin {'-', ' '} and sep != decimalDot - - proc raiseError(i: int; c: char; s: openArray[char]) {.noinline, noreturn.} = - raise newException(ValueError, - "Invalid float containing thousand separators, invalid char $1 at index $2 for input $3" % - [c.repr, $i, s.repr]) - - if str[0] in {sep, decimalDot}: raiseError(0, sep, str) # Fail fast for ",9" - if str[^1] in {sep, decimalDot}: raiseError(str.len, sep, str) # Fail for "9," - # Must be len > 4 to have thousands sep. Fail fast for "9,9", "9,99", etc. - if not(str.len > 4) and sep in str: raiseError(0, sep, str) # "1,1" "1,11" - - var s = newStringOfCap(str.len) - var successive: int - var afterDot, lastWasDot, lastWasSep, hasAnySep, isNegative: bool - for idx, c in str: - if c in '0' .. '9': # Digits - if hasAnySep and successive > 2: - raiseError(idx, c, str) - else: - s.add c - lastWasSep = false - lastWasDot = false - inc successive - if c == sep: # Thousands separator, this is NOT the dot - if lastWasSep or afterDot or (isNegative and idx == 1 or idx == 0): - raiseError(idx, c, str) - else: - lastWasSep = true # Do NOT add the Thousands separator here. - hasAnySep = true - successive = 0 - if c == decimalDot: # This is the dot - if (isNegative and idx == 1 or idx == 0) or (hasAnySep and successive != 3): # Disallow .1 - raiseError(idx, c, str) - else: - s.add '.' # Replace decimalDot to '.' so parseFloat can take it. - successive = 0 - lastWasDot = true - afterDot = true - if c == '-': # Allow negative float - if isNegative or idx != 0: # Disallow ---1.0 - raiseError(idx, c, str) +since (1, 5): + type ParseFloatOptions* = enum ## Options for `parseFloatThousandSep`. + pfLeadingSep, ## Allow leading separator, like ",9" and similar. + pfLeadingDot, ## Allow leading dot, like ".9" and similar. + pfTrailingSep, ## Allow trailing separator, like "9," and similar. + pfTrailingDot, ## Allow trailing dot, like "9." and similar. + pfMultipleMinus, ## Allow multiple minus, like "---9" and similar. + pfSepAnywhere, ## Allow separator anywhere in between, like "9,9", "9,99". + pfDotOptional, ## Allow "9", "-0", integers literals, etc. + pfEmptyString ## Allow "" to return 0.0, so you do not need to do + ## try: parseFloatThousandSep(str) except: 0.0 + + func parseFloatThousandSep*(str: openArray[char]; options: set[ParseFloatOptions]; + sep = ','; decimalDot = '.'): float = + ## Convenience func for `parseFloat` which allows for thousand separators, + ## this is designed to parse floats as found in the wild formatted for humans. + ## + ## Fine grained flexibility and strictness is up to the user, + ## you can set the `options` using `ParseFloatOptions` enum. + ## + ## `parseFloatThousandSep` "prepares" `str` and then calls `parseFloat`, + ## consequently `parseFloatThousandSep` by design is slower than `parseFloat`. + ## + ## The following assumptions and requirements must be met: + ## - `sep` must not be `'-'`. + ## - `decimalDot` must not be `'-'` nor `' '`. + ## - `sep` and `decimalDot` must be different. + ## - `str` must be stripped of trailing and leading whitespaces. + ## - `pfTrailingDot` and `pfLeadingDot` must not be used together. + ## + ## See also: + ## * `parseFloat `_ + runnableExamples: + doAssert parseFloatThousandSep("10,000.000", {}) == 10000.0 + doAssert parseFloatThousandSep("1,000,000.000", {}) == 1000000.0 + doAssert parseFloatThousandSep("10,000,000.000", {}) == 10000000.0 + doAssert parseFloatThousandSep("1,222.0001", {}) == 1222.0001 + doAssert parseFloatThousandSep("10.000,0", {}, '.', ',') == 10000.0 + doAssert parseFloatThousandSep("1'000'000,000", {}, '\'', ',') == 1000000.0 + doAssert parseFloatThousandSep("1000000", {pfDotOptional}) == 1000000.0 + doAssert parseFloatThousandSep("-1,000", {pfDotOptional}) == -1000.0 + ## You can omit `sep`, but then all subsequent `sep` to the left must also be omitted: + doAssert parseFloatThousandSep("1000,000", {pfDotOptional}) == 1000000.0 + ## Examples using different ParseFloatOptions: + doAssert parseFloatThousandSep(",1.0", {pfLeadingSep}) == 1.0 + doAssert parseFloatThousandSep(".1", {pfLeadingDot}) == 0.1 + doAssert parseFloatThousandSep("1,", {pfTrailingSep, pfDotOptional}) == 1.0 + doAssert parseFloatThousandSep("1.", {pfTrailingDot}) == 1.0 + doAssert parseFloatThousandSep("--1.0", {pfMultipleMinus}) == -1.0 + doAssert parseFloatThousandSep("1.0,0,0", {pfSepAnywhere}) == 1.0 + doAssert parseFloatThousandSep("", {pfEmptyString}) == 0.0 + + assert sep != '-' and decimalDot notin {'-', ' '} and sep != decimalDot + assert not(pfTrailingDot in options and pfLeadingDot in options) + + proc parseFloatThousandSepRaise(i: int; c: char; s: openArray[char]) {.noinline, noreturn.} = + raise newException(ValueError, + "Invalid float containing thousand separators, invalid char $1 at index $2 for input $3" % + [c.repr, $i, s.repr]) + + # Fail fast, before looping. + let strLen = str.len + if strLen == 0: # Empty string. + if pfEmptyString notin options: + parseFloatThousandSepRaise(0, ' ', "empty string") else: - s.add '-' - isNegative = true - result = parseFloat(s) + return 0.0 # So user dont need to do `(try: parseFloat(str) except: 0.0)` etc + if pfLeadingSep notin options and str[0] == sep: # ",1" + parseFloatThousandSepRaise(0, sep, str) + if pfLeadingDot notin options and str[0] == decimalDot: # ".1" + parseFloatThousandSepRaise(0, decimalDot, str) + if pfTrailingSep notin options and str[^1] == sep: # "1," + parseFloatThousandSepRaise(strLen, sep, str) + if pfTrailingDot notin options and str[^1] == decimalDot: # "1." + parseFloatThousandSepRaise(strLen, decimalDot, str) + if pfSepAnywhere notin options and (not(str.len > 4) and sep in str) and + pfLeadingSep notin options and pfTrailingSep notin options: + parseFloatThousandSepRaise(0, sep, str) # "1,1" + + var + s = newStringOfCap(strLen) + successive: int + afterDot, lastWasDot, lastWasSep, hasAnySep, isNegative, hasAnyDot: bool + + for idx, c in str: + if c in '0' .. '9': # Digits + if pfSepAnywhere notin options and hasAnySep and not afterDot and successive > 2: + parseFloatThousandSepRaise(idx, c, str) + else: + s.add c + lastWasSep = false + lastWasDot = false + inc successive + if c == sep: # Thousands separator, this is NOT the dot + if pfSepAnywhere notin options and (lastWasSep or afterDot) or + pfLeadingSep notin options and (isNegative and idx == 1 or idx == 0): + parseFloatThousandSepRaise(idx, c, str) + else: + lastWasSep = true # Do NOT add the Thousands separator here. + hasAnySep = true + successive = 0 + if c == decimalDot: # This is the dot + if pfLeadingDot notin options and (isNegative and idx == 1 or idx == 0) or + pfLeadingDot in options and hasAnySep and successive != 3: # Disallow .1 + parseFloatThousandSepRaise(idx, c, str) + else: + s.add '.' # Replace decimalDot to '.' so parseFloat can take it. + successive = 0 + lastWasDot = true + afterDot = true + hasAnyDot = true + if c == '-': # Allow negative float + if pfMultipleMinus notin options and (isNegative or idx != 0): # Disallow ---1.0 + parseFloatThousandSepRaise(idx, c, str) + else: + if idx == 0: s.add '-' + isNegative = true + + if pfDotOptional notin options and not hasAnyDot: + parseFloatThousandSepRaise(0, sep, str) + result = parseFloat(s) when isMainModule: diff --git a/tests/stdlib/tstrmisc.nim b/tests/stdlib/tstrmisc.nim index b4631f12a219..1253909a0939 100644 --- a/tests/stdlib/tstrmisc.nim +++ b/tests/stdlib/tstrmisc.nim @@ -2,30 +2,49 @@ import strmisc func main() = - doAssert parseFloatThousandSep("0") == 0.0 - doAssert parseFloatThousandSep("-0") == -0.0 - doAssert parseFloatThousandSep("0.0") == 0.0 - doAssert parseFloatThousandSep("1.0") == 1.0 - doAssert parseFloatThousandSep("-0.0") == -0.0 - doAssert parseFloatThousandSep("-1.0") == -1.0 - doAssert parseFloatThousandSep("1.000") == 1.0 - doAssert parseFloatThousandSep("-1.000") == -1.0 + doAssert parseFloatThousandSep("0.0", {}) == 0.0 + doAssert parseFloatThousandSep("1.0", {}) == 1.0 + doAssert parseFloatThousandSep("-0.0", {}) == -0.0 + doAssert parseFloatThousandSep("-1.0", {}) == -1.0 + doAssert parseFloatThousandSep("1.000", {}) == 1.0 + doAssert parseFloatThousandSep("1.000", {}) == 1.0 + doAssert parseFloatThousandSep("-1.000", {}) == -1.0 + doAssert parseFloatThousandSep("-1,222.0001", {}) == -1222.0001 + doAssert parseFloatThousandSep("3.141592653589793", {}) == 3.141592653589793 + doAssert parseFloatThousandSep("6.283185307179586", {}) == 6.283185307179586 + doAssert parseFloatThousandSep("2.718281828459045", {}) == 2.718281828459045 - doAssert parseFloatThousandSep("1,111") == 1111.0 - doAssertRaises(ValueError): discard parseFloatThousandSep("1,11") - doAssertRaises(ValueError): discard parseFloatThousandSep("1,1") + doAssertRaises(ValueError): discard parseFloatThousandSep("1,11", {}) + doAssertRaises(ValueError): discard parseFloatThousandSep("1,1", {}) + doAssertRaises(ValueError): discard parseFloatThousandSep("1,0000.000", {}) + doAssertRaises(ValueError): discard parseFloatThousandSep("--", {}) + doAssertRaises(ValueError): discard parseFloatThousandSep("..", {}) + doAssertRaises(ValueError): discard parseFloatThousandSep("1,,000", {}) + doAssertRaises(ValueError): discard parseFloatThousandSep("1..000", {}) + doAssertRaises(ValueError): discard parseFloatThousandSep("1,000000", {}) + doAssertRaises(ValueError): discard parseFloatThousandSep(",1", {}) + doAssertRaises(ValueError): discard parseFloatThousandSep("1,", {}) + doAssertRaises(ValueError): discard parseFloatThousandSep("1.", {}) + doAssertRaises(ValueError): discard parseFloatThousandSep(".1", {}) + doAssertRaises(ValueError): discard parseFloatThousandSep(" ", {pfDotOptional, pfEmptyString}) - doAssertRaises(ValueError): discard parseFloatThousandSep("1,0000.000") - doAssertRaises(ValueError): discard parseFloatThousandSep("--") - doAssertRaises(ValueError): discard parseFloatThousandSep("..") - doAssertRaises(ValueError): discard parseFloatThousandSep("1,,000") - doAssertRaises(ValueError): discard parseFloatThousandSep("1..000") - doAssertRaises(ValueError): discard parseFloatThousandSep("1,000000") - - doAssertRaises(ValueError): discard parseFloatThousandSep(",1") - doAssertRaises(ValueError): discard parseFloatThousandSep("1,") - doAssertRaises(ValueError): discard parseFloatThousandSep("1.") - doAssertRaises(ValueError): discard parseFloatThousandSep(".1") + doAssert parseFloatThousandSep("0", {pfDotOptional}) == 0.0 + doAssert parseFloatThousandSep("-0", {pfDotOptional}) == -0.0 + doAssert parseFloatThousandSep("1,111", {pfDotOptional}) == 1111.0 + doAssert parseFloatThousandSep(",1.0,", {pfLeadingSep, pfTrailingSep}) == 1.0 + doAssert parseFloatThousandSep(".1,", {pfLeadingDot, pfTrailingSep}) == 0.1 + doAssert parseFloatThousandSep("1,", {pfTrailingSep, pfDotOptional}) == 1.0 + doAssert parseFloatThousandSep("1.", {pfTrailingDot}) == 1.0 + doAssert parseFloatThousandSep("--1.0", {pfMultipleMinus}) == -1.0 + doAssert parseFloatThousandSep("1.0,0,0", {pfSepAnywhere}) == 1.0 + doAssert parseFloatThousandSep("", {pfEmptyString}) == 0.0 + doAssert parseFloatThousandSep(".10,", {pfLeadingDot, pfTrailingSep}) == 0.1 + doAssert parseFloatThousandSep(",1.000,", {pfLeadingSep, pfTrailingSep}) == 1.000 + doAssert parseFloatThousandSep(",10.", {pfLeadingSep, pfTrailingDot}) == 10.0 + doAssert parseFloatThousandSep("---1.000,", {pfMultipleMinus, pfTrailingSep}) == -1.0 + doAssert parseFloatThousandSep("10", {pfEmptyString, pfDotOptional, pfSepAnywhere, pfMultipleMinus}) == 10.0 + doAssert parseFloatThousandSep("1.0,0,0,0,0,0,0,0", {pfSepAnywhere}) == 1.0 + doAssert parseFloatThousandSep("0,0,0,0,0,0,0,0.1", {pfSepAnywhere}) == 0.1 main() From 2a95434e0c6a592555854db1ae767d664a20a20b Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Wed, 18 Nov 2020 16:31:17 -0300 Subject: [PATCH 43/64] Resync devel --- tests/stdlib/tstrmisc.nim | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/stdlib/tstrmisc.nim b/tests/stdlib/tstrmisc.nim index 1253909a0939..68f3b638fcaa 100644 --- a/tests/stdlib/tstrmisc.nim +++ b/tests/stdlib/tstrmisc.nim @@ -31,7 +31,6 @@ func main() = doAssert parseFloatThousandSep("0", {pfDotOptional}) == 0.0 doAssert parseFloatThousandSep("-0", {pfDotOptional}) == -0.0 doAssert parseFloatThousandSep("1,111", {pfDotOptional}) == 1111.0 - doAssert parseFloatThousandSep(",1.0,", {pfLeadingSep, pfTrailingSep}) == 1.0 doAssert parseFloatThousandSep(".1,", {pfLeadingDot, pfTrailingSep}) == 0.1 doAssert parseFloatThousandSep("1,", {pfTrailingSep, pfDotOptional}) == 1.0 doAssert parseFloatThousandSep("1.", {pfTrailingDot}) == 1.0 From e18164035903f84eb7ffd3b677e28bd7ef046b37 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Wed, 18 Nov 2020 21:10:09 -0300 Subject: [PATCH 44/64] Clean --- lib/pure/strmisc.nim | 4 +--- tests/stdlib/tstrmisc.nim | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index 38474ab33f9f..3480cdf6b5fa 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -93,7 +93,6 @@ since (1, 5): pfLeadingDot, ## Allow leading dot, like ".9" and similar. pfTrailingSep, ## Allow trailing separator, like "9," and similar. pfTrailingDot, ## Allow trailing dot, like "9." and similar. - pfMultipleMinus, ## Allow multiple minus, like "---9" and similar. pfSepAnywhere, ## Allow separator anywhere in between, like "9,9", "9,99". pfDotOptional, ## Allow "9", "-0", integers literals, etc. pfEmptyString ## Allow "" to return 0.0, so you do not need to do @@ -135,7 +134,6 @@ since (1, 5): doAssert parseFloatThousandSep(".1", {pfLeadingDot}) == 0.1 doAssert parseFloatThousandSep("1,", {pfTrailingSep, pfDotOptional}) == 1.0 doAssert parseFloatThousandSep("1.", {pfTrailingDot}) == 1.0 - doAssert parseFloatThousandSep("--1.0", {pfMultipleMinus}) == -1.0 doAssert parseFloatThousandSep("1.0,0,0", {pfSepAnywhere}) == 1.0 doAssert parseFloatThousandSep("", {pfEmptyString}) == 0.0 @@ -199,7 +197,7 @@ since (1, 5): afterDot = true hasAnyDot = true if c == '-': # Allow negative float - if pfMultipleMinus notin options and (isNegative or idx != 0): # Disallow ---1.0 + if isNegative or idx != 0: # Disallow ---1.0 parseFloatThousandSepRaise(idx, c, str) else: if idx == 0: s.add '-' diff --git a/tests/stdlib/tstrmisc.nim b/tests/stdlib/tstrmisc.nim index 68f3b638fcaa..64bed45d7ac7 100644 --- a/tests/stdlib/tstrmisc.nim +++ b/tests/stdlib/tstrmisc.nim @@ -34,14 +34,12 @@ func main() = doAssert parseFloatThousandSep(".1,", {pfLeadingDot, pfTrailingSep}) == 0.1 doAssert parseFloatThousandSep("1,", {pfTrailingSep, pfDotOptional}) == 1.0 doAssert parseFloatThousandSep("1.", {pfTrailingDot}) == 1.0 - doAssert parseFloatThousandSep("--1.0", {pfMultipleMinus}) == -1.0 doAssert parseFloatThousandSep("1.0,0,0", {pfSepAnywhere}) == 1.0 doAssert parseFloatThousandSep("", {pfEmptyString}) == 0.0 doAssert parseFloatThousandSep(".10,", {pfLeadingDot, pfTrailingSep}) == 0.1 doAssert parseFloatThousandSep(",1.000,", {pfLeadingSep, pfTrailingSep}) == 1.000 doAssert parseFloatThousandSep(",10.", {pfLeadingSep, pfTrailingDot}) == 10.0 - doAssert parseFloatThousandSep("---1.000,", {pfMultipleMinus, pfTrailingSep}) == -1.0 - doAssert parseFloatThousandSep("10", {pfEmptyString, pfDotOptional, pfSepAnywhere, pfMultipleMinus}) == 10.0 + doAssert parseFloatThousandSep("10", {pfEmptyString, pfDotOptional, pfSepAnywhere}) == 10.0 doAssert parseFloatThousandSep("1.0,0,0,0,0,0,0,0", {pfSepAnywhere}) == 1.0 doAssert parseFloatThousandSep("0,0,0,0,0,0,0,0.1", {pfSepAnywhere}) == 0.1 From b27270843aa20d2c667119544858d86b56bfec3d Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Wed, 18 Nov 2020 21:21:47 -0300 Subject: [PATCH 45/64] Clean --- lib/pure/strmisc.nim | 13 +++++-------- tests/stdlib/tstrmisc.nim | 9 ++++----- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index 3480cdf6b5fa..4c7b4b278605 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -89,9 +89,7 @@ proc rpartition*(s: string, sep: string): (string, string, string) since (1, 5): type ParseFloatOptions* = enum ## Options for `parseFloatThousandSep`. - pfLeadingSep, ## Allow leading separator, like ",9" and similar. pfLeadingDot, ## Allow leading dot, like ".9" and similar. - pfTrailingSep, ## Allow trailing separator, like "9," and similar. pfTrailingDot, ## Allow trailing dot, like "9." and similar. pfSepAnywhere, ## Allow separator anywhere in between, like "9,9", "9,99". pfDotOptional, ## Allow "9", "-0", integers literals, etc. @@ -132,7 +130,7 @@ since (1, 5): ## Examples using different ParseFloatOptions: doAssert parseFloatThousandSep(",1.0", {pfLeadingSep}) == 1.0 doAssert parseFloatThousandSep(".1", {pfLeadingDot}) == 0.1 - doAssert parseFloatThousandSep("1,", {pfTrailingSep, pfDotOptional}) == 1.0 + doAssert parseFloatThousandSep("1", {pfDotOptional}) == 1.0 doAssert parseFloatThousandSep("1.", {pfTrailingDot}) == 1.0 doAssert parseFloatThousandSep("1.0,0,0", {pfSepAnywhere}) == 1.0 doAssert parseFloatThousandSep("", {pfEmptyString}) == 0.0 @@ -152,16 +150,15 @@ since (1, 5): parseFloatThousandSepRaise(0, ' ', "empty string") else: return 0.0 # So user dont need to do `(try: parseFloat(str) except: 0.0)` etc - if pfLeadingSep notin options and str[0] == sep: # ",1" + if str[0] == sep: # ",1" parseFloatThousandSepRaise(0, sep, str) if pfLeadingDot notin options and str[0] == decimalDot: # ".1" parseFloatThousandSepRaise(0, decimalDot, str) - if pfTrailingSep notin options and str[^1] == sep: # "1," + if str[^1] == sep: # "1," parseFloatThousandSepRaise(strLen, sep, str) if pfTrailingDot notin options and str[^1] == decimalDot: # "1." parseFloatThousandSepRaise(strLen, decimalDot, str) - if pfSepAnywhere notin options and (not(str.len > 4) and sep in str) and - pfLeadingSep notin options and pfTrailingSep notin options: + if pfSepAnywhere notin options and (not(str.len > 4) and sep in str): parseFloatThousandSepRaise(0, sep, str) # "1,1" var @@ -180,7 +177,7 @@ since (1, 5): inc successive if c == sep: # Thousands separator, this is NOT the dot if pfSepAnywhere notin options and (lastWasSep or afterDot) or - pfLeadingSep notin options and (isNegative and idx == 1 or idx == 0): + (isNegative and idx == 1 or idx == 0): parseFloatThousandSepRaise(idx, c, str) else: lastWasSep = true # Do NOT add the Thousands separator here. diff --git a/tests/stdlib/tstrmisc.nim b/tests/stdlib/tstrmisc.nim index 64bed45d7ac7..27aaacbed039 100644 --- a/tests/stdlib/tstrmisc.nim +++ b/tests/stdlib/tstrmisc.nim @@ -31,14 +31,13 @@ func main() = doAssert parseFloatThousandSep("0", {pfDotOptional}) == 0.0 doAssert parseFloatThousandSep("-0", {pfDotOptional}) == -0.0 doAssert parseFloatThousandSep("1,111", {pfDotOptional}) == 1111.0 - doAssert parseFloatThousandSep(".1,", {pfLeadingDot, pfTrailingSep}) == 0.1 - doAssert parseFloatThousandSep("1,", {pfTrailingSep, pfDotOptional}) == 1.0 + doAssert parseFloatThousandSep(".1", {pfLeadingDot}) == 0.1 + doAssert parseFloatThousandSep("1", {pfDotOptional}) == 1.0 doAssert parseFloatThousandSep("1.", {pfTrailingDot}) == 1.0 doAssert parseFloatThousandSep("1.0,0,0", {pfSepAnywhere}) == 1.0 doAssert parseFloatThousandSep("", {pfEmptyString}) == 0.0 - doAssert parseFloatThousandSep(".10,", {pfLeadingDot, pfTrailingSep}) == 0.1 - doAssert parseFloatThousandSep(",1.000,", {pfLeadingSep, pfTrailingSep}) == 1.000 - doAssert parseFloatThousandSep(",10.", {pfLeadingSep, pfTrailingDot}) == 10.0 + doAssert parseFloatThousandSep(".10", {pfLeadingDot}) == 0.1 + doAssert parseFloatThousandSep("10.", {pfTrailingDot}) == 10.0 doAssert parseFloatThousandSep("10", {pfEmptyString, pfDotOptional, pfSepAnywhere}) == 10.0 doAssert parseFloatThousandSep("1.0,0,0,0,0,0,0,0", {pfSepAnywhere}) == 1.0 doAssert parseFloatThousandSep("0,0,0,0,0,0,0,0.1", {pfSepAnywhere}) == 0.1 From cd9e3b6376156d7a6b2f50525f09c2f5d00cacc8 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Wed, 18 Nov 2020 21:41:46 -0300 Subject: [PATCH 46/64] Clean --- lib/pure/strmisc.nim | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index 4c7b4b278605..bc16c8cf3ba7 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -128,7 +128,6 @@ since (1, 5): ## You can omit `sep`, but then all subsequent `sep` to the left must also be omitted: doAssert parseFloatThousandSep("1000,000", {pfDotOptional}) == 1000000.0 ## Examples using different ParseFloatOptions: - doAssert parseFloatThousandSep(",1.0", {pfLeadingSep}) == 1.0 doAssert parseFloatThousandSep(".1", {pfLeadingDot}) == 0.1 doAssert parseFloatThousandSep("1", {pfDotOptional}) == 1.0 doAssert parseFloatThousandSep("1.", {pfTrailingDot}) == 1.0 From bbfe1b813523fc5e86dc4c7fa4eebc1e91cc06de Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Wed, 18 Nov 2020 23:36:33 -0300 Subject: [PATCH 47/64] Simplify --- lib/pure/strmisc.nim | 19 +++++++--------- tests/stdlib/tstrmisc.nim | 46 +++++++++++++++++++-------------------- 2 files changed, 31 insertions(+), 34 deletions(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index bc16c8cf3ba7..8975d9fc4706 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -96,7 +96,7 @@ since (1, 5): pfEmptyString ## Allow "" to return 0.0, so you do not need to do ## try: parseFloatThousandSep(str) except: 0.0 - func parseFloatThousandSep*(str: openArray[char]; options: set[ParseFloatOptions]; + func parseFloatThousandSep*(str: openArray[char]; options: set[ParseFloatOptions] = {}; sep = ','; decimalDot = '.'): float = ## Convenience func for `parseFloat` which allows for thousand separators, ## this is designed to parse floats as found in the wild formatted for humans. @@ -112,15 +112,12 @@ since (1, 5): ## - `decimalDot` must not be `'-'` nor `' '`. ## - `sep` and `decimalDot` must be different. ## - `str` must be stripped of trailing and leading whitespaces. - ## - `pfTrailingDot` and `pfLeadingDot` must not be used together. ## ## See also: ## * `parseFloat `_ runnableExamples: - doAssert parseFloatThousandSep("10,000.000", {}) == 10000.0 - doAssert parseFloatThousandSep("1,000,000.000", {}) == 1000000.0 - doAssert parseFloatThousandSep("10,000,000.000", {}) == 10000000.0 - doAssert parseFloatThousandSep("1,222.0001", {}) == 1222.0001 + doAssert parseFloatThousandSep("10,000,000.000") == 10000000.0 + doAssert parseFloatThousandSep("1,222.0001") == 1222.0001 doAssert parseFloatThousandSep("10.000,0", {}, '.', ',') == 10000.0 doAssert parseFloatThousandSep("1'000'000,000", {}, '\'', ',') == 1000000.0 doAssert parseFloatThousandSep("1000000", {pfDotOptional}) == 1000000.0 @@ -131,11 +128,11 @@ since (1, 5): doAssert parseFloatThousandSep(".1", {pfLeadingDot}) == 0.1 doAssert parseFloatThousandSep("1", {pfDotOptional}) == 1.0 doAssert parseFloatThousandSep("1.", {pfTrailingDot}) == 1.0 - doAssert parseFloatThousandSep("1.0,0,0", {pfSepAnywhere}) == 1.0 + doAssert parseFloatThousandSep("10,0.0,0,0", {pfSepAnywhere}) == 100.0 doAssert parseFloatThousandSep("", {pfEmptyString}) == 0.0 + doAssert parseFloatThousandSep("01.00") == 1.0 assert sep != '-' and decimalDot notin {'-', ' '} and sep != decimalDot - assert not(pfTrailingDot in options and pfLeadingDot in options) proc parseFloatThousandSepRaise(i: int; c: char; s: openArray[char]) {.noinline, noreturn.} = raise newException(ValueError, @@ -149,15 +146,15 @@ since (1, 5): parseFloatThousandSepRaise(0, ' ', "empty string") else: return 0.0 # So user dont need to do `(try: parseFloat(str) except: 0.0)` etc - if str[0] == sep: # ",1" + if str[0] == sep: # ",1" parseFloatThousandSepRaise(0, sep, str) if pfLeadingDot notin options and str[0] == decimalDot: # ".1" parseFloatThousandSepRaise(0, decimalDot, str) - if str[^1] == sep: # "1," + if str[^1] == sep: # "1," parseFloatThousandSepRaise(strLen, sep, str) if pfTrailingDot notin options and str[^1] == decimalDot: # "1." parseFloatThousandSepRaise(strLen, decimalDot, str) - if pfSepAnywhere notin options and (not(str.len > 4) and sep in str): + if pfSepAnywhere notin options and (str.len <= 4 and sep in str): parseFloatThousandSepRaise(0, sep, str) # "1,1" var diff --git a/tests/stdlib/tstrmisc.nim b/tests/stdlib/tstrmisc.nim index 27aaacbed039..f127627e987b 100644 --- a/tests/stdlib/tstrmisc.nim +++ b/tests/stdlib/tstrmisc.nim @@ -2,30 +2,30 @@ import strmisc func main() = - doAssert parseFloatThousandSep("0.0", {}) == 0.0 - doAssert parseFloatThousandSep("1.0", {}) == 1.0 - doAssert parseFloatThousandSep("-0.0", {}) == -0.0 - doAssert parseFloatThousandSep("-1.0", {}) == -1.0 - doAssert parseFloatThousandSep("1.000", {}) == 1.0 - doAssert parseFloatThousandSep("1.000", {}) == 1.0 - doAssert parseFloatThousandSep("-1.000", {}) == -1.0 - doAssert parseFloatThousandSep("-1,222.0001", {}) == -1222.0001 - doAssert parseFloatThousandSep("3.141592653589793", {}) == 3.141592653589793 - doAssert parseFloatThousandSep("6.283185307179586", {}) == 6.283185307179586 - doAssert parseFloatThousandSep("2.718281828459045", {}) == 2.718281828459045 + doAssert parseFloatThousandSep("0.0") == 0.0 + doAssert parseFloatThousandSep("1.0") == 1.0 + doAssert parseFloatThousandSep("-0.0") == -0.0 + doAssert parseFloatThousandSep("-1.0") == -1.0 + doAssert parseFloatThousandSep("1.000") == 1.0 + doAssert parseFloatThousandSep("1.000") == 1.0 + doAssert parseFloatThousandSep("-1.000") == -1.0 + doAssert parseFloatThousandSep("-1,222.0001") == -1222.0001 + doAssert parseFloatThousandSep("3.141592653589793") == 3.141592653589793 + doAssert parseFloatThousandSep("6.283185307179586") == 6.283185307179586 + doAssert parseFloatThousandSep("2.718281828459045") == 2.718281828459045 - doAssertRaises(ValueError): discard parseFloatThousandSep("1,11", {}) - doAssertRaises(ValueError): discard parseFloatThousandSep("1,1", {}) - doAssertRaises(ValueError): discard parseFloatThousandSep("1,0000.000", {}) - doAssertRaises(ValueError): discard parseFloatThousandSep("--", {}) - doAssertRaises(ValueError): discard parseFloatThousandSep("..", {}) - doAssertRaises(ValueError): discard parseFloatThousandSep("1,,000", {}) - doAssertRaises(ValueError): discard parseFloatThousandSep("1..000", {}) - doAssertRaises(ValueError): discard parseFloatThousandSep("1,000000", {}) - doAssertRaises(ValueError): discard parseFloatThousandSep(",1", {}) - doAssertRaises(ValueError): discard parseFloatThousandSep("1,", {}) - doAssertRaises(ValueError): discard parseFloatThousandSep("1.", {}) - doAssertRaises(ValueError): discard parseFloatThousandSep(".1", {}) + doAssertRaises(ValueError): discard parseFloatThousandSep("1,11") + doAssertRaises(ValueError): discard parseFloatThousandSep("1,1") + doAssertRaises(ValueError): discard parseFloatThousandSep("1,0000.000") + doAssertRaises(ValueError): discard parseFloatThousandSep("--") + doAssertRaises(ValueError): discard parseFloatThousandSep("..") + doAssertRaises(ValueError): discard parseFloatThousandSep("1,,000") + doAssertRaises(ValueError): discard parseFloatThousandSep("1..000") + doAssertRaises(ValueError): discard parseFloatThousandSep("1,000000") + doAssertRaises(ValueError): discard parseFloatThousandSep(",1") + doAssertRaises(ValueError): discard parseFloatThousandSep("1,") + doAssertRaises(ValueError): discard parseFloatThousandSep("1.") + doAssertRaises(ValueError): discard parseFloatThousandSep(".1") doAssertRaises(ValueError): discard parseFloatThousandSep(" ", {pfDotOptional, pfEmptyString}) doAssert parseFloatThousandSep("0", {pfDotOptional}) == 0.0 From bf6cf496eeae7f59645d91bbe725f22cdd665b98 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Wed, 18 Nov 2020 23:39:48 -0300 Subject: [PATCH 48/64] Simplify --- lib/pure/strmisc.nim | 10 ++-------- tests/stdlib/tstrmisc.nim | 5 ++--- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index 8975d9fc4706..1281a4e847fa 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -92,9 +92,7 @@ since (1, 5): pfLeadingDot, ## Allow leading dot, like ".9" and similar. pfTrailingDot, ## Allow trailing dot, like "9." and similar. pfSepAnywhere, ## Allow separator anywhere in between, like "9,9", "9,99". - pfDotOptional, ## Allow "9", "-0", integers literals, etc. - pfEmptyString ## Allow "" to return 0.0, so you do not need to do - ## try: parseFloatThousandSep(str) except: 0.0 + pfDotOptional ## Allow "9", "-0", integers literals, etc. func parseFloatThousandSep*(str: openArray[char]; options: set[ParseFloatOptions] = {}; sep = ','; decimalDot = '.'): float = @@ -129,7 +127,6 @@ since (1, 5): doAssert parseFloatThousandSep("1", {pfDotOptional}) == 1.0 doAssert parseFloatThousandSep("1.", {pfTrailingDot}) == 1.0 doAssert parseFloatThousandSep("10,0.0,0,0", {pfSepAnywhere}) == 100.0 - doAssert parseFloatThousandSep("", {pfEmptyString}) == 0.0 doAssert parseFloatThousandSep("01.00") == 1.0 assert sep != '-' and decimalDot notin {'-', ' '} and sep != decimalDot @@ -142,10 +139,7 @@ since (1, 5): # Fail fast, before looping. let strLen = str.len if strLen == 0: # Empty string. - if pfEmptyString notin options: - parseFloatThousandSepRaise(0, ' ', "empty string") - else: - return 0.0 # So user dont need to do `(try: parseFloat(str) except: 0.0)` etc + parseFloatThousandSepRaise(0, ' ', "empty string") if str[0] == sep: # ",1" parseFloatThousandSepRaise(0, sep, str) if pfLeadingDot notin options and str[0] == decimalDot: # ".1" diff --git a/tests/stdlib/tstrmisc.nim b/tests/stdlib/tstrmisc.nim index f127627e987b..772bdf42c089 100644 --- a/tests/stdlib/tstrmisc.nim +++ b/tests/stdlib/tstrmisc.nim @@ -26,7 +26,7 @@ func main() = doAssertRaises(ValueError): discard parseFloatThousandSep("1,") doAssertRaises(ValueError): discard parseFloatThousandSep("1.") doAssertRaises(ValueError): discard parseFloatThousandSep(".1") - doAssertRaises(ValueError): discard parseFloatThousandSep(" ", {pfDotOptional, pfEmptyString}) + doAssertRaises(ValueError): discard parseFloatThousandSep(" ", {pfDotOptional}) doAssert parseFloatThousandSep("0", {pfDotOptional}) == 0.0 doAssert parseFloatThousandSep("-0", {pfDotOptional}) == -0.0 @@ -35,10 +35,9 @@ func main() = doAssert parseFloatThousandSep("1", {pfDotOptional}) == 1.0 doAssert parseFloatThousandSep("1.", {pfTrailingDot}) == 1.0 doAssert parseFloatThousandSep("1.0,0,0", {pfSepAnywhere}) == 1.0 - doAssert parseFloatThousandSep("", {pfEmptyString}) == 0.0 doAssert parseFloatThousandSep(".10", {pfLeadingDot}) == 0.1 doAssert parseFloatThousandSep("10.", {pfTrailingDot}) == 10.0 - doAssert parseFloatThousandSep("10", {pfEmptyString, pfDotOptional, pfSepAnywhere}) == 10.0 + doAssert parseFloatThousandSep("10", {pfDotOptional, pfSepAnywhere}) == 10.0 doAssert parseFloatThousandSep("1.0,0,0,0,0,0,0,0", {pfSepAnywhere}) == 1.0 doAssert parseFloatThousandSep("0,0,0,0,0,0,0,0.1", {pfSepAnywhere}) == 0.1 From d11359a3eac34edd2e424b07ac93efc82079770b Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Thu, 19 Nov 2020 00:31:03 -0300 Subject: [PATCH 49/64] Allow {pfLeadingDot,pfTrailingDot} --- tests/stdlib/tstrmisc.nim | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/stdlib/tstrmisc.nim b/tests/stdlib/tstrmisc.nim index 772bdf42c089..d2c35a681b17 100644 --- a/tests/stdlib/tstrmisc.nim +++ b/tests/stdlib/tstrmisc.nim @@ -32,8 +32,10 @@ func main() = doAssert parseFloatThousandSep("-0", {pfDotOptional}) == -0.0 doAssert parseFloatThousandSep("1,111", {pfDotOptional}) == 1111.0 doAssert parseFloatThousandSep(".1", {pfLeadingDot}) == 0.1 - doAssert parseFloatThousandSep("1", {pfDotOptional}) == 1.0 doAssert parseFloatThousandSep("1.", {pfTrailingDot}) == 1.0 + doAssert parseFloatThousandSep(".1", {pfLeadingDot,pfTrailingDot}) == 0.1 + doAssert parseFloatThousandSep("1.", {pfLeadingDot,pfTrailingDot}) == 1.0 + doAssert parseFloatThousandSep("1", {pfDotOptional}) == 1.0 doAssert parseFloatThousandSep("1.0,0,0", {pfSepAnywhere}) == 1.0 doAssert parseFloatThousandSep(".10", {pfLeadingDot}) == 0.1 doAssert parseFloatThousandSep("10.", {pfTrailingDot}) == 10.0 From 3766512c3179a2eebae4af041f458679310fa2c1 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Thu, 19 Nov 2020 00:33:42 -0300 Subject: [PATCH 50/64] Allow {pfLeadingDot,pfTrailingDot} --- tests/stdlib/tstrmisc.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/stdlib/tstrmisc.nim b/tests/stdlib/tstrmisc.nim index d2c35a681b17..fd01c8418d81 100644 --- a/tests/stdlib/tstrmisc.nim +++ b/tests/stdlib/tstrmisc.nim @@ -27,6 +27,7 @@ func main() = doAssertRaises(ValueError): discard parseFloatThousandSep("1.") doAssertRaises(ValueError): discard parseFloatThousandSep(".1") doAssertRaises(ValueError): discard parseFloatThousandSep(" ", {pfDotOptional}) + doAssertRaises(ValueError): discard parseFloatThousandSep(".1.", {pfLeadingDot,pfTrailingDot}) doAssert parseFloatThousandSep("0", {pfDotOptional}) == 0.0 doAssert parseFloatThousandSep("-0", {pfDotOptional}) == -0.0 From 4bad51a7503d09221ccecd85a1e66176333bd452 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Thu, 19 Nov 2020 07:02:25 -0300 Subject: [PATCH 51/64] https://github.com/nim-lang/Nim/pull/15421#discussion_r526731954 --- lib/pure/strmisc.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index 1281a4e847fa..05d960c39222 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -134,7 +134,7 @@ since (1, 5): proc parseFloatThousandSepRaise(i: int; c: char; s: openArray[char]) {.noinline, noreturn.} = raise newException(ValueError, "Invalid float containing thousand separators, invalid char $1 at index $2 for input $3" % - [c.repr, $i, s.repr]) + [$c, $i, $s]) # Fail fast, before looping. let strLen = str.len From 68a82288c1fd05789d9839ddfe3f0c0ef081f705 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Mon, 23 Nov 2020 20:26:38 -0300 Subject: [PATCH 52/64] https://github.com/nim-lang/Nim/pull/15421/files#r526547810 --- lib/pure/strmisc.nim | 9 +++++---- tests/stdlib/tstrmisc.nim | 2 ++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index 05d960c39222..64a9dd51269b 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -158,7 +158,7 @@ since (1, 5): for idx, c in str: if c in '0' .. '9': # Digits - if pfSepAnywhere notin options and hasAnySep and not afterDot and successive > 2: + if hasAnySep and not afterDot and successive > 2: parseFloatThousandSepRaise(idx, c, str) else: s.add c @@ -174,8 +174,9 @@ since (1, 5): hasAnySep = true successive = 0 if c == decimalDot: # This is the dot - if pfLeadingDot notin options and (isNegative and idx == 1 or idx == 0) or - pfLeadingDot in options and hasAnySep and successive != 3: # Disallow .1 + if (not afterDot and not hasAnyDot and not lastWasDot) and + (pfLeadingDot notin options and (isNegative and idx == 1 or idx == 0)) or + (hasAnySep and pfSepAnywhere notin options and successive != 3): # Disallow .1 parseFloatThousandSepRaise(idx, c, str) else: s.add '.' # Replace decimalDot to '.' so parseFloat can take it. @@ -187,7 +188,7 @@ since (1, 5): if isNegative or idx != 0: # Disallow ---1.0 parseFloatThousandSepRaise(idx, c, str) else: - if idx == 0: s.add '-' + s.add '-' isNegative = true if pfDotOptional notin options and not hasAnyDot: diff --git a/tests/stdlib/tstrmisc.nim b/tests/stdlib/tstrmisc.nim index fd01c8418d81..dce0b144bf25 100644 --- a/tests/stdlib/tstrmisc.nim +++ b/tests/stdlib/tstrmisc.nim @@ -28,7 +28,9 @@ func main() = doAssertRaises(ValueError): discard parseFloatThousandSep(".1") doAssertRaises(ValueError): discard parseFloatThousandSep(" ", {pfDotOptional}) doAssertRaises(ValueError): discard parseFloatThousandSep(".1.", {pfLeadingDot,pfTrailingDot}) + doAssertRaises(ValueError): discard parseFloatThousandSep("10,00.0") + doAssert parseFloatThousandSep("10,00.0", {pfSepAnywhere}) == 1000.0 doAssert parseFloatThousandSep("0", {pfDotOptional}) == 0.0 doAssert parseFloatThousandSep("-0", {pfDotOptional}) == -0.0 doAssert parseFloatThousandSep("1,111", {pfDotOptional}) == 1111.0 From 4f3b1ee7c4094ecc11ce9a374b6b836ec1b1a0b5 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Tue, 24 Nov 2020 09:48:44 -0300 Subject: [PATCH 53/64] peer review feedbacks --- lib/pure/strmisc.nim | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index 64a9dd51269b..630ee74d3aee 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -92,7 +92,7 @@ since (1, 5): pfLeadingDot, ## Allow leading dot, like ".9" and similar. pfTrailingDot, ## Allow trailing dot, like "9." and similar. pfSepAnywhere, ## Allow separator anywhere in between, like "9,9", "9,99". - pfDotOptional ## Allow "9", "-0", integers literals, etc. + pfDotOptional ## Allow "9", "-0", integer literals, etc. func parseFloatThousandSep*(str: openArray[char]; options: set[ParseFloatOptions] = {}; sep = ','; decimalDot = '.'): float = @@ -165,7 +165,7 @@ since (1, 5): lastWasSep = false lastWasDot = false inc successive - if c == sep: # Thousands separator, this is NOT the dot + elif c == sep: # Thousands separator, this is NOT the dot if pfSepAnywhere notin options and (lastWasSep or afterDot) or (isNegative and idx == 1 or idx == 0): parseFloatThousandSepRaise(idx, c, str) @@ -173,7 +173,7 @@ since (1, 5): lastWasSep = true # Do NOT add the Thousands separator here. hasAnySep = true successive = 0 - if c == decimalDot: # This is the dot + elif c == decimalDot: # This is the dot if (not afterDot and not hasAnyDot and not lastWasDot) and (pfLeadingDot notin options and (isNegative and idx == 1 or idx == 0)) or (hasAnySep and pfSepAnywhere notin options and successive != 3): # Disallow .1 @@ -184,7 +184,7 @@ since (1, 5): lastWasDot = true afterDot = true hasAnyDot = true - if c == '-': # Allow negative float + elif c == '-': # Allow negative float if isNegative or idx != 0: # Disallow ---1.0 parseFloatThousandSepRaise(idx, c, str) else: From e90786f075f441d4ec147750a937f41d822d0636 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Tue, 24 Nov 2020 11:28:47 -0300 Subject: [PATCH 54/64] 1.0e9 --- lib/pure/strmisc.nim | 17 +++++++++++++---- tests/stdlib/tstrmisc.nim | 10 ++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index 630ee74d3aee..e19e39d12fbb 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -93,6 +93,7 @@ since (1, 5): pfTrailingDot, ## Allow trailing dot, like "9." and similar. pfSepAnywhere, ## Allow separator anywhere in between, like "9,9", "9,99". pfDotOptional ## Allow "9", "-0", integer literals, etc. + pfScientific ## Allow Scientific Notation "1.0e9", "1.0e-9", etc. func parseFloatThousandSep*(str: openArray[char]; options: set[ParseFloatOptions] = {}; sep = ','; decimalDot = '.'): float = @@ -128,6 +129,7 @@ since (1, 5): doAssert parseFloatThousandSep("1.", {pfTrailingDot}) == 1.0 doAssert parseFloatThousandSep("10,0.0,0,0", {pfSepAnywhere}) == 100.0 doAssert parseFloatThousandSep("01.00") == 1.0 + doAssert parseFloatThousandSep("1,000.000e-9", {pfScientific}) == 1e-06 assert sep != '-' and decimalDot notin {'-', ' '} and sep != decimalDot @@ -154,7 +156,7 @@ since (1, 5): var s = newStringOfCap(strLen) successive: int - afterDot, lastWasDot, lastWasSep, hasAnySep, isNegative, hasAnyDot: bool + afterDot, lastWasDot, lastWasSep, hasAnySep, isNegative, hasAnyDot, isScientific: bool for idx, c in str: if c in '0' .. '9': # Digits @@ -185,11 +187,18 @@ since (1, 5): afterDot = true hasAnyDot = true elif c == '-': # Allow negative float - if isNegative or idx != 0: # Disallow ---1.0 - parseFloatThousandSepRaise(idx, c, str) + if isNegative or idx != 0 and pfScientific notin options: # Disallow ---1.0 + parseFloatThousandSepRaise(idx, c, str) # Allow 1.0e-9 else: s.add '-' - isNegative = true + if idx == 0: # Allow 1.0e-9 + isNegative = true + elif c in {'e', 'E'}: # Allow scientific notation + if isScientific or pfScientific notin options: + parseFloatThousandSepRaise(idx, c, str) + else: + s.add 'e' + isScientific = true if pfDotOptional notin options and not hasAnyDot: parseFloatThousandSepRaise(0, sep, str) diff --git a/tests/stdlib/tstrmisc.nim b/tests/stdlib/tstrmisc.nim index dce0b144bf25..31ce40ead2c8 100644 --- a/tests/stdlib/tstrmisc.nim +++ b/tests/stdlib/tstrmisc.nim @@ -29,6 +29,10 @@ func main() = doAssertRaises(ValueError): discard parseFloatThousandSep(" ", {pfDotOptional}) doAssertRaises(ValueError): discard parseFloatThousandSep(".1.", {pfLeadingDot,pfTrailingDot}) doAssertRaises(ValueError): discard parseFloatThousandSep("10,00.0") + doAssertRaises(ValueError): discard parseFloatThousandSep("1.0e9") + doAssertRaises(ValueError): discard parseFloatThousandSep("1.0e-9") + doAssertRaises(ValueError): discard parseFloatThousandSep("1,000.000ee9", {pfScientific}) + doAssertRaises(ValueError): discard parseFloatThousandSep("1ee9", {pfScientific, pfDotOptional}) doAssert parseFloatThousandSep("10,00.0", {pfSepAnywhere}) == 1000.0 doAssert parseFloatThousandSep("0", {pfDotOptional}) == 0.0 @@ -45,6 +49,12 @@ func main() = doAssert parseFloatThousandSep("10", {pfDotOptional, pfSepAnywhere}) == 10.0 doAssert parseFloatThousandSep("1.0,0,0,0,0,0,0,0", {pfSepAnywhere}) == 1.0 doAssert parseFloatThousandSep("0,0,0,0,0,0,0,0.1", {pfSepAnywhere}) == 0.1 + doAssert parseFloatThousandSep("1.0e9", {pfScientific}) == 1000000000.0 + doAssert parseFloatThousandSep("1.0e-9", {pfScientific}) == 1e-09 + doAssert parseFloatThousandSep("1,000.000e9", {pfScientific}) == 1000000000000.0 + doAssert parseFloatThousandSep("1e9", {pfScientific, pfDotOptional}) == 1000000000.0 + doAssert parseFloatThousandSep("1.0E9", {pfScientific}) == 1000000000.0 + doAssert parseFloatThousandSep("1.0E-9", {pfScientific}) == 1e-09 main() From 87ca6aed1bd53d1937af31d4e78fd4fdb3a4cddd Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Tue, 24 Nov 2020 11:31:42 -0300 Subject: [PATCH 55/64] 1.0e9 --- tests/stdlib/tstrmisc.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/stdlib/tstrmisc.nim b/tests/stdlib/tstrmisc.nim index 31ce40ead2c8..1bacd3aca487 100644 --- a/tests/stdlib/tstrmisc.nim +++ b/tests/stdlib/tstrmisc.nim @@ -33,6 +33,7 @@ func main() = doAssertRaises(ValueError): discard parseFloatThousandSep("1.0e-9") doAssertRaises(ValueError): discard parseFloatThousandSep("1,000.000ee9", {pfScientific}) doAssertRaises(ValueError): discard parseFloatThousandSep("1ee9", {pfScientific, pfDotOptional}) + doAssertRaises(ValueError): discard parseFloatThousandSep("1e02.2", {pfScientific}) doAssert parseFloatThousandSep("10,00.0", {pfSepAnywhere}) == 1000.0 doAssert parseFloatThousandSep("0", {pfDotOptional}) == 0.0 From bdfc4f539ce71198be9f07166b04f3960013010a Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Tue, 24 Nov 2020 11:41:25 -0300 Subject: [PATCH 56/64] 1.0e9 --- tests/stdlib/tstrmisc.nim | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/stdlib/tstrmisc.nim b/tests/stdlib/tstrmisc.nim index 1bacd3aca487..8dcfcc501f0f 100644 --- a/tests/stdlib/tstrmisc.nim +++ b/tests/stdlib/tstrmisc.nim @@ -34,6 +34,7 @@ func main() = doAssertRaises(ValueError): discard parseFloatThousandSep("1,000.000ee9", {pfScientific}) doAssertRaises(ValueError): discard parseFloatThousandSep("1ee9", {pfScientific, pfDotOptional}) doAssertRaises(ValueError): discard parseFloatThousandSep("1e02.2", {pfScientific}) + doAssertRaises(ValueError): discard parseFloatThousandSep("1.0e--9", {pfScientific}) doAssert parseFloatThousandSep("10,00.0", {pfSepAnywhere}) == 1000.0 doAssert parseFloatThousandSep("0", {pfDotOptional}) == 0.0 From 21140876f266e59717c8e087c80201b7d2cbce46 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Tue, 24 Nov 2020 15:03:27 -0300 Subject: [PATCH 57/64] NaN --- lib/pure/strmisc.nim | 11 +++++++++++ tests/stdlib/tstrmisc.nim | 8 ++++++++ 2 files changed, 19 insertions(+) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index e19e39d12fbb..6de918bd8b40 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -94,6 +94,7 @@ since (1, 5): pfSepAnywhere, ## Allow separator anywhere in between, like "9,9", "9,99". pfDotOptional ## Allow "9", "-0", integer literals, etc. pfScientific ## Allow Scientific Notation "1.0e9", "1.0e-9", etc. + pfNanInf ## Allow "NaN", "Inf", "-Inf", etc. func parseFloatThousandSep*(str: openArray[char]; options: set[ParseFloatOptions] = {}; sep = ','; decimalDot = '.'): float = @@ -153,6 +154,16 @@ since (1, 5): if pfSepAnywhere notin options and (str.len <= 4 and sep in str): parseFloatThousandSepRaise(0, sep, str) # "1,1" + if (strLen == 3 or strLen == 4) and ( + (str[0] in {'i', 'I'} and str[1] in {'n', 'N'} and str[2] in {'f', 'F'}) or + (str[0] in {'n', 'N'} and str[1] in {'a', 'A'} and str[2] in {'n', 'N'}) or + (str[0] in {'+', '-'} and str[1] in {'i', 'I'} and str[2] in {'n', 'N'} and str[3] in {'f', 'F'}) or + (str[0] in {'+', '-'} and str[1] in {'n', 'N'} and str[2] in {'a', 'A'} and str[3] in {'n', 'N'})): + if pfNanInf notin options: + parseFloatThousandSepRaise(0, sep, str) + else: + return parseFloat(str.join) # Allow NaN, Inf, -Inf, +Inf + var s = newStringOfCap(strLen) successive: int diff --git a/tests/stdlib/tstrmisc.nim b/tests/stdlib/tstrmisc.nim index 8dcfcc501f0f..9cc08b65b288 100644 --- a/tests/stdlib/tstrmisc.nim +++ b/tests/stdlib/tstrmisc.nim @@ -35,6 +35,10 @@ func main() = doAssertRaises(ValueError): discard parseFloatThousandSep("1ee9", {pfScientific, pfDotOptional}) doAssertRaises(ValueError): discard parseFloatThousandSep("1e02.2", {pfScientific}) doAssertRaises(ValueError): discard parseFloatThousandSep("1.0e--9", {pfScientific}) + doAssertRaises(ValueError): discard parseFloatThousandSep("Inf") + doAssertRaises(ValueError): discard parseFloatThousandSep("-Inf") + doAssertRaises(ValueError): discard parseFloatThousandSep("+Inf") + doAssertRaises(ValueError): discard parseFloatThousandSep("NaN") doAssert parseFloatThousandSep("10,00.0", {pfSepAnywhere}) == 1000.0 doAssert parseFloatThousandSep("0", {pfDotOptional}) == 0.0 @@ -57,6 +61,10 @@ func main() = doAssert parseFloatThousandSep("1e9", {pfScientific, pfDotOptional}) == 1000000000.0 doAssert parseFloatThousandSep("1.0E9", {pfScientific}) == 1000000000.0 doAssert parseFloatThousandSep("1.0E-9", {pfScientific}) == 1e-09 + doAssert parseFloatThousandSep("Inf", {pfNanInf}) == Inf + doAssert parseFloatThousandSep("-Inf", {pfNanInf}) == -Inf + doAssert parseFloatThousandSep("+Inf", {pfNanInf}) == +Inf + discard parseFloatThousandSep("NaN", {pfNanInf}) main() From 15d832bbc6842920c0e9de3b63802be4f17f29f2 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Tue, 24 Nov 2020 15:43:41 -0300 Subject: [PATCH 58/64] is always scientific --- lib/pure/strmisc.nim | 9 ++++----- tests/stdlib/tstrmisc.nim | 22 ++++++++++------------ 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index 6de918bd8b40..998720fa8cd6 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -93,7 +93,6 @@ since (1, 5): pfTrailingDot, ## Allow trailing dot, like "9." and similar. pfSepAnywhere, ## Allow separator anywhere in between, like "9,9", "9,99". pfDotOptional ## Allow "9", "-0", integer literals, etc. - pfScientific ## Allow Scientific Notation "1.0e9", "1.0e-9", etc. pfNanInf ## Allow "NaN", "Inf", "-Inf", etc. func parseFloatThousandSep*(str: openArray[char]; options: set[ParseFloatOptions] = {}; @@ -130,7 +129,7 @@ since (1, 5): doAssert parseFloatThousandSep("1.", {pfTrailingDot}) == 1.0 doAssert parseFloatThousandSep("10,0.0,0,0", {pfSepAnywhere}) == 100.0 doAssert parseFloatThousandSep("01.00") == 1.0 - doAssert parseFloatThousandSep("1,000.000e-9", {pfScientific}) == 1e-06 + doAssert parseFloatThousandSep("1,000.000e-9") == 1e-06 assert sep != '-' and decimalDot notin {'-', ' '} and sep != decimalDot @@ -198,14 +197,14 @@ since (1, 5): afterDot = true hasAnyDot = true elif c == '-': # Allow negative float - if isNegative or idx != 0 and pfScientific notin options: # Disallow ---1.0 - parseFloatThousandSepRaise(idx, c, str) # Allow 1.0e-9 + if isNegative or idx != 0 and not isScientific: # Disallow ---1.0 + parseFloatThousandSepRaise(idx, c, str) # Allow 1.0e-9 else: s.add '-' if idx == 0: # Allow 1.0e-9 isNegative = true elif c in {'e', 'E'}: # Allow scientific notation - if isScientific or pfScientific notin options: + if isScientific: parseFloatThousandSepRaise(idx, c, str) else: s.add 'e' diff --git a/tests/stdlib/tstrmisc.nim b/tests/stdlib/tstrmisc.nim index 9e42a208e237..8f70b99a964d 100644 --- a/tests/stdlib/tstrmisc.nim +++ b/tests/stdlib/tstrmisc.nim @@ -29,12 +29,10 @@ func main() = doAssertRaises(ValueError): discard parseFloatThousandSep(" ", {pfDotOptional}) doAssertRaises(ValueError): discard parseFloatThousandSep(".1.", {pfLeadingDot,pfTrailingDot}) doAssertRaises(ValueError): discard parseFloatThousandSep("10,00.0") - doAssertRaises(ValueError): discard parseFloatThousandSep("1.0e9") - doAssertRaises(ValueError): discard parseFloatThousandSep("1.0e-9") - doAssertRaises(ValueError): discard parseFloatThousandSep("1,000.000ee9", {pfScientific}) - doAssertRaises(ValueError): discard parseFloatThousandSep("1ee9", {pfScientific, pfDotOptional}) - doAssertRaises(ValueError): discard parseFloatThousandSep("1e02.2", {pfScientific}) - doAssertRaises(ValueError): discard parseFloatThousandSep("1.0e--9", {pfScientific}) + doAssertRaises(ValueError): discard parseFloatThousandSep("1,000.000ee9") + doAssertRaises(ValueError): discard parseFloatThousandSep("1ee9", {pfDotOptional}) + doAssertRaises(ValueError): discard parseFloatThousandSep("1e02.2") + doAssertRaises(ValueError): discard parseFloatThousandSep("1.0e--9") doAssertRaises(ValueError): discard parseFloatThousandSep("Inf") doAssertRaises(ValueError): discard parseFloatThousandSep("-Inf") doAssertRaises(ValueError): discard parseFloatThousandSep("+Inf") @@ -57,12 +55,12 @@ func main() = doAssert parseFloatThousandSep("10", {pfDotOptional, pfSepAnywhere}) == 10.0 doAssert parseFloatThousandSep("1.0,0,0,0,0,0,0,0", {pfSepAnywhere}) == 1.0 doAssert parseFloatThousandSep("0,0,0,0,0,0,0,0.1", {pfSepAnywhere}) == 0.1 - doAssert parseFloatThousandSep("1.0e9", {pfScientific}) == 1000000000.0 - doAssert parseFloatThousandSep("1.0e-9", {pfScientific}) == 1e-09 - doAssert parseFloatThousandSep("1,000.000e9", {pfScientific}) == 1000000000000.0 - doAssert parseFloatThousandSep("1e9", {pfScientific, pfDotOptional}) == 1000000000.0 - doAssert parseFloatThousandSep("1.0E9", {pfScientific}) == 1000000000.0 - doAssert parseFloatThousandSep("1.0E-9", {pfScientific}) == 1e-09 + doAssert parseFloatThousandSep("1.0e9") == 1000000000.0 + doAssert parseFloatThousandSep("1.0e-9") == 1e-09 + doAssert parseFloatThousandSep("1,000.000e9") == 1000000000000.0 + doAssert parseFloatThousandSep("1e9", {pfDotOptional}) == 1000000000.0 + doAssert parseFloatThousandSep("1.0E9") == 1000000000.0 + doAssert parseFloatThousandSep("1.0E-9") == 1e-09 doAssert parseFloatThousandSep("Inf", {pfNanInf}) == Inf doAssert parseFloatThousandSep("-Inf", {pfNanInf}) == -Inf doAssert parseFloatThousandSep("+Inf", {pfNanInf}) == +Inf From 12854c1965f17272af85d55d7da5b9b55d707ee7 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Tue, 24 Nov 2020 16:02:42 -0300 Subject: [PATCH 59/64] https://github.com/nim-lang/Nim/pull/15421#discussion_r529793041 --- lib/pure/strmisc.nim | 2 +- tests/stdlib/tstrmisc.nim | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index 998720fa8cd6..5e65777969cc 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -179,7 +179,7 @@ since (1, 5): inc successive elif c == sep: # Thousands separator, this is NOT the dot if pfSepAnywhere notin options and (lastWasSep or afterDot) or - (isNegative and idx == 1 or idx == 0): + (isNegative and idx == 1 or idx == 0) or isScientific: parseFloatThousandSepRaise(idx, c, str) else: lastWasSep = true # Do NOT add the Thousands separator here. diff --git a/tests/stdlib/tstrmisc.nim b/tests/stdlib/tstrmisc.nim index 8f70b99a964d..f64fbf0819af 100644 --- a/tests/stdlib/tstrmisc.nim +++ b/tests/stdlib/tstrmisc.nim @@ -39,6 +39,7 @@ func main() = doAssertRaises(ValueError): discard parseFloatThousandSep("NaN") doAssertRaises(ValueError): discard parseFloatThousandSep("aNa", {pfNanInf}) doAssertRaises(ValueError): discard parseFloatThousandSep("fnI", {pfNanInf}) + doAssertRaises(ValueError): discard parseFloatThousandSep("1,000.000,000,E,+,9,0", {pfSepAnywhere}) doAssert parseFloatThousandSep("10,00.0", {pfSepAnywhere}) == 1000.0 doAssert parseFloatThousandSep("0", {pfDotOptional}) == 0.0 @@ -64,6 +65,7 @@ func main() = doAssert parseFloatThousandSep("Inf", {pfNanInf}) == Inf doAssert parseFloatThousandSep("-Inf", {pfNanInf}) == -Inf doAssert parseFloatThousandSep("+Inf", {pfNanInf}) == +Inf + doAssert parseFloatThousandSep("1000.000000E+90") == 1e93 discard parseFloatThousandSep("NaN", {pfNanInf}) From d747b188d1a9c02ffee4e39b3af4d6943ebfd5c9 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Tue, 24 Nov 2020 16:08:07 -0300 Subject: [PATCH 60/64] Clean out --- lib/pure/strmisc.nim | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index 5e65777969cc..499a2d444135 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -213,25 +213,3 @@ since (1, 5): if pfDotOptional notin options and not hasAnyDot: parseFloatThousandSepRaise(0, sep, str) result = parseFloat(s) - - -when isMainModule: - doAssert expandTabs("\t", 4) == " " - doAssert expandTabs("\tfoo\t", 4) == " foo " - doAssert expandTabs("\tfoo\tbar", 4) == " foo bar" - doAssert expandTabs("\tfoo\tbar\t", 4) == " foo bar " - doAssert expandTabs("", 4) == "" - doAssert expandTabs("", 0) == "" - doAssert expandTabs("\t\t\t", 0) == "" - - doAssert partition("foo:bar", ":") == ("foo", ":", "bar") - doAssert partition("foobarbar", "bar") == ("foo", "bar", "bar") - doAssert partition("foobarbar", "bank") == ("foobarbar", "", "") - doAssert partition("foobarbar", "foo") == ("", "foo", "barbar") - doAssert partition("foofoobar", "bar") == ("foofoo", "bar", "") - - doAssert rpartition("foo:bar", ":") == ("foo", ":", "bar") - doAssert rpartition("foobarbar", "bar") == ("foobar", "bar", "") - doAssert rpartition("foobarbar", "bank") == ("", "", "foobarbar") - doAssert rpartition("foobarbar", "foo") == ("", "foo", "barbar") - doAssert rpartition("foofoobar", "bar") == ("foofoo", "bar", "") From 093b8264ec170070f3ce82febeec1f788b6eeefa Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Tue, 24 Nov 2020 16:20:23 -0300 Subject: [PATCH 61/64] moar test --- tests/stdlib/tstrmisc.nim | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/stdlib/tstrmisc.nim b/tests/stdlib/tstrmisc.nim index f64fbf0819af..ed7472c1a65a 100644 --- a/tests/stdlib/tstrmisc.nim +++ b/tests/stdlib/tstrmisc.nim @@ -66,6 +66,8 @@ func main() = doAssert parseFloatThousandSep("-Inf", {pfNanInf}) == -Inf doAssert parseFloatThousandSep("+Inf", {pfNanInf}) == +Inf doAssert parseFloatThousandSep("1000.000000E+90") == 1e93 + doAssert parseFloatThousandSep("-10 000 000 000.0001", sep=' ') == -10000000000.0001 + doAssert parseFloatThousandSep("-10 000 000 000,0001", sep=' ', decimalDot = ',') == -10000000000.0001 discard parseFloatThousandSep("NaN", {pfNanInf}) From c61993e01e697d33e88a405e3e82220deb908a3c Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Tue, 24 Nov 2020 16:33:32 -0300 Subject: [PATCH 62/64] sep and dot must not be '+', 'e', 'i', 'n', 'f', 'a' --- lib/pure/strmisc.nim | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index 499a2d444135..ea019aa71b63 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -131,7 +131,9 @@ since (1, 5): doAssert parseFloatThousandSep("01.00") == 1.0 doAssert parseFloatThousandSep("1,000.000e-9") == 1e-06 - assert sep != '-' and decimalDot notin {'-', ' '} and sep != decimalDot + assert decimalDot notin {'-', '+', 'e', 'i', 'n', 'f', 'a', ' ', '\t', '\v', '\c', '\n', '\f'} + assert sep notin {'-', '+', 'e', 'i', 'n', 'f', 'a', '\n'} + assert sep != decimalDot proc parseFloatThousandSepRaise(i: int; c: char; s: openArray[char]) {.noinline, noreturn.} = raise newException(ValueError, From 8040f52c6b3f90cc7d8954469146e6c310db8ccf Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Tue, 24 Nov 2020 16:44:14 -0300 Subject: [PATCH 63/64] sep and dot must not be '+', 'e', 'i', 'n', 'f', 'a' --- lib/pure/strmisc.nim | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index ea019aa71b63..abdd0de4a36c 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -107,10 +107,11 @@ since (1, 5): ## consequently `parseFloatThousandSep` by design is slower than `parseFloat`. ## ## The following assumptions and requirements must be met: - ## - `sep` must not be `'-'`. - ## - `decimalDot` must not be `'-'` nor `' '`. - ## - `sep` and `decimalDot` must be different. + ## - `str` must not be empty string. ## - `str` must be stripped of trailing and leading whitespaces. + ## - `sep` and `decimalDot` must be different. + ## - `sep` must not be in `{'-', '+', 'e', 'i', 'n', 'f', 'a', '\n'}`. + ## - `decimalDot` must not be in `{'-', '+', 'e', 'i', 'n', 'f', 'a', ' ', '\t', '\v', '\c', '\n', '\f'}`. ## ## See also: ## * `parseFloat `_ From fcc5c3594a197124fe7f402e17b9746b3cff5825 Mon Sep 17 00:00:00 2001 From: Juan Carlos Date: Sun, 29 Nov 2020 11:02:49 -0300 Subject: [PATCH 64/64] We need doAssert that takes varargs for tests --- tests/stdlib/tstrmisc.nim | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/tests/stdlib/tstrmisc.nim b/tests/stdlib/tstrmisc.nim index ed7472c1a65a..584504058387 100644 --- a/tests/stdlib/tstrmisc.nim +++ b/tests/stdlib/tstrmisc.nim @@ -1,4 +1,4 @@ -import strmisc +import strmisc, math func main() = @@ -14,32 +14,16 @@ func main() = doAssert parseFloatThousandSep("6.283185307179586") == 6.283185307179586 doAssert parseFloatThousandSep("2.718281828459045") == 2.718281828459045 - doAssertRaises(ValueError): discard parseFloatThousandSep("1,11") - doAssertRaises(ValueError): discard parseFloatThousandSep("1,1") - doAssertRaises(ValueError): discard parseFloatThousandSep("1,0000.000") - doAssertRaises(ValueError): discard parseFloatThousandSep("--") - doAssertRaises(ValueError): discard parseFloatThousandSep("..") - doAssertRaises(ValueError): discard parseFloatThousandSep("1,,000") - doAssertRaises(ValueError): discard parseFloatThousandSep("1..000") - doAssertRaises(ValueError): discard parseFloatThousandSep("1,000000") - doAssertRaises(ValueError): discard parseFloatThousandSep(",1") - doAssertRaises(ValueError): discard parseFloatThousandSep("1,") - doAssertRaises(ValueError): discard parseFloatThousandSep("1.") - doAssertRaises(ValueError): discard parseFloatThousandSep(".1") doAssertRaises(ValueError): discard parseFloatThousandSep(" ", {pfDotOptional}) doAssertRaises(ValueError): discard parseFloatThousandSep(".1.", {pfLeadingDot,pfTrailingDot}) - doAssertRaises(ValueError): discard parseFloatThousandSep("10,00.0") - doAssertRaises(ValueError): discard parseFloatThousandSep("1,000.000ee9") doAssertRaises(ValueError): discard parseFloatThousandSep("1ee9", {pfDotOptional}) - doAssertRaises(ValueError): discard parseFloatThousandSep("1e02.2") - doAssertRaises(ValueError): discard parseFloatThousandSep("1.0e--9") - doAssertRaises(ValueError): discard parseFloatThousandSep("Inf") - doAssertRaises(ValueError): discard parseFloatThousandSep("-Inf") - doAssertRaises(ValueError): discard parseFloatThousandSep("+Inf") - doAssertRaises(ValueError): discard parseFloatThousandSep("NaN") doAssertRaises(ValueError): discard parseFloatThousandSep("aNa", {pfNanInf}) doAssertRaises(ValueError): discard parseFloatThousandSep("fnI", {pfNanInf}) doAssertRaises(ValueError): discard parseFloatThousandSep("1,000.000,000,E,+,9,0", {pfSepAnywhere}) + for s in ["1,11", "1,1", "1,0000.000", "--", "..", "1,,000", "1..000", + "1,000000", ",1", "1,", "1.", ".1", "10,00.0", "1,000.000ee9", "1e02.2", + "1.0e--9", "Inf", "-Inf", "+Inf", "NaN"]: + doAssertRaises(ValueError): discard parseFloatThousandSep(s) doAssert parseFloatThousandSep("10,00.0", {pfSepAnywhere}) == 1000.0 doAssert parseFloatThousandSep("0", {pfDotOptional}) == 0.0 @@ -68,7 +52,7 @@ func main() = doAssert parseFloatThousandSep("1000.000000E+90") == 1e93 doAssert parseFloatThousandSep("-10 000 000 000.0001", sep=' ') == -10000000000.0001 doAssert parseFloatThousandSep("-10 000 000 000,0001", sep=' ', decimalDot = ',') == -10000000000.0001 - discard parseFloatThousandSep("NaN", {pfNanInf}) + doAssert classify(parseFloatThousandSep("NaN", {pfNanInf})) == fcNan main()