implemented Str.trimRight

This commit is contained in:
Theo Felippe 2021-11-13 18:02:58 +00:00
parent f92a1f83e3
commit 8d7c252fce
12 changed files with 237 additions and 1 deletions

View file

@ -127,6 +127,7 @@ comptime {
exportStrFn(str.repeat, "repeat");
exportStrFn(str.strTrim, "trim");
exportStrFn(str.strTrimLeft, "trim_left");
exportStrFn(str.strTrimRight, "trim_right");
inline for (INTEGERS) |T| {
str.exportFromInt(T, ROC_BUILTINS ++ "." ++ STR ++ ".from_int.");

View file

@ -1584,6 +1584,42 @@ pub fn strTrimLeft(string: RocStr) callconv(.C) RocStr {
return RocStr.empty();
}
pub fn strTrimRight(string: RocStr) callconv(.C) RocStr {
if (string.str_bytes) |bytes_ptr| {
const leading_bytes = countLeadingWhitespaceBytes(string);
const trailing_bytes = countTrailingWhitespaceBytes(string);
const original_len = string.len();
if (original_len == trailing_bytes) {
string.deinit();
return RocStr.empty();
}
const new_len = original_len - trailing_bytes;
const small_or_shared = new_len <= SMALL_STR_MAX_LENGTH or !string.isRefcountOne();
if (small_or_shared) {
return RocStr.init(string.asU8ptr(), new_len);
}
// nonempty, large, and unique:
var i: usize = 0;
while (i < new_len) : (i += 1) {
const dest = bytes_ptr + i;
const source = dest;
@memcpy(dest, source, 1);
}
var new_string = string;
new_string.str_len = new_len;
return new_string;
}
return RocStr.empty();
}
fn countLeadingWhitespaceBytes(string: RocStr) usize {
var byte_count: usize = 0;
@ -1820,6 +1856,77 @@ test "strTrimLeft: small to small" {
try expect(trimmed.isSmallStr());
}
test "strTrimRight: empty" {
const trimmedEmpty = strTrimRight(RocStr.empty());
try expect(trimmedEmpty.eq(RocStr.empty()));
}
test "strTrimRight: blank" {
const original_bytes = " ";
const original = RocStr.init(original_bytes, original_bytes.len);
defer original.deinit();
const trimmed = strTrimRight(original);
try expect(trimmed.eq(RocStr.empty()));
}
test "strTrimRight: large to large" {
const original_bytes = " hello giant world ";
const original = RocStr.init(original_bytes, original_bytes.len);
defer original.deinit();
try expect(!original.isSmallStr());
const expected_bytes = " hello giant world";
const expected = RocStr.init(expected_bytes, expected_bytes.len);
defer expected.deinit();
try expect(!expected.isSmallStr());
const trimmed = strTrimRight(original);
try expect(trimmed.eq(expected));
}
test "strTrimRight: large to small" {
const original_bytes = " hello world ";
const original = RocStr.init(original_bytes, original_bytes.len);
defer original.deinit();
try expect(!original.isSmallStr());
const expected_bytes = " hello world";
const expected = RocStr.init(expected_bytes, expected_bytes.len);
defer expected.deinit();
try expect(expected.isSmallStr());
const trimmed = strTrimRight(original);
try expect(trimmed.eq(expected));
try expect(trimmed.isSmallStr());
}
test "strTrimRight: small to small" {
const original_bytes = " hello world ";
const original = RocStr.init(original_bytes, original_bytes.len);
defer original.deinit();
try expect(original.isSmallStr());
const expected_bytes = " hello world";
const expected = RocStr.init(expected_bytes, expected_bytes.len);
defer expected.deinit();
try expect(expected.isSmallStr());
const trimmed = strTrimRight(original);
try expect(trimmed.eq(expected));
try expect(trimmed.isSmallStr());
}
test "ReverseUtf8View: hello world" {
const original_bytes = "hello world";
const expected_bytes = "dlrow olleh";

View file

@ -149,6 +149,7 @@ pub const STR_FROM_UTF8_RANGE: &str = "roc_builtins.str.from_utf8_range";
pub const STR_REPEAT: &str = "roc_builtins.str.repeat";
pub const STR_TRIM: &str = "roc_builtins.str.trim";
pub const STR_TRIM_LEFT: &str = "roc_builtins.str.trim_left";
pub const STR_TRIM_RIGHT: &str = "roc_builtins.str.trim_right";
pub const DICT_HASH: &str = "roc_builtins.dict.hash";
pub const DICT_HASH_STR: &str = "roc_builtins.dict.hash_str";

View file

@ -639,6 +639,13 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
Box::new(str_type())
);
// trimRight : Str -> Str
add_top_level_function_type!(
Symbol::STR_TRIM_RIGHT,
vec![str_type()],
Box::new(str_type())
);
// trim : Str -> Str
add_top_level_function_type!(Symbol::STR_TRIM, vec![str_type()], Box::new(str_type()));

View file

@ -69,6 +69,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option<Def>
STR_REPEAT => str_repeat,
STR_TRIM => str_trim,
STR_TRIM_LEFT => str_trim_left,
STR_TRIM_RIGHT => str_trim_right,
LIST_LEN => list_len,
LIST_GET => list_get,
LIST_SET => list_set,
@ -1295,6 +1296,11 @@ fn str_trim_left(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_1(symbol, LowLevel::StrTrimLeft, var_store)
}
/// Str.trimRight : Str -> Str
fn str_trim_right(symbol: Symbol, var_store: &mut VarStore) -> Def {
lowlevel_1(symbol, LowLevel::StrTrimRight, var_store)
}
/// Str.repeat : Str, Nat -> Str
fn str_repeat(symbol: Symbol, var_store: &mut VarStore) -> Def {
let str_var = var_store.fresh();

View file

@ -18,6 +18,7 @@ use crate::llvm::build_str::{
empty_str, str_concat, str_count_graphemes, str_ends_with, str_from_float, str_from_int,
str_from_utf8, str_from_utf8_range, str_join_with, str_number_of_bytes, str_repeat, str_split,
str_starts_with, str_starts_with_code_point, str_to_utf8, str_trim, str_trim_left,
str_trim_right,
};
use crate::llvm::compare::{generic_eq, generic_neq};
use crate::llvm::convert::{
@ -5070,6 +5071,12 @@ fn run_low_level<'a, 'ctx, 'env>(
str_trim_left(env, scope, args[0])
}
StrTrimRight => {
// Str.trim : Str -> Str
debug_assert_eq!(args.len(), 1);
str_trim_right(env, scope, args[0])
}
ListLen => {
// List.len : List * -> Int
debug_assert_eq!(args.len(), 1);

View file

@ -269,6 +269,16 @@ pub fn str_trim_left<'a, 'ctx, 'env>(
call_bitcode_fn(env, &[str_i128.into()], bitcode::STR_TRIM_LEFT)
}
/// Str.trimRight : Str -> Str
pub fn str_trim_right<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>,
str_symbol: Symbol,
) -> BasicValueEnum<'ctx> {
let str_i128 = str_symbol_to_c_abi(env, scope, str_symbol);
call_bitcode_fn(env, &[str_i128.into()], bitcode::STR_TRIM_RIGHT)
}
/// Str.fromInt : Int -> Str
pub fn str_from_int<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,

View file

@ -28,7 +28,7 @@ pub fn build_call_low_level<'a>(
match lowlevel {
StrConcat | StrJoinWith | StrIsEmpty | StrStartsWith | StrStartsWithCodePt
| StrEndsWith | StrSplit | StrCountGraphemes | StrFromInt | StrFromUtf8 | StrTrimLeft
| StrEndsWith | StrSplit | StrCountGraphemes | StrFromInt | StrFromUtf8 | StrTrimLeft | StrTrimRight
| StrFromUtf8Range | StrToUtf8 | StrRepeat | StrFromFloat | StrTrim | ListLen
| ListGetUnsafe | ListSet | ListSingle | ListRepeat | ListReverse | ListConcat
| ListContains | ListAppend | ListPrepend | ListJoin | ListRange | ListMap | ListMap2

View file

@ -19,6 +19,7 @@ pub enum LowLevel {
StrFromFloat,
StrTrim,
StrTrimLeft,
StrTrimRight,
ListLen,
ListGetUnsafe,
ListSet,
@ -131,6 +132,7 @@ macro_rules! first_order {
| StrRepeat
| StrTrim
| StrTrimLeft
| StrTrimRight
| StrFromFloat
| ListLen
| ListGetUnsafe

View file

@ -1019,6 +1019,7 @@ define_builtins! {
19 STR_REPEAT: "repeat"
20 STR_TRIM: "trim"
21 STR_TRIM_LEFT: "trimLeft"
22 STR_TRIM_RIGHT: "trimRight"
}
4 LIST: "List" => {
0 LIST_LIST: "List" imported // the List.List type alias

View file

@ -941,6 +941,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
StrConcat => arena.alloc_slice_copy(&[owned, borrowed]),
StrTrim => arena.alloc_slice_copy(&[owned]),
StrTrimLeft => arena.alloc_slice_copy(&[owned]),
StrTrimRight => arena.alloc_slice_copy(&[owned]),
StrSplit => arena.alloc_slice_copy(&[borrowed, borrowed]),
ListSingle => arena.alloc_slice_copy(&[irrelevant]),
ListRepeat => arena.alloc_slice_copy(&[irrelevant, borrowed]),

View file

@ -1244,3 +1244,96 @@ fn str_trim_left_small_to_small_shared() {
(RocStr, RocStr)
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn str_trim_right_small_blank_string() {
assert_evals_to!(indoc!(r#"Str.trimRight " ""#), RocStr::from(""), RocStr);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn str_trim_right_small_to_small() {
assert_evals_to!(
indoc!(r#"Str.trimRight " hello world ""#),
RocStr::from(" hello world"),
RocStr
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn str_trim_right_large_to_large_unique() {
assert_evals_to!(
indoc!(r#"Str.trimRight (Str.concat " hello world from a large string" " ")"#),
RocStr::from(" hello world from a large string"),
RocStr
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn str_trim_right_large_to_small_unique() {
assert_evals_to!(
indoc!(r#"Str.trimRight (Str.concat " hello world" " ")"#),
RocStr::from(" hello world"),
RocStr
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn str_trim_right_large_to_large_shared() {
assert_evals_to!(
indoc!(
r#"
original : Str
original = " hello world world "
{ trimmed: Str.trimRight original, original: original }
"#
),
(
RocStr::from(" hello world world "),
RocStr::from(" hello world world"),
),
(RocStr, RocStr)
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn str_trim_right_large_to_small_shared() {
assert_evals_to!(
indoc!(
r#"
original : Str
original = " hello world "
{ trimmed: Str.trimRight original, original: original }
"#
),
(
RocStr::from(" hello world "),
RocStr::from(" hello world"),
),
(RocStr, RocStr)
);
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn str_trim_right_small_to_small_shared() {
assert_evals_to!(
indoc!(
r#"
original : Str
original = " hello world "
{ trimmed: Str.trimRight original, original: original }
"#
),
(RocStr::from(" hello world "), RocStr::from(" hello world"),),
(RocStr, RocStr)
);
}