From 739a8ea06095871a92cc3ef9d35a7ca782390195 Mon Sep 17 00:00:00 2001 From: Magnus Jöud Date: Wed, 14 Oct 2015 13:44:20 +0200 Subject: added maxsplit argument to strutils.split --- lib/pure/strutils.nim | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) (limited to 'lib/pure/strutils.nim') diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index a78fed4b9..c9b23daf2 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -322,7 +322,7 @@ proc toOctal*(c: char): string {.noSideEffect, rtl, extern: "nsuToOctal".} = result[i] = chr(val mod 8 + ord('0')) val = val div 8 -iterator split*(s: string, seps: set[char] = Whitespace): string = +iterator split*(s: string, seps: set[char] = Whitespace, maxsplit: int = -1): string = ## Splits the string `s` into substrings using a group of separators. ## ## Substrings are separated by a substring containing only `seps`. Note @@ -367,15 +367,20 @@ iterator split*(s: string, seps: set[char] = Whitespace): string = ## "08.398990" ## var last = 0 + var splits = maxsplit assert(not ('\0' in seps)) while last < len(s): while s[last] in seps: inc(last) var first = last while last < len(s) and s[last] notin seps: inc(last) # BUGFIX! if first <= last-1: + if splits == 0: + yield substr(s, first, len(s)-1) + break yield substr(s, first, last-1) + dec(splits) -iterator split*(s: string, sep: char): string = +iterator split*(s: string, sep: char, maxsplit: int = -1): string = ## Splits the string `s` into substrings using a single separator. ## ## Substrings are separated by the character `sep`. @@ -402,26 +407,36 @@ iterator split*(s: string, sep: char): string = ## "" ## var last = 0 + var splits = maxsplit assert('\0' != sep) if len(s) > 0: # `<=` is correct here for the edge cases! while last <= len(s): var first = last while last < len(s) and s[last] != sep: inc(last) + if splits == 0: + yield substr(s, first, len(s)-1) + break yield substr(s, first, last-1) + dec(splits) inc(last) -iterator split*(s: string, sep: string): string = +iterator split*(s: string, sep: string, maxsplit: int = -1): string = ## Splits the string `s` into substrings using a string separator. ## ## Substrings are separated by the string `sep`. var last = 0 + var splits = maxsplit if len(s) > 0: while last <= len(s): var first = last while last < len(s) and s.substr(last, last + `_, but is a ## proc that returns a sequence of substrings. - accumulateResult(split(s, seps)) + accumulateResult(split(s, seps, maxsplit)) -proc split*(s: string, sep: char): seq[string] {.noSideEffect, +proc split*(s: string, sep: char, maxsplit: int = -1): seq[string] {.noSideEffect, rtl, extern: "nsuSplitChar".} = ## The same as the `split iterator <#split.i,string,char>`_, but is a proc ## that returns a sequence of substrings. - accumulateResult(split(s, sep)) + accumulateResult(split(s, sep, maxsplit)) -proc split*(s: string, sep: string): seq[string] {.noSideEffect, +proc split*(s: string, sep: string, maxsplit: int = -1): seq[string] {.noSideEffect, rtl, extern: "nsuSplitString".} = ## Splits the string `s` into substrings using a string separator. ## ## Substrings are separated by the string `sep`. This is a wrapper around the ## `split iterator <#split.i,string,string>`_. - accumulateResult(split(s, sep)) + accumulateResult(split(s, sep, maxsplit)) proc toHex*(x: BiggestInt, len: Positive): string {.noSideEffect, rtl, extern: "nsuToHex".} = @@ -1660,7 +1675,7 @@ when isMainModule: doAssert isAlpha("Rasp") doAssert isAlpha("Args") doAssert(not isAlpha("$Tomato")) - + doAssert isAlphaNumeric('3') doAssert isAlphaNumeric('R') doAssert(not isAlphaNumeric('!')) -- cgit 1.4.1-2-gfad0 From 4e8e5af934d7c44f5c6fe659b4dcca1e16cf964d Mon Sep 17 00:00:00 2001 From: Magnus Jöud Date: Wed, 14 Oct 2015 14:00:51 +0200 Subject: added tests for strutils.split --- lib/pure/strutils.nim | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'lib/pure/strutils.nim') diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index c9b23daf2..516ca953b 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -1719,3 +1719,9 @@ when isMainModule: doAssert isUpper("ABC") doAssert(not isUpper("AAcc")) doAssert(not isUpper("A#$")) + + let s = " this is an example " + doAssert s.split() == @["this", "is", "an", "example"] + doAssert s.split(maxsplit=4) == @["this", "is", "an", "example"] + doAssert s.split(' ', maxsplit=4) == @["", "this", "", "", "is an example "] + doAssert s.split(" ", maxsplit=4) == @["", "this", "", "", "is an example "] -- cgit 1.4.1-2-gfad0 From 755d89e32d39eb08e85a98b421909a7535051c3c Mon Sep 17 00:00:00 2001 From: Magnus Jöud Date: Wed, 14 Oct 2015 15:29:27 +0200 Subject: modified strutils.split --- lib/pure/strutils.nim | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) (limited to 'lib/pure/strutils.nim') diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 516ca953b..6eb87d91b 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -374,10 +374,9 @@ iterator split*(s: string, seps: set[char] = Whitespace, maxsplit: int = -1): st var first = last while last < len(s) and s[last] notin seps: inc(last) # BUGFIX! if first <= last-1: - if splits == 0: - yield substr(s, first, len(s)-1) - break + if splits == 0: last = len(s) yield substr(s, first, last-1) + if splits == 0: break dec(splits) iterator split*(s: string, sep: char, maxsplit: int = -1): string = @@ -414,10 +413,9 @@ iterator split*(s: string, sep: char, maxsplit: int = -1): string = while last <= len(s): var first = last while last < len(s) and s[last] != sep: inc(last) - if splits == 0: - yield substr(s, first, len(s)-1) - break + if splits == 0: last = len(s) yield substr(s, first, last-1) + if splits == 0: break dec(splits) inc(last) @@ -432,10 +430,9 @@ iterator split*(s: string, sep: string, maxsplit: int = -1): string = var first = last while last < len(s) and s.substr(last, last + Date: Wed, 30 Mar 2016 17:34:41 +0600 Subject: Add parseUInt and parseBiggestUInt functions to stdlib (parseutils, strutils) --- lib/pure/parseutils.nim | 51 ++++++++++++++++++++++++++++++++++++++++++++ lib/pure/strutils.nim | 18 ++++++++++++++++ tests/stdlib/tparseuints.nim | 11 ++++++++++ 3 files changed, 80 insertions(+) create mode 100644 tests/stdlib/tparseuints.nim (limited to 'lib/pure/strutils.nim') diff --git a/lib/pure/parseutils.nim b/lib/pure/parseutils.nim index 698bde42a..66bdadb29 100644 --- a/lib/pure/parseutils.nim +++ b/lib/pure/parseutils.nim @@ -234,6 +234,57 @@ proc parseInt*(s: string, number: var int, start = 0): int {. elif result != 0: number = int(res) +# overflowChecks doesn't work with uint64 +proc rawParseUInt(s: string, b: var uint64, start = 0): int = + var + res = 0'u64 + prev = 0'u64 + i = start + if s[i] == '+': inc(i) # Allow + if s[i] in {'0'..'9'}: + b = 0 + while s[i] in {'0'..'9'}: + prev = res + res = res * 10 + (ord(s[i]) - ord('0')).uint64 + if prev > res: + return 0 # overflowChecks emulation + inc(i) + while s[i] == '_': inc(i) # underscores are allowed and ignored + b = res + result = i - start + +proc parseBiggestUInt*(s: string, number: var uint64, start = 0): int {. + rtl, extern: "npuParseBiggestUInt", noSideEffect.} = + ## parses an unsigned integer starting at `start` and stores the value into `number`. + ## Result is the number of processed chars or 0 if there is no integer or overflow detected. + var res: uint64 + # use 'res' for exception safety (don't write to 'number' in case of an + # overflow exception): + result = rawParseUInt(s, res, start) + number = res + +# Workaround for high(uint) +proc highUInt(): uint64 = + when sizeof(uint) == 4: + 0xFFFFFFFF'u64 + elif sizeof(uint) == 8: + 0xFFFFFFFFFFFFFFFF'u64 + else: + {.fatal: "Unknoun uint size: " & $sizeof(uint).} + +proc parseUInt*(s: string, number: var uint, start = 0): int {. + rtl, extern: "npuParseUInt", noSideEffect.} = + ## parses an unsigned integer starting at `start` and stores the value into `number`. + ## Result is the number of processed chars or 0 if there is no integer. + ## Result is the number of processed chars or 0 if there is no integer or overflow detected. + var res: uint64 + result = parseBiggestUInt(s, res, start) + if (sizeof(uint) <= 4) and + (res > highUInt()): + raise newException(OverflowError, "overflow") + elif result != 0: + number = uint(res) + proc parseBiggestFloat*(s: string, number: var BiggestFloat, start = 0): int {. magic: "ParseBiggestFloat", importc: "nimParseBiggestFloat", noSideEffect.} ## parses a float starting at `start` and stores the value into `number`. diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index f2c1e77e1..eebadf4c0 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -560,6 +560,24 @@ proc parseBiggestInt*(s: string): BiggestInt {.noSideEffect, procvar, if L != s.len or L == 0: raise newException(ValueError, "invalid integer: " & s) +proc parseUInt*(s: string): uint {.noSideEffect, procvar, + rtl, extern: "nsuParseUInt".} = + ## Parses a decimal unsigned integer value contained in `s`. + ## + ## If `s` is not a valid integer, `ValueError` is raised. + var L = parseutils.parseUInt(s, result, 0) + if L != s.len or L == 0: + raise newException(ValueError, "invalid unsigned integer: " & s) + +proc parseBiggestUInt*(s: string): uint64 {.noSideEffect, procvar, + rtl, extern: "nsuParseBiggestUInt".} = + ## Parses a decimal unsigned integer value contained in `s`. + ## + ## If `s` is not a valid integer, `ValueError` is raised. + var L = parseutils.parseBiggestUInt(s, result, 0) + if L != s.len or L == 0: + raise newException(ValueError, "invalid unsigned integer: " & s) + proc parseFloat*(s: string): float {.noSideEffect, procvar, rtl, extern: "nsuParseFloat".} = ## Parses a decimal floating point value contained in `s`. If `s` is not diff --git a/tests/stdlib/tparseuints.nim b/tests/stdlib/tparseuints.nim new file mode 100644 index 000000000..5be3bcbd0 --- /dev/null +++ b/tests/stdlib/tparseuints.nim @@ -0,0 +1,11 @@ +discard """ + action: run +""" +import unittest, strutils + +suite "parseutils": + test "uint": + check: parseBiggestUInt("0") == 0'u64 + check: parseBiggestUInt("18446744073709551615") == 0xFFFF_FFFF_FFFF_FFFF'u64 + expect(ValueError): + discard parseBiggestUInt("18446744073709551616") -- cgit 1.4.1-2-gfad0 From 73e48f9c9cfa3d6f2dad1d9e11efec2975eca1e5 Mon Sep 17 00:00:00 2001 From: Dominik Picheta Date: Mon, 4 Apr 2016 13:39:42 +0100 Subject: Fixes #4037 --- lib/pure/strutils.nim | 90 +++++++++++++++++++++++++++++++++++---------------- web/news.txt | 3 ++ 2 files changed, 66 insertions(+), 27 deletions(-) (limited to 'lib/pure/strutils.nim') diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index eebadf4c0..a5a4ee509 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -784,8 +784,7 @@ proc indent*(s: string, count: Natural, padding: string = " "): string {.noSideEffect, rtl, extern: "nsuIndent".} = ## Indents each line in ``s`` by ``count`` amount of ``padding``. ## - ## **Note:** This currently does not preserve the specific new line characters - ## used. + ## **Note:** This does not preserve the new line characters used in ``s``. result = "" var i = 0 for line in s.splitLines(): @@ -796,32 +795,39 @@ proc indent*(s: string, count: Natural, padding: string = " "): string result.add(line) i.inc -proc unindent*(s: string, eatAllIndent = false): string {. - noSideEffect, rtl, extern: "nsuUnindent".} = - ## Unindents `s`. - result = newStringOfCap(s.len) +proc unindent*(s: string, count: Natural, padding: string = " "): string + {.noSideEffect, rtl, extern: "nsuUnindent".} = + ## Unindents each line in ``s`` by ``count`` amount of ``padding``. + ## + ## **Note:** This does not preserve the new line characters used in ``s``. + result = "" var i = 0 - var pattern = true - var indent = 0 - while s[i] == ' ': inc i - var level = if i == 0: -1 else: i - while i < s.len: - if s[i] == ' ': - if i > 0 and s[i-1] in {'\l', '\c'}: - pattern = true - indent = 0 - if pattern: - inc(indent) - if indent > level and not eatAllIndent: - result.add(s[i]) - if level < 0: level = indent - else: - # a space somewhere: do not delete - result.add(s[i]) - else: - pattern = false - result.add(s[i]) - inc i + for line in s.splitLines(): + if i != 0: + result.add("\n") + var indentCount = 0 + for j in 0.. Date: Tue, 10 May 2016 21:54:33 +0200 Subject: minor whitespace change --- lib/pure/strutils.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/pure/strutils.nim') diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index a5a4ee509..c3d6d75bd 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -669,7 +669,7 @@ proc repeat*(s: string, n: Natural): string {.noSideEffect, result = newStringOfCap(n * s.len) for i in 1..n: result.add(s) -template spaces*(n: Natural): string = repeat(' ',n) +template spaces*(n: Natural): string = repeat(' ', n) ## Returns a String with `n` space characters. You can use this proc ## to left align strings. Example: ## -- cgit 1.4.1-2-gfad0 From d855ad70453e4454992db87d0ab73f027e2fde61 Mon Sep 17 00:00:00 2001 From: "A. S. Budden" Date: Wed, 1 Jun 2016 12:56:01 +0100 Subject: Reimplementation of formatSize to return a more meaningful value (fixes #4198). --- lib/pure/strutils.nim | 113 +++++++++++++++++++++++++++++++++++++++----------- web/news.txt | 7 ++++ 2 files changed, 96 insertions(+), 24 deletions(-) (limited to 'lib/pure/strutils.nim') diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 22641bca2..5bd304325 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -1392,7 +1392,7 @@ type proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault, precision: range[0..32] = 16; - decimalSep = '.'): string {. + decimalSep = '.', trim = false): string {. noSideEffect, rtl, extern: "nsu$1".} = ## Converts a floating point value `f` to a string. ## @@ -1404,6 +1404,8 @@ proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault, ## after the decimal point for Nim's ``biggestFloat`` type. ## ## If ``precision == 0``, it tries to format it nicely. + ## + ## If ``trim == true``, trailing zeros will be removed. when defined(js): var res: cstring case format @@ -1423,6 +1425,7 @@ proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault, var frmtstr {.noinit.}: array[0..5, char] buf {.noinit.}: array[0..2500, char] + splResult: seq[string] L: cint frmtstr[0] = '%' if precision > 0: @@ -1443,8 +1446,21 @@ proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault, if buf[i] in {'.', ','}: result[i] = decimalsep else: result[i] = buf[i] + # Trim trailing zeros if required (used by formatSize) + if trim and result.contains(decimalSep): + if result.contains('e'): + splResult = result.split('e') + result = splResult[0] + while result[result.high] == '0': + result.setLen(result.len-1) + if result[result.high] == decimalSep: + result.setLen(result.len-1) + if splResult.len > 0: + result &= "e" & splResult[1] + proc formatFloat*(f: float, format: FloatFormatMode = ffDefault, - precision: range[0..32] = 16; decimalSep = '.'): string {. + precision: range[0..32] = 16; decimalSep = '.', + trim = false): string {. noSideEffect, rtl, extern: "nsu$1".} = ## Converts a floating point value `f` to a string. ## @@ -1454,30 +1470,69 @@ proc formatFloat*(f: float, format: FloatFormatMode = ffDefault, ## of significant digits to be printed. ## `precision`'s default value is the maximum number of meaningful digits ## after the decimal point for Nim's ``float`` type. - result = formatBiggestFloat(f, format, precision, decimalSep) + ## If `trim` is set to true, trailing zeros will be removed. + result = formatBiggestFloat(f, format, precision, decimalSep, trim) + +type + BinaryPrefixMode* = enum ## the different names for binary prefixes + bpIEC, # use the IEC/ISO standard prefixes such as kibi + bpColloquial # use the colloquial kilo, mega etc -proc formatSize*(bytes: BiggestInt, decimalSep = '.'): string = - ## Rounds and formats `bytes`. Examples: +proc formatSize*(bytes: int64, + decimalSep = '.', + prefix = bpIEC, + includeSpace = false): string = + ## Rounds and formats `bytes`. + ## + ## By default, uses the IEC/ISO standard binary prefixes, so 1024 will be + ## formatted as 1KiB. Set prefix to `bpColloquial` to use the colloquial + ## names from the SI standard (e.g. k for 1000 being reused as 1024). + ## + ## `includeSpace` can be set to true to include the (SI preferred) space + ## between the number and the unit (e.g. 1 KiB). + ## + ## Examples: ## ## .. code-block:: nim ## - ## formatSize(1'i64 shl 31 + 300'i64) == "2.204GB" - ## formatSize(4096) == "4KB" - ## - template frmt(a, b, c: expr): expr = - let bs = $b - insertSep($a) & decimalSep & bs.substr(0, 2) & c - let gigabytes = bytes shr 30 - let megabytes = bytes shr 20 - let kilobytes = bytes shr 10 - if gigabytes != 0: - result = frmt(gigabytes, megabytes, "GB") - elif megabytes != 0: - result = frmt(megabytes, kilobytes, "MB") - elif kilobytes != 0: - result = frmt(kilobytes, bytes, "KB") + ## formatSize((1'i64 shl 31) + (300'i64 shl 20)) == "2.293GiB" + ## formatSize((2.234*1024*1024).int) == "2.234MiB" + ## formatSize(4096, includeSpace=true) == "4 KiB" + ## formatSize(4096, prefix=bpColloquial, includeSpace=true) == "4 kB" + ## formatSize(4096) == "4KiB" + ## formatSize(5_378_934, prefix=bpColloquial, decimalSep=',') == "5,13MB" + ## + const iecPrefixes = ["", "Ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi"] + const collPrefixes = ["", "k", "M", "G", "T", "P", "E", "Z", "Y"] + var + xb: int64 = bytes + fbytes: float + last_xb: int64 = bytes + matchedIndex: int + prefixes: array[9, string] + if prefix == bpColloquial: + prefixes = collPrefixes else: - result = insertSep($bytes) & "B" + prefixes = iecPrefixes + + # Iterate through prefixes seeing if value will be greater than + # 0 in each case + for index in 1.. Date: Wed, 1 Jun 2016 14:30:02 +0100 Subject: Moved zero trimming into a separate function (code review comment) --- lib/pure/strutils.nim | 48 +++++++++++++++++++++--------------------------- web/news.txt | 4 ++-- 2 files changed, 23 insertions(+), 29 deletions(-) (limited to 'lib/pure/strutils.nim') diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 5bd304325..4f9d29c8c 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -1392,7 +1392,7 @@ type proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault, precision: range[0..32] = 16; - decimalSep = '.', trim = false): string {. + decimalSep = '.'): string {. noSideEffect, rtl, extern: "nsu$1".} = ## Converts a floating point value `f` to a string. ## @@ -1404,8 +1404,6 @@ proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault, ## after the decimal point for Nim's ``biggestFloat`` type. ## ## If ``precision == 0``, it tries to format it nicely. - ## - ## If ``trim == true``, trailing zeros will be removed. when defined(js): var res: cstring case format @@ -1425,7 +1423,6 @@ proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault, var frmtstr {.noinit.}: array[0..5, char] buf {.noinit.}: array[0..2500, char] - splResult: seq[string] L: cint frmtstr[0] = '%' if precision > 0: @@ -1446,21 +1443,8 @@ proc formatBiggestFloat*(f: BiggestFloat, format: FloatFormatMode = ffDefault, if buf[i] in {'.', ','}: result[i] = decimalsep else: result[i] = buf[i] - # Trim trailing zeros if required (used by formatSize) - if trim and result.contains(decimalSep): - if result.contains('e'): - splResult = result.split('e') - result = splResult[0] - while result[result.high] == '0': - result.setLen(result.len-1) - if result[result.high] == decimalSep: - result.setLen(result.len-1) - if splResult.len > 0: - result &= "e" & splResult[1] - proc formatFloat*(f: float, format: FloatFormatMode = ffDefault, - precision: range[0..32] = 16; decimalSep = '.', - trim = false): string {. + precision: range[0..32] = 16; decimalSep = '.'): string {. noSideEffect, rtl, extern: "nsu$1".} = ## Converts a floating point value `f` to a string. ## @@ -1470,8 +1454,23 @@ proc formatFloat*(f: float, format: FloatFormatMode = ffDefault, ## of significant digits to be printed. ## `precision`'s default value is the maximum number of meaningful digits ## after the decimal point for Nim's ``float`` type. - ## If `trim` is set to true, trailing zeros will be removed. - result = formatBiggestFloat(f, format, precision, decimalSep, trim) + result = formatBiggestFloat(f, format, precision, decimalSep) + +proc trimZeros*(x: string): string {.noSideEffect.} = + ## Trim trailing zeros from a formatted floating point + ## value (`x`). + var splResult: seq[string] + result = x + if result.contains('.') or result.contains(','): + if result.contains('e'): + splResult = result.split('e') + result = splResult[0] + while result[result.high] == '0': + result.setLen(result.len-1) + if result[result.high] in [',', '.']: + result.setLen(result.len-1) + if splResult.len > 0: + result &= "e" & splResult[1] type BinaryPrefixMode* = enum ## the different names for binary prefixes @@ -1527,8 +1526,8 @@ proc formatSize*(bytes: int64, break # xb has the integer number for the latest value; index should be correct fbytes = bytes.float / (1'i64 shl (matchedIndex*10)).float - result = formatFloat(fbytes, format=ffDecimal, precision=3, - decimalSep=decimalSep, trim=true) + result = formatFloat(fbytes, format=ffDecimal, precision=3, decimalSep=decimalSep) + result = trimZeros(result) if includeSpace: result &= " " result &= prefixes[matchedIndex] @@ -1729,11 +1728,6 @@ when isMainModule: doAssert "$# $3 $# $#" % ["a", "b", "c"] == "a c b c" - block: # formatFloat trim tests - doAssert formatFloat(2.33, trim=true) == "2.33" - doAssert formatFloat(2.34e50, ffScientific, precision=8, trim=true) in - ["2.34e+50", "2.34e+050"] - block: # formatSize tests doAssert formatSize((1'i64 shl 31) + (300'i64 shl 20)) == "2.293GiB" doAssert formatSize((2.234*1024*1024).int) == "2.234MiB" diff --git a/web/news.txt b/web/news.txt index 6ef44d3a8..2ede07151 100644 --- a/web/news.txt +++ b/web/news.txt @@ -68,8 +68,8 @@ Library Additions versions are for ``char`` and ``set[char]``. - Added ``splitDecimal`` to ``math.nim`` to split a floating point value into an integer part and a floating part (in the range -1 Date: Wed, 1 Jun 2016 17:59:21 +0200 Subject: adds strutils.toHex variant that knows about the used integer size --- lib/pure/strutils.nim | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'lib/pure/strutils.nim') diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 22641bca2..f59313450 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -542,6 +542,10 @@ proc toHex*(x: BiggestInt, len: Positive): string {.noSideEffect, # handle negative overflow if n == 0 and x < 0: n = -1 +proc toHex*[T](x: T): string = + ## Shortcut for ``toHex(x, T.sizeOf * 2)`` + toHex(x, T.sizeOf * 2) + proc intToStr*(x: int, minchars: Positive = 1): string {.noSideEffect, rtl, extern: "nsuIntToStr".} = ## Converts `x` to its decimal representation. -- cgit 1.4.1-2-gfad0 From f125f2e4ceca08de862d5856d6d14c86f9ff943f Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Wed, 1 Jun 2016 19:34:02 +0200 Subject: make split with strings as separator faster --- lib/pure/strutils.nim | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'lib/pure/strutils.nim') diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index f59313450..85e67f508 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -421,6 +421,11 @@ iterator split*(s: string, sep: char, maxsplit: int = -1): string = dec(splits) inc(last) +proc substrEq(s: string, a, L: int, x: string): bool = + var i = 0 + while i < L and s[a+i] == x[i]: inc i + result = i == L + iterator split*(s: string, sep: string, maxsplit: int = -1): string = ## Splits the string `s` into substrings using a string separator. ## @@ -430,7 +435,7 @@ iterator split*(s: string, sep: string, maxsplit: int = -1): string = if len(s) > 0: while last <= len(s): var first = last - while last < len(s) and s.substr(last, last + Date: Wed, 1 Jun 2016 20:15:55 +0100 Subject: Modified trimZeros to modify the passed value. --- lib/pure/strutils.nim | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) (limited to 'lib/pure/strutils.nim') diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 4f9d29c8c..88f74b893 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -1456,21 +1456,20 @@ proc formatFloat*(f: float, format: FloatFormatMode = ffDefault, ## after the decimal point for Nim's ``float`` type. result = formatBiggestFloat(f, format, precision, decimalSep) -proc trimZeros*(x: string): string {.noSideEffect.} = +proc trimZeros*(x: var string) {.noSideEffect.} = ## Trim trailing zeros from a formatted floating point - ## value (`x`). - var splResult: seq[string] - result = x - if result.contains('.') or result.contains(','): - if result.contains('e'): - splResult = result.split('e') - result = splResult[0] - while result[result.high] == '0': - result.setLen(result.len-1) - if result[result.high] in [',', '.']: - result.setLen(result.len-1) - if splResult.len > 0: - result &= "e" & splResult[1] + ## value (`x`). Modifies the passed value. + var spl: seq[string] + if x.contains('.') or x.contains(','): + if x.contains('e'): + spl= x.split('e') + x = spl[0] + while x[x.high] == '0': + x.setLen(x.len-1) + if x[x.high] in [',', '.']: + x.setLen(x.len-1) + if spl.len > 0: + x &= "e" & spl[1] type BinaryPrefixMode* = enum ## the different names for binary prefixes @@ -1527,7 +1526,7 @@ proc formatSize*(bytes: int64, # xb has the integer number for the latest value; index should be correct fbytes = bytes.float / (1'i64 shl (matchedIndex*10)).float result = formatFloat(fbytes, format=ffDecimal, precision=3, decimalSep=decimalSep) - result = trimZeros(result) + result.trimZeros() if includeSpace: result &= " " result &= prefixes[matchedIndex] -- cgit 1.4.1-2-gfad0 From d16f864b7b4d2a5907e25b4e9d50dbdbe08abe97 Mon Sep 17 00:00:00 2001 From: "A. S. Budden" Date: Thu, 2 Jun 2016 10:25:51 +0100 Subject: Added missing noSideEffect pragma --- lib/pure/strutils.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/pure/strutils.nim') diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 55494a8b5..5ba8a6fd5 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -1488,7 +1488,7 @@ type proc formatSize*(bytes: int64, decimalSep = '.', prefix = bpIEC, - includeSpace = false): string = + includeSpace = false): string {.noSideEffect.} = ## Rounds and formats `bytes`. ## ## By default, uses the IEC/ISO standard binary prefixes, so 1024 will be -- cgit 1.4.1-2-gfad0 From 47b707cd32499905ee9671387ea7203111a53be9 Mon Sep 17 00:00:00 2001 From: "A. S. Budden" Date: Thu, 2 Jun 2016 10:27:55 +0100 Subject: Implemented string formatting functions for engineering format (fixes #4197). --- lib/pure/strutils.nim | 165 ++++++++++++++++++++++++++++++++++++++++++++++++++ web/news.txt | 2 + 2 files changed, 167 insertions(+) (limited to 'lib/pure/strutils.nim') diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 55494a8b5..a5c75b4ec 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -14,6 +14,7 @@ ## `_. import parseutils +from math import pow, round, floor, log10 {.deadCodeElim: on.} @@ -1541,6 +1542,141 @@ proc formatSize*(bytes: int64, result &= prefixes[matchedIndex] result &= "B" +proc formatEng*(f: BiggestFloat, + precision: range[0..32] = 10, + trim: bool = true, + siPrefix: bool = false, + unit: string = nil, + decimalSep = '.'): string {.noSideEffect.} = + ## Converts a floating point value `f` to a string using engineering notation. + ## + ## Numbers in of the range -1000.0= 1000.0: + significand *= 0.001 + fexponent += 3 + # Components of the result: + result = significand.formatBiggestFloat(ffDecimal, precision, decimalSep='.') + exponent = fexponent.int() + + splitResult = result.split('.') + result = splitResult[0] + # result should have at most one decimal character + if splitResult.len() > 1: + # If trim is set, we get rid of trailing zeros. Don't use trimZeros here as + # we can be a bit more efficient through knowledge that there will never be + # an exponent in this part. + if trim: + while splitResult[1].endsWith("0"): + # Trim last character + splitResult[1].setLen(splitResult[1].len-1) + if splitResult[1].len() > 0: + result &= decimalSep & splitResult[1] + else: + result &= decimalSep & splitResult[1] + + # Combine the results accordingly + if siPrefix and exponent != 0: + var p = getPrefix(exponent) + if p != ' ': + suffix = " " & p + exponent = 0 # Exponent replaced by SI prefix + if suffix == "" and unit != nil: + suffix = " " + if unit != nil: + suffix &= unit + if exponent != 0: + result &= "e" & $exponent + result &= suffix + proc findNormalized(x: string, inArray: openArray[string]): int = var i = 0 while i < high(inArray): @@ -1864,4 +2000,33 @@ bar doAssert s.split(' ', maxsplit=4) == @["", "this", "", "", "is an example "] doAssert s.split(" ", maxsplit=4) == @["", "this", "", "", "is an example "] + block: # formatEng tests + doAssert formatEng(0, 2, trim=false) == "0.00" + doAssert formatEng(0, 2) == "0" + doAssert formatEng(53, 2, trim=false) == "53.00" + doAssert formatEng(0.053, 2, trim=false) == "53.00e-3" + doAssert formatEng(0.053, 4, trim=false) == "53.0000e-3" + doAssert formatEng(0.053, 4, trim=true) == "53e-3" + doAssert formatEng(0.053, 0) == "53e-3" + doAssert formatEng(52731234) == "52.731234e6" + doAssert formatEng(-52731234) == "-52.731234e6" + doAssert formatEng(52731234, 1) == "52.7e6" + doAssert formatEng(-52731234, 1) == "-52.7e6" + doAssert formatEng(52731234, 1, decimalSep=',') == "52,7e6" + doAssert formatEng(-52731234, 1, decimalSep=',') == "-52,7e6" + + doAssert formatEng(4100, siPrefix=true, unit="V") == "4.1 kV" + doAssert formatEng(4.1, siPrefix=true, unit="V") == "4.1 V" + doAssert formatEng(4.1, siPrefix=true) == "4.1" # Note lack of space + doAssert formatEng(4100, siPrefix=true) == "4.1 k" + doAssert formatEng(4.1, siPrefix=true, unit="") == "4.1 " # Includes space + doAssert formatEng(4100, siPrefix=true, unit="") == "4.1 k" + doAssert formatEng(4100) == "4.1e3" + doAssert formatEng(4100, unit="V") == "4.1e3 V" + doAssert formatEng(4100, unit="") == "4.1e3 " # Space with unit="" + # Don't use SI prefix as number is too big + doAssert formatEng(3.1e22, siPrefix=true, unit="a") == "31e21 a" + # Don't use SI prefix as number is too small + doAssert formatEng(3.1e-25, siPrefix=true, unit="A") == "310e-27 A" + #echo("strutils tests passed") diff --git a/web/news.txt b/web/news.txt index 2ede07151..12f08e5eb 100644 --- a/web/news.txt +++ b/web/news.txt @@ -70,6 +70,8 @@ Library Additions into an integer part and a floating part (in the range -1 Date: Mon, 6 Jun 2016 10:03:46 +0100 Subject: Added startsWith/endsWith implementations for character prefix/suffix (fixes #4252). --- lib/pure/strutils.nim | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'lib/pure/strutils.nim') diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index be80685ab..f9aa79483 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -862,6 +862,10 @@ proc startsWith*(s, prefix: string): bool {.noSideEffect, if s[i] != prefix[i]: return false inc(i) +proc startsWith*(s: string, prefix: char): bool {.noSideEffect.} = + ## Returns true iff ``s`` starts with ``prefix``. + result = s[0] == prefix + proc endsWith*(s, suffix: string): bool {.noSideEffect, rtl, extern: "nsuEndsWith".} = ## Returns true iff ``s`` ends with ``suffix``. @@ -874,6 +878,10 @@ proc endsWith*(s, suffix: string): bool {.noSideEffect, inc(i) if suffix[i] == '\0': return true +proc endsWith*(s: string, suffix: char): bool {.noSideEffect.} = + ## Returns true iff ``s`` ends with ``suffix``. + result = s[s.high] == suffix + proc continuesWith*(s, substr: string, start: Natural): bool {.noSideEffect, rtl, extern: "nsuContinuesWith".} = ## Returns true iff ``s`` continues with ``substr`` at position ``start``. @@ -2029,4 +2037,12 @@ bar # Don't use SI prefix as number is too small doAssert formatEng(3.1e-25, siPrefix=true, unit="A") == "310e-27 A" + block: # startsWith / endsWith char tests + var s = "abcdef" + doAssert s.startsWith('a') + doAssert s.startsWith('b') == false + doAssert s.endsWith('f') + doAssert s.endsWith('a') == false + doAssert s.endsWith('\0') == false + #echo("strutils tests passed") -- cgit 1.4.1-2-gfad0 From 2f16854d0f6c9cac1c045aeb20aa401b571315ed Mon Sep 17 00:00:00 2001 From: "A. S. Budden" Date: Mon, 6 Jun 2016 18:43:39 +0100 Subject: Added inline pragmas. --- lib/pure/strutils.nim | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'lib/pure/strutils.nim') diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index f9aa79483..86af6ac88 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -862,7 +862,7 @@ proc startsWith*(s, prefix: string): bool {.noSideEffect, if s[i] != prefix[i]: return false inc(i) -proc startsWith*(s: string, prefix: char): bool {.noSideEffect.} = +proc startsWith*(s: string, prefix: char): bool {.noSideEffect, inline.} = ## Returns true iff ``s`` starts with ``prefix``. result = s[0] == prefix @@ -878,7 +878,7 @@ proc endsWith*(s, suffix: string): bool {.noSideEffect, inc(i) if suffix[i] == '\0': return true -proc endsWith*(s: string, suffix: char): bool {.noSideEffect.} = +proc endsWith*(s: string, suffix: char): bool {.noSideEffect, inline.} = ## Returns true iff ``s`` ends with ``suffix``. result = s[s.high] == suffix -- cgit 1.4.1-2-gfad0 From f6e30981a84466aa0a0cf7d51023d5edbb3c2703 Mon Sep 17 00:00:00 2001 From: Joey Payne Date: Sat, 4 Jun 2016 15:05:30 -0600 Subject: Add new procs for string manipulation Add center, isTitle, title, partition, rpartition, rsplit, swapCase, translate, and expandTabs --- lib/pure/strutils.nim | 306 +++++++++++++++++++++++++++++++++++++++++++++++++- lib/pure/unicode.nim | 238 ++++++++++++++++++++++++++++++++++----- 2 files changed, 510 insertions(+), 34 deletions(-) (limited to 'lib/pure/strutils.nim') diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 86af6ac88..b8b3c77c7 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -15,6 +15,7 @@ import parseutils from math import pow, round, floor, log10 +from algorithm import reverse {.deadCodeElim: on.} @@ -325,7 +326,8 @@ proc toOctal*(c: char): string {.noSideEffect, rtl, extern: "nsuToOctal".} = result[i] = chr(val mod 8 + ord('0')) val = val div 8 -iterator split*(s: string, seps: set[char] = Whitespace, maxsplit: int = -1): string = +iterator split*(s: string, seps: set[char] = Whitespace, + maxsplit: int = -1): string = ## Splits the string `s` into substrings using a group of separators. ## ## Substrings are separated by a substring containing only `seps`. Note @@ -422,10 +424,13 @@ iterator split*(s: string, sep: char, maxsplit: int = -1): string = dec(splits) inc(last) -proc substrEq(s: string, a, L: int, x: string): bool = +proc substrEq(s: string, pos: int, substr: string): bool = var i = 0 - while i < L and s[a+i] == x[i]: inc i - result = i == L + var length = substr.len + while i < length and s[pos+i] == substr[i]: + inc i + + return i == length iterator split*(s: string, sep: string, maxsplit: int = -1): string = ## Splits the string `s` into substrings using a string separator. @@ -433,10 +438,11 @@ iterator split*(s: string, sep: string, maxsplit: int = -1): string = ## Substrings are separated by the string `sep`. var last = 0 var splits = maxsplit + if len(s) > 0: while last <= len(s): var first = last - while last < len(s) and not s.substrEq(last, sep.len, sep): + while last < len(s) and not s.substrEq(last, sep): inc(last) if splits == 0: last = len(s) yield substr(s, first, last-1) @@ -444,6 +450,108 @@ iterator split*(s: string, sep: string, maxsplit: int = -1): string = dec(splits) inc(last, sep.len) +# --------- Private templates for different rsplit separators ----------- + +template stringHasSep(s: string, index: int, seps: set[char]): bool = + s[index] in seps + +template stringHasSep(s: string, index: int, sep: char): bool = + s[index] == sep + +template stringHasSep(s: string, index: int, sep: string): bool = + s.substrEq(index, sep) + +template rsplitCommon(s, sep, maxsplit, sepLen) = + ## Common code for rsplit functions + var + last = s.len - 1 + first = last + splits = maxsplit + startPos = 0 + + if len(s) > 0: + # go to -1 in order to get separators at the beginning + while first >= -1: + while first >= 0 and not stringHasSep(s, first, sep): + dec(first) + + if splits == 0: + # No more splits means set first to the beginning + first = -1 + + if first == -1: + startPos = 0 + else: + startPos = first + sepLen + + yield substr(s, startPos, last) + + if splits == 0: + break + + dec(splits) + dec(first) + + last = first + +iterator rsplit*(s: string, seps: set[char] = Whitespace, + maxsplit: int = -1): string = + ## Splits the string `s` into substrings from the right using a + ## string separator. Works exactly the same as `split iterator + ## <#split.i,string,char>`_ except in reverse order. + ## + ## .. code-block:: nim + ## for piece in "foo bar".rsplit(WhiteSpace): + ## echo piece + ## + ## Results in: + ## + ## .. code-block:: nim + ## "bar" + ## "foo" + ## + ## Substrings are separated from the right by the set of chars `seps` + + rsplitCommon(s, seps, maxsplit, 1) + +iterator rsplit*(s: string, sep: char, + maxsplit: int = -1): string = + ## Splits the string `s` into substrings from the right using a + ## string separator. Works exactly the same as `split iterator + ## <#split.i,string,char>`_ except in reverse order. + ## + ## .. code-block:: nim + ## for piece in "foo:bar".rsplit(':'): + ## echo piece + ## + ## Results in: + ## + ## .. code-block:: nim + ## "bar" + ## "foo" + ## + ## Substrings are separated from the right by the char `sep` + rsplitCommon(s, sep, maxsplit, 1) + +iterator rsplit*(s: string, sep: string, maxsplit: int = -1, + keepSeparators: bool = false): string = + ## Splits the string `s` into substrings from the right using a + ## string separator. Works exactly the same as `split iterator + ## <#split.i,string,string>`_ except in reverse order. + ## + ## .. code-block:: nim + ## for piece in "foothebar".rsplit("the"): + ## echo piece + ## + ## Results in: + ## + ## .. code-block:: nim + ## "bar" + ## "foo" + ## + ## Substrings are separated from the right by the string `sep` + rsplitCommon(s, sep, maxsplit, sep.len) + iterator splitLines*(s: string): string = ## Splits the string `s` into its containing lines. ## @@ -531,6 +639,73 @@ proc split*(s: string, sep: string, maxsplit: int = -1): seq[string] {.noSideEff ## `split iterator <#split.i,string,string>`_. accumulateResult(split(s, sep, maxsplit)) +proc rsplit*(s: string, seps: set[char] = Whitespace, + maxsplit: int = -1): seq[string] + {.noSideEffect, rtl, extern: "nsuRSplitCharSet".} = + ## The same as the `rsplit iterator <#rsplit.i,string,set[char]>`_, but is a + ## proc that returns a sequence of substrings. + ## + ## A possible common use case for `rsplit` is path manipulation, + ## particularly on systems that don't use a common delimiter. + ## + ## For example, if a system had `#` as a delimiter, you could + ## do the following to get the tail of the path: + ## + ## .. code-block:: nim + ## var tailSplit = rsplit("Root#Object#Method#Index", {'#'}, maxsplit=1) + ## + ## Results in `tailSplit` containing: + ## + ## .. code-block:: nim + ## @["Root#Object#Method", "Index"] + ## + accumulateResult(rsplit(s, seps, maxsplit)) + result.reverse() + +proc rsplit*(s: string, sep: char, maxsplit: int = -1): seq[string] + {.noSideEffect, rtl, extern: "nsuRSplitChar".} = + ## The same as the `split iterator <#rsplit.i,string,char>`_, but is a proc + ## that returns a sequence of substrings. + ## + ## A possible common use case for `rsplit` is path manipulation, + ## particularly on systems that don't use a common delimiter. + ## + ## For example, if a system had `#` as a delimiter, you could + ## do the following to get the tail of the path: + ## + ## .. code-block:: nim + ## var tailSplit = rsplit("Root#Object#Method#Index", '#', maxsplit=1) + ## + ## Results in `tailSplit` containing: + ## + ## .. code-block:: nim + ## @["Root#Object#Method", "Index"] + ## + accumulateResult(rsplit(s, sep, maxsplit)) + result.reverse() + +proc rsplit*(s: string, sep: string, maxsplit: int = -1): seq[string] + {.noSideEffect, rtl, extern: "nsuRSplitString".} = + ## The same as the `split iterator <#rsplit.i,string,string>`_, but is a proc + ## that returns a sequence of substrings. + ## + ## A possible common use case for `rsplit` is path manipulation, + ## particularly on systems that don't use a common delimiter. + ## + ## For example, if a system had `#` as a delimiter, you could + ## do the following to get the tail of the path: + ## + ## .. code-block:: nim + ## var tailSplit = rsplit("Root#Object#Method#Index", "#", maxsplit=1) + ## + ## Results in `tailSplit` containing: + ## + ## .. code-block:: nim + ## @["Root#Object#Method", "Index"] + ## + accumulateResult(rsplit(s, sep, maxsplit)) + result.reverse() + proc toHex*(x: BiggestInt, len: Positive): string {.noSideEffect, rtl, extern: "nsuToHex".} = ## Converts `x` to its hexadecimal representation. @@ -1035,6 +1210,62 @@ proc rfind*(s: string, sub: char, start: int = -1): int {.noSideEffect, if sub == s[i]: return i return -1 +proc partition*(s: string, sep: string, + right: bool = false): (string, string, string) + {.noSideEffect, procvar, rtl, extern: "nsuPartitionStr".} = + ## Split the string at the first or last occurrence of `sep` into a 3-tuple + ## + ## Returns a 3 string tuple of (beforeSep, `sep`, afterSep) or + ## (`s`, "", "") if `sep` is not found and `right` is false or + ## ("", "", `s`) if `sep` is not found and `right` is true + + let position = if right: s.rfind(sep) else: s.find(sep) + + if position != -1: + let + beforeSep = s[0 ..< position] + afterSep = s[position + sep.len ..< s.len] + + return (s[0 ..< position], sep, afterSep) + + return if right: ("", "", s) else: (s, "", "") + +proc rpartition*(s: string, sep: string): (string, string, string) + {.noSideEffect, procvar, rtl, extern: "nsuRPartitionStr".} = + ## Split the string at the last occurrence of `sep` into a 3-tuple + ## + ## Returns a 3 string tuple of (beforeSep, `sep`, afterSep) or + ## ("", "", `s`) if `sep` is not found + return partition(s, sep, right = true) + +proc center*(s: string, width: int, fillChar: char = ' '): string {. + noSideEffect, rtl, extern: "nsuCenterString".} = + ## Return the contents of `s` centered in a string `width` long using + ## `fillChar` as padding. + ## + ## The original string is returned if `width` is less than or equal + ## to `s.len`. + if width <= s.len: + return s + + result = newString(width) + + # Left padding will be one fillChar + # smaller if there are an odd number + # of characters + let + charsLeft = (width - s.len) + leftPadding = charsLeft div 2 + + for i in 0 ..< width: + if i >= leftPadding and i < leftPadding + s.len: + # we are where the string should be located + result[i] = s[i-leftPadding] + else: + # we are either before or after where + # the string s should go + result[i] = fillChar + proc count*(s: string, sub: string, overlapping: bool = false): int {. noSideEffect, rtl, extern: "nsuCountString".} = ## Count the occurrences of a substring `sub` in the string `s`. @@ -1116,6 +1347,38 @@ proc replace*(s: string, sub, by: char): string {.noSideEffect, else: result[i] = s[i] inc(i) +proc expandTabs*(s: string, tabSize: int = 8): string {.noSideEffect, + procvar, rtl, extern: "nsuExpandTabsStr".} = + ## Expand tab characters in `s` by `tabSize` spaces + + if len(s) == 0: + return s + + result = newStringOfCap(s.len + s.len shr 2) + + var pos = 0 + + template addSpaces(n) = + for j in 0 ..< n: + result.add(' ') + pos += 1 + + for i in 0 ..< len(s): + let c = s[i] + + if c == '\t': + let + denominator = if tabSize > 0: tabSize else: 1 + numSpaces = tabSize - pos mod denominator + + addSpaces(numSpaces) + else: + result.add(c) + pos += 1 + + if c == '\l': + pos = 0 + proc replaceWord*(s, sub: string, by = ""): string {.noSideEffect, rtl, extern: "nsuReplaceWord".} = ## Replaces `sub` in `s` by the string `by`. @@ -1899,6 +2162,11 @@ when isMainModule: doAssert parseEnum("invalid enum value", enC) == enC + doAssert center("foo", 13) == " foo " + doAssert center("foo", 0) == "foo" + doAssert center("foo", 3, fillChar = 'a') == "foo" + doAssert center("foo", 10, fillChar = '\t') == "\t\t\tfoo\t\t\t\t" + doAssert count("foofoofoo", "foofoo") == 1 doAssert count("foofoofoo", "foofoo", overlapping = true) == 2 doAssert count("foofoofoo", 'f') == 3 @@ -1967,6 +2235,34 @@ when isMainModule: doAssert(not isUpper("AAcc")) doAssert(not isUpper("A#$")) + 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", "") + + doAssert rsplit("foo bar", seps=Whitespace) == @["foo", "bar"] + doAssert rsplit(" foo bar", seps=Whitespace, maxsplit=1) == @[" foo", "bar"] + doAssert rsplit(" foo bar ", seps=Whitespace, maxsplit=1) == @[" foo bar", ""] + doAssert rsplit(":foo:bar", sep=':') == @["", "foo", "bar"] + doAssert rsplit(":foo:bar", sep=':', maxsplit=2) == @["", "foo", "bar"] + doAssert rsplit(":foo:bar", sep=':', maxsplit=3) == @["", "foo", "bar"] + doAssert rsplit("foothebar", sep="the") == @["foo", "bar"] + doAssert(unescape(r"\x013", "", "") == "\x013") doAssert join(["foo", "bar", "baz"]) == "foobarbaz" diff --git a/lib/pure/unicode.nim b/lib/pure/unicode.nim index 5d302c9dc..ac25dccef 100644 --- a/lib/pure/unicode.nim +++ b/lib/pure/unicode.nim @@ -135,45 +135,62 @@ proc runeAt*(s: string, i: Natural): Rune = ## Returns the unicode character in ``s`` at byte index ``i`` fastRuneAt(s, i, result, false) -proc toUTF8*(c: Rune): string {.rtl, extern: "nuc$1".} = - ## Converts a rune into its UTF-8 representation +template fastToUTF8Copy*(c: Rune, s: var string, pos: int, doInc = true) = + ## Copies UTF-8 representation of `c` into the preallocated string `s` + ## starting at position `pos`. If `doInc == true`, `pos` is incremented + ## by the number of bytes that have been processed. + ## + ## To be the most efficient, make sure `s` is preallocated + ## with an additional amount equal to the byte length of + ## `c`. var i = RuneImpl(c) if i <=% 127: - result = newString(1) - result[0] = chr(i) + s.setLen(pos+1) + s[pos+0] = chr(i) + when doInc: inc(pos) elif i <=% 0x07FF: - result = newString(2) - result[0] = chr((i shr 6) or 0b110_00000) - result[1] = chr((i and ones(6)) or 0b10_0000_00) + s.setLen(pos+2) + s[pos+0] = chr((i shr 6) or 0b110_00000) + s[pos+1] = chr((i and ones(6)) or 0b10_0000_00) + when doInc: inc(pos, 2) elif i <=% 0xFFFF: - result = newString(3) - result[0] = chr(i shr 12 or 0b1110_0000) - result[1] = chr(i shr 6 and ones(6) or 0b10_0000_00) - result[2] = chr(i and ones(6) or 0b10_0000_00) + s.setLen(pos+3) + s[pos+0] = chr(i shr 12 or 0b1110_0000) + s[pos+1] = chr(i shr 6 and ones(6) or 0b10_0000_00) + s[pos+2] = chr(i and ones(6) or 0b10_0000_00) + when doInc: inc(pos, 3) elif i <=% 0x001FFFFF: - result = newString(4) - result[0] = chr(i shr 18 or 0b1111_0000) - result[1] = chr(i shr 12 and ones(6) or 0b10_0000_00) - result[2] = chr(i shr 6 and ones(6) or 0b10_0000_00) - result[3] = chr(i and ones(6) or 0b10_0000_00) + s.setLen(pos+4) + s[pos+0] = chr(i shr 18 or 0b1111_0000) + s[pos+1] = chr(i shr 12 and ones(6) or 0b10_0000_00) + s[pos+2] = chr(i shr 6 and ones(6) or 0b10_0000_00) + s[pos+3] = chr(i and ones(6) or 0b10_0000_00) + when doInc: inc(pos, 4) elif i <=% 0x03FFFFFF: - result = newString(5) - result[0] = chr(i shr 24 or 0b111110_00) - result[1] = chr(i shr 18 and ones(6) or 0b10_0000_00) - result[2] = chr(i shr 12 and ones(6) or 0b10_0000_00) - result[3] = chr(i shr 6 and ones(6) or 0b10_0000_00) - result[4] = chr(i and ones(6) or 0b10_0000_00) + s.setLen(pos+5) + s[pos+0] = chr(i shr 24 or 0b111110_00) + s[pos+1] = chr(i shr 18 and ones(6) or 0b10_0000_00) + s[pos+2] = chr(i shr 12 and ones(6) or 0b10_0000_00) + s[pos+3] = chr(i shr 6 and ones(6) or 0b10_0000_00) + s[pos+4] = chr(i and ones(6) or 0b10_0000_00) + when doInc: inc(pos, 5) elif i <=% 0x7FFFFFFF: - result = newString(6) - result[0] = chr(i shr 30 or 0b1111110_0) - result[1] = chr(i shr 24 and ones(6) or 0b10_0000_00) - result[2] = chr(i shr 18 and ones(6) or 0b10_0000_00) - result[3] = chr(i shr 12 and ones(6) or 0b10_0000_00) - result[4] = chr(i shr 6 and ones(6) or 0b10_0000_00) - result[5] = chr(i and ones(6) or 0b10_0000_00) + s.setLen(pos+6) + s[pos+0] = chr(i shr 30 or 0b1111110_0) + s[pos+1] = chr(i shr 24 and ones(6) or 0b10_0000_00) + s[pos+2] = chr(i shr 18 and ones(6) or 0b10_0000_00) + s[pos+3] = chr(i shr 12 and ones(6) or 0b10_0000_00) + s[pos+4] = chr(i shr 6 and ones(6) or 0b10_0000_00) + s[pos+5] = chr(i and ones(6) or 0b10_0000_00) + when doInc: inc(pos, 6) else: discard # error, exception? +proc toUTF8*(c: Rune): string {.rtl, extern: "nuc$1".} = + ## Converts a rune into its UTF-8 representation + result = "" + fastToUTF8Copy(c, result, 0, false) + proc `$`*(rune: Rune): string = ## Converts a Rune to a string rune.toUTF8 @@ -1352,6 +1369,136 @@ proc isCombining*(c: Rune): bool {.rtl, extern: "nuc$1", procvar.} = (c >= 0x20d0 and c <= 0x20ff) or (c >= 0xfe20 and c <= 0xfe2f)) +proc swapCase*(s: string): string {.noSideEffect, procvar, + rtl, extern: "nuc$1".} = + ## Swaps the case of unicode characters in `s` + ## + ## Returns a new string such that the cases of all unicode characters + ## are swapped if possible + + var + i = 0 + lastIndex = 0 + rune: Rune + + result = newString(len(s)) + + while i < len(s): + lastIndex = i + + fastRuneAt(s, i, rune) + + if rune.isUpper(): + rune = rune.toLower() + elif rune.isLower(): + rune = rune.toUpper() + + rune.fastToUTF8Copy(result, lastIndex) + +proc translate*(s: string, replacements: proc(key: string): string): string {. + rtl, extern: "nuc$1".} = + ## Translates words in a string using the `replacements` proc to substitute + ## words inside `s` with their replacements + ## + ## `replacements` is any proc that takes a word and returns + ## a new word to fill it's place. + + # Allocate memory for the new string based on the old one. + # If the new string length is less than the old, no allocations + # will be needed. If the new string length is greater than the + # old, then maybe only one allocation is needed + result = newStringOfCap(s.len) + + var + index = 0 + lastIndex = 0 + wordStart = 0 + inWord = false + rune: Rune + + while index < len(s): + lastIndex = index + + fastRuneAt(s, index, rune) + + let whiteSpace = rune.isWhiteSpace() + + if whiteSpace and inWord: + # If we've reached the end of a word + let word = s[wordStart ..< lastIndex] + result.add(replacements(word)) + result.add($rune) + + inWord = false + elif not whiteSpace and not inWord: + # If we've hit a non space character and + # are not currently in a word, track + # the starting index of the word + inWord = true + wordStart = lastIndex + elif whiteSpace: + result.add($rune) + + if wordStart < len(s) and inWord: + # Get the trailing word at the end + let word = s[wordStart .. ^1] + result.add(replacements(word)) + +proc title*(s: string): string {.noSideEffect, procvar, + rtl, extern: "nuc$1".} = + ## Converts `s` to a unicode title. + ## + ## Returns a new string such that the first character + ## in each word inside `s` is capitalized + + var + i = 0 + lastIndex = 0 + rune: Rune + + result = newString(len(s)) + + var firstRune = true + + while i < len(s): + lastIndex = i + + fastRuneAt(s, i, rune) + + if not rune.isWhiteSpace() and firstRune: + rune = rune.toUpper() + firstRune = false + elif rune.isWhiteSpace(): + firstRune = true + + rune.fastToUTF8Copy(result, lastIndex) + +proc isTitle*(s: string): bool {.noSideEffect, procvar, + rtl, extern: "nuc$1Str".}= + ## Checks whether or not `s` is a unicode title. + ## + ## Returns true if the first character in each word inside `s` + ## are upper case and there is at least one character in `s`. + if s.len() == 0: + return false + + result = true + + var + i = 0 + rune: Rune + + var firstRune = true + + while i < len(s) and result: + fastRuneAt(s, i, rune, doInc=true) + + if not rune.isWhiteSpace() and firstRune: + result = rune.isUpper() and result + firstRune = false + elif rune.isWhiteSpace(): + firstRune = true + iterator runes*(s: string): Rune = ## Iterates over any unicode character of the string ``s`` var @@ -1451,6 +1598,39 @@ when isMainModule: compared = (someString == $someRunes) doAssert compared == true + proc test_replacements(word: string): string = + case word + of "two": + return "2" + of "foo": + return "BAR" + of "βeta": + return "beta" + of "alpha": + return "αlpha" + else: + return "12345" + + doAssert translate("two not alpha foo βeta", test_replacements) == "2 12345 αlpha BAR beta" + doAssert translate(" two not foo βeta ", test_replacements) == " 2 12345 BAR beta " + + doAssert title("foo bar") == "Foo Bar" + doAssert title("αlpha βeta γamma") == "Αlpha Βeta Γamma" + doAssert title("") == "" + + doAssert isTitle("Foo") + doAssert(not isTitle("Foo bar")) + doAssert(not isTitle("αlpha Βeta")) + doAssert(isTitle("Αlpha Βeta Γamma")) + doAssert(not isTitle("fFoo")) + + doAssert swapCase("FooBar") == "fOObAR" + doAssert swapCase(" ") == " " + doAssert swapCase("Αlpha Βeta Γamma") == "αLPHA βETA γAMMA" + doAssert swapCase("a✓B") == "A✓b" + doAssert swapCase("") == "" + + doAssert reversed("Reverse this!") == "!siht esreveR" doAssert reversed("先秦兩漢") == "漢兩秦先" doAssert reversed("as⃝df̅") == "f̅ds⃝a" -- cgit 1.4.1-2-gfad0 From 84d9081e21783151b40c0320ca50df4845043559 Mon Sep 17 00:00:00 2001 From: Joey Payne Date: Sat, 11 Jun 2016 18:59:41 -0600 Subject: Move partition and rpartition to new strmisc module This was done because partition is an uncommonly used proc that is still useful in rare cases. There is also a desire to add more procs to this module at a later time. --- doc/lib.rst | 4 +++ lib/pure/strmisc.nim | 56 ++++++++++++++++++++++++++++++++++++++ lib/pure/strutils.nim | 40 --------------------------- web/news/version_0_15_released.rst | 9 ++++++ web/website.ini | 2 +- 5 files changed, 70 insertions(+), 41 deletions(-) create mode 100644 lib/pure/strmisc.nim (limited to 'lib/pure/strutils.nim') diff --git a/doc/lib.rst b/doc/lib.rst index f749763a5..66928055a 100644 --- a/doc/lib.rst +++ b/doc/lib.rst @@ -97,6 +97,10 @@ String handling case of a string, splitting a string into substrings, searching for substrings, replacing substrings. +* `strmisc `_ + This module contains uncommon string handling operations that do not + fit with the commonly used operations in strutils. + * `parseutils `_ This module contains helpers for parsing tokens, numbers, identifiers, etc. diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim new file mode 100644 index 000000000..359014d8c --- /dev/null +++ b/lib/pure/strmisc.nim @@ -0,0 +1,56 @@ +# +# +# Nim's Runtime Library +# (c) Copyright 2016 Joey Payne +# +# See the file "copying.txt", included in this +# distribution, for details about the copyright. +# + +## This module contains various string utility routines that are uncommonly +## used in comparison to `strutils `_. + +import strutils + +{.deadCodeElim: on.} + +proc partition*(s: string, sep: string, + right: bool = false): (string, string, string) + {.noSideEffect, procvar.} = + ## Split the string at the first or last occurrence of `sep` into a 3-tuple + ## + ## Returns a 3 string tuple of (beforeSep, `sep`, afterSep) or + ## (`s`, "", "") if `sep` is not found and `right` is false or + ## ("", "", `s`) if `sep` is not found and `right` is true + + let position = if right: s.rfind(sep) else: s.find(sep) + + if position != -1: + let + beforeSep = s[0 ..< position] + afterSep = s[position + sep.len ..< s.len] + + return (beforeSep, sep, afterSep) + + return if right: ("", "", s) else: (s, "", "") + +proc rpartition*(s: string, sep: string): (string, string, string) + {.noSideEffect, procvar.} = + ## Split the string at the last occurrence of `sep` into a 3-tuple + ## + ## Returns a 3 string tuple of (beforeSep, `sep`, afterSep) or + ## ("", "", `s`) if `sep` is not found + return partition(s, sep, right = true) + +when isMainModule: + 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", "") diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index b8b3c77c7..708f9ed4b 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -1210,34 +1210,6 @@ proc rfind*(s: string, sub: char, start: int = -1): int {.noSideEffect, if sub == s[i]: return i return -1 -proc partition*(s: string, sep: string, - right: bool = false): (string, string, string) - {.noSideEffect, procvar, rtl, extern: "nsuPartitionStr".} = - ## Split the string at the first or last occurrence of `sep` into a 3-tuple - ## - ## Returns a 3 string tuple of (beforeSep, `sep`, afterSep) or - ## (`s`, "", "") if `sep` is not found and `right` is false or - ## ("", "", `s`) if `sep` is not found and `right` is true - - let position = if right: s.rfind(sep) else: s.find(sep) - - if position != -1: - let - beforeSep = s[0 ..< position] - afterSep = s[position + sep.len ..< s.len] - - return (s[0 ..< position], sep, afterSep) - - return if right: ("", "", s) else: (s, "", "") - -proc rpartition*(s: string, sep: string): (string, string, string) - {.noSideEffect, procvar, rtl, extern: "nsuRPartitionStr".} = - ## Split the string at the last occurrence of `sep` into a 3-tuple - ## - ## Returns a 3 string tuple of (beforeSep, `sep`, afterSep) or - ## ("", "", `s`) if `sep` is not found - return partition(s, sep, right = true) - proc center*(s: string, width: int, fillChar: char = ' '): string {. noSideEffect, rtl, extern: "nsuCenterString".} = ## Return the contents of `s` centered in a string `width` long using @@ -2243,18 +2215,6 @@ when isMainModule: 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", "") - doAssert rsplit("foo bar", seps=Whitespace) == @["foo", "bar"] doAssert rsplit(" foo bar", seps=Whitespace, maxsplit=1) == @[" foo", "bar"] doAssert rsplit(" foo bar ", seps=Whitespace, maxsplit=1) == @[" foo bar", ""] diff --git a/web/news/version_0_15_released.rst b/web/news/version_0_15_released.rst index ad3119cca..2f6c2736c 100644 --- a/web/news/version_0_15_released.rst +++ b/web/news/version_0_15_released.rst @@ -17,6 +17,15 @@ Library Additions - Added ``readHeaderRow`` and ``rowEntry`` to ``parsecsv.nim`` to provide a lightweight alternative to python's ``csv.DictReader``. +- Added ``center``, ``rsplit``, and ``expandTabs`` to ``strutils.nim`` to + provide similar Python functionality for Nim's strings. + +- Added ``isTitle``, ``title``, and ``swapCase`` to ``unicode.nim`` to + provide unicode aware string case manipulation. + +- Added a new module ``lib/pure/strmisc.nim`` to hold uncommon string + operations. Currently contains ``partition`` and ``rpartition``. + Compiler Additions ------------------ diff --git a/web/website.ini b/web/website.ini index ae363afbd..860ab9338 100644 --- a/web/website.ini +++ b/web/website.ini @@ -43,7 +43,7 @@ srcdoc2: "pure/stats;impure/nre;windows/winlean;pure/random" srcdoc2: "pure/complex;pure/times;pure/osproc;pure/pegs;pure/dynlib;pure/strscans" srcdoc2: "pure/parseopt;pure/parseopt2;pure/hashes;pure/strtabs;pure/lexbase" srcdoc2: "pure/parsecfg;pure/parsexml;pure/parsecsv;pure/parsesql" -srcdoc2: "pure/streams;pure/terminal;pure/cgi;pure/unicode" +srcdoc2: "pure/streams;pure/terminal;pure/cgi;pure/unicode;pure/strmisc" srcdoc2: "pure/htmlgen;pure/parseutils;pure/browsers" srcdoc2: "impure/db_postgres;impure/db_mysql;impure/db_sqlite" srcdoc2: "pure/httpserver;pure/httpclient;pure/smtp;impure/ssl;pure/fsmonitor" -- cgit 1.4.1-2-gfad0 From 42251f0b6bbee0734b9b0805ab85b3770a28a78c Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Tue, 14 Jun 2016 10:06:22 +0200 Subject: moved expandTabs to strmisc --- lib/pure/strmisc.nim | 43 +++++++++++++++++++++++++++++++------- lib/pure/strutils.nim | 40 ----------------------------------- web/news/version_0_15_released.rst | 5 +++-- 3 files changed, 38 insertions(+), 50 deletions(-) (limited to 'lib/pure/strutils.nim') diff --git a/lib/pure/strmisc.nim b/lib/pure/strmisc.nim index 359014d8c..89ef2fcd2 100644 --- a/lib/pure/strmisc.nim +++ b/lib/pure/strmisc.nim @@ -14,6 +14,32 @@ import strutils {.deadCodeElim: on.} +proc expandTabs*(s: string, tabSize: int = 8): string {.noSideEffect, + procvar.} = + ## Expand tab characters in `s` by `tabSize` spaces + + result = newStringOfCap(s.len + s.len shr 2) + var pos = 0 + + template addSpaces(n) = + for j in 0 ..< n: + result.add(' ') + pos += 1 + + for i in 0 ..< len(s): + let c = s[i] + if c == '\t': + let + denominator = if tabSize > 0: tabSize else: 1 + numSpaces = tabSize - pos mod denominator + + addSpaces(numSpaces) + else: + result.add(c) + pos += 1 + if c == '\l': + pos = 0 + proc partition*(s: string, sep: string, right: bool = false): (string, string, string) {.noSideEffect, procvar.} = @@ -22,16 +48,9 @@ proc partition*(s: string, sep: string, ## Returns a 3 string tuple of (beforeSep, `sep`, afterSep) or ## (`s`, "", "") if `sep` is not found and `right` is false or ## ("", "", `s`) if `sep` is not found and `right` is true - let position = if right: s.rfind(sep) else: s.find(sep) - if position != -1: - let - beforeSep = s[0 ..< position] - afterSep = s[position + sep.len ..< s.len] - - return (beforeSep, sep, afterSep) - + return (s[0 ..< position], sep, s[position + sep.len ..< s.len]) return if right: ("", "", s) else: (s, "", "") proc rpartition*(s: string, sep: string): (string, string, string) @@ -43,6 +62,14 @@ proc rpartition*(s: string, sep: string): (string, string, string) return partition(s, sep, right = true) 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", "", "") diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 708f9ed4b..b6edb834c 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -1319,38 +1319,6 @@ proc replace*(s: string, sub, by: char): string {.noSideEffect, else: result[i] = s[i] inc(i) -proc expandTabs*(s: string, tabSize: int = 8): string {.noSideEffect, - procvar, rtl, extern: "nsuExpandTabsStr".} = - ## Expand tab characters in `s` by `tabSize` spaces - - if len(s) == 0: - return s - - result = newStringOfCap(s.len + s.len shr 2) - - var pos = 0 - - template addSpaces(n) = - for j in 0 ..< n: - result.add(' ') - pos += 1 - - for i in 0 ..< len(s): - let c = s[i] - - if c == '\t': - let - denominator = if tabSize > 0: tabSize else: 1 - numSpaces = tabSize - pos mod denominator - - addSpaces(numSpaces) - else: - result.add(c) - pos += 1 - - if c == '\l': - pos = 0 - proc replaceWord*(s, sub: string, by = ""): string {.noSideEffect, rtl, extern: "nsuReplaceWord".} = ## Replaces `sub` in `s` by the string `by`. @@ -2207,14 +2175,6 @@ when isMainModule: doAssert(not isUpper("AAcc")) doAssert(not isUpper("A#$")) - 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 rsplit("foo bar", seps=Whitespace) == @["foo", "bar"] doAssert rsplit(" foo bar", seps=Whitespace, maxsplit=1) == @[" foo", "bar"] doAssert rsplit(" foo bar ", seps=Whitespace, maxsplit=1) == @[" foo bar", ""] diff --git a/web/news/version_0_15_released.rst b/web/news/version_0_15_released.rst index 7adc545d4..76d78ce71 100644 --- a/web/news/version_0_15_released.rst +++ b/web/news/version_0_15_released.rst @@ -20,14 +20,15 @@ Library Additions a lightweight alternative to python's ``csv.DictReader``. - Added ``setStdIoUnbuffered`` proc to ``system.nim`` to enable unbuffered I/O. -- Added ``center``, ``rsplit``, and ``expandTabs`` to ``strutils.nim`` to +- Added ``center`` and ``rsplit`` to ``strutils.nim`` to provide similar Python functionality for Nim's strings. - Added ``isTitle``, ``title``, and ``swapCase`` to ``unicode.nim`` to provide unicode aware string case manipulation. - Added a new module ``lib/pure/strmisc.nim`` to hold uncommon string - operations. Currently contains ``partition`` and ``rpartition``. + operations. Currently contains ``partition``, ``rpartition`` + and ``expandTabs``. Compiler Additions ------------------ -- cgit 1.4.1-2-gfad0 From d932cb1e515a52a8afde33feaf36ad5567e4e7df Mon Sep 17 00:00:00 2001 From: Euan Date: Fri, 24 Jun 2016 10:18:46 +0100 Subject: Adding isNilOrEmpty and isNilOrWhitespace As discussed in #4184, this patch adds `isNilOrEmpty` and `isNilOrWhitespace` to `strutils`. It also modifies the existing `isSpace` proc slightly to exit early rather than looping through all characters in a string. --- lib/pure/strutils.nim | 23 ++++++++++++++++++++++- tests/stdlib/tstrutil.nim | 11 +++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) (limited to 'lib/pure/strutils.nim') diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index b6edb834c..082f02c07 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -160,7 +160,8 @@ proc isSpace*(s: string): bool {.noSideEffect, procvar, result = true for c in s: - result = c.isSpace() and result + if not c.isSpace(): + return false proc isLower*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nsuIsLowerStr".}= @@ -326,6 +327,15 @@ proc toOctal*(c: char): string {.noSideEffect, rtl, extern: "nsuToOctal".} = result[i] = chr(val mod 8 + ord('0')) val = val div 8 +proc isNilOrEmpty*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nsuIsNilOrEmpty".} = + ## Checks if `s` is nil or empty. + result = len(s) == 0 + +proc isNilOrWhitespace*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nsuIsNilOrWhitespace".} = isSpace(s) + ## Checks if `s` is nil or consists entirely of whitespace characters. + ## + ## This is an alias to `isSpace`. + iterator split*(s: string, seps: set[char] = Whitespace, maxsplit: int = -1): string = ## Splits the string `s` into substrings using a group of separators. @@ -2156,6 +2166,17 @@ when isMainModule: doAssert isSpace(" ") doAssert(not isSpace("ABc \td")) + doAssert(isNilOrEmpty("")) + doAssert(isNilOrEmpty(nil)) + doAssert(not isNilOrEmpty("test")) + doAssert(not isNilOrEmpty(" ")) + + doAssert(isNilOrWhitespace("")) + doAssert(isNilOrWhitespace(nil)) + doAssert(isNilOrWhitespace(" ")) + doAssert(isNilOrWhitespace("\t\l \v\r\f")) + doAssert(not isNilOrWhitespace("ABc \td")) + doAssert isLower('a') doAssert isLower('z') doAssert(not isLower('A')) diff --git a/tests/stdlib/tstrutil.nim b/tests/stdlib/tstrutil.nim index b97f2b1e9..a080506d0 100644 --- a/tests/stdlib/tstrutil.nim +++ b/tests/stdlib/tstrutil.nim @@ -95,5 +95,16 @@ assert(' '.repeat(0) == "") assert(" ".repeat(0) == "") assert(spaces(0) == "") +assert(isNilOrEmpty("")) +assert(isNilOrEmpty(nil)) +assert(not isNilOrEmpty("test")) +assert(not isNilOrEmpty(" ")) + +assert(isNilOrWhitespace("")) +assert(isNilOrWhitespace(nil)) +assert(isNilOrWhitespace(" ")) +assert(isNilOrWhitespace("\t\l \v\r\f")) +assert(not isNilOrWhitespace("ABc \td")) + main() #OUT ha/home/a1xyz/usr/bin -- cgit 1.4.1-2-gfad0 From 746357404cc2ec93407c04cc1720e17032fc0efd Mon Sep 17 00:00:00 2001 From: Euan Date: Fri, 24 Jun 2016 10:50:26 +0100 Subject: Fixing isNilOrWhitespace for empty/nil strings. `isSpace` returns false for an empty string, which is the opposite of this method. --- lib/pure/strutils.nim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/pure/strutils.nim') diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 082f02c07..51694e6ad 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -331,7 +331,7 @@ proc isNilOrEmpty*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nsuIs ## Checks if `s` is nil or empty. result = len(s) == 0 -proc isNilOrWhitespace*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nsuIsNilOrWhitespace".} = isSpace(s) +proc isNilOrWhitespace*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nsuIsNilOrWhitespace".} = ## Checks if `s` is nil or consists entirely of whitespace characters. ## ## This is an alias to `isSpace`. -- cgit 1.4.1-2-gfad0 From f84911364a8cb6c8737367a2b34a1125f9f02e39 Mon Sep 17 00:00:00 2001 From: Euan Date: Fri, 24 Jun 2016 10:51:13 +0100 Subject: Fixing isNilOrWhitespace to handle empty/nil. --- lib/pure/strutils.nim | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'lib/pure/strutils.nim') diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 51694e6ad..7d1b1a3d9 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -333,8 +333,13 @@ proc isNilOrEmpty*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nsuIs proc isNilOrWhitespace*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nsuIsNilOrWhitespace".} = ## Checks if `s` is nil or consists entirely of whitespace characters. - ## - ## This is an alias to `isSpace`. + if len(s) == 0: + return true + + result = true + for c in s: + if not c.isSpace(): + return false iterator split*(s: string, seps: set[char] = Whitespace, maxsplit: int = -1): string = -- cgit 1.4.1-2-gfad0 From 76f81d4aa4fa79de19019b77d0ca972b68e7be6e Mon Sep 17 00:00:00 2001 From: Joey Payne Date: Thu, 16 Jun 2016 13:40:56 -0600 Subject: Fix #4305: Make split proc for set[char] consistent --- lib/pure/strutils.nim | 133 ++++++++++++++++++++++++-------------------------- 1 file changed, 63 insertions(+), 70 deletions(-) (limited to 'lib/pure/strutils.nim') diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 7d1b1a3d9..623ab3199 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -341,17 +341,49 @@ proc isNilOrWhitespace*(s: string): bool {.noSideEffect, procvar, rtl, extern: " if not c.isSpace(): return false +proc substrEq(s: string, pos: int, substr: string): bool = + var i = 0 + var length = substr.len + while i < length and s[pos+i] == substr[i]: + inc i + + return i == length + +# --------- Private templates for different split separators ----------- + +template stringHasSep(s: string, index: int, seps: set[char]): bool = + s[index] in seps + +template stringHasSep(s: string, index: int, sep: char): bool = + s[index] == sep + +template stringHasSep(s: string, index: int, sep: string): bool = + s.substrEq(index, sep) + +template splitCommon(s, sep, maxsplit, sepLen) = + ## Common code for split procedures + var last = 0 + var splits = maxsplit + + if len(s) > 0: + while last <= len(s): + var first = last + while last < len(s) and not stringHasSep(s, last, sep): + inc(last) + if splits == 0: last = len(s) + yield substr(s, first, last-1) + if splits == 0: break + dec(splits) + inc(last, sepLen) + iterator split*(s: string, seps: set[char] = Whitespace, maxsplit: int = -1): string = ## Splits the string `s` into substrings using a group of separators. ## - ## Substrings are separated by a substring containing only `seps`. Note - ## that whole sequences of characters found in ``seps`` will be counted as - ## a single split point and leading/trailing separators will be ignored. - ## The following example: + ## Substrings are separated by a substring containing only `seps`. ## ## .. code-block:: nim - ## for word in split(" this is an example "): + ## for word in split("this\lis an\texample"): ## writeLine(stdout, word) ## ## ...generates this output: @@ -365,7 +397,7 @@ iterator split*(s: string, seps: set[char] = Whitespace, ## And the following code: ## ## .. code-block:: nim - ## for word in split(";;this;is;an;;example;;;", {';'}): + ## for word in split("this:is;an$example", {';', ':', '$'}): ## writeLine(stdout, word) ## ## ...produces the same output as the first example. The code: @@ -386,26 +418,13 @@ iterator split*(s: string, seps: set[char] = Whitespace, ## "08" ## "08.398990" ## - var last = 0 - var splits = maxsplit - assert(not ('\0' in seps)) - while last < len(s): - while s[last] in seps: inc(last) - var first = last - while last < len(s) and s[last] notin seps: inc(last) # BUGFIX! - if first <= last-1: - if splits == 0: last = len(s) - yield substr(s, first, last-1) - if splits == 0: break - dec(splits) + splitCommon(s, seps, maxsplit, 1) iterator split*(s: string, sep: char, maxsplit: int = -1): string = ## Splits the string `s` into substrings using a single separator. ## ## Substrings are separated by the character `sep`. - ## Unlike the version of the iterator which accepts a set of separator - ## characters, this proc will not coalesce groups of the - ## separator, returning a string for each found character. The code: + ## The code: ## ## .. code-block:: nim ## for word in split(";;this;is;an;;example;;;", ';'): @@ -425,56 +444,27 @@ iterator split*(s: string, sep: char, maxsplit: int = -1): string = ## "" ## "" ## - var last = 0 - var splits = maxsplit - assert('\0' != sep) - if len(s) > 0: - # `<=` is correct here for the edge cases! - while last <= len(s): - var first = last - while last < len(s) and s[last] != sep: inc(last) - if splits == 0: last = len(s) - yield substr(s, first, last-1) - if splits == 0: break - dec(splits) - inc(last) - -proc substrEq(s: string, pos: int, substr: string): bool = - var i = 0 - var length = substr.len - while i < length and s[pos+i] == substr[i]: - inc i - - return i == length + splitCommon(s, sep, maxsplit, 1) iterator split*(s: string, sep: string, maxsplit: int = -1): string = ## Splits the string `s` into substrings using a string separator. ## ## Substrings are separated by the string `sep`. - var last = 0 - var splits = maxsplit - - if len(s) > 0: - while last <= len(s): - var first = last - while last < len(s) and not s.substrEq(last, sep): - inc(last) - if splits == 0: last = len(s) - yield substr(s, first, last-1) - if splits == 0: break - dec(splits) - inc(last, sep.len) - -# --------- Private templates for different rsplit separators ----------- - -template stringHasSep(s: string, index: int, seps: set[char]): bool = - s[index] in seps - -template stringHasSep(s: string, index: int, sep: char): bool = - s[index] == sep + ## The code: + ## + ## .. code-block:: nim + ## for word in split("thisDATAisDATAcorrupted", "DATA"): + ## writeLine(stdout, word) + ## + ## Results in: + ## + ## .. code-block:: + ## "this" + ## "is" + ## "corrupted" + ## -template stringHasSep(s: string, index: int, sep: string): bool = - s.substrEq(index, sep) + splitCommon(s, sep, maxsplit, sep.len) template rsplitCommon(s, sep, maxsplit, sepLen) = ## Common code for rsplit functions @@ -2244,11 +2234,14 @@ bar bar """.unindent() == "foo\nfoo\nbar\n" - let s = " this is an example " - doAssert s.split() == @["this", "is", "an", "example"] - doAssert s.split(maxsplit=4) == @["this", "is", "an", "example"] - doAssert s.split(' ', maxsplit=4) == @["", "this", "", "", "is an example "] - doAssert s.split(" ", maxsplit=4) == @["", "this", "", "", "is an example "] + let s = " this is an example " + let s2 = ":this;is;an:example;;" + + doAssert s.split() == @["", "this", "is", "an", "example", "", ""] + doAssert s2.split(seps={':', ';'}) == @["", "this", "is", "an", "example", "", ""] + doAssert s.split(maxsplit=4) == @["", "this", "is", "an", "example "] + doAssert s.split(' ', maxsplit=1) == @["", "this is an example "] + doAssert s.split(" ", maxsplit=4) == @["", "this", "is", "an", "example "] block: # formatEng tests doAssert formatEng(0, 2, trim=false) == "0.00" -- cgit 1.4.1-2-gfad0 From 79a8a5ee72b7aca09e1dfdb03744020ebd4dae2b Mon Sep 17 00:00:00 2001 From: Joey Payne Date: Sat, 18 Jun 2016 13:58:14 -0600 Subject: Add transition define for old split behavior --- lib/pure/strutils.nim | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) (limited to 'lib/pure/strutils.nim') diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 623ab3199..15bef1578 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -26,6 +26,12 @@ include "system/inclrtl" {.pop.} +# Support old split with set[char] +when defined(nimOldSplit): + {.pragma: deprecatedSplit, deprecated.} +else: + {.pragma: deprecatedSplit.} + type CharSet* {.deprecated.} = set[char] # for compatibility with Nim {.deprecated: [TCharSet: CharSet].} @@ -376,6 +382,22 @@ template splitCommon(s, sep, maxsplit, sepLen) = dec(splits) inc(last, sepLen) +when defined(nimOldSplit): + template oldSplit(s, seps, maxsplit) = + ## Deprecated split[char] for transition period + var last = 0 + var splits = maxsplit + assert(not ('\0' in seps)) + while last < len(s): + while s[last] in seps: inc(last) + var first = last + while last < len(s) and s[last] notin seps: inc(last) # BUGFIX! + if first <= last-1: + if splits == 0: last = len(s) + yield substr(s, first, last-1) + if splits == 0: break + dec(splits) + iterator split*(s: string, seps: set[char] = Whitespace, maxsplit: int = -1): string = ## Splits the string `s` into substrings using a group of separators. @@ -418,7 +440,10 @@ iterator split*(s: string, seps: set[char] = Whitespace, ## "08" ## "08.398990" ## - splitCommon(s, seps, maxsplit, 1) + when defined(nimOldSplit): + oldSplit(s, seps, maxsplit) + else: + splitCommon(s, seps, maxsplit, 1) iterator split*(s: string, sep: char, maxsplit: int = -1): string = ## Splits the string `s` into substrings using a single separator. -- cgit 1.4.1-2-gfad0 From e0203a44630d834224879187a44739eaeb122a81 Mon Sep 17 00:00:00 2001 From: Joey Payne Date: Tue, 21 Jun 2016 15:06:01 -0600 Subject: Add useful unicode procs for string manipulation Added: isUpper, isLower, isAlpha, isWhiteSpace, toUpper, toLower, and capitalize Renamed strutils procs that are similar to avoid conflicts --- lib/pure/strutils.nim | 255 ++++++++++++++++++++++++++++++++++++++------------ lib/pure/unicode.nim | 134 ++++++++++++++++++++++++++ 2 files changed, 327 insertions(+), 62 deletions(-) (limited to 'lib/pure/strutils.nim') diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 7d1b1a3d9..38807b29a 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -64,8 +64,8 @@ const ## doAssert "01234".find(invalid) == -1 ## doAssert "01A34".find(invalid) == 2 -proc isAlpha*(c: char): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsAlphaChar".}= +proc isAlphaAscii*(c: char): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsAlphaAsciiChar".}= ## Checks whether or not `c` is alphabetical. ## ## This checks a-z, A-Z ASCII characters only. @@ -85,27 +85,27 @@ proc isDigit*(c: char): bool {.noSideEffect, procvar, ## This checks 0-9 ASCII characters only. return c in Digits -proc isSpace*(c: char): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsSpaceChar".}= +proc isSpaceAscii*(c: char): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsSpaceAsciiChar".}= ## Checks whether or not `c` is a whitespace character. return c in Whitespace -proc isLower*(c: char): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsLowerChar".}= +proc isLowerAscii*(c: char): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsLowerAsciiChar".}= ## Checks whether or not `c` is a lower case character. ## ## This checks ASCII characters only. return c in {'a'..'z'} -proc isUpper*(c: char): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsUpperChar".}= +proc isUpperAscii*(c: char): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsUpperAsciiChar".}= ## Checks whether or not `c` is an upper case character. ## ## This checks ASCII characters only. return c in {'A'..'Z'} -proc isAlpha*(s: string): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsAlphaStr".}= +proc isAlphaAscii*(s: string): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsAlphaAsciiStr".}= ## Checks whether or not `s` is alphabetical. ## ## This checks a-z, A-Z ASCII characters only. @@ -117,7 +117,7 @@ proc isAlpha*(s: string): bool {.noSideEffect, procvar, result = true for c in s: - result = c.isAlpha() and result + result = c.isAlphaAscii() and result proc isAlphaNumeric*(s: string): bool {.noSideEffect, procvar, rtl, extern: "nsuIsAlphaNumericStr".}= @@ -149,8 +149,8 @@ proc isDigit*(s: string): bool {.noSideEffect, procvar, for c in s: result = c.isDigit() and result -proc isSpace*(s: string): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsSpaceStr".}= +proc isSpaceAscii*(s: string): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsSpaceAsciiStr".}= ## Checks whether or not `s` is completely whitespace. ## ## Returns true if all characters in `s` are whitespace @@ -160,11 +160,11 @@ proc isSpace*(s: string): bool {.noSideEffect, procvar, result = true for c in s: - if not c.isSpace(): + if not c.isSpaceAscii(): return false -proc isLower*(s: string): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsLowerStr".}= +proc isLowerAscii*(s: string): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsLowerAsciiStr".}= ## Checks whether or not `s` contains all lower case characters. ## ## This checks ASCII characters only. @@ -175,10 +175,10 @@ proc isLower*(s: string): bool {.noSideEffect, procvar, result = true for c in s: - result = c.isLower() and result + result = c.isLowerAscii() and result -proc isUpper*(s: string): bool {.noSideEffect, procvar, - rtl, extern: "nsuIsUpperStr".}= +proc isUpperAscii*(s: string): bool {.noSideEffect, procvar, + rtl, extern: "nsuIsUpperAsciiStr".}= ## Checks whether or not `s` contains all upper case characters. ## ## This checks ASCII characters only. @@ -189,10 +189,10 @@ proc isUpper*(s: string): bool {.noSideEffect, procvar, result = true for c in s: - result = c.isUpper() and result + result = c.isUpperAscii() and result -proc toLower*(c: char): char {.noSideEffect, procvar, - rtl, extern: "nsuToLowerChar".} = +proc toLowerAscii*(c: char): char {.noSideEffect, procvar, + rtl, extern: "nsuToLowerAsciiChar".} = ## Converts `c` into lower case. ## ## This works only for the letters ``A-Z``. See `unicode.toLower @@ -203,8 +203,8 @@ proc toLower*(c: char): char {.noSideEffect, procvar, else: result = c -proc toLower*(s: string): string {.noSideEffect, procvar, - rtl, extern: "nsuToLowerStr".} = +proc toLowerAscii*(s: string): string {.noSideEffect, procvar, + rtl, extern: "nsuToLowerAsciiStr".} = ## Converts `s` into lower case. ## ## This works only for the letters ``A-Z``. See `unicode.toLower @@ -212,10 +212,10 @@ proc toLower*(s: string): string {.noSideEffect, procvar, ## character. result = newString(len(s)) for i in 0..len(s) - 1: - result[i] = toLower(s[i]) + result[i] = toLowerAscii(s[i]) -proc toUpper*(c: char): char {.noSideEffect, procvar, - rtl, extern: "nsuToUpperChar".} = +proc toUpperAscii*(c: char): char {.noSideEffect, procvar, + rtl, extern: "nsuToUpperAsciiChar".} = ## Converts `c` into upper case. ## ## This works only for the letters ``A-Z``. See `unicode.toUpper @@ -226,8 +226,8 @@ proc toUpper*(c: char): char {.noSideEffect, procvar, else: result = c -proc toUpper*(s: string): string {.noSideEffect, procvar, - rtl, extern: "nsuToUpperStr".} = +proc toUpperAscii*(s: string): string {.noSideEffect, procvar, + rtl, extern: "nsuToUpperAsciiStr".} = ## Converts `s` into upper case. ## ## This works only for the letters ``A-Z``. See `unicode.toUpper @@ -235,14 +235,145 @@ proc toUpper*(s: string): string {.noSideEffect, procvar, ## character. result = newString(len(s)) for i in 0..len(s) - 1: - result[i] = toUpper(s[i]) + result[i] = toUpperAscii(s[i]) + +proc capitalizeAscii*(s: string): string {.noSideEffect, procvar, + rtl, extern: "nsuCapitalizeAscii".} = + ## Converts the first character of `s` into upper case. + ## + ## This works only for the letters ``A-Z``. + result = toUpperAscii(s[0]) & substr(s, 1) + +proc isSpace*(c: char): bool {.noSideEffect, procvar, + rtl, deprecated, extern: "nsuIsSpaceChar".}= + ## Checks whether or not `c` is a whitespace character. + ## + ## **Deprecated since version 0.15.0**: use ``isSpaceAscii`` instead. + isSpaceAscii(c) + +proc isLower*(c: char): bool {.noSideEffect, procvar, + rtl, deprecated, extern: "nsuIsLowerChar".}= + ## Checks whether or not `c` is a lower case character. + ## + ## This checks ASCII characters only. + ## + ## **Deprecated since version 0.15.0**: use ``isLowerAscii`` instead. + isLowerAscii(c) + +proc isUpper*(c: char): bool {.noSideEffect, procvar, + rtl, deprecated, extern: "nsuIsUpperChar".}= + ## Checks whether or not `c` is an upper case character. + ## + ## This checks ASCII characters only. + ## + ## **Deprecated since version 0.15.0**: use ``isUpperAscii`` instead. + isUpperAscii(c) + +proc isAlpha*(c: char): bool {.noSideEffect, procvar, + rtl, deprecated, extern: "nsuIsAlphaChar".}= + ## Checks whether or not `c` is alphabetical. + ## + ## This checks a-z, A-Z ASCII characters only. + ## + ## **Deprecated since version 0.15.0**: use ``isAlphaAscii`` instead. + isAlphaAscii(c) + +proc isAlpha*(s: string): bool {.noSideEffect, procvar, + rtl, deprecated, extern: "nsuIsAlphaStr".}= + ## Checks whether or not `s` is alphabetical. + ## + ## This checks a-z, A-Z ASCII characters only. + ## Returns true if all characters in `s` are + ## alphabetic and there is at least one character + ## in `s`. + ## + ## **Deprecated since version 0.15.0**: use ``isAlphaAscii`` instead. + isAlphaAscii(s) + +proc isSpace*(s: string): bool {.noSideEffect, procvar, + rtl, deprecated, extern: "nsuIsSpaceStr".}= + ## Checks whether or not `s` is completely whitespace. + ## + ## Returns true if all characters in `s` are whitespace + ## characters and there is at least one character in `s`. + ## + ## **Deprecated since version 0.15.0**: use ``isSpaceAscii`` instead. + isSpaceAscii(s) + +proc isLower*(s: string): bool {.noSideEffect, procvar, + rtl, deprecated, extern: "nsuIsLowerStr".}= + ## Checks whether or not `s` contains all lower case characters. + ## + ## This checks ASCII characters only. + ## Returns true if all characters in `s` are lower case + ## and there is at least one character in `s`. + ## + ## **Deprecated since version 0.15.0**: use ``isLowerAscii`` instead. + isLowerAscii(s) + +proc isUpper*(s: string): bool {.noSideEffect, procvar, + rtl, deprecated, extern: "nsuIsUpperStr".}= + ## Checks whether or not `s` contains all upper case characters. + ## + ## This checks ASCII characters only. + ## Returns true if all characters in `s` are upper case + ## and there is at least one character in `s`. + ## + ## **Deprecated since version 0.15.0**: use ``isUpperAscii`` instead. + isUpperAscii(s) + +proc toLower*(c: char): char {.noSideEffect, procvar, + rtl, deprecated, extern: "nsuToLowerChar".} = + ## Converts `c` into lower case. + ## + ## This works only for the letters ``A-Z``. See `unicode.toLower + ## `_ for a version that works for any Unicode + ## character. + ## + ## **Deprecated since version 0.15.0**: use ``toLowerAscii`` instead. + toLowerAscii(c) + +proc toLower*(s: string): string {.noSideEffect, procvar, + rtl, deprecated, extern: "nsuToLowerStr".} = + ## Converts `s` into lower case. + ## + ## This works only for the letters ``A-Z``. See `unicode.toLower + ## `_ for a version that works for any Unicode + ## character. + ## + ## **Deprecated since version 0.15.0**: use ``toLowerAscii`` instead. + toLowerAscii(s) + +proc toUpper*(c: char): char {.noSideEffect, procvar, + rtl, deprecated, extern: "nsuToUpperChar".} = + ## Converts `c` into upper case. + ## + ## This works only for the letters ``A-Z``. See `unicode.toUpper + ## `_ for a version that works for any Unicode + ## character. + ## + ## **Deprecated since version 0.15.0**: use ``toUpperAscii`` instead. + toUpperAscii(c) + +proc toUpper*(s: string): string {.noSideEffect, procvar, + rtl, deprecated, extern: "nsuToUpperStr".} = + ## Converts `s` into upper case. + ## + ## This works only for the letters ``A-Z``. See `unicode.toUpper + ## `_ for a version that works for any Unicode + ## character. + ## + ## **Deprecated since version 0.15.0**: use ``toUpperAscii`` instead. + toUpperAscii(s) proc capitalize*(s: string): string {.noSideEffect, procvar, - rtl, extern: "nsuCapitalize".} = + rtl, deprecated, extern: "nsuCapitalize".} = ## Converts the first character of `s` into upper case. ## ## This works only for the letters ``A-Z``. - result = toUpper(s[0]) & substr(s, 1) + ## + ## **Deprecated since version 0.15.0**: use ``capitalizeAscii`` instead. + capitalizeAscii(s) proc normalize*(s: string): string {.noSideEffect, procvar, rtl, extern: "nsuNormalize".} = @@ -271,7 +402,7 @@ proc cmpIgnoreCase*(a, b: string): int {.noSideEffect, var i = 0 var m = min(a.len, b.len) while i < m: - result = ord(toLower(a[i])) - ord(toLower(b[i])) + result = ord(toLowerAscii(a[i])) - ord(toLowerAscii(b[i])) if result != 0: return inc(i) result = a.len - b.len @@ -292,8 +423,8 @@ proc cmpIgnoreStyle*(a, b: string): int {.noSideEffect, while true: while a[i] == '_': inc(i) while b[j] == '_': inc(j) # BUGFIX: typo - var aa = toLower(a[i]) - var bb = toLower(b[j]) + var aa = toLowerAscii(a[i]) + var bb = toLowerAscii(b[j]) result = ord(aa) - ord(bb) if result != 0 or aa == '\0': break inc(i) @@ -2138,13 +2269,13 @@ when isMainModule: doAssert " foo\n bar".indent(4, "Q") == "QQQQ foo\nQQQQ bar" - doAssert isAlpha('r') - doAssert isAlpha('A') - doAssert(not isAlpha('$')) + doAssert isAlphaAscii('r') + doAssert isAlphaAscii('A') + doAssert(not isAlphaAscii('$')) - doAssert isAlpha("Rasp") - doAssert isAlpha("Args") - doAssert(not isAlpha("$Tomato")) + doAssert isAlphaAscii("Rasp") + doAssert isAlphaAscii("Args") + doAssert(not isAlphaAscii("$Tomato")) doAssert isAlphaNumeric('3') doAssert isAlphaNumeric('R') @@ -2163,13 +2294,13 @@ when isMainModule: doAssert(not isDigit("12.33")) doAssert(not isDigit("A45b")) - doAssert isSpace('\t') - doAssert isSpace('\l') - doAssert(not isSpace('A')) + doAssert isSpaceAscii('\t') + doAssert isSpaceAscii('\l') + doAssert(not isSpaceAscii('A')) - doAssert isSpace("\t\l \v\r\f") - doAssert isSpace(" ") - doAssert(not isSpace("ABc \td")) + doAssert isSpaceAscii("\t\l \v\r\f") + doAssert isSpaceAscii(" ") + doAssert(not isSpaceAscii("ABc \td")) doAssert(isNilOrEmpty("")) doAssert(isNilOrEmpty(nil)) @@ -2182,24 +2313,24 @@ when isMainModule: doAssert(isNilOrWhitespace("\t\l \v\r\f")) doAssert(not isNilOrWhitespace("ABc \td")) - doAssert isLower('a') - doAssert isLower('z') - doAssert(not isLower('A')) - doAssert(not isLower('5')) - doAssert(not isLower('&')) + doAssert isLowerAscii('a') + doAssert isLowerAscii('z') + doAssert(not isLowerAscii('A')) + doAssert(not isLowerAscii('5')) + doAssert(not isLowerAscii('&')) - doAssert isLower("abcd") - doAssert(not isLower("abCD")) - doAssert(not isLower("33aa")) + doAssert isLowerAscii("abcd") + doAssert(not isLowerAscii("abCD")) + doAssert(not isLowerAscii("33aa")) - doAssert isUpper('A') - doAssert(not isUpper('b')) - doAssert(not isUpper('5')) - doAssert(not isUpper('%')) + doAssert isUpperAscii('A') + doAssert(not isUpperAscii('b')) + doAssert(not isUpperAscii('5')) + doAssert(not isUpperAscii('%')) - doAssert isUpper("ABC") - doAssert(not isUpper("AAcc")) - doAssert(not isUpper("A#$")) + doAssert isUpperAscii("ABC") + doAssert(not isUpperAscii("AAcc")) + doAssert(not isUpperAscii("A#$")) doAssert rsplit("foo bar", seps=Whitespace) == @["foo", "bar"] doAssert rsplit(" foo bar", seps=Whitespace, maxsplit=1) == @[" foo", "bar"] diff --git a/lib/pure/unicode.nim b/lib/pure/unicode.nim index ac25dccef..b5383c5d3 100644 --- a/lib/pure/unicode.nim +++ b/lib/pure/unicode.nim @@ -1369,6 +1369,64 @@ proc isCombining*(c: Rune): bool {.rtl, extern: "nuc$1", procvar.} = (c >= 0x20d0 and c <= 0x20ff) or (c >= 0xfe20 and c <= 0xfe2f)) +template runeCheck(s, runeProc) = + ## Common code for rune.isLower, rune.isUpper, etc + result = if len(s) == 0: false else: true + + var + i = 0 + rune: Rune + + while i < len(s) and result: + fastRuneAt(s, i, rune, doInc=true) + result = runeProc(rune) and result + +proc isUpper*(s: string): bool {.noSideEffect, procvar, + rtl, extern: "nuc$1Str".} = + ## Returns true iff `s` contains all upper case unicode characters. + runeCheck(s, isUpper) + +proc isLower*(s: string): bool {.noSideEffect, procvar, + rtl, extern: "nuc$1Str".} = + ## Returns true iff `s` contains all lower case unicode characters. + runeCheck(s, isLower) + +proc isAlpha*(s: string): bool {.noSideEffect, procvar, + rtl, extern: "nuc$1Str".} = + ## Returns true iff `s` contains all alphabetic unicode characters. + runeCheck(s, isAlpha) + +proc isSpace*(s: string): bool {.noSideEffect, procvar, + rtl, extern: "nuc$1Str".} = + ## Returns true iff `s` contains all whitespace unicode characters. + runeCheck(s, isWhiteSpace) + +template convertRune(s, runeProc) = + ## Convert runes in `s` using `runeProc` as the converter. + result = newString(len(s)) + + var + i = 0 + lastIndex = 0 + rune: Rune + + while i < len(s): + lastIndex = i + fastRuneAt(s, i, rune, doInc=true) + rune = runeProc(rune) + + rune.fastToUTF8Copy(result, lastIndex) + +proc toUpper*(s: string): string {.noSideEffect, procvar, + rtl, extern: "nuc$1Str".} = + ## Converts `s` into upper-case unicode characters. + convertRune(s, toUpper) + +proc toLower*(s: string): string {.noSideEffect, procvar, + rtl, extern: "nuc$1Str".} = + ## Converts `s` into lower-case unicode characters. + convertRune(s, toLower) + proc swapCase*(s: string): string {.noSideEffect, procvar, rtl, extern: "nuc$1".} = ## Swaps the case of unicode characters in `s` @@ -1395,6 +1453,20 @@ proc swapCase*(s: string): string {.noSideEffect, procvar, rune.fastToUTF8Copy(result, lastIndex) +proc capitalize*(s: string): string {.noSideEffect, procvar, + rtl, extern: "nuc$1".} = + ## Converts the first character of `s` into an upper-case unicode character. + if len(s) == 0: + return s + + var + rune: Rune + i = 0 + + fastRuneAt(s, i, rune, doInc=true) + + result = $toUpper(rune) & substr(s, i) + proc translate*(s: string, replacements: proc(key: string): string): string {. rtl, extern: "nuc$1".} = ## Translates words in a string using the `replacements` proc to substitute @@ -1618,6 +1690,10 @@ when isMainModule: doAssert title("αlpha βeta γamma") == "Αlpha Βeta Γamma" doAssert title("") == "" + doAssert capitalize("βeta") == "Βeta" + doAssert capitalize("foo") == "Foo" + doAssert capitalize("") == "" + doAssert isTitle("Foo") doAssert(not isTitle("Foo bar")) doAssert(not isTitle("αlpha Βeta")) @@ -1630,6 +1706,64 @@ when isMainModule: doAssert swapCase("a✓B") == "A✓b" doAssert swapCase("") == "" + doAssert isAlpha("r") + doAssert isAlpha("α") + doAssert(not isAlpha("$")) + doAssert(not isAlpha("")) + + doAssert isAlpha("Βeta") + doAssert isAlpha("Args") + doAssert(not isAlpha("$Foo✓")) + + doAssert isSpace("\t") + doAssert isSpace("\l") + doAssert(not isSpace("Β")) + doAssert(not isSpace("Βeta")) + + doAssert isSpace("\t\l \v\r\f") + doAssert isSpace(" ") + doAssert(not isSpace("")) + doAssert(not isSpace("ΑΓc \td")) + + doAssert isLower("a") + doAssert isLower("γ") + doAssert(not isLower("Γ")) + doAssert(not isLower("4")) + doAssert(not isLower("")) + + doAssert isLower("abcdγ") + doAssert(not isLower("abCDΓ")) + doAssert(not isLower("33aaΓ")) + + doAssert isUpper("Γ") + doAssert(not isUpper("b")) + doAssert(not isUpper("α")) + doAssert(not isUpper("✓")) + doAssert(not isUpper("")) + + doAssert isUpper("ΑΒΓ") + doAssert(not isUpper("AAccβ")) + doAssert(not isUpper("A#$β")) + + doAssert toUpper("Γ") == "Γ" + doAssert toUpper("b") == "B" + doAssert toUpper("α") == "Α" + doAssert toUpper("✓") == "✓" + doAssert toUpper("") == "" + + doAssert toUpper("ΑΒΓ") == "ΑΒΓ" + doAssert toUpper("AAccβ") == "AACCΒ" + doAssert toUpper("A✓$β") == "A✓$Β" + + doAssert toLower("a") == "a" + doAssert toLower("γ") == "γ" + doAssert toLower("Γ") == "γ" + doAssert toLower("4") == "4" + doAssert toLower("") == "" + + doAssert toLower("abcdγ") == "abcdγ" + doAssert toLower("abCDΓ") == "abcdγ" + doAssert toLower("33aaΓ") == "33aaγ" doAssert reversed("Reverse this!") == "!siht esreveR" doAssert reversed("先秦兩漢") == "漢兩秦先" -- cgit 1.4.1-2-gfad0 From 6d9177c6f1c668a517eaaa1e6946a4885ef531de Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Wed, 6 Jul 2016 11:59:20 +0200 Subject: added strutils.splitWhitespace --- lib/pure/strutils.nim | 38 +++++++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 15 deletions(-) (limited to 'lib/pure/strutils.nim') diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 757268cd1..9b6cf45c5 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -513,21 +513,19 @@ template splitCommon(s, sep, maxsplit, sepLen) = dec(splits) inc(last, sepLen) -when defined(nimOldSplit): - template oldSplit(s, seps, maxsplit) = - ## Deprecated split[char] for transition period - var last = 0 - var splits = maxsplit - assert(not ('\0' in seps)) - while last < len(s): - while s[last] in seps: inc(last) - var first = last - while last < len(s) and s[last] notin seps: inc(last) # BUGFIX! - if first <= last-1: - if splits == 0: last = len(s) - yield substr(s, first, last-1) - if splits == 0: break - dec(splits) +template oldSplit(s, seps, maxsplit) = + var last = 0 + var splits = maxsplit + assert(not ('\0' in seps)) + while last < len(s): + while s[last] in seps: inc(last) + var first = last + while last < len(s) and s[last] notin seps: inc(last) + if first <= last-1: + if splits == 0: last = len(s) + yield substr(s, first, last-1) + if splits == 0: break + dec(splits) iterator split*(s: string, seps: set[char] = Whitespace, maxsplit: int = -1): string = @@ -576,6 +574,16 @@ iterator split*(s: string, seps: set[char] = Whitespace, else: splitCommon(s, seps, maxsplit, 1) +iterator splitWhitespace*(s: string): string = + ## Splits at whitespace. + oldSplit(s, Whitespace, -1) + +proc splitWhitespace*(s: string): seq[string] {.noSideEffect, + rtl, extern: "nsuSplitWhitespace".} = + ## The same as the `splitWhitespace <#splitWhitespace.i,string>`_ + ## iterator, but is a proc that returns a sequence of substrings. + accumulateResult(splitWhitespace(s)) + iterator split*(s: string, sep: char, maxsplit: int = -1): string = ## Splits the string `s` into substrings using a single separator. ## -- cgit 1.4.1-2-gfad0 From 60b187513ed8e2a061816a912e372a1c81794d50 Mon Sep 17 00:00:00 2001 From: Andreas Rumpf Date: Fri, 29 Jul 2016 17:35:51 +0200 Subject: stdlib and compiler don't use .immediate anymore --- compiler/ast.nim | 2 ++ compiler/ccgmerge.nim | 2 +- compiler/ccgstmts.nim | 2 +- compiler/extccomp.nim | 2 +- compiler/jsgen.nim | 2 +- compiler/parser.nim | 2 +- compiler/pragmas.nim | 5 ++- compiler/semfold.nim | 4 +-- compiler/semgnrc.nim | 9 ++++-- compiler/semparallel.nim | 2 +- compiler/semtempl.nim | 8 +++-- compiler/sigmatch.nim | 6 ++-- compiler/suggest.nim | 2 +- compiler/types.nim | 2 +- compiler/vm.nim | 19 ++++++------ compiler/vmdef.nim | 12 ++++---- compiler/vmgen.nim | 4 +-- compiler/vmhooks.nim | 4 +-- compiler/vmops.nim | 16 +++++----- lib/core/macros.nim | 17 +++++----- lib/impure/nre.nim | 6 ++-- lib/pure/asyncdispatch.nim | 8 ++--- lib/pure/collections/sequtils.nim | 24 +++++++-------- lib/pure/collections/sets.nim | 4 +-- lib/pure/collections/tableimpl.nim | 14 ++++----- lib/pure/collections/tables.nim | 16 +++++----- lib/pure/future.nim | 6 ++-- lib/pure/net.nim | 4 +-- lib/pure/os.nim | 18 +++++------ lib/pure/strutils.nim | 2 +- lib/pure/times.nim | 63 +++++++++++++------------------------- lib/pure/unittest.nim | 18 +++++------ lib/system.nim | 26 ++++++++-------- tests/macros/tdumptree.nim | 2 +- todo.txt | 4 ++- web/news/version_0_15_released.rst | 4 +++ 36 files changed, 169 insertions(+), 172 deletions(-) (limited to 'lib/pure/strutils.nim') diff --git a/compiler/ast.nim b/compiler/ast.nim index 4c98219f9..8a3c76435 100644 --- a/compiler/ast.nim +++ b/compiler/ast.nim @@ -278,6 +278,8 @@ const sfImmediate* = sfDeadCodeElim # macro or template is immediately expanded # without considering any possible overloads + sfAllUntyped* = sfVolatile # macro or template is immediately expanded \ + # in a generic context sfDirty* = sfPure # template is not hygienic (old styled template) diff --git a/compiler/ccgmerge.nim b/compiler/ccgmerge.nim index 2e77cd2a6..1b2104174 100644 --- a/compiler/ccgmerge.nim +++ b/compiler/ccgmerge.nim @@ -229,7 +229,7 @@ proc processMergeInfo(L: var TBaseLexer, m: BModule) = when not defined(nimhygiene): {.pragma: inject.} -template withCFile(cfilename: string, body: stmt) {.immediate.} = +template withCFile(cfilename: string, body: untyped) = var s = llStreamOpen(cfilename, fmRead) if s == nil: return var L {.inject.}: TBaseLexer diff --git a/compiler/ccgstmts.nim b/compiler/ccgstmts.nim index 70d50b060..4836527a2 100644 --- a/compiler/ccgstmts.nim +++ b/compiler/ccgstmts.nim @@ -136,7 +136,7 @@ proc exprBlock(p: BProc, n: PNode, d: var TLoc) = expr(p, n, d) endBlock(p) -template preserveBreakIdx(body: stmt): stmt {.immediate.} = +template preserveBreakIdx(body: untyped): untyped = var oldBreakIdx = p.breakIdx body p.breakIdx = oldBreakIdx diff --git a/compiler/extccomp.nim b/compiler/extccomp.nim index eb9aac490..2dcfc0226 100644 --- a/compiler/extccomp.nim +++ b/compiler/extccomp.nim @@ -61,7 +61,7 @@ type # When adding new compilers, the cmake sources could be a good reference: # http://cmake.org/gitweb?p=cmake.git;a=tree;f=Modules/Platform; -template compiler(name: expr, settings: stmt): stmt {.immediate.} = +template compiler(name, settings: untyped): untyped = proc name: TInfoCC {.compileTime.} = settings # GNU C and C++ Compiler diff --git a/compiler/jsgen.nim b/compiler/jsgen.nim index a60b0cc5e..244bba681 100644 --- a/compiler/jsgen.nim +++ b/compiler/jsgen.nim @@ -95,7 +95,7 @@ type up: PProc # up the call chain; required for closure support declaredGlobals: IntSet -template `|`(a, b: expr): expr {.immediate, dirty.} = +template `|`(a, b: untyped): untyped {.dirty.} = (if p.target == targetJS: a else: b) proc newGlobals(): PGlobals = diff --git a/compiler/parser.nim b/compiler/parser.nim index 8b20b56db..2623dea9e 100644 --- a/compiler/parser.nim +++ b/compiler/parser.nim @@ -98,7 +98,7 @@ proc parMessage(p: TParser, msg: TMsgKind, tok: TToken) = ## Produce and emit a parser message to output about the token `tok` parMessage(p, msg, prettyTok(tok)) -template withInd(p: expr, body: stmt) {.immediate.} = +template withInd(p, body: untyped) = let oldInd = p.currInd p.currInd = p.tok.indent body diff --git a/compiler/pragmas.nim b/compiler/pragmas.nim index d7d1ed838..50d5bb1b5 100644 --- a/compiler/pragmas.nim +++ b/compiler/pragmas.nim @@ -626,7 +626,10 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: int, processImportCompilerProc(sym, getOptionalStr(c, it, "$1")) of wExtern: setExternName(sym, expectStrLit(c, it)) of wImmediate: - if sym.kind in {skTemplate, skMacro}: incl(sym.flags, sfImmediate) + if sym.kind in {skTemplate, skMacro}: + incl(sym.flags, sfImmediate) + incl(sym.flags, sfAllUntyped) + message(n.info, warnDeprecated, "use 'untyped' parameters instead; immediate") else: invalidPragma(it) of wDirty: if sym.kind == skTemplate: incl(sym.flags, sfDirty) diff --git a/compiler/semfold.nim b/compiler/semfold.nim index e9ee50d74..7c734e33b 100644 --- a/compiler/semfold.nim +++ b/compiler/semfold.nim @@ -143,7 +143,7 @@ proc getIntervalType*(m: TMagic, n: PNode): PType = const ordIntLit = {nkIntLit..nkUInt64Lit} result = n.typ - template commutativeOp(opr: expr) {.immediate.} = + template commutativeOp(opr: untyped) = let a = n.sons[1] let b = n.sons[2] if isIntRangeOrLit(a.typ) and isIntRangeOrLit(b.typ): @@ -151,7 +151,7 @@ proc getIntervalType*(m: TMagic, n: PNode): PType = opr(pickMinInt(a), pickMinInt(b)), opr(pickMaxInt(a), pickMaxInt(b))) - template binaryOp(opr: expr) {.immediate.} = + template binaryOp(opr: untyped) = let a = n.sons[1] let b = n.sons[2] if isIntRange(a.typ) and b.kind in {nkIntLit..nkUInt64Lit}: diff --git a/compiler/semgnrc.nim b/compiler/semgnrc.nim index b78679411..0fd2e546e 100644 --- a/compiler/semgnrc.nim +++ b/compiler/semgnrc.nim @@ -48,7 +48,10 @@ proc semGenericStmtScope(c: PContext, n: PNode, closeScope(c) template macroToExpand(s: expr): expr = - s.kind in {skMacro, skTemplate} and (s.typ.len == 1 or sfImmediate in s.flags) + s.kind in {skMacro, skTemplate} and (s.typ.len == 1 or sfAllUntyped in s.flags) + +template macroToExpandSym(s: expr): expr = + s.kind in {skMacro, skTemplate} and (s.typ.len == 1) proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym, ctx: var GenericCtx): PNode = @@ -61,14 +64,14 @@ proc semGenericStmtSymbol(c: PContext, n: PNode, s: PSym, of skProc, skMethod, skIterator, skConverter, skModule: result = symChoice(c, n, s, scOpen) of skTemplate: - if macroToExpand(s): + if macroToExpandSym(s): styleCheckUse(n.info, s) result = semTemplateExpr(c, n, s, {efNoSemCheck}) result = semGenericStmt(c, result, {}, ctx) else: result = symChoice(c, n, s, scOpen) of skMacro: - if macroToExpand(s): + if macroToExpandSym(s): styleCheckUse(n.info, s) result = semMacroExpr(c, n, n, s, {efNoSemCheck}) result = semGenericStmt(c, result, {}, ctx) diff --git a/compiler/semparallel.nim b/compiler/semparallel.nim index a4ec14250..21429a8d8 100644 --- a/compiler/semparallel.nim +++ b/compiler/semparallel.nim @@ -260,7 +260,7 @@ proc min(a, b: PNode): PNode = proc fromSystem(op: PSym): bool = sfSystemModule in getModule(op).flags -template pushSpawnId(c: expr, body: stmt) {.immediate, dirty.} = +template pushSpawnId(c, body) {.dirty.} = inc c.spawns let oldSpawnId = c.currentSpawnId c.currentSpawnId = c.spawns diff --git a/compiler/semtempl.nim b/compiler/semtempl.nim index a4498a3ae..3ee019674 100644 --- a/compiler/semtempl.nim +++ b/compiler/semtempl.nim @@ -577,13 +577,16 @@ proc semTemplateDef(c: PContext, n: PNode): PNode = else: gp = newNodeI(nkGenericParams, n.info) # process parameters: + var allUntyped = true if n.sons[paramsPos].kind != nkEmpty: semParamList(c, n.sons[paramsPos], gp, s) # a template's parameters are not gensym'ed even if that was originally the # case as we determine whether it's a template parameter in the template # body by the absence of the sfGenSym flag: for i in 1 .. s.typ.n.len-1: - s.typ.n.sons[i].sym.flags.excl sfGenSym + let param = s.typ.n.sons[i].sym + param.flags.excl sfGenSym + if param.typ.kind != tyExpr: allUntyped = false if sonsLen(gp) > 0: if n.sons[genericParamsPos].kind == nkEmpty: # we have a list of implicit type parameters: @@ -599,6 +602,7 @@ proc semTemplateDef(c: PContext, n: PNode): PNode = s.typ.n = newNodeI(nkFormalParams, n.info) rawAddSon(s.typ, newTypeS(tyStmt, c)) addSon(s.typ.n, newNodeIT(nkType, n.info, s.typ.sons[0])) + if allUntyped: incl(s.flags, sfAllUntyped) if n.sons[patternPos].kind != nkEmpty: n.sons[patternPos] = semPattern(c, n.sons[patternPos]) var ctx: TemplCtx @@ -629,7 +633,7 @@ proc semTemplateDef(c: PContext, n: PNode): PNode = proc semPatternBody(c: var TemplCtx, n: PNode): PNode = template templToExpand(s: expr): expr = - s.kind == skTemplate and (s.typ.len == 1 or sfImmediate in s.flags) + s.kind == skTemplate and (s.typ.len == 1 or sfAllUntyped in s.flags) proc newParam(c: var TemplCtx, n: PNode, s: PSym): PNode = # the param added in the current scope is actually wrong here for diff --git a/compiler/sigmatch.nim b/compiler/sigmatch.nim index 9a8f865a6..b8fd8bf1e 100644 --- a/compiler/sigmatch.nim +++ b/compiler/sigmatch.nim @@ -691,7 +691,7 @@ proc typeRel(c: var TCandidate, f, aOrig: PType, doBind = true): TTypeRelation = put(c.bindings, f, bound) return res - template considerPreviousT(body: stmt) {.immediate.} = + template considerPreviousT(body: untyped) = var prev = PType(idTableGet(c.bindings, f)) if prev == nil: body else: return typeRel(c, prev, a) @@ -1591,7 +1591,7 @@ template isVarargsUntyped(x): expr = proc matchesAux(c: PContext, n, nOrig: PNode, m: var TCandidate, marker: var IntSet) = - template checkConstraint(n: expr) {.immediate, dirty.} = + template checkConstraint(n: untyped) {.dirty.} = if not formal.constraint.isNil: if matchNodeKinds(formal.constraint, n): # better match over other routines with no such restriction: @@ -1816,7 +1816,7 @@ proc instTypeBoundOp*(c: PContext; dc: PSym; t: PType; info: TLineInfo; include suggest when not declared(tests): - template tests(s: stmt) {.immediate.} = discard + template tests(s: untyped) = discard tests: var dummyOwner = newSym(skModule, getIdent("test_module"), nil, UnknownLineInfo()) diff --git a/compiler/suggest.nim b/compiler/suggest.nim index f35ff2142..c127b968c 100644 --- a/compiler/suggest.nim +++ b/compiler/suggest.nim @@ -138,7 +138,7 @@ proc suggestField(c: PContext, s: PSym, outputs: var int) = suggestResult(symToSuggest(s, isLocal=true, $ideSug, 100)) inc outputs -template wholeSymTab(cond, section: expr) {.immediate.} = +template wholeSymTab(cond, section: untyped) = var isLocal = true for scope in walkScopes(c.currentScope): if scope == c.topLevelScope: isLocal = false diff --git a/compiler/types.nim b/compiler/types.nim index 5fbab85b9..42ca0f9fc 100644 --- a/compiler/types.nim +++ b/compiler/types.nim @@ -806,7 +806,7 @@ proc sameTuple(a, b: PType, c: var TSameTypeClosure): bool = elif a.n != b.n and (a.n == nil or b.n == nil) and IgnoreTupleFields notin c.flags: result = false -template ifFastObjectTypeCheckFailed(a, b: PType, body: stmt) {.immediate.} = +template ifFastObjectTypeCheckFailed(a, b: PType, body: untyped) = if tfFromGeneric notin a.flags + b.flags: # fast case: id comparison suffices: result = a.id == b.id diff --git a/compiler/vm.nim b/compiler/vm.nim index f275b7b9b..ac4ea6ad9 100644 --- a/compiler/vm.nim +++ b/compiler/vm.nim @@ -92,34 +92,34 @@ when not defined(nimComputedGoto): proc myreset(n: var TFullReg) = reset(n) -template ensureKind(k: expr) {.immediate, dirty.} = +template ensureKind(k: untyped) {.dirty.} = if regs[ra].kind != k: myreset(regs[ra]) regs[ra].kind = k -template decodeB(k: expr) {.immediate, dirty.} = +template decodeB(k: untyped) {.dirty.} = let rb = instr.regB ensureKind(k) -template decodeBC(k: expr) {.immediate, dirty.} = +template decodeBC(k: untyped) {.dirty.} = let rb = instr.regB let rc = instr.regC ensureKind(k) -template declBC() {.immediate, dirty.} = +template declBC() {.dirty.} = let rb = instr.regB let rc = instr.regC -template decodeBImm(k: expr) {.immediate, dirty.} = +template decodeBImm(k: untyped) {.dirty.} = let rb = instr.regB let imm = instr.regC - byteExcess ensureKind(k) -template decodeBx(k: expr) {.immediate, dirty.} = +template decodeBx(k: expr) {.dirty.} = let rbx = instr.regBx - wordExcess ensureKind(k) -template move(a, b: expr) {.immediate, dirty.} = system.shallowCopy(a, b) +template move(a, b: untyped) {.dirty.} = system.shallowCopy(a, b) # XXX fix minor 'shallowCopy' overloading bug in compiler proc createStrKeepNode(x: var TFullReg; keepNode=true) = @@ -1573,7 +1573,7 @@ proc setupMacroParam(x: PNode, typ: PType): TFullReg = var evalMacroCounter: int proc evalMacroCall*(module: PSym, n, nOrig: PNode, sym: PSym): PNode = - # XXX GlobalError() is ugly here, but I don't know a better solution for now + # XXX globalError() is ugly here, but I don't know a better solution for now inc(evalMacroCounter) if evalMacroCounter > 100: globalError(n.info, errTemplateInstantiationTooNested) @@ -1603,7 +1603,7 @@ proc evalMacroCall*(module: PSym, n, nOrig: PNode, sym: PSym): PNode = # return value: tos.slots[0].kind = rkNode - tos.slots[0].node = newNodeIT(nkEmpty, n.info, sym.typ.sons[0]) + tos.slots[0].node = newNodeI(nkEmpty, n.info) # setup parameters: for i in 1.. = r and r > j) or (r > j and j > i) or (j > i and i >= r)): + doWhile((i >= r and r > j) or (r > j and j > i) or (j > i and i >= r)): i = (i + 1) and msk # increment mod table size if isEmpty(s.data[i].hcode): # end of collision cluster; So all done return @@ -615,7 +615,7 @@ proc card*[A](s: OrderedSet[A]): int {.inline.} = ## `_ of a set. result = s.counter -template forAllOrderedPairs(yieldStmt: stmt) {.dirty, immediate.} = +template forAllOrderedPairs(yieldStmt: untyped) {.dirty.} = var h = s.first while h >= 0: var nxt = s.data[h].next diff --git a/lib/pure/collections/tableimpl.nim b/lib/pure/collections/tableimpl.nim index 1bbf19ee9..f4a135e01 100644 --- a/lib/pure/collections/tableimpl.nim +++ b/lib/pure/collections/tableimpl.nim @@ -72,14 +72,14 @@ proc rawInsert[X, A, B](t: var X, data: var KeyValuePairSeq[A, B], key: A, val: B, hc: Hash, h: Hash) = rawInsertImpl() -template addImpl(enlarge) {.dirty, immediate.} = +template addImpl(enlarge) {.dirty.} = if mustRehash(t.dataLen, t.counter): enlarge(t) var hc: Hash var j = rawGetDeep(t, key, hc) rawInsert(t, t.data, key, val, hc, j) inc(t.counter) -template maybeRehashPutImpl(enlarge) {.dirty, immediate.} = +template maybeRehashPutImpl(enlarge) {.dirty.} = if mustRehash(t.dataLen, t.counter): enlarge(t) index = rawGetKnownHC(t, key, hc) @@ -87,13 +87,13 @@ template maybeRehashPutImpl(enlarge) {.dirty, immediate.} = rawInsert(t, t.data, key, val, hc, index) inc(t.counter) -template putImpl(enlarge) {.dirty, immediate.} = +template putImpl(enlarge) {.dirty.} = var hc: Hash var index = rawGet(t, key, hc) if index >= 0: t.data[index].val = val else: maybeRehashPutImpl(enlarge) -template mgetOrPutImpl(enlarge) {.dirty, immediate.} = +template mgetOrPutImpl(enlarge) {.dirty.} = var hc: Hash var index = rawGet(t, key, hc) if index < 0: @@ -102,7 +102,7 @@ template mgetOrPutImpl(enlarge) {.dirty, immediate.} = # either way return modifiable val result = t.data[index].val -template hasKeyOrPutImpl(enlarge) {.dirty, immediate.} = +template hasKeyOrPutImpl(enlarge) {.dirty.} = var hc: Hash var index = rawGet(t, key, hc) if index < 0: @@ -112,7 +112,7 @@ template hasKeyOrPutImpl(enlarge) {.dirty, immediate.} = proc default[T](t: typedesc[T]): T {.inline.} = discard -template delImpl() {.dirty, immediate.} = +template delImpl() {.dirty.} = var hc: Hash var i = rawGet(t, key, hc) let msk = maxHash(t) @@ -140,7 +140,7 @@ template delImpl() {.dirty, immediate.} = else: shallowCopy(t.data[j], t.data[i]) # data[j] will be marked EMPTY next loop -template clearImpl() {.dirty, immediate.} = +template clearImpl() {.dirty.} = for i in 0 .. = 0: var nxt = t.data[h].next @@ -649,7 +649,7 @@ proc len*[A, B](t: OrderedTableRef[A, B]): int {.inline.} = ## returns the number of keys in `t`. result = t.counter -template forAllOrderedPairs(yieldStmt: stmt) {.dirty, immediate.} = +template forAllOrderedPairs(yieldStmt: untyped) {.dirty.} = var h = t.first while h >= 0: var nxt = t.data[h].next @@ -824,7 +824,7 @@ proc rawGet[A](t: CountTable[A], key: A): int = h = nextTry(h, high(t.data)) result = -1 - h # < 0 => MISSING; insert idx = -1 - result -template ctget(t, key: untyped): untyped {.immediate.} = +template ctget(t, key: untyped): untyped = var index = rawGet(t, key) if index >= 0: result = t.data[index].val else: diff --git a/lib/pure/future.nim b/lib/pure/future.nim index 4e2c1d893..67975cfcb 100644 --- a/lib/pure/future.nim +++ b/lib/pure/future.nim @@ -46,7 +46,7 @@ proc createProcType(p, b: NimNode): NimNode {.compileTime.} = #echo(treeRepr(result)) #echo(result.toStrLit()) -macro `=>`*(p, b: expr): expr {.immediate.} = +macro `=>`*(p, b: untyped): untyped = ## Syntax sugar for anonymous procedures. ## ## .. code-block:: nim @@ -107,7 +107,7 @@ macro `=>`*(p, b: expr): expr {.immediate.} = #echo(result.toStrLit()) #return result # TODO: Bug? -macro `->`*(p, b: expr): expr {.immediate.} = +macro `->`*(p, b: untyped): untyped = ## Syntax sugar for procedure types. ## ## .. code-block:: nim @@ -125,7 +125,7 @@ macro `->`*(p, b: expr): expr {.immediate.} = type ListComprehension = object var lc*: ListComprehension -macro `[]`*(lc: ListComprehension, comp, typ: expr): expr = +macro `[]`*(lc: ListComprehension, comp, typ: untyped): untyped = ## List comprehension, returns a sequence. `comp` is the actual list ## comprehension, for example ``x | (x <- 1..10, x mod 2 == 0)``. `typ` is ## the type that will be stored inside the result seq. diff --git a/lib/pure/net.nim b/lib/pure/net.nim index 7898883cf..9501f6dc7 100644 --- a/lib/pure/net.nim +++ b/lib/pure/net.nim @@ -1000,11 +1000,11 @@ proc readLine*(socket: Socket, line: var TaintedString, timeout = -1, ## ## **Warning**: Only the ``SafeDisconn`` flag is currently supported. - template addNLIfEmpty(): stmt = + template addNLIfEmpty() = if line.len == 0: line.string.add("\c\L") - template raiseSockError(): stmt {.dirty, immediate.} = + template raiseSockError() {.dirty.} = let lastError = getSocketError(socket) if flags.isDisconnectionError(lastError): setLen(line.string, 0); return socket.socketError(n, lastError = lastError) diff --git a/lib/pure/os.nim b/lib/pure/os.nim index 9eb4cf57d..65bbab3d4 100644 --- a/lib/pure/os.nim +++ b/lib/pure/os.nim @@ -173,24 +173,24 @@ proc osLastError*(): OSErrorCode = when defined(windows): when useWinUnicode: - template wrapUnary(varname, winApiProc, arg: expr) {.immediate.} = + template wrapUnary(varname, winApiProc, arg: untyped) = var varname = winApiProc(newWideCString(arg)) - template wrapBinary(varname, winApiProc, arg, arg2: expr) {.immediate.} = + template wrapBinary(varname, winApiProc, arg, arg2: untyped) = var varname = winApiProc(newWideCString(arg), arg2) proc findFirstFile(a: string, b: var WIN32_FIND_DATA): Handle = result = findFirstFileW(newWideCString(a), b) - template findNextFile(a, b: expr): expr = findNextFileW(a, b) - template getCommandLine(): expr = getCommandLineW() + template findNextFile(a, b: untyped): untyped = findNextFileW(a, b) + template getCommandLine(): untyped = getCommandLineW() - template getFilename(f: expr): expr = + template getFilename(f: untyped): untyped = $cast[WideCString](addr(f.cFilename[0])) else: - template findFirstFile(a, b: expr): expr = findFirstFileA(a, b) - template findNextFile(a, b: expr): expr = findNextFileA(a, b) - template getCommandLine(): expr = getCommandLineA() + template findFirstFile(a, b: untyped): untyped = findFirstFileA(a, b) + template findNextFile(a, b: untyped): untyped = findNextFileA(a, b) + template getCommandLine(): untyped = getCommandLineA() - template getFilename(f: expr): expr = $f.cFilename + template getFilename(f: untyped): untyped = $f.cFilename proc skipFindData(f: WIN32_FIND_DATA): bool {.inline.} = # Note - takes advantage of null delimiter in the cstring diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 9b6cf45c5..bfc32bc71 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -475,7 +475,7 @@ proc isNilOrWhitespace*(s: string): bool {.noSideEffect, procvar, rtl, extern: " result = true for c in s: - if not c.isSpace(): + if not c.isSpaceAscii(): return false proc substrEq(s: string, pos: int, substr: string): bool = diff --git a/lib/pure/times.nim b/lib/pure/times.nim index d6cb5c9da..d6eb29e1c 100644 --- a/lib/pure/times.nim +++ b/lib/pure/times.nim @@ -998,21 +998,14 @@ proc parseToken(info: var TimeInfo; token, value: string; j: var int) = info.monthday = value[j..j+1].parseInt() j += 2 of "ddd": - case value[j..j+2].toLower() - of "sun": - info.weekday = dSun - of "mon": - info.weekday = dMon - of "tue": - info.weekday = dTue - of "wed": - info.weekday = dWed - of "thu": - info.weekday = dThu - of "fri": - info.weekday = dFri - of "sat": - info.weekday = dSat + case value[j..j+2].toLowerAscii() + of "sun": info.weekday = dSun + of "mon": info.weekday = dMon + of "tue": info.weekday = dTue + of "wed": info.weekday = dWed + of "thu": info.weekday = dThu + of "fri": info.weekday = dFri + of "sat": info.weekday = dSat else: raise newException(ValueError, "Couldn't parse day of week (ddd), got: " & value[j..j+2]) @@ -1066,31 +1059,19 @@ proc parseToken(info: var TimeInfo; token, value: string; j: var int) = j += 2 info.month = Month(month-1) of "MMM": - case value[j..j+2].toLower(): - of "jan": - info.month = mJan - of "feb": - info.month = mFeb - of "mar": - info.month = mMar - of "apr": - info.month = mApr - of "may": - info.month = mMay - of "jun": - info.month = mJun - of "jul": - info.month = mJul - of "aug": - info.month = mAug - of "sep": - info.month = mSep - of "oct": - info.month = mOct - of "nov": - info.month = mNov - of "dec": - info.month = mDec + case value[j..j+2].toLowerAscii(): + of "jan": info.month = mJan + of "feb": info.month = mFeb + of "mar": info.month = mMar + of "apr": info.month = mApr + of "may": info.month = mMay + of "jun": info.month = mJun + of "jul": info.month = mJul + of "aug": info.month = mAug + of "sep": info.month = mSep + of "oct": info.month = mOct + of "nov": info.month = mNov + of "dec": info.month = mDec else: raise newException(ValueError, "Couldn't parse month (MMM), got: " & value) @@ -1187,7 +1168,7 @@ proc parseToken(info: var TimeInfo; token, value: string; j: var int) = "Couldn't parse timezone offset (zzz), got: " & value[j]) j += 6 of "ZZZ": - info.tzname = value[j..j+2].toUpper() + info.tzname = value[j..j+2].toUpperAscii() j += 3 else: # Ignore the token and move forward in the value string by the same length diff --git a/lib/pure/unittest.nim b/lib/pure/unittest.nim index 7e3983a0d..92ddc3e75 100644 --- a/lib/pure/unittest.nim +++ b/lib/pure/unittest.nim @@ -77,7 +77,7 @@ checkpoints = @[] proc shouldRun(testName: string): bool = result = true -template suite*(name: expr, body: stmt): stmt {.immediate, dirty.} = +template suite*(name, body) {.dirty.} = ## Declare a test suite identified by `name` with optional ``setup`` ## and/or ``teardown`` section. ## @@ -106,13 +106,13 @@ template suite*(name: expr, body: stmt): stmt {.immediate, dirty.} = ## [OK] 2 + 2 = 4 ## [OK] (2 + -2) != 4 block: - template setup(setupBody: stmt): stmt {.immediate, dirty.} = + template setup(setupBody: untyped) {.dirty.} = var testSetupIMPLFlag = true - template testSetupIMPL: stmt {.immediate, dirty.} = setupBody + template testSetupIMPL: untyped {.dirty.} = setupBody - template teardown(teardownBody: stmt): stmt {.immediate, dirty.} = + template teardown(teardownBody: untyped) {.dirty.} = var testTeardownIMPLFlag = true - template testTeardownIMPL: stmt {.immediate, dirty.} = teardownBody + template testTeardownIMPL: untyped {.dirty.} = teardownBody body @@ -135,7 +135,7 @@ proc testDone(name: string, s: TestStatus) = else: rawPrint() -template test*(name: expr, body: stmt): stmt {.immediate, dirty.} = +template test*(name, body) {.dirty.} = ## Define a single test case identified by `name`. ## ## .. code-block:: nim @@ -226,7 +226,7 @@ template skip* = testStatusIMPL = SKIPPED checkpoints = @[] -macro check*(conditions: stmt): stmt {.immediate.} = +macro check*(conditions: untyped): untyped = ## Verify if a statement or a list of statements is true. ## A helpful error message and set checkpoints are printed out on ## failure (if ``outputLevel`` is not ``PRINT_NONE``). @@ -318,7 +318,7 @@ macro check*(conditions: stmt): stmt {.immediate.} = result = getAst(rewrite(checked, checked.lineinfo, checked.toStrLit)) -template require*(conditions: stmt): stmt {.immediate.} = +template require*(conditions: untyped) = ## Same as `check` except any failed test causes the program to quit ## immediately. Any teardown statements are not executed and the failed ## test output is not generated. @@ -328,7 +328,7 @@ template require*(conditions: stmt): stmt {.immediate.} = check conditions abortOnError = savedAbortOnError -macro expect*(exceptions: varargs[expr], body: stmt): stmt {.immediate.} = +macro expect*(exceptions: varargs[typed], body: untyped): untyped = ## Test if `body` raises an exception found in the passed `exceptions`. ## The test passes if the raised exception is part of the acceptable ## exceptions. Otherwise, it fails. diff --git a/lib/system.nim b/lib/system.nim index b4919db2d..2b19b5633 100644 --- a/lib/system.nim +++ b/lib/system.nim @@ -338,15 +338,15 @@ proc `<` *[T](x, y: ref T): bool {.magic: "LtPtr", noSideEffect.} proc `<` *[T](x, y: ptr T): bool {.magic: "LtPtr", noSideEffect.} proc `<` *(x, y: pointer): bool {.magic: "LtPtr", noSideEffect.} -template `!=` * (x, y: expr): expr {.immediate.} = +template `!=` * (x, y: untyped): untyped = ## unequals operator. This is a shorthand for ``not (x == y)``. not (x == y) -template `>=` * (x, y: expr): expr {.immediate.} = +template `>=` * (x, y: untyped): untyped = ## "is greater or equals" operator. This is the same as ``y <= x``. y <= x -template `>` * (x, y: expr): expr {.immediate.} = +template `>` * (x, y: untyped): untyped = ## "is greater" operator. This is the same as ``y < x``. y < x @@ -1098,13 +1098,13 @@ proc contains*[T](s: Slice[T], value: T): bool {.noSideEffect, inline.} = ## assert((1..3).contains(4) == false) result = s.a <= value and value <= s.b -template `in` * (x, y: expr): expr {.immediate, dirty.} = contains(y, x) +template `in` * (x, y: untyped): untyped {.dirty.} = contains(y, x) ## Sugar for contains ## ## .. code-block:: Nim ## assert(1 in (1..3) == true) ## assert(5 in (1..3) == false) -template `notin` * (x, y: expr): expr {.immediate, dirty.} = not contains(y, x) +template `notin` * (x, y: untyped): untyped {.dirty.} = not contains(y, x) ## Sugar for not containing ## ## .. code-block:: Nim @@ -1123,7 +1123,7 @@ proc `is` *[T, S](x: T, y: S): bool {.magic: "Is", noSideEffect.} ## ## assert(test[int](3) == 3) ## assert(test[string]("xyz") == 0) -template `isnot` *(x, y: expr): expr {.immediate.} = not (x is y) +template `isnot` *(x, y: untyped): untyped = not (x is y) ## Negated version of `is`. Equivalent to ``not(x is y)``. proc `of` *[T, S](x: T, y: S): bool {.magic: "Of", noSideEffect.} @@ -1742,11 +1742,11 @@ proc swap*[T](a, b: var T) {.magic: "Swap", noSideEffect.} ## swaps the values `a` and `b`. This is often more efficient than ## ``tmp = a; a = b; b = tmp``. Particularly useful for sorting algorithms. -template `>=%` *(x, y: expr): expr {.immediate.} = y <=% x +template `>=%` *(x, y: untyped): untyped = y <=% x ## treats `x` and `y` as unsigned and compares them. ## Returns true iff ``unsigned(x) >= unsigned(y)``. -template `>%` *(x, y: expr): expr {.immediate.} = y <% x +template `>%` *(x, y: untyped): untyped = y <% x ## treats `x` and `y` as unsigned and compares them. ## Returns true iff ``unsigned(x) > unsigned(y)``. @@ -1874,7 +1874,7 @@ iterator countdown*[T](a, b: T, step = 1): T {.inline.} = yield res dec(res, step) -template countupImpl(incr: stmt) {.immediate, dirty.} = +template countupImpl(incr: untyped) {.dirty.} = when T is IntLikeForCount: var res = int(a) while res <= int(b): @@ -3181,7 +3181,7 @@ proc `/`*(x, y: int): float {.inline, noSideEffect.} = ## integer division that results in a float. result = toFloat(x) / toFloat(y) -template spliceImpl(s, a, L, b: expr): stmt {.immediate.} = +template spliceImpl(s, a, L, b: untyped): untyped = # make room for additional elements or cut: var slen = s.len var shift = b.len - L @@ -3467,7 +3467,7 @@ iterator mitems*(a: var string): var char {.inline.} = when not defined(nimhygiene): {.pragma: inject.} -template onFailedAssert*(msg: expr, code: stmt): stmt {.dirty, immediate.} = +template onFailedAssert*(msg, code: untyped): untyped {.dirty.} = ## Sets an assertion failure handler that will intercept any assert ## statements following `onFailedAssert` in the current module scope. ## @@ -3480,7 +3480,7 @@ template onFailedAssert*(msg: expr, code: stmt): stmt {.dirty, immediate.} = ## e.lineinfo = instantiationInfo(-2) ## raise e ## - template failedAssertImpl(msgIMPL: string): stmt {.dirty.} = + template failedAssertImpl(msgIMPL: string): untyped {.dirty.} = let msg = msgIMPL code @@ -3643,7 +3643,7 @@ proc `==` *(x, y: cstring): bool {.magic: "EqCString", noSideEffect, elif x.isNil or y.isNil: result = false else: result = strcmp(x, y) == 0 -template closureScope*(body: untyped): stmt = +template closureScope*(body: untyped): untyped = ## Useful when creating a closure in a loop to capture local loop variables by ## their current iteration values. Example: ## diff --git a/tests/macros/tdumptree.nim b/tests/macros/tdumptree.nim index e5160b7ba..58b011b45 100644 --- a/tests/macros/tdumptree.nim +++ b/tests/macros/tdumptree.nim @@ -3,7 +3,7 @@ msg: '''StmtList VarSection IdentDefs Ident !"x" - nil + Empty Call DotExpr Ident !"foo" diff --git a/todo.txt b/todo.txt index 235d79b78..38d0fef58 100644 --- a/todo.txt +++ b/todo.txt @@ -1,7 +1,9 @@ version 1.0 battle plan ======================= -- Deprecate ``immediate`` for templates and macros +- tests/stdlib/tnre.nim +- tests/closure/tflatmap.nim + - fix "high priority" bugs - try to fix as many compiler crashes as reasonable diff --git a/web/news/version_0_15_released.rst b/web/news/version_0_15_released.rst index e16bdc9ae..c440161c4 100644 --- a/web/news/version_0_15_released.rst +++ b/web/news/version_0_15_released.rst @@ -30,6 +30,10 @@ Changes affecting backwards compatibility discussion. - Overloading the special operators ``.``, ``.()``, ``.=``, ``()`` now should be enabled via ``{.experimental.}``. +- ``immediate`` templates and macros are now deprecated. + Instead use ``untyped`` parameters. +- The metatype ``expr`` is deprecated. Use ``untyped`` instead. +- The metatype ``stmt`` is deprecated. Use ``typed`` instead. Library Additions -- cgit 1.4.1-2-gfad0