Merge pull request #8442 from roc-lang/builtin-str-impls2

More Builtin `Str` implementations
This commit is contained in:
Luke Boswell 2025-11-28 08:20:00 +11:00 committed by GitHub
commit f0310302f3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 736 additions and 9 deletions

View file

@ -121,6 +121,30 @@ fn replaceStrIsEmptyWithLowLevel(env: *ModuleEnv) !std.ArrayList(CIR.Def.Idx) {
if (env.common.findIdent("Builtin.Str.drop_suffix")) |str_drop_suffix_ident| {
try low_level_map.put(str_drop_suffix_ident, .str_drop_suffix);
}
if (env.common.findIdent("Builtin.Str.count_utf8_bytes")) |str_count_utf8_bytes_ident| {
try low_level_map.put(str_count_utf8_bytes_ident, .str_count_utf8_bytes);
}
if (env.common.findIdent("Builtin.Str.with_capacity")) |str_with_capacity_ident| {
try low_level_map.put(str_with_capacity_ident, .str_with_capacity);
}
if (env.common.findIdent("Builtin.Str.reserve")) |str_reserve_ident| {
try low_level_map.put(str_reserve_ident, .str_reserve);
}
if (env.common.findIdent("Builtin.Str.release_excess_capacity")) |str_release_excess_capacity_ident| {
try low_level_map.put(str_release_excess_capacity_ident, .str_release_excess_capacity);
}
if (env.common.findIdent("Builtin.Str.to_utf8")) |str_to_utf8_ident| {
try low_level_map.put(str_to_utf8_ident, .str_to_utf8);
}
if (env.common.findIdent("Builtin.Str.from_utf8_lossy")) |str_from_utf8_lossy_ident| {
try low_level_map.put(str_from_utf8_lossy_ident, .str_from_utf8_lossy);
}
if (env.common.findIdent("Builtin.Str.split_on")) |str_split_on_ident| {
try low_level_map.put(str_split_on_ident, .str_split_on);
}
if (env.common.findIdent("Builtin.Str.join_with")) |str_join_with_ident| {
try low_level_map.put(str_join_with_ident, .str_join_with);
}
if (env.common.findIdent("Builtin.List.len")) |list_len_ident| {
try low_level_map.put(list_len_ident, .list_len);
}

View file

@ -15,6 +15,14 @@ Builtin :: [].{
with_prefix : Str, Str -> Str
drop_prefix : Str, Str -> Str
drop_suffix : Str, Str -> Str
count_utf8_bytes : Str -> U64
with_capacity : U64 -> Str
reserve : Str, U64 -> Str
release_excess_capacity : Str -> Str
to_utf8 : Str -> List(U8)
from_utf8_lossy : List(U8) -> Str
split_on : Str, Str -> List(Str)
join_with : List(Str), Str -> Str
is_eq : Str, Str -> Bool
}

View file

@ -443,6 +443,14 @@ pub const Expr = union(enum) {
str_with_prefix,
str_drop_prefix,
str_drop_suffix,
str_count_utf8_bytes,
str_with_capacity,
str_reserve,
str_release_excess_capacity,
str_to_utf8,
str_from_utf8_lossy,
str_split_on,
str_join_with,
// Numeric to_str operations
u8_to_str,

View file

@ -3279,6 +3279,202 @@ pub const Interpreter = struct {
out.is_initialized = true;
return out;
},
.str_count_utf8_bytes => {
// Str.count_utf8_bytes : Str -> U64
std.debug.assert(args.len == 1);
const string_arg = args[0];
std.debug.assert(string_arg.ptr != null);
const string: *const RocStr = @ptrCast(@alignCast(string_arg.ptr.?));
const byte_count = builtins.str.countUtf8Bytes(string.*);
const result_layout = layout.Layout.int(.u64);
var out = try self.pushRaw(result_layout, 0);
out.is_initialized = false;
try out.setInt(@intCast(byte_count));
out.is_initialized = true;
return out;
},
.str_with_capacity => {
// Str.with_capacity : U64 -> Str
std.debug.assert(args.len == 1);
const capacity_arg = args[0];
const capacity_value = try self.extractNumericValue(capacity_arg);
const capacity: u64 = @intCast(capacity_value.int);
const result_str = builtins.str.withCapacityC(capacity, roc_ops);
const result_layout = layout.Layout.str();
var out = try self.pushRaw(result_layout, 0);
out.is_initialized = false;
const result_ptr: *RocStr = @ptrCast(@alignCast(out.ptr.?));
result_ptr.* = result_str;
out.is_initialized = true;
return out;
},
.str_reserve => {
// Str.reserve : Str, U64 -> Str
std.debug.assert(args.len == 2);
const string_arg = args[0];
const spare_arg = args[1];
std.debug.assert(string_arg.ptr != null);
const string: *const RocStr = @ptrCast(@alignCast(string_arg.ptr.?));
const spare_value = try self.extractNumericValue(spare_arg);
const spare: u64 = @intCast(spare_value.int);
const result_str = builtins.str.reserveC(string.*, spare, roc_ops);
const result_layout = string_arg.layout;
var out = try self.pushRaw(result_layout, 0);
out.is_initialized = false;
const result_ptr: *RocStr = @ptrCast(@alignCast(out.ptr.?));
result_ptr.* = result_str;
out.is_initialized = true;
return out;
},
.str_release_excess_capacity => {
// Str.release_excess_capacity : Str -> Str
std.debug.assert(args.len == 1);
const string_arg = args[0];
std.debug.assert(string_arg.ptr != null);
const string: *const RocStr = @ptrCast(@alignCast(string_arg.ptr.?));
const result_str = builtins.str.strReleaseExcessCapacity(roc_ops, string.*);
const result_layout = string_arg.layout;
var out = try self.pushRaw(result_layout, 0);
out.is_initialized = false;
const result_ptr: *RocStr = @ptrCast(@alignCast(out.ptr.?));
result_ptr.* = result_str;
out.is_initialized = true;
return out;
},
.str_to_utf8 => {
// Str.to_utf8 : Str -> List(U8)
std.debug.assert(args.len == 1);
const string_arg = args[0];
std.debug.assert(string_arg.ptr != null);
const string: *const RocStr = @ptrCast(@alignCast(string_arg.ptr.?));
const result_list = builtins.str.strToUtf8C(string.*, roc_ops);
const result_rt_var = return_rt_var orelse {
self.triggerCrash("str_to_utf8 requires return type info", false, roc_ops);
return error.Crash;
};
const result_layout = try self.getRuntimeLayout(result_rt_var);
var out = try self.pushRaw(result_layout, 0);
out.is_initialized = false;
const result_ptr: *builtins.list.RocList = @ptrCast(@alignCast(out.ptr.?));
result_ptr.* = result_list;
out.is_initialized = true;
return out;
},
.str_from_utf8_lossy => {
// Str.from_utf8_lossy : List(U8) -> Str
std.debug.assert(args.len == 1);
const list_arg = args[0];
std.debug.assert(list_arg.ptr != null);
const roc_list: *const builtins.list.RocList = @ptrCast(@alignCast(list_arg.ptr.?));
const result_str = builtins.str.fromUtf8Lossy(roc_list.*, roc_ops);
const result_layout = layout.Layout.str();
var out = try self.pushRaw(result_layout, 0);
out.is_initialized = false;
const result_ptr: *RocStr = @ptrCast(@alignCast(out.ptr.?));
result_ptr.* = result_str;
out.is_initialized = true;
return out;
},
.str_split_on => {
// Str.split_on : Str, Str -> List(Str)
std.debug.assert(args.len == 2);
const string_arg = args[0];
const delimiter_arg = args[1];
std.debug.assert(string_arg.ptr != null);
std.debug.assert(delimiter_arg.ptr != null);
const string: *const RocStr = @ptrCast(@alignCast(string_arg.ptr.?));
const delimiter: *const RocStr = @ptrCast(@alignCast(delimiter_arg.ptr.?));
const result_list = builtins.str.strSplitOn(string.*, delimiter.*, roc_ops);
// str_split_on has a fixed return type of List(Str).
// Prefer the caller's return_rt_var when it matches that shape, but fall back
// to the known layout if type information is missing or incorrect.
const result_layout = blk: {
const expected_idx = try self.runtime_layout_store.insertList(layout.Idx.str);
const expected_layout = self.runtime_layout_store.getLayout(expected_idx);
if (return_rt_var) |rt_var| {
const candidate = self.getRuntimeLayout(rt_var) catch expected_layout;
if (candidate.tag == .list) {
const elem_layout = self.runtime_layout_store.getLayout(candidate.data.list);
if (elem_layout.tag == .scalar and elem_layout.data.scalar.tag == .str) {
break :blk candidate;
}
}
}
break :blk expected_layout;
};
var out = try self.pushRaw(result_layout, 0);
out.is_initialized = false;
const result_ptr: *builtins.list.RocList = @ptrCast(@alignCast(out.ptr.?));
result_ptr.* = result_list;
out.is_initialized = true;
return out;
},
.str_join_with => {
// Str.join_with : List(Str), Str -> Str
std.debug.assert(args.len == 2);
const list_arg = args[0];
const separator_arg = args[1];
std.debug.assert(list_arg.ptr != null);
std.debug.assert(separator_arg.ptr != null);
const roc_list: *const builtins.list.RocList = @ptrCast(@alignCast(list_arg.ptr.?));
const separator: *const RocStr = @ptrCast(@alignCast(separator_arg.ptr.?));
const result_str = builtins.str.strJoinWithC(roc_list.*, separator.*, roc_ops);
const result_layout = layout.Layout.str();
var out = try self.pushRaw(result_layout, 0);
out.is_initialized = false;
const result_ptr: *RocStr = @ptrCast(@alignCast(out.ptr.?));
result_ptr.* = result_str;
out.is_initialized = true;
return out;
},
.list_len => {
// List.len : List(a) -> U64
// Note: listLen returns usize, but List.len always returns U64.

View file

@ -6,7 +6,6 @@
const std = @import("std");
const parse = @import("parse");
const types = @import("types");
const base = @import("base");
const can = @import("can");
const check = @import("check");
@ -1122,3 +1121,247 @@ test "e_low_level_lambda - Str.drop_suffix suffix longer than string" {
defer test_allocator.free(value);
try testing.expectEqualStrings("\"hi\"", value);
}
// count_utf8_bytes tests
test "e_low_level_lambda - Str.count_utf8_bytes empty string" {
const src =
\\x = Str.count_utf8_bytes("")
;
const value = try evalModuleAndGetInt(src, 0);
try testing.expectEqual(@as(i128, 0), value);
}
test "e_low_level_lambda - Str.count_utf8_bytes ASCII string" {
const src =
\\x = Str.count_utf8_bytes("hello")
;
const value = try evalModuleAndGetInt(src, 0);
try testing.expectEqual(@as(i128, 5), value);
}
test "e_low_level_lambda - Str.count_utf8_bytes multi-byte UTF-8" {
const src =
\\x = Str.count_utf8_bytes("é")
;
const value = try evalModuleAndGetInt(src, 0);
try testing.expectEqual(@as(i128, 2), value);
}
test "e_low_level_lambda - Str.count_utf8_bytes emoji" {
const src =
\\x = Str.count_utf8_bytes("🎉")
;
const value = try evalModuleAndGetInt(src, 0);
try testing.expectEqual(@as(i128, 4), value);
}
// with_capacity tests
test "e_low_level_lambda - Str.with_capacity returns empty string" {
const src =
\\x = Str.with_capacity(0)
;
const value = try evalModuleAndGetString(src, 0, test_allocator);
defer test_allocator.free(value);
try testing.expectEqualStrings("\"\"", value);
}
test "e_low_level_lambda - Str.with_capacity with capacity returns empty string" {
const src =
\\x = Str.with_capacity(100)
;
const value = try evalModuleAndGetString(src, 0, test_allocator);
defer test_allocator.free(value);
try testing.expectEqualStrings("\"\"", value);
}
// reserve tests
test "e_low_level_lambda - Str.reserve preserves content" {
const src =
\\x = Str.reserve("hello", 100)
;
const value = try evalModuleAndGetString(src, 0, test_allocator);
defer test_allocator.free(value);
try testing.expectEqualStrings("\"hello\"", value);
}
test "e_low_level_lambda - Str.reserve empty string" {
const src =
\\x = Str.reserve("", 50)
;
const value = try evalModuleAndGetString(src, 0, test_allocator);
defer test_allocator.free(value);
try testing.expectEqualStrings("\"\"", value);
}
// release_excess_capacity tests
test "e_low_level_lambda - Str.release_excess_capacity preserves content" {
const src =
\\x = Str.release_excess_capacity("hello")
;
const value = try evalModuleAndGetString(src, 0, test_allocator);
defer test_allocator.free(value);
try testing.expectEqualStrings("\"hello\"", value);
}
test "e_low_level_lambda - Str.release_excess_capacity empty string" {
const src =
\\x = Str.release_excess_capacity("")
;
const value = try evalModuleAndGetString(src, 0, test_allocator);
defer test_allocator.free(value);
try testing.expectEqualStrings("\"\"", value);
}
// to_utf8 tests (using List.len to verify)
test "e_low_level_lambda - Str.to_utf8 empty string" {
const src =
\\x = List.len(Str.to_utf8(""))
;
const value = try evalModuleAndGetInt(src, 0);
try testing.expectEqual(@as(i128, 0), value);
}
test "e_low_level_lambda - Str.to_utf8 ASCII string" {
const src =
\\x = List.len(Str.to_utf8("hello"))
;
const value = try evalModuleAndGetInt(src, 0);
try testing.expectEqual(@as(i128, 5), value);
}
test "e_low_level_lambda - Str.to_utf8 multi-byte UTF-8" {
const src =
\\x = List.len(Str.to_utf8("é"))
;
const value = try evalModuleAndGetInt(src, 0);
try testing.expectEqual(@as(i128, 2), value);
}
// from_utf8_lossy tests (roundtrip through to_utf8)
test "e_low_level_lambda - Str.from_utf8_lossy roundtrip ASCII" {
const src =
\\x = Str.from_utf8_lossy(Str.to_utf8("hello"))
;
const value = try evalModuleAndGetString(src, 0, test_allocator);
defer test_allocator.free(value);
try testing.expectEqualStrings("\"hello\"", value);
}
test "e_low_level_lambda - Str.from_utf8_lossy roundtrip empty" {
const src =
\\x = Str.from_utf8_lossy(Str.to_utf8(""))
;
const value = try evalModuleAndGetString(src, 0, test_allocator);
defer test_allocator.free(value);
try testing.expectEqualStrings("\"\"", value);
}
test "e_low_level_lambda - Str.from_utf8_lossy roundtrip UTF-8" {
const src =
\\x = Str.from_utf8_lossy(Str.to_utf8("hello 🎉 world"))
;
const value = try evalModuleAndGetString(src, 0, test_allocator);
defer test_allocator.free(value);
try testing.expectEqualStrings("\"hello 🎉 world\"", value);
}
// split_on tests
test "e_low_level_lambda - Str.split_on basic split count" {
const src =
\\x = List.len(Str.split_on("hello world", " "))
;
const value = try evalModuleAndGetInt(src, 0);
try testing.expectEqual(@as(i128, 2), value);
}
test "e_low_level_lambda - Str.split_on basic split first element" {
const src =
\\parts = Str.split_on("hello world", " ")
\\first = List.first(parts)
;
const value = try evalModuleAndGetString(src, 1, test_allocator);
defer test_allocator.free(value);
try testing.expectEqualStrings("Ok(\"hello\")", value);
}
test "e_low_level_lambda - Str.split_on multiple delimiters count" {
const src =
\\x = List.len(Str.split_on("a,b,c,d", ","))
;
const value = try evalModuleAndGetInt(src, 0);
try testing.expectEqual(@as(i128, 4), value);
}
test "e_low_level_lambda - Str.split_on multiple delimiters first element" {
const src =
\\parts = Str.split_on("a,b,c,d", ",")
\\first = List.first(parts)
;
const value = try evalModuleAndGetString(src, 1, test_allocator);
defer test_allocator.free(value);
try testing.expectEqualStrings("Ok(\"a\")", value);
}
test "e_low_level_lambda - Str.split_on no match" {
const src =
\\parts = Str.split_on("hello", "x")
\\first = List.first(parts)
;
const value = try evalModuleAndGetString(src, 1, test_allocator);
defer test_allocator.free(value);
try testing.expectEqualStrings("Ok(\"hello\")", value);
}
test "e_low_level_lambda - Str.split_on empty string" {
const src =
\\x = List.len(Str.split_on("", ","))
;
const value = try evalModuleAndGetInt(src, 0);
try testing.expectEqual(@as(i128, 1), value);
}
// join_with tests
test "e_low_level_lambda - Str.join_with basic join" {
const src =
\\x = Str.join_with(["hello", "world"], " ")
;
const value = try evalModuleAndGetString(src, 0, test_allocator);
defer test_allocator.free(value);
try testing.expectEqualStrings("\"hello world\"", value);
}
test "e_low_level_lambda - Str.join_with multiple elements" {
const src =
\\x = Str.join_with(["a", "b", "c", "d"], ",")
;
const value = try evalModuleAndGetString(src, 0, test_allocator);
defer test_allocator.free(value);
try testing.expectEqualStrings("\"a,b,c,d\"", value);
}
test "e_low_level_lambda - Str.join_with single element" {
const src =
\\x = Str.join_with(["hello"], "-")
;
const value = try evalModuleAndGetString(src, 0, test_allocator);
defer test_allocator.free(value);
try testing.expectEqualStrings("\"hello\"", value);
}
test "e_low_level_lambda - Str.join_with empty list" {
const src =
\\x = Str.join_with([], ",")
;
const value = try evalModuleAndGetString(src, 0, test_allocator);
defer test_allocator.free(value);
try testing.expectEqualStrings("\"\"", value);
}
test "e_low_level_lambda - Str.join_with roundtrip with split_on" {
const src =
\\x = Str.join_with(Str.split_on("hello world", " "), " ")
;
const value = try evalModuleAndGetString(src, 0, test_allocator);
defer test_allocator.free(value);
try testing.expectEqualStrings("\"hello world\"", value);
}

View file

@ -89,7 +89,12 @@ DOES NOT EXIST - Color.md:51:75:51:85
DOES NOT EXIST - Color.md:51:93:51:103
DOES NOT EXIST - Color.md:68:14:68:27
MISSING METHOD - Color.md:22:17:22:24
MISSING METHOD - Color.md:29:17:29:24
MISSING METHOD - Color.md:35:19:35:39
MISSING METHOD - Color.md:36:23:36:43
MISSING METHOD - Color.md:37:23:37:43
MISSING METHOD - Color.md:38:23:38:43
MISSING METHOD - Color.md:39:23:39:43
MISSING METHOD - Color.md:40:23:40:43
TYPE MISMATCH - Color.md:32:5:45:6
MISSING METHOD - Color.md:62:12:62:26
MISSING METHOD - Color.md:56:26:56:32
@ -224,18 +229,88 @@ The value's type, which does not have a method named **to_frac**, is:
**Hint: **For this to work, the type would need to have a method named **to_frac** associated with it in the type's declaration.
**MISSING METHOD**
This **to_utf8** method is being called on a value whose type doesn't have that method:
**Color.md:29:17:29:24:**
This **is_char_in_hex_range** method is being called on a value whose type doesn't have that method:
**Color.md:35:19:35:39:**
```roc
bytes = str.to_utf8()
a.is_char_in_hex_range()
```
^^^^^^^
^^^^^^^^^^^^^^^^^^^^
The value's type, which does not have a method named **to_utf8**, is:
The value's type, which does not have a method named **is_char_in_hex_range**, is:
_Str_
_U8_
**Hint: **For this to work, the type would need to have a method named **to_utf8** associated with it in the type's declaration.
**Hint: **For this to work, the type would need to have a method named **is_char_in_hex_range** associated with it in the type's declaration.
**MISSING METHOD**
This **is_char_in_hex_range** method is being called on a value whose type doesn't have that method:
**Color.md:36:23:36:43:**
```roc
and b.is_char_in_hex_range()
```
^^^^^^^^^^^^^^^^^^^^
The value's type, which does not have a method named **is_char_in_hex_range**, is:
_U8_
**Hint: **For this to work, the type would need to have a method named **is_char_in_hex_range** associated with it in the type's declaration.
**MISSING METHOD**
This **is_char_in_hex_range** method is being called on a value whose type doesn't have that method:
**Color.md:37:23:37:43:**
```roc
and c.is_char_in_hex_range()
```
^^^^^^^^^^^^^^^^^^^^
The value's type, which does not have a method named **is_char_in_hex_range**, is:
_U8_
**Hint: **For this to work, the type would need to have a method named **is_char_in_hex_range** associated with it in the type's declaration.
**MISSING METHOD**
This **is_char_in_hex_range** method is being called on a value whose type doesn't have that method:
**Color.md:38:23:38:43:**
```roc
and d.is_char_in_hex_range()
```
^^^^^^^^^^^^^^^^^^^^
The value's type, which does not have a method named **is_char_in_hex_range**, is:
_U8_
**Hint: **For this to work, the type would need to have a method named **is_char_in_hex_range** associated with it in the type's declaration.
**MISSING METHOD**
This **is_char_in_hex_range** method is being called on a value whose type doesn't have that method:
**Color.md:39:23:39:43:**
```roc
and e.is_char_in_hex_range()
```
^^^^^^^^^^^^^^^^^^^^
The value's type, which does not have a method named **is_char_in_hex_range**, is:
_U8_
**Hint: **For this to work, the type would need to have a method named **is_char_in_hex_range** associated with it in the type's declaration.
**MISSING METHOD**
This **is_char_in_hex_range** method is being called on a value whose type doesn't have that method:
**Color.md:40:23:40:43:**
```roc
and f.is_char_in_hex_range()
```
^^^^^^^^^^^^^^^^^^^^
The value's type, which does not have a method named **is_char_in_hex_range**, is:
_U8_
**Hint: **For this to work, the type would need to have a method named **is_char_in_hex_range** associated with it in the type's declaration.
**TYPE MISMATCH**
This expression is used in an unexpected way:

View file

@ -0,0 +1,25 @@
# META
~~~ini
description=Str.count_utf8_bytes should return the number of bytes in the string
type=repl
~~~
# SOURCE
~~~roc
» Str.count_utf8_bytes("")
» Str.count_utf8_bytes("hello")
» Str.count_utf8_bytes("hello world")
» Str.count_utf8_bytes("é")
» Str.count_utf8_bytes("🎉")
~~~
# OUTPUT
0
---
5
---
11
---
2
---
4
# PROBLEMS
NIL

View file

@ -0,0 +1,25 @@
# META
~~~ini
description=Str.from_utf8_lossy should convert a list of UTF-8 bytes to a string
type=repl
~~~
# SOURCE
~~~roc
» Str.from_utf8_lossy(Str.to_utf8(""))
» Str.from_utf8_lossy(Str.to_utf8("hello"))
» Str.from_utf8_lossy(Str.to_utf8("hello world"))
» Str.from_utf8_lossy(Str.to_utf8("é"))
» Str.from_utf8_lossy(Str.to_utf8("🎉"))
~~~
# OUTPUT
""
---
"hello"
---
"hello world"
---
"é"
---
"🎉"
# PROBLEMS
NIL

View file

@ -0,0 +1,22 @@
# META
~~~ini
description=Str.join_with should join strings with a separator
type=repl
~~~
# SOURCE
~~~roc
» Str.join_with(["hello", "world"], " ")
» Str.join_with(["a", "b", "c"], ",")
» Str.join_with(["single"], "-")
» Str.join_with([], ",")
~~~
# OUTPUT
"hello world"
---
"a,b,c"
---
"single"
---
""
# PROBLEMS
NIL

View file

@ -0,0 +1,19 @@
# META
~~~ini
description=Str.release_excess_capacity should return the same string with excess capacity released
type=repl
~~~
# SOURCE
~~~roc
» Str.release_excess_capacity("hello")
» Str.release_excess_capacity("")
» Str.release_excess_capacity("hello world")
~~~
# OUTPUT
"hello"
---
""
---
"hello world"
# PROBLEMS
NIL

View file

@ -0,0 +1,19 @@
# META
~~~ini
description=Str.reserve should return the same string with additional capacity reserved
type=repl
~~~
# SOURCE
~~~roc
» Str.reserve("hello", 0)
» Str.reserve("hello", 10)
» Str.reserve("", 100)
~~~
# OUTPUT
"hello"
---
"hello"
---
""
# PROBLEMS
NIL

View file

@ -0,0 +1,22 @@
# META
~~~ini
description=Str.split_on should split a string on a delimiter
type=repl
~~~
# SOURCE
~~~roc
» List.len(Str.split_on("hello world", " "))
» List.len(Str.split_on("a,b,c", ","))
» List.len(Str.split_on("no match", "x"))
» List.len(Str.split_on("", ","))
~~~
# OUTPUT
2
---
3
---
1
---
1
# PROBLEMS
NIL

View file

@ -0,0 +1,22 @@
# META
~~~ini
description=Str.to_utf8 should convert a string to a list of UTF-8 bytes
type=repl
~~~
# SOURCE
~~~roc
» List.len(Str.to_utf8(""))
» List.len(Str.to_utf8("hello"))
» List.len(Str.to_utf8("é"))
» List.len(Str.to_utf8("🎉"))
~~~
# OUTPUT
0
---
5
---
2
---
4
# PROBLEMS
NIL

View file

@ -0,0 +1,19 @@
# META
~~~ini
description=Str.with_capacity should create an empty string with preallocated capacity
type=repl
~~~
# SOURCE
~~~roc
» Str.with_capacity(0)
» Str.with_capacity(10)
» Str.with_capacity(100)
~~~
# OUTPUT
""
---
""
---
""
# PROBLEMS
NIL