Skip to content

Commit df66010

Browse files
authored
Add Str.contains built-in (#8436)
1 parent 036b069 commit df66010

File tree

7 files changed

+121
-3
lines changed

7 files changed

+121
-3
lines changed

src/build/builtin_compiler/main.zig

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ fn replaceStrIsEmptyWithLowLevel(env: *ModuleEnv) !std.ArrayList(CIR.Def.Idx) {
101101
if (env.common.findIdent("Builtin.Str.concat")) |str_concat_ident| {
102102
try low_level_map.put(str_concat_ident, .str_concat);
103103
}
104+
if (env.common.findIdent("Builtin.Str.contains")) |str_contains_ident| {
105+
try low_level_map.put(str_contains_ident, .str_contains);
106+
}
104107
if (env.common.findIdent("Builtin.Str.trim")) |str_trim_ident| {
105108
try low_level_map.put(str_trim_ident, .str_trim);
106109
}

src/build/roc/Builtin.roc

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@ Builtin :: [].{
22
Str :: [ProvidedByCompiler].{
33
is_empty : Str -> Bool
44
concat : Str, Str -> Str
5+
contains : Str, Str -> Bool
56
trim : Str -> Str
67
caseless_ascii_equals : Str, Str -> Bool
78
with_ascii_lowercased : Str -> Str
89
with_ascii_uppercased : Str -> Str
9-
10-
contains : Str, Str -> Bool
11-
contains = |_str, _other| True
1210
}
1311

1412
List(_item) :: [ProvidedByCompiler].{

src/builtins/str.zig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,11 @@ pub fn strConcat(
801801
}
802802
}
803803

804+
/// Str.contains
805+
pub fn strContains(haystack: RocStr, needle: RocStr) callconv(.c) bool {
806+
return std.mem.indexOf(u8, haystack.asSlice(), needle.asSlice()) != null;
807+
}
808+
804809
/// TODO: Document RocListStr.
805810
pub const RocListStr = extern struct {
806811
list_elements: ?[*]RocStr,

src/canonicalize/Expression.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,7 @@ pub const Expr = union(enum) {
402402
// String operations
403403
str_is_empty,
404404
str_concat,
405+
str_contains,
405406
str_trim,
406407
str_caseless_ascii_equals,
407408
str_with_ascii_lowercased,

src/eval/interpreter.zig

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2801,6 +2801,23 @@ pub const Interpreter = struct {
28012801
out.is_initialized = true;
28022802
return out;
28032803
},
2804+
.str_contains => {
2805+
// Str.contains : Str, Str -> Bool
2806+
std.debug.assert(args.len == 2);
2807+
2808+
const haystack_arg = args[0];
2809+
const needle_arg = args[1];
2810+
2811+
std.debug.assert(haystack_arg.ptr != null);
2812+
std.debug.assert(needle_arg.ptr != null);
2813+
2814+
const haystack: *const RocStr = @ptrCast(@alignCast(haystack_arg.ptr.?));
2815+
const needle: *const RocStr = @ptrCast(@alignCast(needle_arg.ptr.?));
2816+
2817+
const result = builtins.str.strContains(haystack.*, needle.*);
2818+
2819+
return try self.makeBoolValue(result);
2820+
},
28042821
.str_trim => {
28052822
// Str.trim : Str -> Str
28062823
std.debug.assert(args.len == 1);

src/eval/test/low_level_interp_test.zig

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,69 @@ test "e_low_level_lambda - Str.concat with longer strings" {
276276
try testing.expectEqualStrings("\"This is a longer string that contains about one hundred characters for testing concatenation. This is the second string that also has many characters in it for testing longer string operations.\"", value);
277277
}
278278

279+
test "e_low_level_lambda - Str.contains with substring in middle" {
280+
const src =
281+
\\x = Str.contains("foobarbaz", "bar")
282+
;
283+
const value = try evalModuleAndGetString(src, 0, test_allocator);
284+
defer test_allocator.free(value);
285+
try testing.expectEqualStrings("True", value);
286+
}
287+
288+
test "e_low_level_lambda - Str.contains with non-matching strings" {
289+
const src =
290+
\\x = Str.contains("apple", "orange")
291+
;
292+
const value = try evalModuleAndGetString(src, 0, test_allocator);
293+
defer test_allocator.free(value);
294+
try testing.expectEqualStrings("False", value);
295+
}
296+
297+
test "e_low_level_lambda - Str.contains with empty needle" {
298+
const src =
299+
\\x = Str.contains("anything", "")
300+
;
301+
const value = try evalModuleAndGetString(src, 0, test_allocator);
302+
defer test_allocator.free(value);
303+
try testing.expectEqualStrings("True", value);
304+
}
305+
306+
test "e_low_level_lambda - Str.contains with substring at start" {
307+
const src =
308+
\\x = Str.contains("hello world", "hello")
309+
;
310+
const value = try evalModuleAndGetString(src, 0, test_allocator);
311+
defer test_allocator.free(value);
312+
try testing.expectEqualStrings("True", value);
313+
}
314+
315+
test "e_low_level_lambda - Str.contains with substring at end" {
316+
const src =
317+
\\x = Str.contains("hello world", "world")
318+
;
319+
const value = try evalModuleAndGetString(src, 0, test_allocator);
320+
defer test_allocator.free(value);
321+
try testing.expectEqualStrings("True", value);
322+
}
323+
324+
test "e_low_level_lambda - Str.contains with empty haystack" {
325+
const src =
326+
\\x = Str.contains("", "hello")
327+
;
328+
const value = try evalModuleAndGetString(src, 0, test_allocator);
329+
defer test_allocator.free(value);
330+
try testing.expectEqualStrings("False", value);
331+
}
332+
333+
test "e_low_level_lambda - Str.contains with identical strings" {
334+
const src =
335+
\\x = Str.contains("test", "test")
336+
;
337+
const value = try evalModuleAndGetString(src, 0, test_allocator);
338+
defer test_allocator.free(value);
339+
try testing.expectEqualStrings("True", value);
340+
}
341+
279342
test "e_low_level_lambda - Str.caseless_ascii_equals with equal strings" {
280343
const src =
281344
\\x = Str.caseless_ascii_equals("hello", "hello")
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# META
2+
~~~ini
3+
description=Str.contains should work with various string combinations
4+
type=repl
5+
~~~
6+
# SOURCE
7+
~~~roc
8+
» Str.contains("foobarbaz", "bar")
9+
» Str.contains("apple", "orange")
10+
» Str.contains("anything", "")
11+
» Str.contains("hello world", "hello")
12+
» Str.contains("hello world", "world")
13+
» Str.contains("test", "test")
14+
» Str.contains("", "hello")
15+
~~~
16+
# OUTPUT
17+
True
18+
---
19+
False
20+
---
21+
True
22+
---
23+
True
24+
---
25+
True
26+
---
27+
True
28+
---
29+
False
30+
# PROBLEMS
31+
NIL

0 commit comments

Comments
 (0)