Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(stdlib): Add replacement functions to String module #1441

Merged
merged 11 commits into from
Nov 8, 2022
22 changes: 22 additions & 0 deletions compiler/test/stdlib/string.test.gr
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,28 @@ assert String.endsWith(emojis, emojis)
assert !String.endsWith("bluefox", short)
assert !String.endsWith("q", emoji)
assert !String.endsWith("LAZY DOG.", fox)
// Replace First
assert String.replaceFirst("Hello", "Hi", "Hey Hello World") == "Hey Hi World"
assert String.replaceFirst("Hello", "Hi", "Hello World") == "Hi World"
assert String.replaceFirst("Hello", "Hi", "Hey World Hello") == "Hey World Hi"
assert String.replaceFirst("Hello", "Hi", "Hello Hello") == "Hi Hello"
assert String.replaceFirst("Hello", "Hi", "Hello") == "Hi"
assert String.replaceFirst("Hello", "Hi", "World") == "World"
assert String.replaceFirst("Hello", "Hi", "Hel") == "Hel"
assert String.replaceFirst("🌾", "Grain", "Hello") == "Hello"
assert String.replaceFirst("🌾", "Hello", "Hello Grain") == "Hello Grain"
spotandjake marked this conversation as resolved.
Show resolved Hide resolved
assert String.replaceFirst("Hello", "🌾", "Hello Grain") == "🌾 Grain"
spotandjake marked this conversation as resolved.
Show resolved Hide resolved
// Replace Last
assert String.replaceLast("Hello", "Hi", "Hey Hello World") == "Hey Hi World"
assert String.replaceLast("Hello", "Hi", "Hello World") == "Hi World"
assert String.replaceLast("Hello", "Hi", "Hey World Hello") == "Hey World Hi"
assert String.replaceLast("Hello", "Hi", "Hello Hello") == "Hello Hi"
assert String.replaceLast("Hello", "Hi", "Hello") == "Hi"
assert String.replaceLast("Hello", "Hi", "World") == "World"
assert String.replaceLast("Hello", "Hi", "Hel") == "Hel"
assert String.replaceLast("🌾", "Grain", "Hello") == "Hello"
assert String.replaceLast("🌾", "Hello", "Hello Grain") == "Hello Grain"
spotandjake marked this conversation as resolved.
Show resolved Hide resolved
assert String.replaceLast("Hello", "🌾", "Hello Grain") == "🌾 Grain"
spotandjake marked this conversation as resolved.
Show resolved Hide resolved

let processBytes = b => {
let ret = Array.make(Bytes.length(b), 0l)
Expand Down
150 changes: 150 additions & 0 deletions stdlib/string.gr
Original file line number Diff line number Diff line change
Expand Up @@ -872,6 +872,156 @@ export let endsWith = (search: String, string: String) => {
}
}

/**
* Replaces the first appearance of the search pattern in the string with the replacement value.
*
* @param searchPattern: The string to replace
* @param replacement: The string to replace with
* @param string: The string to replace on
spotandjake marked this conversation as resolved.
Show resolved Hide resolved
* @returns A new string with the search pattern replaced
spotandjake marked this conversation as resolved.
Show resolved Hide resolved
*
* @example String.replaceFirst("Hello", "Hi", "Hello World Hello") == "Hi World Hello"
spotandjake marked this conversation as resolved.
Show resolved Hide resolved
*
* @since v0.5.3
*/
@unsafe
export let replaceFirst =
(
searchPattern: String,
replacement: String,
string: String,
) => {
let (+) = WasmI32.add
let (-) = WasmI32.sub
let (>) = WasmI32.gtU
let (<) = WasmI32.ltU
let (==) = WasmI32.eq

let mut patternPtr = WasmI32.fromGrain(searchPattern)
let mut stringPtr = WasmI32.fromGrain(string)
let mut replacementPtr = WasmI32.fromGrain(replacement)

let patternLen = WasmI32.load(patternPtr, 4n)
let stringLen = WasmI32.load(stringPtr, 4n)
let replacementLen = WasmI32.load(replacementPtr, 4n)

patternPtr += 8n
stringPtr += 8n
replacementPtr += 8n
// Bail if search str is longer then the string
spotandjake marked this conversation as resolved.
Show resolved Hide resolved
if (stringLen < patternLen) {
string
} else {
let str = allocateString(stringLen - patternLen + replacementLen)
spotandjake marked this conversation as resolved.
Show resolved Hide resolved
let mut strPtr = str + 8n
let mut startOffset = 0n
let mut strOffset = 0n
let mut found = false
// Search for an instance of the string
for (let mut i = 0n; i < stringLen; i += 1n) {
spotandjake marked this conversation as resolved.
Show resolved Hide resolved
// check for match
if (Memory.compare(stringPtr + i, patternPtr, patternLen) == 0n) {
// Create A Copy of the string
Memory.copy(strPtr + startOffset, stringPtr, i)
startOffset += i
strOffset += i
Memory.copy(strPtr + startOffset, replacementPtr, replacementLen)
startOffset += patternLen
strOffset += replacementLen
Memory.copy(
strPtr + strOffset,
stringPtr + startOffset,
stringLen - startOffset
)
found = true
break // break on first match
}
}
if (!found) {
string
} else {
WasmI32.toGrain(str): String
}
}
}

/**
* Replaces the last appearance of the search pattern in the string with the replacement value.
*
* @param searchPattern: The string to replace
* @param replacement: The string to replace with
* @param string: The string to replace on
spotandjake marked this conversation as resolved.
Show resolved Hide resolved
* @returns A new string with the search pattern replaced
spotandjake marked this conversation as resolved.
Show resolved Hide resolved
*
* @example String.replaceLast("Hello", "Hi", "Hello World Hello") == "Hello World Hi"
spotandjake marked this conversation as resolved.
Show resolved Hide resolved
*
* @since v0.5.3
*/
@unsafe
export let replaceLast =
(
searchPattern: String,
replacement: String,
string: String,
) => {
let (+) = WasmI32.add
let (-) = WasmI32.sub
let (>) = WasmI32.gtU
let (<) = WasmI32.ltU
let (==) = WasmI32.eq

let mut patternPtr = WasmI32.fromGrain(searchPattern)
let mut stringPtr = WasmI32.fromGrain(string)
let mut replacementPtr = WasmI32.fromGrain(replacement)

let patternLen = WasmI32.load(patternPtr, 4n)
let stringLen = WasmI32.load(stringPtr, 4n)
let replacementLen = WasmI32.load(replacementPtr, 4n)

patternPtr += 8n
stringPtr += 8n
replacementPtr += 8n
// Bail if search str is longer then the string
spotandjake marked this conversation as resolved.
Show resolved Hide resolved
if (stringLen < patternLen) {
string
} else {
let strLen = stringLen - patternLen + replacementLen
let str = allocateString(strLen)
let mut strPtr = str + 7n + strLen + patternLen
let mut found = false
// Search for an instance of the string
let stringEndPtr = stringPtr + stringLen - 1n
for (let mut i = stringEndPtr; i > stringPtr - 1n; i -= 1n) {
spotandjake marked this conversation as resolved.
Show resolved Hide resolved
// check for match
if (Memory.compare(i, patternPtr, patternLen) == 0n) {
Memory.copy(
strPtr,
i + patternLen,
stringEndPtr - i + 1n + patternLen
) // copy last part of string
Memory.copy(
strPtr - replacementLen,
replacementPtr,
replacementLen
) // Replace replacement
Memory.copy(
str + 8n,
stringPtr,
i - stringPtr
) // copy first part of string
found = true
break
}
strPtr -= 1n
}
if (!found) {
string
} else {
WasmI32.toGrain(str): String
}
}
}

// String->Byte encoding and helper functions:

// these are globals to avoid memory leaks
Expand Down
Loading