diff --git a/bin/nim-gdb b/bin/nim-gdb deleted file mode 100755 index f7f9a79c628e2..0000000000000 --- a/bin/nim-gdb +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash - -# Exit if anything fails -set -e - -which nim > /dev/null || (echo "nim not in PATH"; exit 1) -which gdb > /dev/null || (echo "gdb not in PATH"; exit 1) -which readlink > /dev/null || (echo "readlink not in PATH."; exit 1) - -if [[ "$(uname -s)" == "Darwin" || "(uname -s)" == *"BSD" ]]; then - NIM_SYSROOT=$(dirname $(dirname $(readlink -f $(which nim)))) -else - NIM_SYSROOT=$(dirname $(dirname $(readlink -e $(which nim)))) -fi - -# Find out where the pretty printer Python module is -GDB_PYTHON_MODULE_PATH="$NIM_SYSROOT/tools/nim-gdb.py" - -# Run GDB with the additional arguments that load the pretty printers -# Set the environment variable `NIM_GDB` to overwrite the call to a -# different/specific command (defaults to `gdb`). -NIM_GDB="${NIM_GDB:-gdb}" -# exec replaces the new process of bash with gdb. It is always good to -# have fewer processes. -exec "${NIM_GDB}" -eval-command="source $GDB_PYTHON_MODULE_PATH" "$@" diff --git a/bin/nim-gdb.bash b/bin/nim-gdb.bash deleted file mode 120000 index f6eba4b0c4040..0000000000000 --- a/bin/nim-gdb.bash +++ /dev/null @@ -1 +0,0 @@ -nim-gdb \ No newline at end of file diff --git a/bin/nim-gdb.bat b/bin/nim-gdb.bat deleted file mode 100644 index e98a2063c8cf2..0000000000000 --- a/bin/nim-gdb.bat +++ /dev/null @@ -1,14 +0,0 @@ -@echo off -for %%i in (nim.exe) do (set NIM_BIN=%%~dp$PATH:i) - -for %%i in ("%NIM_BIN%\..\") do (set NIM_ROOT=%%~fi) - -set @GDB_PYTHON_MODULE_PATH=%NIM_ROOT%\tools\nim-gdb.py -set @NIM_GDB=gdb.exe - -@echo source %@GDB_PYTHON_MODULE_PATH%> wingdbcommand.txt -%@NIM_GDB% --command="wingdbcommand.txt" %* -del wingdbcommand.txt /f /q - -EXIT /B %ERRORLEVEL% -@echo on diff --git a/changelog.md b/changelog.md index 6672f7538ab1e..fd03d9da98716 100644 --- a/changelog.md +++ b/changelog.md @@ -339,6 +339,9 @@ - Added `dom.scrollIntoView` proc with options +- Added a `max` parameter to `strutils.replace`, this way it can be used to + only replace some occurrences. + ## Language changes - `nimscript` now handles `except Exception as e`. diff --git a/lib/pure/strutils.nim b/lib/pure/strutils.nim index 9e08379421707..caf072a883df9 100644 --- a/lib/pure/strutils.nim +++ b/lib/pure/strutils.nim @@ -81,6 +81,7 @@ export toLower, toUpper include "system/inclrtl" import std/private/since from std/private/strimpl import cmpIgnoreStyleImpl, cmpIgnoreCaseImpl, startsWithImpl, endsWithImpl +import std/strbasics const @@ -2050,9 +2051,45 @@ func contains*(s: string, chars: set[char]): bool = ## * `find func<#find,string,set[char],Natural,int>`_ return find(s, chars) >= 0 -func replace*(s, sub: string, by = ""): string {.rtl, +func replace*(s: string, sub, by: char, max = -1): string {.rtl, + extern: "nsuReplaceChar".} = + ## Replaces `max` occurrences of the character `sub` in `s` with + ## the character `by`. + ## If `max == -1`, it replaces all occurrences. + ## + ## Optimized version of `replace <#replace,string,string,string>`_ for + ## characters. + ## + ## See also: + ## * `find func<#find,string,char,Natural,int>`_ + ## * `replaceWord func<#replaceWord,string,string,string>`_ + ## * `multiReplace func<#multiReplace,string,varargs[]>`_ + runnableExamples: + assert "valid variable name".replace(' ', '_') == "valid_variable_name" + assert "Faabar!".replace('a', 'o', 2) == "Foobar!" + result = newStringOfCap(s.len) + var + i = 0 + occLeft = max + while i <= s.high: + if occLeft == 0: break + if s[i] == sub: + result.add by + if occLeft > 0: dec occLeft + else: + result.add s[i] + inc i + if occLeft == 0: + # copy the rest: + result.add s.substr(i) + +# consider adding: `func replace*(s: string, subs: set[char], by: char, max = -1): string` + +func replace*(s, sub: string, by = "", max = -1): string {.rtl, extern: "nsuReplaceStr".} = - ## Replaces every occurrence of the string `sub` in `s` with the string `by`. + ## Replaces `max` occurrences of the string `sub` in `s` with the + ## string `by`. + ## If `max` is set to `-1`, it replaces all occurrences. ## ## See also: ## * `find func<#find,string,string,Natural,int>`_ @@ -2060,87 +2097,82 @@ func replace*(s, sub: string, by = ""): string {.rtl, ## single characters ## * `replaceWord func<#replaceWord,string,string,string>`_ ## * `multiReplace func<#multiReplace,string,varargs[]>`_ + runnableExamples: + assert "FooFooBaz".replace("Foo", "Bar") == "BarBarBaz" + assert "BarBarBaz".replace("Bar", "Foo", 1) == "FooBarBaz" result = "" let subLen = sub.len - if subLen == 0: - result = s + if subLen == 0: result = s elif subLen == 1: + let subChar = sub[0] # when the pattern is a single char, we use a faster # char-based search that doesn't need a skip table: - let c = sub[0] - let last = s.high - var i = 0 + var + i = 0 + occLeft = max while true: - let j = find(s, c, i, last) - if j < 0: break - add result, substr(s, i, j - 1) - add result, by + let j = s.find(subChar, i) + if j == -1 or occLeft == 0: break + if occLeft > 0: dec occLeft + + result.add s.substr(i, j - 1) + result.add by i = j + subLen # copy the rest: - add result, substr(s, i) + result.add s.substr(i) else: var a {.noinit.}: SkipTable initSkipTable(a, sub) - let last = s.high - var i = 0 + var + i = 0 + occLeft = max while true: - let j = find(a, s, sub, i, last) - if j < 0: break - add result, substr(s, i, j - 1) - add result, by + let j = find(a, s, sub, i) + if j == -1 or occLeft == 0: break + if occLeft > 0: dec occLeft + result.add s.toOpenArray(i, j - 1) + result.add by i = j + subLen # copy the rest: - add result, substr(s, i) + result.add s.substr(i) -func replace*(s: string, sub, by: char): string {.rtl, - extern: "nsuReplaceChar".} = - ## Replaces every occurrence of the character `sub` in `s` with the character - ## `by`. - ## - ## Optimized version of `replace <#replace,string,string,string>`_ for - ## characters. - ## - ## See also: - ## * `find func<#find,string,char,Natural,int>`_ - ## * `replaceWord func<#replaceWord,string,string,string>`_ - ## * `multiReplace func<#multiReplace,string,varargs[]>`_ - result = newString(s.len) - var i = 0 - while i < s.len: - if s[i] == sub: result[i] = by - else: result[i] = s[i] - inc(i) - -func replaceWord*(s, sub: string, by = ""): string {.rtl, +func replaceWord*(s, sub: string, by = "", max = -1): string {.rtl, extern: "nsuReplaceWord".} = - ## Replaces every occurrence of the string `sub` in `s` with the string `by`. + ## Replaces `max` occurrences of the string `sub` in `s` with the + ## string `by`. + ## If `max` is set to `-1`, it replaces all occurrences. ## ## Each occurrence of `sub` has to be surrounded by word boundaries ## (comparable to `\b` in regular expressions), otherwise it is not ## replaced. - if sub.len == 0: return s - const wordChars = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\128'..'\255'} - var a {.noinit.}: SkipTable + runnableExamples: + assert "Hello, Helloworld".replaceWord("Hello", "Hi") == "Hi, Helloworld" + assert "no, yes, nono, no, no?".replaceWord("no", "yes", 2) == "yes, yes, nono, yes, no?" result = "" - initSkipTable(a, sub) - var i = 0 - let last = s.high - let sublen = sub.len - if sublen > 0: + let subLen = sub.len + if subLen == 0: result = s + else: + const wordChars = {'a'..'z', 'A'..'Z', '0'..'9', '_', '\128'..'\255'} + var a {.noinit.}: SkipTable + initSkipTable(a, sub) + var + i = 0 + occLeft = max while true: - var j = find(a, s, sub, i, last) - if j < 0: break - # word boundary? + var j = find(a, s, sub, i) + if j == -1 or occLeft == 0: break + # check for word boundaries if (j == 0 or s[j-1] notin wordChars) and - (j+sub.len >= s.len or s[j+sub.len] notin wordChars): - add result, substr(s, i, j - 1) - add result, by - i = j + sublen + (j+sub.len >= s.len or s[j+sub.len] notin wordChars): + result.add s.substr(i, j - 1) + result.add by + i = j + subLen + if occLeft > 0: dec occLeft else: - add result, substr(s, i, j) + result.add s.substr(i, j) i = j + 1 # copy the rest: - add result, substr(s, i) + result.add s.substr(i) func multiReplace*(s: string, replacements: varargs[(string, string)]): string = ## Same as replace, but specialized for doing multiple replacements in a single @@ -2176,7 +2208,6 @@ func multiReplace*(s: string, replacements: varargs[(string, string)]): string = inc(i) - func insertSep*(s: string, sep = '_', digits = 3): string {.rtl, extern: "nsuInsertSep".} = ## Inserts the separator `sep` after `digits` characters (default: 3) diff --git a/tests/stdlib/tstrutils.nim b/tests/stdlib/tstrutils.nim index 68ee5812bc49e..5bd08fb34e9a4 100644 --- a/tests/stdlib/tstrutils.nim +++ b/tests/stdlib/tstrutils.nim @@ -514,9 +514,16 @@ template main() = block: let c = "" let c2 = c.replace("\n", "\\n") + + doAssert "aaoaaao".replace("aa", "ii") == "iioiiao" + doAssert "Hello World".replace("o", "", 1) == "Hell World" + doAssert "Hello World".replace("l", "", 10) == "Heo Word" + doAssert "aaaaaa".replace('a', 'b', 2) == "bbaaaa" + doAssert "aaaaaa".replace('a', 'b', 10) == "bbbbbb" block: # replaceWord doAssert "-ld a-ldz -ld".replaceWord("-ld") == " a-ldz " + doAssert "-ld a-ldz -ld -ld".replaceWord("-ld", "", 2) == " a-ldz -ld" doAssert "-lda-ldz -ld abc".replaceWord("-ld") == "-lda-ldz abc" doAssert "-lda-ldz -ld abc".replaceWord("") == "-lda-ldz -ld abc"