Skip to content

Commit b73d9bf

Browse files
authored
feat(stdlib): Add lastIndexOf function to String module (#1372)
1 parent 2db6e78 commit b73d9bf

File tree

3 files changed

+105
-2
lines changed

3 files changed

+105
-2
lines changed

compiler/test/stdlib/string.test.gr

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,17 @@ assert String.indexOf("🚀", emojis) == Some(15)
6969
assert String.indexOf(short, short) == Some(0)
7070
assert String.indexOf(emoji, emojis) == None
7171
assert String.indexOf("aa", "aaa") == Some(0)
72+
assert String.indexOf("world", "Hello world world") == Some(6)
73+
// lastIndexOf tests
74+
assert String.lastIndexOf(empty, empty) == Some(0)
75+
assert String.lastIndexOf(empty, short) == Some(3)
76+
assert String.lastIndexOf(short, fox) == Some(16)
77+
assert String.lastIndexOf("🚀", emojis) == Some(15)
78+
assert String.lastIndexOf("🌾", "🌾🌾🌾🌾🌾") == Some(4)
79+
assert String.lastIndexOf(short, short) == Some(0)
80+
assert String.lastIndexOf(emoji, emojis) == None
81+
assert String.lastIndexOf("aa", "aaa") == Some(1)
82+
assert String.lastIndexOf("world", "Hello world world") == Some(12)
7283

7384
// charAt tests
7485
assert String.charAt(0, emojis) == 'w'

stdlib/string.gr

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import Memory from "runtime/unsafe/memory"
1111
import Exception from "runtime/exception"
1212
import Conv from "runtime/unsafe/conv"
1313
import {
14+
untagSimpleNumber,
1415
tagSimpleNumber,
1516
tagChar,
1617
untagChar,
@@ -100,7 +101,7 @@ export let byteLength = (string: String) => {
100101
}
101102

102103
/**
103-
* Finds the position of a substring in the input string.
104+
* Finds the first position of a substring in the input string.
104105
*
105106
* @param search: The substring to find
106107
* @param string: The string to inspect
@@ -161,6 +162,65 @@ export let indexOf = (search: String, string: String) => {
161162
}
162163
}
163164

165+
/**
166+
* Finds the last position of a substring in the input string.
167+
*
168+
* @param search: The substring to find
169+
* @param string: The string to inspect
170+
* @returns `Some(position)` containing the starting position of the substring if found or `None` otherwise
171+
*
172+
* @example String.lastIndexOf("world", "Hello world world") == Some(12)
173+
*
174+
* @since v0.5.3
175+
*/
176+
@unsafe
177+
export let lastIndexOf = (search: String, string: String) => {
178+
// The last possible index in the string given the length of the search
179+
let lastIndex = length(string) - length(search)
180+
181+
let (>) = WasmI32.gtU
182+
let (>=) = WasmI32.geU
183+
let (==) = WasmI32.eq
184+
let (!=) = WasmI32.ne
185+
let (+) = WasmI32.add
186+
let (-) = WasmI32.sub
187+
let (&) = WasmI32.and
188+
189+
let search = WasmI32.fromGrain(search)
190+
let string = WasmI32.fromGrain(string)
191+
let searchSize = WasmI32.load(search, 4n)
192+
let stringSize = WasmI32.load(string, 4n)
193+
if (searchSize > stringSize) {
194+
None
195+
} else {
196+
let mut matchIndex = -1n
197+
let mut stringIndex = untagSimpleNumber(lastIndex)
198+
let searchPtr = search + 8n
199+
let stringStartPtr = string + 8n
200+
for (
201+
let mut stringPtr = stringStartPtr + stringSize - searchSize;
202+
stringPtr >= stringStartPtr;
203+
stringPtr -= 1n
204+
) {
205+
let byte = WasmI32.load8U(stringPtr, 0n)
206+
if ((byte & 0xC0n) == 0x80n) {
207+
// We're not at the start of a codepoint, so move on
208+
continue
209+
}
210+
if (Memory.compare(stringPtr, searchPtr, searchSize) == 0n) {
211+
matchIndex = stringIndex
212+
break
213+
}
214+
stringIndex -= 1n
215+
}
216+
if (matchIndex == -1n) {
217+
None
218+
} else {
219+
Some(tagSimpleNumber(matchIndex))
220+
}
221+
}
222+
}
223+
164224
@disableGC
165225
let getCodePoint = (ptr: WasmI32) => {
166226
// Algorithm from https://encoding.spec.whatwg.org/#utf-8-decoder

stdlib/string.md

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ No other changes yet.
148148
indexOf : (String, String) -> Option<Number>
149149
```
150150

151-
Finds the position of a substring in the input string.
151+
Finds the first position of a substring in the input string.
152152

153153
Parameters:
154154

@@ -169,6 +169,38 @@ Examples:
169169
String.indexOf("world", "Hello world") == Some(6)
170170
```
171171

172+
### String.**lastIndexOf**
173+
174+
<details disabled>
175+
<summary tabindex="-1">Added in <code>next</code></summary>
176+
No other changes yet.
177+
</details>
178+
179+
```grain
180+
lastIndexOf : (String, String) -> Option<Number>
181+
```
182+
183+
Finds the last position of a substring in the input string.
184+
185+
Parameters:
186+
187+
|param|type|description|
188+
|-----|----|-----------|
189+
|`search`|`String`|The substring to find|
190+
|`string`|`String`|The string to inspect|
191+
192+
Returns:
193+
194+
|type|description|
195+
|----|-----------|
196+
|`Option<Number>`|`Some(position)` containing the starting position of the substring if found or `None` otherwise|
197+
198+
Examples:
199+
200+
```grain
201+
String.lastIndexOf("world", "Hello world world") == Some(12)
202+
```
203+
172204
### String.**charAt**
173205

174206
<details disabled>

0 commit comments

Comments
 (0)