Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 0 additions & 25 deletions bin/nim-gdb

This file was deleted.

1 change: 0 additions & 1 deletion bin/nim-gdb.bash

This file was deleted.

14 changes: 0 additions & 14 deletions bin/nim-gdb.bat

This file was deleted.

3 changes: 3 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
147 changes: 89 additions & 58 deletions lib/pure/strutils.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -2050,97 +2051,128 @@ 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>`_
## * `replace func<#replace,string,char,char>`_ for replacing
## 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
Expand Down Expand Up @@ -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)
Expand Down
7 changes: 7 additions & 0 deletions tests/stdlib/tstrutils.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down